@inseefr/lunatic 3.6.6 → 3.6.8-rc.0

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.
Files changed (43) hide show
  1. package/README.md +25 -0
  2. package/components/Loop/Loop.d.ts +1 -1
  3. package/components/Loop/Loop.js +5 -27
  4. package/components/Loop/Loop.js.map +1 -1
  5. package/components/Loop/utils.d.ts +8 -0
  6. package/components/Loop/utils.js +66 -0
  7. package/components/Loop/utils.js.map +1 -0
  8. package/components/RosterForLoop/RosterForLoop.js +4 -27
  9. package/components/RosterForLoop/RosterForLoop.js.map +1 -1
  10. package/esm/components/Loop/Loop.d.ts +1 -1
  11. package/esm/components/Loop/Loop.js +5 -28
  12. package/esm/components/Loop/Loop.js.map +1 -1
  13. package/esm/components/Loop/utils.d.ts +8 -0
  14. package/esm/components/Loop/utils.js +63 -0
  15. package/esm/components/Loop/utils.js.map +1 -0
  16. package/esm/components/RosterForLoop/RosterForLoop.js +5 -29
  17. package/esm/components/RosterForLoop/RosterForLoop.js.map +1 -1
  18. package/esm/use-lunatic/commons/compile-controls.js +8 -0
  19. package/esm/use-lunatic/commons/compile-controls.js.map +1 -1
  20. package/esm/use-lunatic/props/propIterations.js +12 -1
  21. package/esm/use-lunatic/props/propIterations.js.map +1 -1
  22. package/esm/use-lunatic/reducer/controls/check-base-control.js.map +1 -1
  23. package/package.json +15 -3
  24. package/src/components/Loop/Loop.tsx +6 -35
  25. package/src/components/Loop/utils.test.ts +61 -0
  26. package/src/components/Loop/utils.ts +74 -0
  27. package/src/components/RosterForLoop/RosterForLoop.tsx +3 -33
  28. package/src/stories/behaviour/controls/controls.stories.tsx +12 -3
  29. package/src/stories/behaviour/controls/data-standalone-loop.json +17 -0
  30. package/src/stories/behaviour/controls/source-standalone-loop.json +5503 -0
  31. package/src/stories/loop/source-paginated.json +1 -1
  32. package/src/stories/roundabout/sourceWithControl.json +14 -0
  33. package/src/use-lunatic/commons/compile-controls.ts +13 -0
  34. package/src/use-lunatic/props/propIterations.ts +30 -15
  35. package/src/use-lunatic/reducer/controls/check-base-control.ts +0 -1
  36. package/src/use-lunatic/use-lunatic-bug.test.ts +42 -0
  37. package/tsconfig.build.tsbuildinfo +1 -1
  38. package/use-lunatic/commons/compile-controls.js +7 -0
  39. package/use-lunatic/commons/compile-controls.js.map +1 -1
  40. package/use-lunatic/props/propIterations.js +12 -1
  41. package/use-lunatic/props/propIterations.js.map +1 -1
  42. package/use-lunatic/reducer/controls/check-base-control.js.map +1 -1
  43. /package/src/stories/behaviour/controls/{source-loop.json → source-roster-for-loop.json} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inseefr/lunatic",
3
- "version": "3.6.6",
3
+ "version": "3.6.8-rc.0",
4
4
  "description": "Library of questionnaire components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -70,6 +70,8 @@
70
70
  "src/components/Loop/Loop.spec.tsx",
71
71
  "src/components/Loop/Loop.tsx",
72
72
  "src/components/Loop/constant.ts",
73
+ "src/components/Loop/utils.test.ts",
74
+ "src/components/Loop/utils.ts",
73
75
  "src/components/LunaticComponents.tsx",
74
76
  "src/components/PairwiseLinks/PairwiseLinks.tsx",
75
77
  "src/components/Question/Question.tsx",
@@ -238,11 +240,13 @@
238
240
  "src/stories/behaviour/cleaning/source-loop.json",
239
241
  "src/stories/behaviour/cleaning/source.json",
240
242
  "src/stories/behaviour/controls/controls.stories.tsx",
243
+ "src/stories/behaviour/controls/data-standalone-loop.json",
241
244
  "src/stories/behaviour/controls/source-boucles-n.json",
242
- "src/stories/behaviour/controls/source-loop.json",
245
+ "src/stories/behaviour/controls/source-roster-for-loop.json",
243
246
  "src/stories/behaviour/controls/source-roundabout.json",
244
247
  "src/stories/behaviour/controls/source-simple-numeric.json",
245
248
  "src/stories/behaviour/controls/source-simple.json",
249
+ "src/stories/behaviour/controls/source-standalone-loop.json",
246
250
  "src/stories/behaviour/disabled/disabled.stories.tsx",
247
251
  "src/stories/behaviour/disabled/source.json",
248
252
  "src/stories/behaviour/filter/filter.stories.tsx",
@@ -411,6 +415,7 @@
411
415
  "src/use-lunatic/reducer/reducer.ts",
412
416
  "src/use-lunatic/reducer/reducerInitializer.tsx",
413
417
  "src/use-lunatic/type.ts",
418
+ "src/use-lunatic/use-lunatic-bug.test.ts",
414
419
  "src/use-lunatic/use-lunatic.test.ts",
415
420
  "src/use-lunatic/use-lunatic.ts",
416
421
  "src/utils/array.spec.ts",
@@ -535,6 +540,9 @@
535
540
  "components/Loop/constant.d.ts",
536
541
  "components/Loop/constant.js",
537
542
  "components/Loop/constant.js.map",
543
+ "components/Loop/utils.d.ts",
544
+ "components/Loop/utils.js",
545
+ "components/Loop/utils.js.map",
538
546
  "components/LunaticComponents.d.ts",
539
547
  "components/LunaticComponents.js",
540
548
  "components/LunaticComponents.js.map",
@@ -958,6 +966,9 @@
958
966
  "esm/components/Loop/constant.d.ts",
959
967
  "esm/components/Loop/constant.js",
960
968
  "esm/components/Loop/constant.js.map",
969
+ "esm/components/Loop/utils.d.ts",
970
+ "esm/components/Loop/utils.js",
971
+ "esm/components/Loop/utils.js.map",
961
972
  "esm/components/LunaticComponents.d.ts",
962
973
  "esm/components/LunaticComponents.js",
963
974
  "esm/components/LunaticComponents.js.map",
@@ -1976,7 +1987,8 @@
1976
1987
  },
1977
1988
  "devDependencies": {
1978
1989
  "@eslint/js": "^9.9.0",
1979
- "@playwright/test": "^1.46.0",
1990
+ "@playwright/browser-chromium": "^1.51.1",
1991
+ "@playwright/test": "^1.52.0",
1980
1992
  "@storybook/addon-a11y": "^8.2.9",
1981
1993
  "@storybook/addon-essentials": "^8.2.9",
1982
1994
  "@storybook/addon-interactions": "^8.2.9",
@@ -1,4 +1,4 @@
1
- import { type PropsWithChildren, useCallback, useState } from 'react';
1
+ import { type PropsWithChildren } from 'react';
2
2
  import D from '../../i18n';
3
3
  import { times } from '../../utils/array';
4
4
  import { LunaticComponents } from '../LunaticComponents';
@@ -13,45 +13,16 @@ import {
13
13
  getComponentErrors,
14
14
  } from '../shared/ComponentErrors/ComponentErrors';
15
15
  import type { LunaticError } from '../../use-lunatic/type';
16
+ import { useLoopUtils } from './utils';
16
17
 
17
18
  /**
18
19
  * Loop without specific markup (stack of subcomponents)
19
20
  */
20
- export function Loop({
21
- lines,
22
- iterations,
23
- value,
24
- handleChanges,
25
- getComponents,
26
- errors,
27
- ...props
28
- }: LunaticComponentProps<'Loop'>) {
29
- const min = lines?.min ?? 0;
30
- const max = lines?.max ?? Infinity;
31
- const [nbRows, setNbRows] = useState(() => {
32
- return Math.max(iterations, min);
33
- });
34
- const addRow = useCallback(() => {
35
- if (nbRows < max) {
36
- setNbRows(nbRows + 1);
37
- }
38
- }, [max, nbRows]);
39
- const removeRow = useCallback(() => {
40
- if (nbRows > 1) {
41
- const newNbRows = nbRows - 1;
42
- setNbRows(newNbRows);
43
- // Downsize all variables by 1
44
- const newResponses = Object.entries(value).map(([k, v]) => {
45
- return {
46
- name: k,
47
- value: v?.filter((_, i) => i < newNbRows),
48
- };
49
- });
50
- handleChanges(newResponses);
51
- }
52
- }, [nbRows, handleChanges, value]);
21
+ export function Loop(props: LunaticComponentProps<'Loop'>) {
22
+ const { min, max, nbRows, addRow, removeRow } = useLoopUtils(props);
23
+ const { getComponents, errors } = props;
53
24
 
54
- if (nbRows <= 0) {
25
+ if (nbRows === 0) {
55
26
  return null;
56
27
  }
57
28
 
@@ -0,0 +1,61 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { useLoopUtils } from './utils';
4
+
5
+ describe('useLoopUtils()', () => {
6
+ it('should handleChange where values have not the right size', () => {
7
+ const mockHandleChange = vi.fn();
8
+
9
+ renderHook(() =>
10
+ useLoopUtils({
11
+ handleChanges: mockHandleChange,
12
+ iterations: 5,
13
+ lines: { min: 2, max: 10 },
14
+ value: { NAME: ['John', 'Doe'], AGE: [10, 20] },
15
+ })
16
+ );
17
+
18
+ expect(mockHandleChange).toHaveBeenCalledOnce();
19
+ expect(mockHandleChange).toHaveBeenCalledWith([
20
+ { name: 'NAME', value: ['John', 'Doe', null, null, null] },
21
+ { name: 'AGE', value: [10, 20, null, null, null] },
22
+ ]);
23
+ });
24
+
25
+ it('should NOT handleChange where values have the right size', () => {
26
+ const mockHandleChange = vi.fn();
27
+
28
+ renderHook(() =>
29
+ useLoopUtils({
30
+ handleChanges: mockHandleChange,
31
+ iterations: 2,
32
+ lines: { min: 2, max: 10 },
33
+ value: { NAME: ['John', 'Doe'], AGE: [10, 20] },
34
+ })
35
+ );
36
+
37
+ expect(mockHandleChange).toHaveBeenCalledTimes(0);
38
+ });
39
+
40
+ it('should handleChange with iteration when adding row', () => {
41
+ const mockHandleChange = vi.fn();
42
+
43
+ // Given
44
+ const { result } = renderHook(() =>
45
+ useLoopUtils({
46
+ handleChanges: mockHandleChange,
47
+ iterations: 2,
48
+ lines: { min: 2, max: 10 },
49
+ value: { NAME: ['John', 'Doe'], AGE: [10, 20] },
50
+ })
51
+ );
52
+ // When
53
+ result.current.addRow();
54
+
55
+ // Then
56
+ expect(mockHandleChange).toHaveBeenCalledWith([
57
+ { name: 'NAME', value: null, iteration: [2] },
58
+ { name: 'AGE', value: null, iteration: [2] },
59
+ ]);
60
+ });
61
+ });
@@ -0,0 +1,74 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { LunaticComponentProps } from '../type';
3
+ import { resizeArrayVariable } from '../../use-lunatic/reducer/commons';
4
+
5
+ const DEFAULT_MIN_ROWS = 1;
6
+ const DEFAULT_MAX_ROWS = 12;
7
+
8
+ export const useLoopUtils = (
9
+ props: Pick<
10
+ LunaticComponentProps<'RosterForLoop'> | LunaticComponentProps<'Loop'>,
11
+ 'lines' | 'iterations' | 'value' | 'handleChanges'
12
+ >
13
+ ) => {
14
+ const { lines, iterations, value: valueMap, handleChanges } = props;
15
+ const min = lines?.min ?? DEFAULT_MIN_ROWS;
16
+ const max = lines?.max ?? DEFAULT_MAX_ROWS;
17
+ const [nbRows, setNbRows] = useState(
18
+ Math.max(min, iterations ?? DEFAULT_MIN_ROWS)
19
+ );
20
+
21
+ /**
22
+ * For Loop & rosterForLoop,
23
+ * Value can be inconsistent i.e the value has not the right size
24
+ * The function add null values to the end of value (array), only when component is mount
25
+ *
26
+ * This the case when size of value is defined by VTL (but the value is not triggered by change)
27
+ * - ex: external variable indicate the size of variable
28
+ * - ex: min != max
29
+ *
30
+ * Improvment: do some kind of dynamic resizing and remove this useEffect.
31
+ * Variables must be consistent in variable-store.
32
+ */
33
+ useEffect(() => {
34
+ const initialResponses = Object.entries(valueMap)
35
+ .filter(([, v]) => (v?.length ?? 0) < nbRows)
36
+ .map(([k, v]) => ({
37
+ name: k,
38
+ value: resizeArrayVariable(v, nbRows, null),
39
+ }));
40
+ if (initialResponses.length > 0) handleChanges(initialResponses);
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, []);
43
+
44
+ const addRow = useCallback(() => {
45
+ if (nbRows < max) {
46
+ const newNbRows = nbRows + 1;
47
+ setNbRows(newNbRows);
48
+ const newResponses = Object.entries(valueMap).map(([k]) => ({
49
+ name: k,
50
+ value: null,
51
+ iteration: [nbRows],
52
+ }));
53
+ handleChanges(newResponses);
54
+ }
55
+ }, [max, nbRows, valueMap, handleChanges]);
56
+
57
+ const removeRow = useCallback(() => {
58
+ if (nbRows <= min) {
59
+ return;
60
+ }
61
+ const newNbRows = nbRows - 1;
62
+ setNbRows(newNbRows);
63
+ // Downsize all variables by 1
64
+ const newResponses = Object.entries(valueMap).map(([k, v]) => {
65
+ return {
66
+ name: k,
67
+ value: v?.filter((_, i) => i < newNbRows),
68
+ };
69
+ });
70
+ handleChanges(newResponses);
71
+ }, [nbRows, min, valueMap, handleChanges]);
72
+
73
+ return { min, max, nbRows, addRow, removeRow };
74
+ };
@@ -1,4 +1,4 @@
1
- import { Fragment, useCallback, useState } from 'react';
1
+ import { Fragment } from 'react';
2
2
  import type { LunaticComponentProps } from '../type';
3
3
  import { Table, Tbody, Td, Tr, TableHeader } from '../shared/Table';
4
4
  import { times } from '../../utils/array';
@@ -9,9 +9,7 @@ import {
9
9
  getComponentErrors,
10
10
  } from '../shared/ComponentErrors/ComponentErrors';
11
11
  import { CustomLoop } from '../Loop/Loop';
12
-
13
- const DEFAULT_MIN_ROWS = 1;
14
- const DEFAULT_MAX_ROWS = 12;
12
+ import { useLoopUtils } from '../Loop/utils';
15
13
 
16
14
  /**
17
15
  * Loop displayed as a table
@@ -19,46 +17,18 @@ const DEFAULT_MAX_ROWS = 12;
19
17
  export const RosterForLoop = (
20
18
  props: LunaticComponentProps<'RosterForLoop'>
21
19
  ) => {
20
+ const { min, max, nbRows, addRow, removeRow } = useLoopUtils(props);
22
21
  const {
23
- value: valueMap,
24
- lines,
25
22
  errors,
26
- handleChanges,
27
23
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
24
  declarations,
29
25
  header,
30
- iterations,
31
26
  id,
32
27
  getComponents,
33
28
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
34
29
  label,
35
30
  ...otherProps // These props will be passed down to the child components
36
31
  } = props;
37
- const min = lines?.min ?? DEFAULT_MIN_ROWS;
38
- const max = lines?.max ?? DEFAULT_MAX_ROWS;
39
- const [nbRows, setNbRows] = useState(Math.max(min, iterations));
40
-
41
- const addRow = useCallback(() => {
42
- if (nbRows < max) {
43
- setNbRows(nbRows + 1);
44
- }
45
- }, [max, nbRows]);
46
-
47
- const removeRow = useCallback(() => {
48
- if (nbRows <= min) {
49
- return;
50
- }
51
- const newNbRows = nbRows - 1;
52
- setNbRows(newNbRows);
53
- // Downsize all variables by 1
54
- const newResponses = Object.entries(valueMap).map(([k, v]) => {
55
- return {
56
- name: k,
57
- value: v?.filter((_, i) => i < newNbRows),
58
- };
59
- });
60
- handleChanges(newResponses);
61
- }, [nbRows, min, valueMap, handleChanges]);
62
32
 
63
33
  if (nbRows === 0) {
64
34
  return null;
@@ -5,7 +5,9 @@ import {
5
5
  } from '../../utils/Orchestrator';
6
6
  import simple from './source-simple.json';
7
7
  import simpleNum from './source-simple-numeric.json';
8
- import sourceLoop from './source-loop.json';
8
+ import sourceRosterForLoop from './source-roster-for-loop.json';
9
+ import sourceStandaloneLoop from './source-standalone-loop.json';
10
+ import dataStandaloneLoop from './data-standalone-loop.json';
9
11
  import sourceRoundabout from './source-roundabout.json';
10
12
  import boucleNTabDynamique from './source-boucles-n.json';
11
13
 
@@ -38,9 +40,16 @@ export const SimpleNum: OrchestratorStory = {
38
40
 
39
41
  export const LinkedLoop: OrchestratorStory = {};
40
42
 
41
- export const Loop: OrchestratorStory = {
43
+ export const RosterForLoop: OrchestratorStory = {
42
44
  args: {
43
- source: sourceLoop,
45
+ source: sourceRosterForLoop,
46
+ },
47
+ };
48
+
49
+ export const StandaloneLoop: OrchestratorStory = {
50
+ args: {
51
+ source: sourceStandaloneLoop,
52
+ data: dataStandaloneLoop,
44
53
  },
45
54
  };
46
55
 
@@ -0,0 +1,17 @@
1
+ {
2
+ "EXTERNAL": {
3
+ "NIR": ["23456", "23456", "23456", "23456", "23456", "12345"],
4
+ "CODE_CC": ["CODE", "CODE", "CODE", "CODE", "CODE", "CODE"],
5
+ "NBSAL_INT": "6",
6
+ "LIBELLE_CC": ["LIB CC", "LIB CC", "LIB CC", "LIB CC", "LIB CC", "LIB CC"],
7
+ "NUM_ORDRE_SALARIE": ["1", "2", "3", "4", "5", "6"],
8
+ "NOM_COMPLET_SALARIE": [
9
+ "THREEPWOOD GUYBRUSH",
10
+ "LINK",
11
+ "JANE DOE",
12
+ "JOHN DOE",
13
+ "IL FAIT CHAUD",
14
+ "POISSON STEVE"
15
+ ]
16
+ }
17
+ }