Skip to content

Commit a12fb72

Browse files
committed
code refactoring and additional unit tests
1 parent e1b4dd7 commit a12fb72

File tree

7 files changed

+203
-59
lines changed

7 files changed

+203
-59
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useCallback, useMemo } from 'react';
2+
import { useRecoilValue } from 'recoil';
3+
import { useIntl } from 'react-intl';
4+
import { type Row } from '@components/Table';
5+
import { useSearchContext } from '@common/hooks/useSearchContext';
6+
import { ComplexLookupSearchResultsProps } from '@components/ComplexLookupField/ComplexLookupSearchResults';
7+
import state from '@state';
8+
9+
export const useComplexLookupSearchResults = ({
10+
onTitleClick,
11+
tableConfig,
12+
searchResultsFormatter,
13+
}: ComplexLookupSearchResultsProps) => {
14+
const { onAssignRecord } = useSearchContext();
15+
const data = useRecoilValue(state.search.data);
16+
const sourceData = useRecoilValue(state.search.sourceData);
17+
const { formatMessage } = useIntl();
18+
19+
const applyActionItems = useCallback(
20+
(rows: Row[]): Row[] =>
21+
rows?.map(row => {
22+
const formattedRow: Row = { ...row };
23+
24+
Object.entries(tableConfig.columns).forEach(([key, column]) => {
25+
formattedRow[key] = {
26+
...row[key],
27+
children: column.formatter
28+
? column.formatter({ row, formatMessage, onAssign: onAssignRecord, onTitleClick })
29+
: row[key].label,
30+
};
31+
});
32+
33+
return formattedRow;
34+
}),
35+
[onAssignRecord, tableConfig],
36+
);
37+
38+
const formattedData = useMemo(
39+
() => applyActionItems(searchResultsFormatter(data || [], sourceData || [])),
40+
[applyActionItems, data],
41+
);
42+
43+
const listHeader = useMemo(
44+
() =>
45+
Object.keys(tableConfig.columns).reduce((accum, key) => {
46+
const { label, position, className } = (tableConfig.columns[key] || {}) as SearchResultsTableColumn;
47+
48+
accum[key] = {
49+
label: label ? formatMessage({ id: label }) : '',
50+
position: position,
51+
className: className,
52+
};
53+
54+
return accum;
55+
}, {} as Row),
56+
[tableConfig],
57+
);
58+
59+
return { formattedData, listHeader };
60+
};

src/common/services/schema/schemaWithDuplicates.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class SchemaWithDuplicatesService implements ISchemaWithDuplicatesService
1010

1111
constructor(
1212
private schema: Map<string, SchemaEntry>,
13-
private selectedEntriesService: ISelectedEntries,
14-
private entryPropertiesGeneratorService?: IEntryPropertiesGeneratorService,
13+
private readonly selectedEntriesService: ISelectedEntries,
14+
private readonly entryPropertiesGeneratorService?: IEntryPropertiesGeneratorService,
1515
) {
1616
this.set(schema);
1717
this.isManualDuplication = true;
@@ -107,7 +107,7 @@ export class SchemaWithDuplicatesService implements ISchemaWithDuplicatesService
107107
if (!entry || entry.cloneOf) return;
108108

109109
const { children } = entry;
110-
let updatedEntryUuid = newUuids?.[index] || uuidv4();
110+
let updatedEntryUuid = newUuids?.[index] ?? uuidv4();
111111
const isFirstAssociatedEntryElem = parentEntry?.dependsOn && newUuids && index === 0;
112112

113113
if (isFirstAssociatedEntryElem) {
@@ -126,7 +126,7 @@ export class SchemaWithDuplicatesService implements ISchemaWithDuplicatesService
126126
newUuids,
127127
});
128128

129-
if (controlledByEntry && controlledByEntry?.uuid) {
129+
if (controlledByEntry?.uuid) {
130130
this.schema.set(controlledByEntry.uuid, controlledByEntry);
131131
}
132132

src/common/services/userValues/userValueTypes/simpleLookup.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export class SimpleLookupUserValueService extends UserValueType implements IUser
1111
private contents?: UserValueContents[];
1212

1313
constructor(
14-
private apiClient: IApiClient,
15-
private cacheService: ILookupCacheService,
14+
private readonly apiClient: IApiClient,
15+
private readonly cacheService: ILookupCacheService,
1616
) {
1717
super();
1818
}
@@ -60,7 +60,7 @@ export class SimpleLookupUserValueService extends UserValueType implements IUser
6060
}
6161

6262
this.value = {
63-
uuid: uuid || '',
63+
uuid: uuid ?? '',
6464
contents: this.contents,
6565
};
6666

@@ -70,7 +70,7 @@ export class SimpleLookupUserValueService extends UserValueType implements IUser
7070
private checkDefaultGroupValues(groupUri?: string, itemUri?: string) {
7171
if (!groupUri || !itemUri) return false;
7272

73-
return (DEFAULT_GROUP_VALUES as DefaultGroupValues)[groupUri as string]?.value === itemUri;
73+
return (DEFAULT_GROUP_VALUES as DefaultGroupValues)[groupUri]?.value === itemUri;
7474
}
7575

7676
private generateContentItem({
@@ -100,7 +100,7 @@ export class SimpleLookupUserValueService extends UserValueType implements IUser
100100
?.find(
101101
({ label: optionLabel, value }) => value.uri === mappedUri || value.label === label || optionLabel === label,
102102
);
103-
const selectedLabel = typesMap && itemUri ? loadedOption?.label || itemUri : loadedOption?.label || label;
103+
const selectedLabel = typesMap && itemUri ? (loadedOption?.label ?? itemUri) : (loadedOption?.label ?? label);
104104

105105
const contentItem = {
106106
label: selectedLabel,

src/components/ComplexLookupField/ComplexLookupSearchResults.tsx

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { FC, useCallback, useMemo } from 'react';
2-
import { useRecoilValue } from 'recoil';
3-
import { FormattedMessage, useIntl } from 'react-intl';
1+
import { FC } from 'react';
42
import { TableFlex, type Row } from '@components/Table';
5-
import { useSearchContext } from '@common/hooks/useSearchContext';
6-
import state from '@state';
3+
import { useComplexLookupSearchResults } from '@common/hooks/useComplexLookupSearchResults';
74

8-
type ComplexLookupSearchResultsProps = {
5+
export type ComplexLookupSearchResultsProps = {
96
onTitleClick?: (id: string, title?: string, headingType?: string) => void;
107
tableConfig: SearchResultsTableConfig;
118
searchResultsFormatter: (data: any[], sourceData?: SourceDataDTO) => Row[];
@@ -16,50 +13,11 @@ export const ComplexLookupSearchResults: FC<ComplexLookupSearchResultsProps> = (
1613
tableConfig,
1714
searchResultsFormatter,
1815
}) => {
19-
const { onAssignRecord } = useSearchContext();
20-
const data = useRecoilValue(state.search.data);
21-
const sourceData = useRecoilValue(state.search.sourceData);
22-
const { formatMessage } = useIntl();
23-
24-
const applyActionItems = useCallback(
25-
(rows: Row[]): Row[] =>
26-
rows.map(row => {
27-
const formattedRow: Row = { ...row };
28-
29-
Object.entries(tableConfig.columns).forEach(([key, column]) => {
30-
formattedRow[key] = {
31-
...row[key],
32-
children: column.formatter
33-
? column.formatter({ row, formatMessage, onAssign: onAssignRecord, onTitleClick })
34-
: row[key].label,
35-
};
36-
});
37-
38-
return formattedRow;
39-
}),
40-
[onAssignRecord, tableConfig],
41-
);
42-
43-
const formattedData = useMemo(
44-
() => applyActionItems(searchResultsFormatter(data || [], sourceData || [])),
45-
[applyActionItems, data],
46-
);
47-
48-
const listHeader = useMemo(
49-
() =>
50-
Object.keys(tableConfig.columns).reduce((accum, key) => {
51-
const { label, position, className } = (tableConfig.columns[key] || {}) as SearchResultsTableColumn;
52-
53-
accum[key] = {
54-
label: label ? <FormattedMessage id={label} /> : '',
55-
position: position,
56-
className: className,
57-
};
58-
59-
return accum;
60-
}, {} as Row),
61-
[tableConfig],
62-
);
16+
const { listHeader, formattedData } = useComplexLookupSearchResults({
17+
onTitleClick,
18+
tableConfig,
19+
searchResultsFormatter,
20+
});
6321

6422
return (
6523
<div className="search-result-list">
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { useRecoilValue } from 'recoil';
2+
import { renderHook } from '@testing-library/react';
3+
import { useSearchContext } from '@common/hooks/useSearchContext';
4+
import { useComplexLookupSearchResults } from '@common/hooks/useComplexLookupSearchResults';
5+
import { ComplexLookupSearchResultsProps } from '@components/ComplexLookupField/ComplexLookupSearchResults';
6+
import { Row } from '@components/Table';
7+
8+
jest.mock('recoil');
9+
jest.mock('@common/hooks/useSearchContext', () => ({
10+
useSearchContext: jest.fn(),
11+
}));
12+
13+
const data = [
14+
{
15+
id: '1',
16+
name: { label: 'Item 1' },
17+
description: { label: 'Description 1' },
18+
},
19+
];
20+
const sourceData = [
21+
{
22+
id: '1',
23+
name: 'Item 1',
24+
description: 'Description 1',
25+
},
26+
];
27+
const tableConfig = {
28+
columns: {
29+
name: {
30+
label: 'name.label',
31+
position: 1,
32+
formatter: ({ row }: any) => row.name.label,
33+
},
34+
description: {
35+
label: 'description.label',
36+
position: 2,
37+
},
38+
},
39+
};
40+
const searchResultsFormatter = (data: Row[]) => data;
41+
42+
describe('useComplesLookupSearchResults', () => {
43+
beforeEach(() => {
44+
(useSearchContext as jest.Mock).mockReturnValue({
45+
onAssignRecord: jest.fn(),
46+
});
47+
(useRecoilValue as jest.Mock).mockReturnValueOnce(data).mockReturnValueOnce(sourceData);
48+
});
49+
50+
it('returns "formattedData" and "listHeader"', () => {
51+
const props: ComplexLookupSearchResultsProps = {
52+
onTitleClick: jest.fn(),
53+
tableConfig,
54+
searchResultsFormatter,
55+
};
56+
57+
const { result } = renderHook(() => useComplexLookupSearchResults(props));
58+
59+
expect(result.current.formattedData).toEqual([
60+
{
61+
id: '1',
62+
name: {
63+
label: 'Item 1',
64+
children: 'Item 1',
65+
},
66+
description: {
67+
label: 'Description 1',
68+
children: 'Description 1',
69+
},
70+
},
71+
]);
72+
73+
expect(result.current.listHeader).toEqual({
74+
name: {
75+
label: 'name.label',
76+
position: 1,
77+
className: undefined,
78+
},
79+
description: {
80+
label: 'description.label',
81+
position: 2,
82+
className: undefined,
83+
},
84+
});
85+
});
86+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { render } from '@testing-library/react';
2+
import { useComplexLookupSearchResults } from '@common/hooks/useComplexLookupSearchResults';
3+
import { ComplexLookupSearchResults } from '@components/ComplexLookupField/ComplexLookupSearchResults';
4+
import { TableFlex } from '@components/Table';
5+
6+
jest.mock('@components/Table');
7+
jest.mock('@common/hooks/useComplexLookupSearchResults');
8+
9+
const listHeader = ['Column 1', 'Column 2'];
10+
const formattedData = [
11+
{ id: '1', values: ['Data 1', 'Data 2'] },
12+
{ id: '2', values: ['Data 3', 'Data 4'] },
13+
];
14+
15+
const onTitleClick = jest.fn();
16+
const searchResultsFormatter = jest.fn();
17+
const tableConfig = {} as SearchResultsTableConfig;
18+
19+
describe('ComplexLookupSearchResults', () => {
20+
it('renders "TableFlex" component with the required props', () => {
21+
(useComplexLookupSearchResults as jest.Mock).mockReturnValue({
22+
listHeader,
23+
formattedData,
24+
});
25+
(TableFlex as jest.Mock).mockReturnValue(<div>Mock TableFlex</div>);
26+
27+
render(
28+
<ComplexLookupSearchResults
29+
onTitleClick={onTitleClick}
30+
tableConfig={tableConfig}
31+
searchResultsFormatter={searchResultsFormatter}
32+
/>,
33+
);
34+
35+
expect(TableFlex as jest.Mock).toHaveBeenCalledWith(
36+
{ header: listHeader, data: formattedData, className: 'results-list' },
37+
{},
38+
);
39+
});
40+
});

0 commit comments

Comments
 (0)