@inseefr/lunatic 3.13.5-rc.4 → 3.13.5

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 (41) hide show
  1. package/components/Duration/getDurationFromValue.d.ts +2 -2
  2. package/components/Duration/getDurationFromValue.js +7 -6
  3. package/components/Duration/getDurationFromValue.js.map +1 -1
  4. package/components/shared/MDLabel/MDLabel.js +2 -1
  5. package/components/shared/MDLabel/MDLabel.js.map +1 -1
  6. package/esm/components/Duration/getDurationFromValue.d.ts +2 -2
  7. package/esm/components/Duration/getDurationFromValue.js +7 -6
  8. package/esm/components/Duration/getDurationFromValue.js.map +1 -1
  9. package/esm/components/shared/MDLabel/MDLabel.js +2 -1
  10. package/esm/components/shared/MDLabel/MDLabel.js.map +1 -1
  11. package/esm/hooks/useMultimode.js +21 -10
  12. package/esm/hooks/useMultimode.js.map +1 -1
  13. package/esm/use-lunatic/commons/compile-controls.d.ts +5 -0
  14. package/esm/use-lunatic/commons/compile-controls.js +6 -1
  15. package/esm/use-lunatic/commons/compile-controls.js.map +1 -1
  16. package/esm/use-lunatic/commons/compile-controls.spec.d.ts +1 -0
  17. package/esm/use-lunatic/commons/compile-controls.spec.js +121 -0
  18. package/esm/use-lunatic/commons/compile-controls.spec.js.map +1 -0
  19. package/esm/utils/getArticulationState.js +12 -6
  20. package/esm/utils/getArticulationState.js.map +1 -1
  21. package/hooks/useMultimode.js +20 -8
  22. package/hooks/useMultimode.js.map +1 -1
  23. package/package.json +12 -2
  24. package/src/components/Duration/getDurationFromValue.ts +10 -6
  25. package/src/components/shared/MDLabel/MDLabel.tsx +2 -2
  26. package/src/hooks/useMultimode.ts +27 -13
  27. package/src/stories/questionnaires/SRCV/data.json +116 -0
  28. package/src/stories/questionnaires/SRCV/source.json +69256 -0
  29. package/src/stories/questionnaires/SRCV/srcv.stories.tsx +23 -0
  30. package/src/use-lunatic/commons/compile-controls.spec.ts +136 -0
  31. package/src/use-lunatic/commons/compile-controls.ts +7 -1
  32. package/src/utils/getArticulationState.ts +16 -8
  33. package/tsconfig.build.tsbuildinfo +1 -1
  34. package/use-lunatic/commons/compile-controls.d.ts +5 -0
  35. package/use-lunatic/commons/compile-controls.js +6 -0
  36. package/use-lunatic/commons/compile-controls.js.map +1 -1
  37. package/use-lunatic/commons/compile-controls.spec.d.ts +1 -0
  38. package/use-lunatic/commons/compile-controls.spec.js +123 -0
  39. package/use-lunatic/commons/compile-controls.spec.js.map +1 -0
  40. package/utils/getArticulationState.js +12 -6
  41. package/utils/getArticulationState.js.map +1 -1
@@ -0,0 +1,23 @@
1
+ import {
2
+ type Orchestrator,
3
+ OrchestratorMeta,
4
+ type OrchestratorStory,
5
+ } from '../../utils/Orchestrator';
6
+ import source from './source.json';
7
+ import interrogation from './data.json';
8
+ import { Meta } from '@storybook/react';
9
+
10
+ const meta: Meta<typeof Orchestrator> = {
11
+ title: 'Questionnaires/SRCV',
12
+ ...OrchestratorMeta,
13
+ };
14
+
15
+ export default meta;
16
+
17
+ export const Default: OrchestratorStory = {
18
+ args: {
19
+ source,
20
+ data: interrogation.data,
21
+ initialPage: '179',
22
+ },
23
+ };
@@ -0,0 +1,136 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { LunaticReducerState } from '../type';
3
+ import type { ComponentDefinition, InterpretedComponent } from './component';
4
+ import { computeIterations } from './compile-controls';
5
+
6
+ // Mock de la fonction executeExpression
7
+ const mockExecuteExpression =
8
+ vi.fn() as unknown as LunaticReducerState['executeExpression'] &
9
+ ReturnType<typeof vi.fn>;
10
+
11
+ describe('computeIterations', () => {
12
+ beforeEach(() => {
13
+ vi.resetAllMocks();
14
+ });
15
+
16
+ it('should return iterations directly if it is a number', () => {
17
+ const component = {
18
+ id: 'test',
19
+ componentType: 'Loop',
20
+ iterations: 5,
21
+ } as InterpretedComponent;
22
+ expect(computeIterations(component, mockExecuteExpression)).toBe(5);
23
+ });
24
+
25
+ it('should execute VTL expression for iterations if it is an object', () => {
26
+ const component = {
27
+ id: 'test',
28
+ componentType: 'Loop',
29
+ iterations: { type: 'VTL', value: 'count(ARRAY)' },
30
+ } as ComponentDefinition;
31
+ mockExecuteExpression.mockReturnValue(3);
32
+ expect(computeIterations(component, mockExecuteExpression)).toBe(3);
33
+ expect(mockExecuteExpression).toHaveBeenCalledWith({
34
+ type: 'VTL',
35
+ value: 'count(ARRAY)',
36
+ });
37
+ });
38
+
39
+ it('should return array length if component has a response with an array value', () => {
40
+ const component = {
41
+ id: 'test',
42
+ componentType: 'Question',
43
+ response: { name: 'ARRAY' },
44
+ } as unknown as ComponentDefinition;
45
+ mockExecuteExpression.mockReturnValue(['a', 'b', 'c', 'd']);
46
+ expect(computeIterations(component, mockExecuteExpression)).toBe(4);
47
+ expect(mockExecuteExpression).toHaveBeenCalledWith({
48
+ type: 'VTL',
49
+ value: 'ARRAY',
50
+ });
51
+ });
52
+
53
+ it('should return 0 if response value is not an array', () => {
54
+ const component: ComponentDefinition = {
55
+ id: 'test',
56
+ componentType: 'Question',
57
+ response: { name: 'VAR' },
58
+ } as unknown as ComponentDefinition;
59
+ mockExecuteExpression.mockReturnValue('not an array');
60
+ expect(computeIterations(component, mockExecuteExpression)).toBe(0);
61
+ });
62
+
63
+ it('should return max iterations of children if component has components', () => {
64
+ const component: ComponentDefinition = {
65
+ id: 'parent',
66
+ componentType: 'Loop',
67
+ components: [
68
+ { id: 'child1', componentType: 'Question', iterations: 2 },
69
+ { id: 'child2', componentType: 'Question', iterations: 5 },
70
+ ],
71
+ } as unknown as ComponentDefinition;
72
+ expect(computeIterations(component, mockExecuteExpression)).toBe(5);
73
+ });
74
+
75
+ it('should return 0 if no iterations, response, or components are present', () => {
76
+ const component: ComponentDefinition = {
77
+ id: 'test',
78
+ componentType: 'Question',
79
+ } as unknown as ComponentDefinition;
80
+ expect(computeIterations(component, mockExecuteExpression)).toBe(0);
81
+ });
82
+
83
+ it('should return 0 if iterations is null or undefined', () => {
84
+ const component1: ComponentDefinition = {
85
+ id: 'test1',
86
+ componentType: 'Loop',
87
+ iterations: null as unknown as number,
88
+ } as unknown as ComponentDefinition;
89
+ const component2: ComponentDefinition = {
90
+ id: 'test2',
91
+ componentType: 'Loop',
92
+ iterations: undefined as unknown as number,
93
+ } as unknown as ComponentDefinition;
94
+ expect(computeIterations(component1, mockExecuteExpression)).toBe(0);
95
+ expect(computeIterations(component2, mockExecuteExpression)).toBe(0);
96
+ });
97
+
98
+ it('should return 0 if all children have no iterations', () => {
99
+ const component: ComponentDefinition = {
100
+ id: 'parent',
101
+ componentType: 'Loop',
102
+ components: [
103
+ { id: 'child1', componentType: 'Question' },
104
+ { id: 'child2', componentType: 'Question' },
105
+ ],
106
+ } as unknown as ComponentDefinition;
107
+ expect(computeIterations(component, mockExecuteExpression)).toBe(0);
108
+ });
109
+
110
+ it('should prioritize iterations over response if both are present', () => {
111
+ const component: ComponentDefinition = {
112
+ id: 'test',
113
+ componentType: 'Loop',
114
+ iterations: 3,
115
+ response: { name: 'ARRAY' },
116
+ } as unknown as ComponentDefinition;
117
+ mockExecuteExpression.mockReturnValue(['a', 'b', 'c', 'd', 'e']);
118
+ expect(computeIterations(component, mockExecuteExpression)).toBe(3);
119
+ expect(mockExecuteExpression).not.toHaveBeenCalled();
120
+ });
121
+
122
+ it('should prioritize iterations expression over response if both are present', () => {
123
+ const component: ComponentDefinition = {
124
+ id: 'test',
125
+ componentType: 'Loop',
126
+ iterations: { type: 'VTL', value: 'count(ARRAY)' },
127
+ response: { name: 'ANOTHER_ARRAY' },
128
+ } as unknown as ComponentDefinition;
129
+ mockExecuteExpression.mockReturnValue(7);
130
+ expect(computeIterations(component, mockExecuteExpression)).toBe(7);
131
+ expect(mockExecuteExpression).toHaveBeenCalledWith({
132
+ type: 'VTL',
133
+ value: 'count(ARRAY)',
134
+ });
135
+ });
136
+ });
@@ -115,10 +115,14 @@ function checkControls(
115
115
  /**
116
116
  * Figure out the number of iterations of a component.
117
117
  */
118
- function computeIterations(
118
+ export function computeIterations(
119
119
  component: InterpretedComponent | ComponentDefinition,
120
120
  executeExpression: LunaticReducerState['executeExpression']
121
121
  ): number {
122
+ // iterations is present and it's already computed, return the actual value (case of InterpretedComponent)
123
+ if ('iterations' in component && typeof component.iterations === 'number')
124
+ return component.iterations;
125
+ // iteratons is present and it's an expression, compute it
122
126
  if (
123
127
  'iterations' in component &&
124
128
  component.iterations &&
@@ -127,6 +131,8 @@ function computeIterations(
127
131
  ) {
128
132
  return executeExpression<number>(component.iterations);
129
133
  }
134
+
135
+ // Fallback to variable collectes by this component, return it's length
130
136
  if ('response' in component) {
131
137
  const value = executeExpression({
132
138
  type: 'VTL',
@@ -9,6 +9,7 @@ import { type ReactNode } from 'react';
9
9
  import { times } from './array';
10
10
  import { forceInt } from './number';
11
11
  import { isRoundaboutComponent } from '../use-lunatic/commons/component';
12
+ import { castNumber } from './cast';
12
13
 
13
14
  export enum ArticulationState {
14
15
  COMPLETED = 1,
@@ -70,13 +71,26 @@ export function getArticulationState(
70
71
  source: LunaticSource & { articulation: Articulation },
71
72
  data: LunaticData
72
73
  ): { items: Item[] } {
74
+ if (!source.articulation) {
75
+ return {
76
+ items: [],
77
+ };
78
+ }
79
+
73
80
  const roundabout = findComponentById(
74
81
  source.components,
75
82
  source.articulation.source
76
83
  );
84
+
85
+ if (!roundabout) {
86
+ return {
87
+ items: [],
88
+ };
89
+ }
90
+
77
91
  const { variables } = reducerInitializer({ source, data });
78
- const iterations = forceInt(
79
- variables.run(roundabout?.iterations.value ?? '0')
92
+ const iterations = castNumber(
93
+ variables.run(roundabout.iterations.value ?? '0')
80
94
  );
81
95
 
82
96
  const rows = times(iterations, (k) =>
@@ -86,12 +100,6 @@ export function getArticulationState(
86
100
  }))
87
101
  );
88
102
 
89
- if (!roundabout) {
90
- return {
91
- items: [],
92
- };
93
- }
94
-
95
103
  return {
96
104
  items: rows.map((row, k) => ({
97
105
  cells: row,