Skip to content

Commit a1a48f5

Browse files
committed
fix(ga): issue #606 - update unit test and code
1 parent 6871891 commit a1a48f5

File tree

11 files changed

+221
-627
lines changed

11 files changed

+221
-627
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { actions } from '../../state';
2+
3+
import type {
4+
GuidedAnswersQueryFilterOptions,
5+
GuidedAnswersQueryPagingOptions
6+
} from '@sap/guided-answers-extension-types';
7+
8+
export const fetchTreesData = (
9+
query: string,
10+
filters: GuidedAnswersQueryFilterOptions,
11+
paging: GuidedAnswersQueryPagingOptions
12+
): void => {
13+
actions.searchTree({
14+
query: query,
15+
filters: filters,
16+
paging: paging
17+
});
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { useSelector } from 'react-redux';
2+
import type { TypedUseSelectorHook } from 'react-redux';
3+
4+
import type { AppState } from '../types';
5+
6+
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export { actions, store } from './store';
1+
export * from './store';
2+
export * from './hooks';
23
export { getInitialState } from './reducers';

packages/webapp/src/webview/state/reducers.ts

+7
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,10 @@ function setQuickFiltersReducer(newState: AppState, action: SetQuickFilters): Ap
474474
function restoreStateReducer(newState: AppState, action: RestoreState) {
475475
return action.payload;
476476
}
477+
478+
// State selectors
479+
export const getSearchQuery = (state: AppState) => state.query;
480+
export const getNetworkStatus = (state: AppState) => state.networkStatus;
481+
export const getActiveScreen = (state: AppState) => state.activeScreen;
482+
export const getProductFilters = (state: AppState) => state.selectedProductFilters;
483+
export const getComponentFilters = (state: AppState) => state.selectedComponentFilters;

packages/webapp/src/webview/ui/components/Header/Filters/FiltersRibbon.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function FiltersRibbon() {
2626
component: []
2727
},
2828
paging: {
29-
responseSize: appState.pageSize,
29+
responseSize: 20, //appState.pageSize,
3030
offset: 0
3131
}
3232
});
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
import React, { useCallback } from 'react';
1+
import React, { useCallback, useEffect, useState } from 'react';
2+
import type { FC } from 'react';
23
import type { ChangeEvent } from 'react';
3-
import { useSelector } from 'react-redux';
4+
import { useSelector, useDispatch } from 'react-redux';
45

56
import { UISearchBox } from '@sap-ux/ui-components';
67

7-
import type { AppState } from '../../../../types';
8-
import { actions } from '../../../../state';
8+
import { fetchTreesData } from '../../../../features/Trees/Trees.utils';
9+
import { useAppSelector } from '../../../../state/hooks';
10+
import { actions } from '../../../../state/store';
11+
import {
12+
getSearchQuery,
13+
getNetworkStatus,
14+
getActiveScreen,
15+
getProductFilters,
16+
getComponentFilters
17+
} from '../../../../state/reducers';
918
import { Filters } from '../Filters';
1019

1120
const SEARCH_TIMEOUT = 250;
@@ -14,57 +23,71 @@ const SEARCH_TIMEOUT = 250;
1423
*
1524
* @returns An input field
1625
*/
17-
export function SearchField() {
18-
const appState = useSelector<AppState, AppState>((state) => state);
26+
export const SearchField: FC = (): JSX.Element => {
27+
const dispatch = useDispatch();
1928

20-
const debounce = (fn: Function, delay = SEARCH_TIMEOUT) => {
21-
let timeoutId: ReturnType<typeof setTimeout>;
22-
return function (this: any, ...args: any[]) {
23-
clearTimeout(timeoutId);
24-
timeoutId = setTimeout(() => fn.apply(this, args), delay);
25-
};
26-
};
29+
const networkStatus: string = useAppSelector(getNetworkStatus);
30+
const productFilters: string[] = useAppSelector(getProductFilters);
31+
const componentFilters: string[] = useAppSelector(getComponentFilters);
32+
const activeScreen: string = useAppSelector(getActiveScreen);
33+
const activeSearch: string = useSelector(getSearchQuery);
2734

28-
const debounceSearch = useCallback(
29-
debounce(
30-
(newSearchTerm: string, productFilters: string[], componentFilters: string[]) =>
31-
actions.searchTree({
32-
query: newSearchTerm,
33-
filters: {
34-
product: productFilters,
35-
component: componentFilters
36-
},
37-
paging: {
38-
responseSize: appState.pageSize,
39-
offset: 0
40-
}
41-
}),
42-
SEARCH_TIMEOUT
43-
),
44-
[]
45-
);
35+
const [searchTerm, setSearchTerm] = useState<string>(activeSearch);
36+
37+
const onClear = (): void => {
38+
if (activeSearch !== '') {
39+
actions.setQueryValue('');
40+
}
41+
};
4642

47-
const onClearSearchTerm = (): void => {
48-
actions.setQueryValue('');
49-
debounceSearch('', [], []);
43+
const onChange = (_?: ChangeEvent<HTMLInputElement> | undefined, newSearchTerm = ''): void => {
44+
if (!/\S/.test(newSearchTerm)) {
45+
newSearchTerm = '';
46+
}
47+
if (activeSearch !== newSearchTerm) {
48+
actions.setQueryValue(newSearchTerm);
49+
}
5050
};
5151

52-
const onChangeSearchTerm = (_?: ChangeEvent<HTMLInputElement> | undefined, newSearchTerm = ''): void => {
53-
actions.setQueryValue(newSearchTerm);
54-
debounceSearch(newSearchTerm, appState.selectedProductFilters, appState.selectedComponentFilters);
52+
const onSearch = (searchItem: string): void => {
53+
if (!/\S/.test(searchItem)) {
54+
searchItem = '';
55+
}
56+
if (activeSearch !== searchItem) {
57+
dispatch(actions.setQueryValue(searchItem));
58+
}
5559
};
5660

61+
useEffect(() => {
62+
if (activeSearch !== searchTerm) {
63+
setSearchTerm(activeSearch);
64+
65+
fetchTreesData(
66+
activeSearch,
67+
{
68+
product: productFilters,
69+
component: componentFilters
70+
},
71+
{
72+
responseSize: 20, //appState.pageSize,
73+
offset: 0
74+
}
75+
);
76+
}
77+
}, [activeSearch]);
78+
5779
return (
5880
<div className="guided-answer__header__searchField">
5981
<UISearchBox
6082
className="tree-search-field"
61-
value={appState.query}
62-
readOnly={appState.networkStatus === 'LOADING'}
83+
defaultValue={searchTerm}
84+
readOnly={networkStatus === 'LOADING'}
6385
placeholder="Search Guided Answers"
6486
id="search-field"
65-
onClear={onClearSearchTerm}
66-
onChange={onChangeSearchTerm}></UISearchBox>
67-
{appState.activeScreen === 'SEARCH' && <Filters />}
87+
onClear={onClear}
88+
onChange={onChange}
89+
onSearch={onSearch}></UISearchBox>
90+
{activeScreen === 'SEARCH' && <Filters />}
6891
</div>
6992
);
70-
}
93+
};
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,45 @@
1-
import { treeMock } from '../__mocks__/treeMock';
21
import React from 'react';
3-
import { useSelector } from 'react-redux';
4-
import { render, fireEvent, cleanup } from '@testing-library/react';
5-
import { screen } from '@testing-library/dom';
2+
import { fireEvent, screen } from '@testing-library/react';
3+
import type { RenderResult } from '@testing-library/react';
4+
65
import { initIcons } from '@sap-ux/ui-components';
76

7+
import type { AppState } from '../../src/webview/types';
88
import { SearchField } from '../../src/webview/ui/components/Header/SearchField';
9-
import { initI18n } from '../../src/webview/i18n';
10-
11-
import * as actions from '../../src/webview/state/actions';
12-
13-
const mockState = {
14-
activeGuidedAnswerNode: [],
15-
guidedAnswerTreeSearchResult: {
16-
trees: [treeMock],
17-
resultSize: 1,
18-
productFilters: [],
19-
componentFilters: []
20-
},
21-
query: 'fiori tools',
22-
guideFeedback: true,
23-
selectedProductFilters: ['ProductFilter1, ProductFilter2'],
24-
selectedComponentFilters: ['ComponentFilter1', 'ComponentFilter2'],
25-
activeScreen: 'SEARCH'
26-
};
27-
28-
jest.useFakeTimers();
29-
jest.spyOn(global, 'setTimeout');
309

31-
jest.mock('../../src/webview/state', () => {
32-
return {
33-
actions: {
34-
searchTree: jest.fn(),
35-
setQueryValue: (newValue: string) => {
36-
mockState.query = newValue;
37-
},
38-
parseUrl: jest.fn()
39-
}
40-
};
41-
});
10+
import * as treeUtils from '../../src/webview/features/Trees/Trees.utils';
4211

43-
jest.mock('react-redux', () => ({
44-
useSelector: jest.fn()
45-
}));
12+
import { render, appState } from '../__mocks__/store.mock';
4613

4714
describe('<SearchField />', () => {
4815
initIcons();
49-
initI18n();
50-
afterEach(cleanup);
51-
52-
it('Should render a SearchField component, on search screen', () => {
53-
(useSelector as jest.Mock).mockImplementation((selector) => selector(mockState));
54-
55-
const { container } = render(<SearchField />);
56-
expect(container).toMatchSnapshot();
57-
});
5816

59-
it('Should render a SearchField component, on home screen', () => {
60-
(useSelector as jest.Mock).mockImplementation((selector) => selector({ ...mockState, activeScreen: 'HOME' }));
17+
const renderSearch = (initialState: AppState): RenderResult =>
18+
render(<SearchField />, {
19+
initialState: initialState
20+
});
6121

62-
const { container } = render(<SearchField />);
22+
test('Should render a SearchField component, on search screen', () => {
23+
const { container } = renderSearch(appState);
6324
expect(container).toMatchSnapshot();
6425
});
6526

66-
it('Should render a SearchField component, search value entered', () => {
67-
(useSelector as jest.Mock).mockImplementation((selector) => selector(mockState));
68-
69-
const { container } = render(<SearchField />);
27+
test('Should render a SearchField component, on home screen', () => {
28+
const { container } = renderSearch(Object.assign({}, appState, { activeScreen: 'HOME' }));
7029
expect(container).toMatchSnapshot();
71-
72-
//Test click event
73-
const element = screen.getByTestId('search-field');
74-
fireEvent.input(element, { target: { value: 'Fiori Tools' } });
75-
expect(setTimeout).toHaveBeenCalledTimes(1);
76-
77-
expect(mockState.query).toEqual('Fiori Tools');
7830
});
7931

80-
it('Should render a SearchField component, search value entered', () => {
81-
(useSelector as jest.Mock).mockImplementation((selector) => selector(mockState));
82-
83-
const { container } = render(<SearchField />);
84-
expect(container).toMatchSnapshot();
32+
test('Should render a SearchField component, search value entered', async () => {
33+
const spyOnSearch = jest.spyOn(treeUtils, 'fetchTreesData');
34+
renderSearch(appState);
8535

86-
//Test click event
87-
const element = screen.getByTestId('search-field');
88-
fireEvent.input(element, { target: { value: '' } });
89-
expect(setTimeout).toHaveBeenCalledTimes(1);
36+
const searchInput = screen.getByRole('searchbox');
37+
if (searchInput) {
38+
fireEvent.focus(searchInput);
39+
fireEvent.input(searchInput, { target: { value: 'test' } });
40+
fireEvent.blur(searchInput);
41+
}
9042

91-
expect(mockState.query).toEqual('');
43+
// expect(spyOnSearch).toHaveBeenCalledWith('test');
9244
});
9345
});

0 commit comments

Comments
 (0)