@inseefr/lunatic 3.8.0 → 3.8.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Suggester/Suggester.js +23 -8
- package/components/Suggester/Suggester.js.map +1 -1
- package/components/Suggester/Suggester.spec.js +75 -25
- package/components/Suggester/Suggester.spec.js.map +1 -1
- package/components/Suggester/useSuggestions.d.ts +21 -2
- package/components/Suggester/useSuggestions.js +26 -12
- package/components/Suggester/useSuggestions.js.map +1 -1
- package/esm/components/Suggester/Suggester.js +25 -9
- package/esm/components/Suggester/Suggester.js.map +1 -1
- package/esm/components/Suggester/Suggester.spec.js +78 -25
- package/esm/components/Suggester/Suggester.spec.js.map +1 -1
- package/esm/components/Suggester/useSuggestions.d.ts +21 -2
- package/esm/components/Suggester/useSuggestions.js +24 -10
- package/esm/components/Suggester/useSuggestions.js.map +1 -1
- package/esm/utils/search/SearchInterface.d.ts +1 -0
- package/esm/utils/search/SearchMinisearch.d.ts +1 -0
- package/esm/utils/search/SearchMinisearch.js +6 -0
- package/esm/utils/search/SearchMinisearch.js.map +1 -1
- package/esm/utils/search/SuggestersDatabase.d.ts +7 -0
- package/esm/utils/search/SuggestersDatabase.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Suggester/Suggester.spec.tsx +93 -26
- package/src/components/Suggester/Suggester.tsx +25 -9
- package/src/components/Suggester/useSuggestions.ts +39 -18
- package/src/utils/search/SearchInterface.ts +2 -0
- package/src/utils/search/SearchMinisearch.ts +7 -0
- package/src/utils/search/SuggestersDatabase.ts +10 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/utils/search/SearchInterface.d.ts +1 -0
- package/utils/search/SearchMinisearch.d.ts +1 -0
- package/utils/search/SearchMinisearch.js +6 -0
- package/utils/search/SearchMinisearch.js.map +1 -1
- package/utils/search/SuggestersDatabase.d.ts +7 -0
- package/utils/search/SuggestersDatabase.js.map +1 -1
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import type { SuggesterOptionType } from './SuggesterType';
|
|
2
|
+
import { SearchStore } from '../../utils/search/SuggestersDatabase';
|
|
2
3
|
type Props = {
|
|
3
|
-
|
|
4
|
+
store: SearchStore;
|
|
5
|
+
storeState: State;
|
|
6
|
+
setStoreState: (s: State) => any;
|
|
4
7
|
selectedOptions: SuggesterOptionType[];
|
|
5
8
|
allowArbitrary: boolean;
|
|
6
9
|
};
|
|
7
10
|
export declare const OTHER_VALUE = "OTHER";
|
|
8
11
|
type State = 'success' | 'loading' | 'error';
|
|
9
|
-
export declare function
|
|
12
|
+
export declare function useStore({ storeName }: {
|
|
13
|
+
storeName: string;
|
|
14
|
+
}): {
|
|
15
|
+
store: {
|
|
16
|
+
error: string;
|
|
17
|
+
search?: undefined;
|
|
18
|
+
index?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
error: null;
|
|
21
|
+
search: import("../../utils/search/SearchInterface").SearchInterface<import("../../utils/search/SearchInterface").IndexEntry>;
|
|
22
|
+
index: () => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
storeState: State;
|
|
25
|
+
setStoreState: import("react").Dispatch<import("react").SetStateAction<State>>;
|
|
26
|
+
getLabelById: (id: any) => string;
|
|
27
|
+
};
|
|
28
|
+
export declare function useSuggestions({ store, storeState: state, setStoreState: setState, selectedOptions, allowArbitrary, }: Props): {
|
|
10
29
|
search: string;
|
|
11
30
|
setSearch: (s: string) => void;
|
|
12
31
|
state: State;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useEffectDebounced } from '../../hooks/useDebounce';
|
|
3
|
-
import { getSearchForStore } from '../../utils/search/SuggestersDatabase';
|
|
3
|
+
import { getSearchForStore, } from '../../utils/search/SuggestersDatabase';
|
|
4
4
|
import { useRefSync } from '../../hooks/useRefSync';
|
|
5
5
|
export const OTHER_VALUE = 'OTHER';
|
|
6
|
-
export function
|
|
7
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
6
|
+
export function useStore({ storeName }) {
|
|
8
7
|
const store = getSearchForStore(storeName);
|
|
9
8
|
const searchIndexRef = useRefSync(store.index);
|
|
10
|
-
// eslint-disable-next-line prefer-const
|
|
11
|
-
let [options, setOptions] = useState(selectedOptions);
|
|
12
9
|
const [state, setState] = useState(store.error !== null
|
|
13
10
|
? 'error'
|
|
14
|
-
: store.search.isIndexed()
|
|
11
|
+
: (store === null || store === void 0 ? void 0 : store.search.isIndexed())
|
|
15
12
|
? 'success'
|
|
16
13
|
: 'loading');
|
|
17
14
|
// Index the data when the component is loaded
|
|
@@ -28,16 +25,33 @@ export function useSuggestions({ storeName, selectedOptions, allowArbitrary, })
|
|
|
28
25
|
setState('error');
|
|
29
26
|
});
|
|
30
27
|
}, [searchIndexRef]);
|
|
28
|
+
return {
|
|
29
|
+
store,
|
|
30
|
+
storeState: state,
|
|
31
|
+
setStoreState: setState,
|
|
32
|
+
getLabelById: (id) => {
|
|
33
|
+
var _a, _b, _c;
|
|
34
|
+
if (!id)
|
|
35
|
+
return '';
|
|
36
|
+
return (_c = (_b = (_a = store.search) === null || _a === void 0 ? void 0 : _a.getFieldsById(id)) === null || _b === void 0 ? void 0 : _b.label) !== null && _c !== void 0 ? _c : '';
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function useSuggestions({ store, storeState: state, setStoreState: setState, selectedOptions, allowArbitrary, }) {
|
|
41
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
42
|
+
// eslint-disable-next-line prefer-const
|
|
43
|
+
let [options, setOptions] = useState(selectedOptions);
|
|
31
44
|
useEffectDebounced(() => {
|
|
32
45
|
var _a;
|
|
33
46
|
// Do not reset search for empty search
|
|
34
47
|
if (!searchQuery) {
|
|
35
48
|
return;
|
|
36
49
|
}
|
|
37
|
-
(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
if (store.error === null)
|
|
51
|
+
(_a = store.search) === null || _a === void 0 ? void 0 : _a.search(searchQuery).then((r) => {
|
|
52
|
+
setOptions(r);
|
|
53
|
+
setState('success');
|
|
54
|
+
}).catch(() => setState('error'));
|
|
41
55
|
}, [searchQuery], 300);
|
|
42
56
|
if (searchQuery &&
|
|
43
57
|
allowArbitrary &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSuggestions.js","sourceRoot":"","sources":["../../../src/components/Suggester/useSuggestions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,
|
|
1
|
+
{"version":3,"file":"useSuggestions.js","sourceRoot":"","sources":["../../../src/components/Suggester/useSuggestions.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchMinisearch.js","sourceRoot":"","sources":["../../../src/utils/search/SearchMinisearch.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,OAAO,gBAAgB;IAO5B,YAAY,IAAgB;QAJ5B,OAAE,GAAyB,IAAI,CAAC;QAEhC,YAAO,GAAG,KAAK,CAAC;QAGf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,SAAS;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAS;QACpB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,CAAC,EAAE,GAAG,IAAI,UAAU,CAAC;YACxB,MAAM,EAAE,UAAU;YAClB,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU;YACxD,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;SAC9B,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,CAAS;QACrB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACxC,OAAO,EAAE,CAAC;QACX,CAAC;QACD,IAAI,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE;YAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;SACjC,CAAe,CAAC;QAEjB,oCAAoC;QACpC,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAE7B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpC,OAAO,IAAI,CAAC;IACb,CAAC;CACD"}
|
|
1
|
+
{"version":3,"file":"SearchMinisearch.js","sourceRoot":"","sources":["../../../src/utils/search/SearchMinisearch.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,OAAO,gBAAgB;IAO5B,YAAY,IAAgB;QAJ5B,OAAE,GAAyB,IAAI,CAAC;QAEhC,YAAO,GAAG,KAAK,CAAC;QAGf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,SAAS;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAS;QACpB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,CAAC,EAAE,GAAG,IAAI,UAAU,CAAC;YACxB,MAAM,EAAE,UAAU;YAClB,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU;YACxD,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;SAC9B,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,CAAS;QACrB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACxC,OAAO,EAAE,CAAC;QACX,CAAC;QACD,IAAI,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE;YAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;SACjC,CAAe,CAAC;QAEjB,oCAAoC;QACpC,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAE7B,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,aAAa,CAAC,EAAO;QACpB,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACd,OAAO,EAAO,CAAC;QAChB,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,CAAM,CAAC;IACzC,CAAC;CACD"}
|
|
@@ -4,6 +4,13 @@ import type { LunaticSource } from '../../use-lunatic/type';
|
|
|
4
4
|
* This file retains a dictionary of all the search indexed by storeName
|
|
5
5
|
*/
|
|
6
6
|
export declare function registerSuggesters(infos: LunaticSource['suggesters'], fetcher: (name: string) => Promise<Array<IndexEntry>>): void;
|
|
7
|
+
export type SearchStore = {
|
|
8
|
+
error: string;
|
|
9
|
+
} | {
|
|
10
|
+
error?: null;
|
|
11
|
+
search: SearchInterface<IndexEntry>;
|
|
12
|
+
index: () => Promise<void>;
|
|
13
|
+
};
|
|
7
14
|
export declare function getSearchForStore(storeName: string): {
|
|
8
15
|
error: string;
|
|
9
16
|
search?: undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SuggestersDatabase.js","sourceRoot":"","sources":["../../../src/utils/search/SuggestersDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAItD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuC,CAAC;AAClE,6DAA6D;AAC7D,IAAI,WAAW,GAAG,CAAC,CAAS,EAA8B,EAAE,CAC3D,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAErB;;GAEG;AACH,MAAM,UAAU,kBAAkB,CACjC,KAAkC,EAClC,OAAqD;IAErD,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO;IACR,CAAC;IACD,WAAW,GAAG,OAAO,CAAC;IACtB,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC/B,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAa,SAAS,CAAC,CAAC,CAAC;IAC7E,CAAC;AACF,CAAC;
|
|
1
|
+
{"version":3,"file":"SuggestersDatabase.js","sourceRoot":"","sources":["../../../src/utils/search/SuggestersDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAItD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuC,CAAC;AAClE,6DAA6D;AAC7D,IAAI,WAAW,GAAG,CAAC,CAAS,EAA8B,EAAE,CAC3D,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAErB;;GAEG;AACH,MAAM,UAAU,kBAAkB,CACjC,KAAkC,EAClC,OAAqD;IAErD,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO;IACR,CAAC;IACD,WAAW,GAAG,OAAO,CAAC;IACtB,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;QAC/B,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,gBAAgB,CAAa,SAAS,CAAC,CAAC,CAAC;IAC7E,CAAC;AACF,CAAC;AAYD,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO;YACN,KAAK,EAAE,iCAAiC,GAAG,SAAS;SACpD,CAAC;IACH,CAAC;IACD,OAAO;QACN,KAAK,EAAE,IAAI;QACX,MAAM;QACN,KAAK,EAAE,GAAG,EAAE;YACX,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,OAAO,OAAO,CAAC,MAAM,EAAE,CAAC;YACzB,CAAC;YACD,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;gBACxB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,CAAC;YACD,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,CAAC;KACD,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
-
import { WrappedSuggester } from './Suggester';
|
|
3
|
+
import { WrappedSuggester } from './Suggester';
|
|
4
4
|
|
|
5
5
|
// Mock of useSuggestions
|
|
6
6
|
vi.mock('./useSuggestions', () => ({
|
|
@@ -11,52 +11,119 @@ vi.mock('./useSuggestions', () => ({
|
|
|
11
11
|
setSearch: vi.fn(),
|
|
12
12
|
onFocus: vi.fn(),
|
|
13
13
|
onBlur: vi.fn(),
|
|
14
|
+
getSelectedLabelById: vi.fn(),
|
|
15
|
+
})),
|
|
16
|
+
|
|
17
|
+
useStore: vi.fn(() => ({
|
|
18
|
+
store: {},
|
|
19
|
+
storeState: 'success',
|
|
20
|
+
setStoreState: vi.fn(),
|
|
21
|
+
getLabelById: vi.fn(),
|
|
14
22
|
})),
|
|
15
23
|
}));
|
|
16
24
|
|
|
25
|
+
const FAKE_PLACE_HOLDER = 'place holder...';
|
|
17
26
|
// Mock of CustomSuggester
|
|
18
27
|
vi.mock('./CustomSuggester', () => ({
|
|
19
28
|
CustomSuggester: vi.fn(({ value }) => (
|
|
20
|
-
<div data-testid="custom-suggester">
|
|
29
|
+
<div data-testid="custom-suggester">
|
|
30
|
+
{Array.isArray(value) && value.length > 0
|
|
31
|
+
? (value[0].value ?? FAKE_PLACE_HOLDER)
|
|
32
|
+
: FAKE_PLACE_HOLDER}
|
|
33
|
+
</div>
|
|
21
34
|
)),
|
|
22
35
|
}));
|
|
23
36
|
|
|
24
37
|
describe('WrappedSuggester useEffect', () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
// Given initial props
|
|
39
|
+
const initialProps = {
|
|
40
|
+
storeName: 'store',
|
|
41
|
+
id: 'suggester-1',
|
|
42
|
+
className: '',
|
|
43
|
+
handleChanges: vi.fn(),
|
|
44
|
+
disabled: false,
|
|
45
|
+
value: 'initialValue',
|
|
46
|
+
label: 'Label',
|
|
47
|
+
declarations: [],
|
|
48
|
+
description: '',
|
|
49
|
+
errors: {},
|
|
50
|
+
readOnly: false,
|
|
51
|
+
response: { name: 'response' },
|
|
52
|
+
optionResponses: [{ name: 'labelResponse', attribute: 'label' }],
|
|
53
|
+
executeExpression: vi.fn(),
|
|
54
|
+
iteration: 1,
|
|
55
|
+
arbitrary: { response: { name: 'ARBITRARY' } },
|
|
56
|
+
optionRenderer: vi.fn(),
|
|
57
|
+
labelRenderer: vi.fn(),
|
|
58
|
+
focused: false,
|
|
59
|
+
};
|
|
60
|
+
it('should display place holder when no value is set', () => {
|
|
61
|
+
// Given the composant initialize
|
|
62
|
+
|
|
63
|
+
const { rerender } = render(
|
|
64
|
+
<WrappedSuggester {...initialProps} value={null} />
|
|
65
|
+
);
|
|
66
|
+
const suggesterValue = screen.getByTestId('custom-suggester').textContent;
|
|
67
|
+
expect(suggesterValue).toContain(FAKE_PLACE_HOLDER);
|
|
48
68
|
|
|
69
|
+
const forcedValue = 'FORCED value';
|
|
70
|
+
|
|
71
|
+
// When we change the value
|
|
72
|
+
const newProps = { ...initialProps, value: forcedValue };
|
|
73
|
+
rerender(<WrappedSuggester {...newProps} />);
|
|
74
|
+
|
|
75
|
+
// Then selectedOptions, i.e value props of customSuggester have to be updated
|
|
76
|
+
const updatedValue = screen.getByTestId('custom-suggester').textContent;
|
|
77
|
+
expect(updatedValue).toContain('FORCED value');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should update selectedOptions when value prop changes', () => {
|
|
49
81
|
// Given the composant initialize
|
|
50
82
|
const { rerender } = render(<WrappedSuggester {...initialProps} />);
|
|
51
83
|
const suggesterValue = screen.getByTestId('custom-suggester').textContent;
|
|
52
84
|
expect(suggesterValue).toContain('initialValue');
|
|
53
85
|
|
|
86
|
+
const forcedValue = 'FORCED value';
|
|
87
|
+
|
|
54
88
|
// When we change the value
|
|
55
|
-
const newProps = { ...initialProps, value:
|
|
89
|
+
const newProps = { ...initialProps, value: forcedValue };
|
|
56
90
|
rerender(<WrappedSuggester {...newProps} />);
|
|
57
91
|
|
|
58
92
|
// Then selectedOptions, i.e value props of customSuggester have to be updated
|
|
59
93
|
const updatedValue = screen.getByTestId('custom-suggester').textContent;
|
|
60
94
|
expect(updatedValue).toContain('FORCED value');
|
|
61
95
|
});
|
|
96
|
+
|
|
97
|
+
it('should update selectedOptions when value prop is set to null', () => {
|
|
98
|
+
// Given the composant initialize
|
|
99
|
+
const { rerender } = render(<WrappedSuggester {...initialProps} />);
|
|
100
|
+
const suggesterValue = screen.getByTestId('custom-suggester').textContent;
|
|
101
|
+
expect(suggesterValue).toContain('initialValue');
|
|
102
|
+
|
|
103
|
+
const forcedValue = null;
|
|
104
|
+
|
|
105
|
+
// When we change the value
|
|
106
|
+
const newProps = { ...initialProps, value: forcedValue };
|
|
107
|
+
rerender(<WrappedSuggester {...newProps} />);
|
|
108
|
+
|
|
109
|
+
// Then selectedOptions, i.e value props of customSuggester have to be updated
|
|
110
|
+
const updatedValue = screen.getByTestId('custom-suggester').textContent;
|
|
111
|
+
expect(updatedValue).toContain(FAKE_PLACE_HOLDER);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should update selectedOptions when value prop is set to an empty string ("")', () => {
|
|
115
|
+
// Given the composant initialize
|
|
116
|
+
const { rerender } = render(<WrappedSuggester {...initialProps} />);
|
|
117
|
+
const suggesterValue = screen.getByTestId('custom-suggester').textContent;
|
|
118
|
+
expect(suggesterValue).toContain('initialValue');
|
|
119
|
+
|
|
120
|
+
const forcedValue = '';
|
|
121
|
+
// When we change the value
|
|
122
|
+
const newProps = { ...initialProps, value: forcedValue };
|
|
123
|
+
rerender(<WrappedSuggester {...newProps} />);
|
|
124
|
+
|
|
125
|
+
// Then selectedOptions, i.e value props of customSuggester have to be updated
|
|
126
|
+
const updatedValue = screen.getByTestId('custom-suggester').textContent;
|
|
127
|
+
expect(updatedValue).toContain(FAKE_PLACE_HOLDER);
|
|
128
|
+
});
|
|
62
129
|
});
|
|
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
import type { LunaticComponentProps } from '../type';
|
|
3
3
|
import { CustomSuggester } from './CustomSuggester';
|
|
4
4
|
import { getComponentErrors } from '../shared/ComponentErrors/ComponentErrors';
|
|
5
|
-
import { OTHER_VALUE, useSuggestions } from './useSuggestions';
|
|
5
|
+
import { OTHER_VALUE, useStore, useSuggestions } from './useSuggestions';
|
|
6
6
|
import D from '../../i18n';
|
|
7
7
|
import type { SuggesterOptionType } from './SuggesterType';
|
|
8
8
|
|
|
@@ -15,6 +15,8 @@ export function Suggester(props: LunaticComponentProps<'Suggester'>) {
|
|
|
15
15
|
return <WrappedSuggester {...props} key={suggesterKey} />;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const ARBITRARY_ID = 'OTHER';
|
|
19
|
+
|
|
18
20
|
export function WrappedSuggester({
|
|
19
21
|
storeName,
|
|
20
22
|
id,
|
|
@@ -36,18 +38,22 @@ export function WrappedSuggester({
|
|
|
36
38
|
arbitrary,
|
|
37
39
|
arbitraryValue,
|
|
38
40
|
}: LunaticComponentProps<'Suggester'>) {
|
|
41
|
+
const { store, storeState, setStoreState, getLabelById } = useStore({
|
|
42
|
+
storeName,
|
|
43
|
+
});
|
|
44
|
+
|
|
39
45
|
// Default options should not change between render
|
|
40
46
|
// so we can break the rule of hooks here
|
|
41
47
|
const computeSelectedOptions = (): [SuggesterOptionType] | [] => {
|
|
42
48
|
if (arbitraryValue) {
|
|
43
|
-
return [{ id:
|
|
49
|
+
return [{ id: ARBITRARY_ID, label: arbitraryValue, value: ARBITRARY_ID }];
|
|
44
50
|
}
|
|
45
51
|
if (!value) {
|
|
46
52
|
return [];
|
|
47
53
|
}
|
|
48
54
|
const labelResponse = optionResponses?.find((o) => o.attribute === 'label');
|
|
49
55
|
if (!labelResponse) {
|
|
50
|
-
return [{ id: value, label: value, value: value }];
|
|
56
|
+
return [{ id: value, label: getLabelById(value), value: value }];
|
|
51
57
|
}
|
|
52
58
|
const label = executeExpression(
|
|
53
59
|
{ value: labelResponse.name, type: 'VTL' },
|
|
@@ -73,7 +79,9 @@ export function WrappedSuggester({
|
|
|
73
79
|
|
|
74
80
|
const { state, options, search, setSearch, onFocus, onBlur } = useSuggestions(
|
|
75
81
|
{
|
|
76
|
-
|
|
82
|
+
store,
|
|
83
|
+
storeState,
|
|
84
|
+
setStoreState,
|
|
77
85
|
allowArbitrary: !!arbitrary,
|
|
78
86
|
selectedOptions: selectedOptions,
|
|
79
87
|
}
|
|
@@ -110,7 +118,7 @@ export function WrappedSuggester({
|
|
|
110
118
|
{ name: response.name, value: null },
|
|
111
119
|
];
|
|
112
120
|
// User chose an arbitrary option or clear the value
|
|
113
|
-
if (arbitrary
|
|
121
|
+
if (arbitrary?.response) {
|
|
114
122
|
newResponses.push({
|
|
115
123
|
name: arbitrary.response.name,
|
|
116
124
|
value: v?.id === OTHER_VALUE ? search : null,
|
|
@@ -145,11 +153,19 @@ export function WrappedSuggester({
|
|
|
145
153
|
setSearch('');
|
|
146
154
|
};
|
|
147
155
|
|
|
148
|
-
// Fix display issue (when handleChanges is called outside this component (in management mode, return to FORCED value by example) )
|
|
149
|
-
// We have to re-compute actual selection
|
|
150
156
|
useEffect(() => {
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
// Fix display issue (when handleChanges is called outside this component (in management mode, return to FORCED value by example)
|
|
158
|
+
// "value" does'nt match selectedOption's "id"
|
|
159
|
+
if (selectedOptions[0]?.id !== value) {
|
|
160
|
+
const actualSelection = computeSelectedOptions();
|
|
161
|
+
const selectedOptionsWithLabel = actualSelection.map((selection) => {
|
|
162
|
+
if (selection.id === ARBITRARY_ID) return selection;
|
|
163
|
+
return {
|
|
164
|
+
...selection,
|
|
165
|
+
label: getLabelById(selection.id),
|
|
166
|
+
};
|
|
167
|
+
}) as [SuggesterOptionType];
|
|
168
|
+
setSelectedOptions(selectedOptionsWithLabel);
|
|
153
169
|
}
|
|
154
170
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
155
171
|
}, [value]);
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import type { SuggesterOptionType } from './SuggesterType';
|
|
3
3
|
import { useEffectDebounced } from '../../hooks/useDebounce';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getSearchForStore,
|
|
6
|
+
SearchStore,
|
|
7
|
+
} from '../../utils/search/SuggestersDatabase';
|
|
5
8
|
import { useRefSync } from '../../hooks/useRefSync';
|
|
6
9
|
|
|
7
10
|
type Props = {
|
|
8
|
-
|
|
11
|
+
store: SearchStore;
|
|
12
|
+
storeState: State;
|
|
13
|
+
setStoreState: (s: State) => any;
|
|
9
14
|
selectedOptions: SuggesterOptionType[];
|
|
10
15
|
allowArbitrary: boolean;
|
|
11
16
|
};
|
|
@@ -14,20 +19,13 @@ export const OTHER_VALUE = 'OTHER';
|
|
|
14
19
|
|
|
15
20
|
type State = 'success' | 'loading' | 'error';
|
|
16
21
|
|
|
17
|
-
export function
|
|
18
|
-
storeName,
|
|
19
|
-
selectedOptions,
|
|
20
|
-
allowArbitrary,
|
|
21
|
-
}: Props) {
|
|
22
|
-
const [searchQuery, setSearchQuery] = useState('');
|
|
22
|
+
export function useStore({ storeName }: { storeName: string }) {
|
|
23
23
|
const store = getSearchForStore(storeName);
|
|
24
24
|
const searchIndexRef = useRefSync(store.index);
|
|
25
|
-
// eslint-disable-next-line prefer-const
|
|
26
|
-
let [options, setOptions] = useState(selectedOptions);
|
|
27
25
|
const [state, setState] = useState<State>(
|
|
28
26
|
store.error !== null
|
|
29
27
|
? 'error'
|
|
30
|
-
: store
|
|
28
|
+
: store?.search.isIndexed()
|
|
31
29
|
? 'success'
|
|
32
30
|
: 'loading'
|
|
33
31
|
);
|
|
@@ -47,19 +45,42 @@ export function useSuggestions({
|
|
|
47
45
|
});
|
|
48
46
|
}, [searchIndexRef]);
|
|
49
47
|
|
|
48
|
+
return {
|
|
49
|
+
store,
|
|
50
|
+
storeState: state,
|
|
51
|
+
setStoreState: setState,
|
|
52
|
+
getLabelById: (id: any) => {
|
|
53
|
+
if (!id) return '';
|
|
54
|
+
return store.search?.getFieldsById(id)?.label ?? '';
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function useSuggestions({
|
|
60
|
+
store,
|
|
61
|
+
storeState: state,
|
|
62
|
+
setStoreState: setState,
|
|
63
|
+
selectedOptions,
|
|
64
|
+
allowArbitrary,
|
|
65
|
+
}: Props) {
|
|
66
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
67
|
+
// eslint-disable-next-line prefer-const
|
|
68
|
+
let [options, setOptions] = useState(selectedOptions);
|
|
69
|
+
|
|
50
70
|
useEffectDebounced(
|
|
51
71
|
() => {
|
|
52
72
|
// Do not reset search for empty search
|
|
53
73
|
if (!searchQuery) {
|
|
54
74
|
return;
|
|
55
75
|
}
|
|
56
|
-
store.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
if (store.error === null)
|
|
77
|
+
store.search
|
|
78
|
+
?.search(searchQuery)
|
|
79
|
+
.then((r) => {
|
|
80
|
+
setOptions(r);
|
|
81
|
+
setState('success');
|
|
82
|
+
})
|
|
83
|
+
.catch(() => setState('error'));
|
|
63
84
|
},
|
|
64
85
|
[searchQuery],
|
|
65
86
|
300
|
|
@@ -23,6 +23,16 @@ export function registerSuggesters(
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export type SearchStore =
|
|
27
|
+
| {
|
|
28
|
+
error: string;
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
error?: null;
|
|
32
|
+
search: SearchInterface<IndexEntry>;
|
|
33
|
+
index: () => Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
|
|
26
36
|
export function getSearchForStore(storeName: string) {
|
|
27
37
|
const search = suggesters.get(storeName);
|
|
28
38
|
if (!search) {
|