@inseefr/lunatic 3.5.7 → 3.6.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 (254) hide show
  1. package/components/shared/Combobox/Combobox.stories.d.ts +3 -2
  2. package/components/shared/Combobox/Combobox.stories.js +26 -21
  3. package/components/shared/Combobox/Combobox.stories.js.map +1 -1
  4. package/esm/components/shared/Combobox/Combobox.stories.d.ts +3 -2
  5. package/esm/components/shared/Combobox/Combobox.stories.js +28 -24
  6. package/esm/components/shared/Combobox/Combobox.stories.js.map +1 -1
  7. package/esm/hooks/useLocalStorage.d.ts +1 -0
  8. package/esm/hooks/useLocalStorage.js +31 -0
  9. package/esm/hooks/useLocalStorage.js.map +1 -0
  10. package/esm/type.source.d.ts +8 -1
  11. package/esm/use-lunatic/commons/compile-controls.js +22 -14
  12. package/esm/use-lunatic/commons/compile-controls.js.map +1 -1
  13. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.d.ts +1 -1
  14. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.js +5 -2
  15. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.js.map +1 -1
  16. package/esm/use-lunatic/commons/fill-components/fill-component.spec.js +42 -0
  17. package/esm/use-lunatic/commons/fill-components/fill-component.spec.js.map +1 -1
  18. package/esm/use-lunatic/commons/fill-components/fill-components.d.ts +2 -2
  19. package/esm/use-lunatic/commons/fill-components/fill-components.js +15 -3
  20. package/esm/use-lunatic/commons/fill-components/fill-components.js.map +1 -1
  21. package/esm/use-lunatic/hooks/use-page-has-response.js +1 -1
  22. package/esm/use-lunatic/hooks/use-page-has-response.js.map +1 -1
  23. package/esm/use-lunatic/hooks/useOverview.spec.js +2 -2
  24. package/esm/use-lunatic/hooks/useOverview.spec.js.map +1 -1
  25. package/esm/use-lunatic/props/getComponentTypeProps.js +1 -1
  26. package/esm/use-lunatic/props/getComponentTypeProps.js.map +1 -1
  27. package/esm/utils/number.d.ts +4 -0
  28. package/esm/utils/number.js +12 -0
  29. package/esm/utils/number.js.map +1 -1
  30. package/esm/utils/object.d.ts +10 -0
  31. package/esm/utils/object.js +26 -0
  32. package/esm/utils/object.js.map +1 -1
  33. package/hooks/useLocalStorage.d.ts +1 -0
  34. package/hooks/useLocalStorage.js +34 -0
  35. package/hooks/useLocalStorage.js.map +1 -0
  36. package/package.json +76 -137
  37. package/src/components/shared/Combobox/Combobox.stories.tsx +48 -49
  38. package/src/hooks/useLocalStorage.ts +37 -0
  39. package/src/json.d.ts +16 -0
  40. package/src/stories/accordion/accordion.stories.tsx +21 -0
  41. package/src/stories/behaviour/cleaning/cleaning.stories.tsx +40 -0
  42. package/src/stories/behaviour/controls/controls.stories.tsx +51 -0
  43. package/src/stories/behaviour/disabled/disabled.stories.tsx +29 -0
  44. package/src/stories/behaviour/filter/filter.stories.tsx +34 -0
  45. package/src/stories/behaviour/missing/missing.stories.tsx +31 -0
  46. package/src/stories/behaviour/overview/overview.stories.tsx +61 -0
  47. package/src/stories/{overview/sourceWithHierarchy.json → behaviour/overview/source.json} +1 -1
  48. package/src/stories/{overview → behaviour/overview}/sourceLoop.json +1 -1
  49. package/src/stories/behaviour/performance/performance.stories.tsx +29 -0
  50. package/src/stories/behaviour/resizing/resizing.stories.tsx +34 -0
  51. package/src/stories/behaviour/slots.stories.tsx +34 -0
  52. package/src/stories/checkbox/checkbox.stories.tsx +75 -0
  53. package/src/stories/{checkbox-group/sourceLoop.json → checkbox/sourceGroupLoop.json} +1 -1
  54. package/src/stories/datepicker/datepicker.stories.tsx +21 -0
  55. package/src/stories/declaration/declaration.stories.tsx +27 -0
  56. package/src/stories/dropdown/dropdown.stories.tsx +27 -0
  57. package/src/stories/duration/duration.stories.tsx +27 -0
  58. package/src/stories/filter-description/filter-description.stories.tsx +28 -0
  59. package/src/stories/input/input.stories.tsx +21 -0
  60. package/src/stories/input-number/input-number.stories.tsx +35 -0
  61. package/src/stories/loop/loop.stories.tsx +35 -0
  62. package/src/stories/loop/roster-for-loop.stories.tsx +59 -0
  63. package/src/stories/pairwise/pairwise.stories.tsx +30 -0
  64. package/src/stories/paste.stories.tsx +85 -0
  65. package/src/stories/question/question.stories.tsx +21 -0
  66. package/src/stories/questionnaires/logement/logement.stories.tsx +26 -0
  67. package/src/stories/questionnaires/recensement/recensement.stories.tsx +28 -0
  68. package/src/stories/questionnaires/rp/rp.stories.tsx +21 -0
  69. package/src/stories/questionnaires/simpsons/simpsons.stories.tsx +31 -0
  70. package/src/stories/radio/radio.stories.tsx +53 -0
  71. package/src/stories/roundabout/roundabout.stories.tsx +37 -0
  72. package/src/stories/roundabout/source.json +1 -0
  73. package/src/stories/sequence/sequence.stories.tsx +28 -0
  74. package/src/stories/suggester/source-option-responses.json +1 -1
  75. package/src/stories/suggester/suggester.stories.tsx +68 -0
  76. package/src/stories/summary/source.json +22 -1
  77. package/src/stories/summary/summary.stories.tsx +31 -0
  78. package/src/stories/table/table.stories.tsx +35 -0
  79. package/src/stories/text/text.stories.tsx +38 -0
  80. package/src/stories/textarea/textarea.stories.tsx +21 -0
  81. package/src/stories/utils/Orchestrator.tsx +310 -0
  82. package/src/stories/utils/OrchestratorData.tsx +176 -0
  83. package/src/stories/utils/OrchestratorOverview.tsx +70 -0
  84. package/src/stories/utils/OrchestratorSidebar.tsx +119 -0
  85. package/src/stories/utils/SchemaValidator.tsx +29 -0
  86. package/src/stories/utils/referentiel.ts +9 -0
  87. package/src/type.source.ts +8 -1
  88. package/src/use-lunatic/commons/compile-controls.ts +36 -18
  89. package/src/use-lunatic/commons/fill-components/fill-component-expressions.ts +6 -2
  90. package/src/use-lunatic/commons/fill-components/fill-component.spec.ts +52 -0
  91. package/src/use-lunatic/commons/fill-components/fill-components.ts +19 -3
  92. package/src/use-lunatic/hooks/use-page-has-response.ts +1 -1
  93. package/src/use-lunatic/hooks/useOverview.spec.ts +3 -2
  94. package/src/use-lunatic/props/getComponentTypeProps.ts +2 -1
  95. package/src/use-lunatic/use-lunatic.test.ts +53 -40
  96. package/src/utils/number.ts +13 -0
  97. package/src/utils/object.ts +40 -0
  98. package/tsconfig.build.tsbuildinfo +1 -1
  99. package/type.source.d.ts +8 -1
  100. package/use-lunatic/commons/compile-controls.js +21 -13
  101. package/use-lunatic/commons/compile-controls.js.map +1 -1
  102. package/use-lunatic/commons/fill-components/fill-component-expressions.d.ts +1 -1
  103. package/use-lunatic/commons/fill-components/fill-component-expressions.js +6 -3
  104. package/use-lunatic/commons/fill-components/fill-component-expressions.js.map +1 -1
  105. package/use-lunatic/commons/fill-components/fill-component.spec.js +42 -0
  106. package/use-lunatic/commons/fill-components/fill-component.spec.js.map +1 -1
  107. package/use-lunatic/commons/fill-components/fill-components.d.ts +2 -2
  108. package/use-lunatic/commons/fill-components/fill-components.js +15 -3
  109. package/use-lunatic/commons/fill-components/fill-components.js.map +1 -1
  110. package/use-lunatic/hooks/use-page-has-response.js +2 -2
  111. package/use-lunatic/hooks/use-page-has-response.js.map +1 -1
  112. package/use-lunatic/hooks/useOverview.spec.js +8 -8
  113. package/use-lunatic/hooks/useOverview.spec.js.map +1 -1
  114. package/use-lunatic/props/getComponentTypeProps.js +1 -1
  115. package/use-lunatic/props/getComponentTypeProps.js.map +1 -1
  116. package/utils/number.d.ts +4 -0
  117. package/utils/number.js +13 -0
  118. package/utils/number.js.map +1 -1
  119. package/utils/object.d.ts +10 -0
  120. package/utils/object.js +30 -0
  121. package/utils/object.js.map +1 -1
  122. package/esm/stories/overview/sourceWithHierarchy.json +0 -5151
  123. package/esm/tests/utils/lunatic.d.ts +0 -15
  124. package/esm/tests/utils/lunatic.js +0 -27
  125. package/esm/tests/utils/lunatic.js.map +0 -1
  126. package/esm/use-lunatic/replace-component-sequence.d.ts +0 -36
  127. package/esm/use-lunatic/replace-component-sequence.js +0 -19
  128. package/esm/use-lunatic/replace-component-sequence.js.map +0 -1
  129. package/esm/use-lunatic/test.utils.d.ts +0 -2
  130. package/esm/use-lunatic/test.utils.js +0 -13
  131. package/esm/use-lunatic/test.utils.js.map +0 -1
  132. package/esm/utils/is-object.d.ts +0 -4
  133. package/esm/utils/is-object.js +0 -7
  134. package/esm/utils/is-object.js.map +0 -1
  135. package/esm/utils/to-number.d.ts +0 -4
  136. package/esm/utils/to-number.js +0 -13
  137. package/esm/utils/to-number.js.map +0 -1
  138. package/src/stories/accordion/accordion.stories.jsx +0 -17
  139. package/src/stories/behaviour/cleaning/cleaning.stories.jsx +0 -69
  140. package/src/stories/behaviour/controls/controls.stories.jsx +0 -81
  141. package/src/stories/behaviour/filter/dataLoop.json +0 -14
  142. package/src/stories/behaviour/filter/filter.stories.jsx +0 -36
  143. package/src/stories/behaviour/missing/missing.stories.jsx +0 -69
  144. package/src/stories/behaviour/others/V2_DeclarationsSimples.json +0 -908
  145. package/src/stories/behaviour/others/V2_MinMaxSum_Boucles.json +0 -489
  146. package/src/stories/behaviour/others/V2_QuestSimple_Boucles.json +0 -3919
  147. package/src/stories/behaviour/others/V2_TCMRallyeGames.json +0 -2760
  148. package/src/stories/behaviour/others/test-dylan.json +0 -538
  149. package/src/stories/behaviour/others/test.stories.jsx +0 -78
  150. package/src/stories/behaviour/paste/source.json +0 -32
  151. package/src/stories/behaviour/paste/test.stories.jsx +0 -62
  152. package/src/stories/behaviour/performance/performance.stories.jsx +0 -26
  153. package/src/stories/behaviour/resizing/resizing.stories.jsx +0 -60
  154. package/src/stories/behaviour/slots.stories.jsx +0 -32
  155. package/src/stories/checkbox-boolean/checkboxBoolean.stories.jsx +0 -17
  156. package/src/stories/checkbox-group/checkbox-group.stories.jsx +0 -77
  157. package/src/stories/checkbox-one/checkboxOne.stories.jsx +0 -53
  158. package/src/stories/date-picker/data.json +0 -3
  159. package/src/stories/date-picker/datepicker.stories.jsx +0 -26
  160. package/src/stories/declaration/data.json +0 -1
  161. package/src/stories/declaration/input.stories.jsx +0 -18
  162. package/src/stories/disabled/data.json +0 -16
  163. package/src/stories/disabled/disabled.stories.jsx +0 -18
  164. package/src/stories/dropdown/data.json +0 -8
  165. package/src/stories/dropdown/dropdown.stories.jsx +0 -25
  166. package/src/stories/duration/duration.stories.jsx +0 -25
  167. package/src/stories/filter-description/filter-description.stories.jsx +0 -37
  168. package/src/stories/input/data.json +0 -1
  169. package/src/stories/input/input.stories.jsx +0 -18
  170. package/src/stories/input-number/input-number.stories.jsx +0 -23
  171. package/src/stories/loop/loop.stories.jsx +0 -29
  172. package/src/stories/loop/roster-for-loop.stories.jsx +0 -46
  173. package/src/stories/markdown/markdown.stories.jsx +0 -20
  174. package/src/stories/overview/data.json +0 -1
  175. package/src/stories/overview/dataLoop.json +0 -93
  176. package/src/stories/overview/overview.stories.jsx +0 -44
  177. package/src/stories/overview/source.json +0 -25
  178. package/src/stories/pairwise/data.json +0 -12
  179. package/src/stories/pairwise/pairwise-links.stories.jsx +0 -48
  180. package/src/stories/question/question.stories.jsx +0 -16
  181. package/src/stories/questionnaires/logement/logement.stories.jsx +0 -59
  182. package/src/stories/questionnaires/recensement/data.json +0 -12
  183. package/src/stories/questionnaires/recensement/recensement.stories.jsx +0 -35
  184. package/src/stories/questionnaires/rp/data.json +0 -5
  185. package/src/stories/questionnaires/rp/rp.stories.jsx +0 -23
  186. package/src/stories/questionnaires/simpsons/simpsons.stories.jsx +0 -246
  187. package/src/stories/radio/radio.stories.jsx +0 -78
  188. package/src/stories/roundabout/data1.json +0 -13
  189. package/src/stories/roundabout/data2.json +0 -16
  190. package/src/stories/roundabout/roundabout.stories.jsx +0 -32
  191. package/src/stories/sequence/sequence.stories.jsx +0 -29
  192. package/src/stories/suggester/suggester.stories.jsx +0 -71
  193. package/src/stories/summary/data.json +0 -16
  194. package/src/stories/summary/summary.stories.jsx +0 -23
  195. package/src/stories/switch/README.md +0 -29
  196. package/src/stories/switch/data-forced.json +0 -40
  197. package/src/stories/switch/source.json +0 -64
  198. package/src/stories/switch/switch.stories.jsx +0 -17
  199. package/src/stories/table/data.json +0 -1
  200. package/src/stories/table/table.stories.jsx +0 -30
  201. package/src/stories/text/data-roster.json +0 -5
  202. package/src/stories/text/text.stories.jsx +0 -20
  203. package/src/stories/textarea/data.json +0 -1
  204. package/src/stories/textarea/textarea.stories.jsx +0 -18
  205. package/src/stories/utils/SchemaValidator.jsx +0 -40
  206. package/src/stories/utils/default-arg-types.js +0 -39
  207. package/src/stories/utils/default-args.js +0 -3
  208. package/src/stories/utils/options.js +0 -19
  209. package/src/stories/utils/orchestrator.jsx +0 -267
  210. package/src/stories/utils/orchestrator.scss +0 -66
  211. package/src/stories/utils/overview.jsx +0 -39
  212. package/src/stories/utils/overview.scss +0 -37
  213. package/src/stories/utils/referentiel.js +0 -7
  214. package/src/tests/utils/e2e.js +0 -91
  215. package/src/tests/utils/lunatic.ts +0 -33
  216. package/src/use-lunatic/replace-component-sequence.ts +0 -25
  217. package/src/use-lunatic/test.utils.ts +0 -17
  218. package/src/utils/is-object.ts +0 -6
  219. package/src/utils/to-number.ts +0 -12
  220. package/stories/overview/sourceWithHierarchy.json +0 -5151
  221. package/tests/utils/lunatic.d.ts +0 -15
  222. package/tests/utils/lunatic.js +0 -31
  223. package/tests/utils/lunatic.js.map +0 -1
  224. package/use-lunatic/replace-component-sequence.d.ts +0 -36
  225. package/use-lunatic/replace-component-sequence.js +0 -22
  226. package/use-lunatic/replace-component-sequence.js.map +0 -1
  227. package/use-lunatic/test.utils.d.ts +0 -2
  228. package/use-lunatic/test.utils.js +0 -17
  229. package/use-lunatic/test.utils.js.map +0 -1
  230. package/utils/is-object.d.ts +0 -4
  231. package/utils/is-object.js +0 -10
  232. package/utils/is-object.js.map +0 -1
  233. package/utils/to-number.d.ts +0 -4
  234. package/utils/to-number.js +0 -16
  235. package/utils/to-number.js.map +0 -1
  236. /package/src/stories/behaviour/cleaning/{loop.json → source-loop-scopes.json} +0 -0
  237. /package/src/stories/behaviour/controls/{boucles-n.json → source-boucles-n.json} +0 -0
  238. /package/src/stories/behaviour/controls/{loop.json → source-loop.json} +0 -0
  239. /package/src/stories/behaviour/controls/{roundabout.json → source-roundabout.json} +0 -0
  240. /package/src/stories/behaviour/controls/{simple-numeric.json → source-simple-numeric.json} +0 -0
  241. /package/src/stories/behaviour/controls/{simple.json → source-simple.json} +0 -0
  242. /package/src/stories/{disabled → behaviour/disabled}/source.json +0 -0
  243. /package/src/stories/{checkbox-boolean → checkbox}/source.json +0 -0
  244. /package/src/stories/{checkbox-group/source.json → checkbox/sourceGroup.json} +0 -0
  245. /package/src/stories/{checkbox-group/sourceCondition.json → checkbox/sourceGroupCondition.json} +0 -0
  246. /package/src/stories/{checkbox-group/sourceDetail.json → checkbox/sourceGroupDetail.json} +0 -0
  247. /package/src/stories/{checkbox-one/source.json → checkbox/sourceOne.json} +0 -0
  248. /package/src/stories/{checkbox-one/sourceDetail.json → checkbox/sourceOneDetail.json} +0 -0
  249. /package/src/stories/{date-picker → datepicker}/source.json +0 -0
  250. /package/src/stories/{markdown/source.json → declaration/sourceMarkdown.json} +0 -0
  251. /package/src/stories/duration/{mois.json → sourceMonths.json} +0 -0
  252. /package/src/stories/duration/{time.json → sourceTime.json} +0 -0
  253. /package/src/stories/filter-description/{source-options.json → sourceOptions.json} +0 -0
  254. /package/src/stories/table/{table-dynamique.json → source-dynamic.json} +0 -0
@@ -239,6 +239,8 @@ export type Variable =
239
239
  variableType: 'EXTERNAL';
240
240
  name: string;
241
241
  value: VariableValue;
242
+ iterationReference?: string;
243
+ dimension?: number;
242
244
  }
243
245
  | {
244
246
  variableType: 'COLLECTED';
@@ -246,6 +248,8 @@ export type Variable =
246
248
  values?: {
247
249
  COLLECTED: VariableValue;
248
250
  };
251
+ iterationReference?: string;
252
+ dimension?: number;
249
253
  }
250
254
  | {
251
255
  variableType: 'CALCULATED';
@@ -253,6 +257,8 @@ export type Variable =
253
257
  expression: VTLExpression;
254
258
  bindingDependencies?: string[];
255
259
  shapeFrom?: string[] | string;
260
+ iterationReference?: string;
261
+ dimension?: number;
256
262
  };
257
263
  export type VariableValue = VariableScalarValue | unknown[];
258
264
  export type VariableScalarValue = string | number | null;
@@ -340,6 +346,7 @@ export type ComponentDefinitionBase = {
340
346
  id: string;
341
347
  isMandatory?: boolean;
342
348
  missingResponse?: ResponseDefinition;
349
+ conditionReadOnly?: VTLScalarExpression;
343
350
  };
344
351
  export type Declaration = {
345
352
  id: string;
@@ -371,7 +378,7 @@ export type VTLScalarExpression = {
371
378
  export type ControlDefinition = {
372
379
  id: string;
373
380
  criticality: 'INFO' | 'WARN' | 'ERROR';
374
- typeOfControl?: 'FORMAT' | 'CONSISTENCY';
381
+ typeOfControl?: 'FORMAT' | 'CONSISTENCY' | 'MANDATORY';
375
382
  control: VTLExpression;
376
383
  errorMessage: VTLExpression;
377
384
  bindingDependencies?: string[];
@@ -1,4 +1,3 @@
1
- import { replaceComponentSequence } from '../replace-component-sequence';
2
1
  import type {
3
2
  LunaticComponentDefinition,
4
3
  LunaticControl,
@@ -27,22 +26,29 @@ type InterpretedLoopComponent = DeepTranslateExpression<
27
26
  >;
28
27
 
29
28
  const isLoopComponent = (
30
- component: InterpretedComponent
29
+ component: ComponentDefinition | InterpretedComponent
31
30
  ): component is InterpretedLoopComponent => {
32
31
  return ['Loop', 'RosterForLoop', 'Roundabout'].includes(
33
32
  component.componentType
34
33
  );
35
34
  };
36
35
 
36
+ const isQuestionComponent = (
37
+ component: ComponentDefinition | InterpretedComponent
38
+ ) => {
39
+ return 'Question' === component.componentType;
40
+ };
41
+
37
42
  /**
38
43
  * Check if components of the current page have errors, and return a map of
39
44
  * errors (indexed by component ID).
40
45
  */
41
46
  function checkComponents(
42
47
  state: StateForControls,
43
- components: InterpretedComponent[]
48
+ components: (ComponentDefinition | InterpretedComponent)[],
49
+ currentErrors?: Record<string, LunaticError[]>
44
50
  ): Record<string, LunaticError[]> {
45
- let errors = {} as Record<string, LunaticError[]>;
51
+ let errors = currentErrors ?? ({} as Record<string, LunaticError[]>);
46
52
 
47
53
  for (const component of components) {
48
54
  // The component has global level controls
@@ -58,24 +64,36 @@ function checkComponents(
58
64
  }
59
65
 
60
66
  // For loop, inspect children
61
- if (isLoopComponent(component)) {
62
- const rowControls = component.controls?.filter((c) => c.type === 'ROW');
63
- if (rowControls?.length) {
64
- errors = checkComponentInLoop(
65
- state,
66
- { ...component, controls: rowControls },
67
- errors
68
- );
69
- }
70
- for (const child of component.components) {
71
- errors = checkComponentInLoop(state, child, errors);
72
- }
73
- }
67
+ if (isLoopComponent(component))
68
+ errors = checkLoop(state, component, errors);
69
+
70
+ // For Question, loop over children
71
+ if (isQuestionComponent(component))
72
+ errors = checkComponents(state, component.components, errors);
74
73
  }
75
74
 
76
75
  return errors;
77
76
  }
78
77
 
78
+ function checkLoop(
79
+ state: StateForControls,
80
+ component: InterpretedLoopComponent,
81
+ errors: Record<string, LunaticError[]>
82
+ ) {
83
+ const rowControls = component.controls?.filter((c) => c.type === 'ROW');
84
+ if (rowControls?.length) {
85
+ errors = checkComponentInLoop(
86
+ state,
87
+ { ...component, controls: rowControls },
88
+ errors
89
+ );
90
+ }
91
+ for (const child of component.components) {
92
+ errors = checkComponentInLoop(state, child, errors);
93
+ }
94
+ return errors;
95
+ }
96
+
79
97
  function checkControls(
80
98
  controls: LunaticControl[],
81
99
  executeExpression: LunaticReducerState['executeExpression'],
@@ -214,7 +232,7 @@ function hasCriticalError(errors?: Record<string, LunaticError[]>): boolean {
214
232
  * Check controls for currently visible components and output errors.
215
233
  */
216
234
  export function compileControls(state: StateForControls) {
217
- const components = replaceComponentSequence(getComponentsFromState(state));
235
+ const components = getComponentsFromState(state);
218
236
  const componentFiltered = components
219
237
  .map((component) => fillComponentExpressions(component, state))
220
238
  .filter((component) => {
@@ -1,10 +1,10 @@
1
1
  import type { ReactNode } from 'react';
2
- import { isObject } from '../../../utils/is-object';
3
2
  import type {
4
3
  LunaticComponentDefinition,
5
4
  LunaticExpression,
6
5
  LunaticReducerState,
7
6
  } from '../../type';
7
+ import { isObject } from '../../../utils/object';
8
8
 
9
9
  const VTL_ATTRIBUTES = [
10
10
  ['label', null],
@@ -28,7 +28,7 @@ const VTL_ATTRIBUTES = [
28
28
  ['conditionFilter', castBool],
29
29
  ['header.label', null],
30
30
  ['disabled', castBool],
31
- ['readOnly', castBool],
31
+ ['conditionReadOnly', castBool],
32
32
  // For suggesters
33
33
  ['arbitrary.label', castString],
34
34
  ['arbitrary.inputLabel', castString],
@@ -47,6 +47,9 @@ function castNumber(v: unknown): number {
47
47
  if (Array.isArray(v) && v.length > 0) {
48
48
  return castNumber(v[0]);
49
49
  }
50
+ if (v === null) {
51
+ return 0;
52
+ }
50
53
  throw new Error(`Cannot cast "${v}" to number`);
51
54
  }
52
55
 
@@ -87,6 +90,7 @@ type UntranslatedProperties =
87
90
  | 'item'
88
91
  | 'controls'
89
92
  | 'conditionFilter'
93
+ | 'conditionReadOnly'
90
94
  | 'components';
91
95
  export type DeepTranslateExpression<T> = T extends LunaticExpression
92
96
  ? ReactNode
@@ -704,4 +704,56 @@ describe('fillComponents', () => {
704
704
  expect(radio.options[1].conditionFilter).toBe(undefined);
705
705
  expect(radio.options[1].shouldBeFiltered).toBe(true);
706
706
  });
707
+
708
+ it('should set component (and all its children) as readOnly if conditionReadOnly is evaluated to true', () => {
709
+ const components = [
710
+ {
711
+ id: 'question-m8ilvkbt',
712
+ componentType: 'Question',
713
+ page: '1',
714
+ label: {
715
+ value: '"Question label"',
716
+ type: 'VTL|MD',
717
+ },
718
+ conditionReadOnly: {
719
+ type: 'VTL',
720
+ value: 'true',
721
+ },
722
+ components: [
723
+ {
724
+ id: 'm8ilvkbt',
725
+ componentType: 'Input',
726
+ page: '1',
727
+ maxLength: 249,
728
+ response: {
729
+ name: 'TESTTEXTE',
730
+ },
731
+ },
732
+ ],
733
+ },
734
+ ];
735
+
736
+ const mockVariables = LunaticVariablesStore.makeFromObject({
737
+ TESTTEXTE: 'some value',
738
+ });
739
+
740
+ const mockState = {
741
+ ...defaultMockState,
742
+ disableFilters: true,
743
+ variables: mockVariables,
744
+ };
745
+
746
+ const filledComponents = fillComponents(
747
+ components as LunaticComponentDefinition[],
748
+ mockState as unknown as FillComponentArgs
749
+ ) as any;
750
+
751
+ const question = filledComponents[0];
752
+
753
+ expect(question.componentType).toBe('Question');
754
+ expect(question.readOnly).toBe(true);
755
+
756
+ const input = question.components[0];
757
+ expect(input.readOnly).toBe(true);
758
+ });
707
759
  });
@@ -38,12 +38,24 @@ export const fillComponent = (
38
38
  component: LunaticComponentDefinition,
39
39
  state: FillComponentArgs,
40
40
  // the given parentConditionFilter is typed as VTLScalarExpression, but it's actually a boolean or undefined
41
- parentConditionFilter?: any
41
+ parentConditionFilter?: any,
42
+ // the given parentReadonly is typed as VTLScalarExpression, but it's actually a boolean or undefined
43
+ parentReadOnly?: any
42
44
  ): LunaticComponentProps & { conditionFilter?: boolean } => {
43
45
  const interpretedProps = fillComponentExpressions(component, state);
44
46
 
45
47
  const shouldParentBeFiltered = parentConditionFilter === false;
46
48
 
49
+ const isParentReadOnly = parentReadOnly === true;
50
+
51
+ // if a parent component is readOnly, then the children also are
52
+ const readOnly =
53
+ isParentReadOnly ||
54
+ ('conditionReadOnly' in interpretedProps
55
+ ? interpretedProps.conditionReadOnly
56
+ : false);
57
+
58
+ // if a parent component is filtered, then the children also are
47
59
  const shouldBeFiltered =
48
60
  shouldParentBeFiltered ||
49
61
  ('conditionFilter' in interpretedProps
@@ -60,6 +72,7 @@ export const fillComponent = (
60
72
  shortcut: state.shortcut,
61
73
  goNextPage: state.goNextPage,
62
74
  goPreviousPage: state.goPreviousPage,
75
+ readOnly: readOnly,
63
76
  shouldBeFiltered: shouldBeFiltered,
64
77
  iteration: state.pager.iteration,
65
78
  required: 'isMandatory' in component ? component.isMandatory : false,
@@ -89,7 +102,8 @@ export function fillComponents(
89
102
  components: LunaticComponentDefinition[],
90
103
  state: FillComponentArgs,
91
104
  parentType?: LunaticComponentDefinition['componentType'],
92
- parentConditionFilter?: VTLScalarExpression
105
+ parentConditionFilter?: VTLScalarExpression,
106
+ parentReadOnly?: VTLScalarExpression
93
107
  ): LunaticComponentProps[] {
94
108
  // Flatmap to directly remove FilterDescription components if disableFiltersDescription is true
95
109
  const filledComponents = components.flatMap((component) => {
@@ -100,7 +114,9 @@ export function fillComponents(
100
114
  return [];
101
115
  }
102
116
 
103
- return [fillComponent(component, state, parentConditionFilter)];
117
+ return [
118
+ fillComponent(component, state, parentConditionFilter, parentReadOnly),
119
+ ];
104
120
  });
105
121
 
106
122
  if (state.disableFilters) {
@@ -1,7 +1,7 @@
1
1
  import { useCallback } from 'react';
2
- import { isObject } from '../../utils/is-object';
3
2
  import type { LunaticComponentDefinition, LunaticReducerState } from '../type';
4
3
  import type { LunaticComponentProps } from '../../components/type';
4
+ import { isObject } from '../../utils/object';
5
5
 
6
6
  /**
7
7
  * Check if a page has one response (value is filled for at least one field).
@@ -1,8 +1,9 @@
1
1
  import { renderHook } from '@testing-library/react';
2
2
  import { describe, expect, it } from 'vitest';
3
- import source from '../../stories/overview/sourceWithHierarchy.json';
3
+ import source from '../../stories/behaviour/overview/source.json';
4
4
  import { useLunatic } from '../use-lunatic';
5
- import { dataFromObject } from '../test.utils';
5
+
6
+ import { dataFromObject } from '../../utils/object';
6
7
 
7
8
  describe('use-overview test with useLunatic()', () => {
8
9
  it('should initialize correctly with disableFilters: false (without data)', () => {
@@ -49,7 +49,8 @@ function getChildComponents(
49
49
  component.components,
50
50
  state,
51
51
  undefined,
52
- component.conditionFilter
52
+ component.conditionFilter,
53
+ component.conditionReadOnly
53
54
  ),
54
55
  };
55
56
  }
@@ -1,25 +1,19 @@
1
1
  import { act, renderHook } from '@testing-library/react';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import sourceWithoutHierarchy from '../stories/overview/source.json';
5
4
  import sourceLogement from '../stories/questionnaires/logement/source.json';
6
5
  import sourceSimpsons from '../stories/questionnaires/simpsons/source.json';
7
- import sourceOverview from '../stories/overview/sourceLoop.json';
8
- import sourceCheckboxGroup from '../stories/checkbox-group/source.json';
9
- import dataOverview from '../stories/overview/dataLoop.json';
6
+ import sourceOverview from '../stories/behaviour/overview/sourceLoop.json';
7
+ import sourceCheckboxGroup from '../stories/checkbox/sourceGroup.json';
10
8
  import sourceCleaningLoop from '../stories/behaviour/cleaning/source-loop.json';
11
9
  import sourceCleaningResizing from '../stories/behaviour/resizing/source-resizing-cleaning.json';
12
10
  import type { PageTag } from './type';
13
11
  import { useLunatic } from './use-lunatic';
14
12
  import { useCallback } from 'react';
15
- import { dataFromObject } from './test.utils';
13
+ import { dataFromObject } from '../utils/object';
16
14
 
17
15
  describe('use-lunatic()', () => {
18
- const defaultParams = [
19
- sourceSimpsons as any,
20
- dataFromObject({}),
21
- {},
22
- ] as const;
16
+ const defaultParams = [sourceSimpsons, dataFromObject({}), {}] as const;
23
17
 
24
18
  it('should initialize correctly', () => {
25
19
  const { result } = renderHook(() => useLunatic(...defaultParams));
@@ -39,7 +33,7 @@ describe('use-lunatic()', () => {
39
33
  });
40
34
  it('should jump to a specific page', () => {
41
35
  const params = [
42
- sourceSimpsons as any,
36
+ sourceSimpsons,
43
37
  dataFromObject({
44
38
  COMMENT: 'Hello world',
45
39
  READY: true,
@@ -102,7 +96,7 @@ describe('use-lunatic()', () => {
102
96
  it('should not generate a new Provider every render', () => {
103
97
  const { result } = renderHook(() => {
104
98
  const missingStrategy = useCallback(() => {}, []);
105
- return useLunatic(sourceSimpsons as any, undefined, {
99
+ return useLunatic(sourceSimpsons, undefined, {
106
100
  management: false,
107
101
  missing: false,
108
102
  missingStrategy,
@@ -146,7 +140,7 @@ describe('use-lunatic()', () => {
146
140
  it('should not calculate overview by default', function () {
147
141
  const { result } = renderHook(() =>
148
142
  useLunatic(
149
- sourceLogement as any,
143
+ sourceLogement,
150
144
  undefined,
151
145
  lunaticConfigurationWithoutOverview
152
146
  )
@@ -156,7 +150,7 @@ describe('use-lunatic()', () => {
156
150
  });
157
151
  it('should make the first sequence visible', function () {
158
152
  const { result } = renderHook(() =>
159
- useLunatic(sourceLogement as any, undefined, {
153
+ useLunatic(sourceLogement, undefined, {
160
154
  ...lunaticConfiguration,
161
155
  })
162
156
  );
@@ -167,11 +161,7 @@ describe('use-lunatic()', () => {
167
161
  });
168
162
  it('should be empty when no hierarchy', function () {
169
163
  const { result } = renderHook(() =>
170
- useLunatic(
171
- sourceWithoutHierarchy as any,
172
- undefined,
173
- lunaticConfiguration
174
- )
164
+ useLunatic(sourceCheckboxGroup, undefined, lunaticConfiguration)
175
165
  );
176
166
  expect(result.current.overview).toHaveLength(0);
177
167
  });
@@ -179,7 +169,7 @@ describe('use-lunatic()', () => {
179
169
  describe('with initial data', function () {
180
170
  it('should make second sequence visible', function () {
181
171
  const { result } = renderHook(() =>
182
- useLunatic(sourceLogement as any, advancedQestionnaireData, {
172
+ useLunatic(sourceLogement, advancedQestionnaireData, {
183
173
  ...lunaticConfiguration,
184
174
  initialPage: '16',
185
175
  })
@@ -192,19 +182,42 @@ describe('use-lunatic()', () => {
192
182
  });
193
183
 
194
184
  describe('with loop', function () {
185
+ const data = dataFromObject({
186
+ ETAT: '1',
187
+ SATISFAIT: '1',
188
+ T_NHAB: 3,
189
+ T_PRENOM: ['Quentin', 'Luna', 'Paul'],
190
+ COMMCOMPO: 'super',
191
+ T_SEXE: ['1', '2', '1'],
192
+ T_DATENAIS: [null, null, null],
193
+ REMARQUES: [null, 'a', 'b'],
194
+ SUPERQUEST: ['ok', 'ok', null],
195
+ AUTRESUPERQUEST: ['a', 'c', 'ras'],
196
+ ENCOREUNEQ: ['wow', 'b', null],
197
+ COMMENT_QE: null,
198
+ ETAT_MISSING: null,
199
+ SATISFAIT_MISSING: null,
200
+ T_NHAB_MISSING: null,
201
+ T_PRENOM_MISSING: null,
202
+ COMMCOMPO_MISSING: null,
203
+ T_SEXE_MISSING: null,
204
+ T_DATENAIS_MISSING: [null, 'DK', 'RF'],
205
+ REMARQUES_MISSING: [null, null, null],
206
+ SUPERQUEST_MISSING: [null, null, 'DK'],
207
+ ENCOREUNEQ_MISSING: [null, null, 'RF'],
208
+ AUTRESUPERQUEST_MISSING: null,
209
+ COMMENT_QE_MISSING: null,
210
+ });
211
+
195
212
  it('should work with loop', async () => {
196
213
  const { result } = renderHook(() =>
197
- useLunatic(
198
- sourceOverview as any,
199
- dataOverview.data,
200
- lunaticConfiguration
201
- )
214
+ useLunatic(sourceOverview, data, lunaticConfiguration)
202
215
  );
203
216
  expect(result.current.overview).toMatchSnapshot();
204
217
  });
205
218
  it('should handle initialPage', async () => {
206
219
  const { result } = renderHook(() =>
207
- useLunatic(sourceOverview as any, dataOverview.data, {
220
+ useLunatic(sourceOverview, data, {
208
221
  ...lunaticConfiguration,
209
222
  initialPage: '10.1#1',
210
223
  })
@@ -230,7 +243,7 @@ describe('use-lunatic()', () => {
230
243
  it('should filter out some components by default', function () {
231
244
  const { result } = renderHook(() =>
232
245
  useLunatic(
233
- sourceLogement as any,
246
+ sourceLogement,
234
247
  undefined,
235
248
  lunaticConfigurationWithoutDisableFilters
236
249
  )
@@ -241,7 +254,7 @@ describe('use-lunatic()', () => {
241
254
  });
242
255
  it('should filter out some components when false', function () {
243
256
  const { result } = renderHook(() =>
244
- useLunatic(sourceLogement as any, undefined, {
257
+ useLunatic(sourceLogement, undefined, {
245
258
  ...lunaticConfigurationWithoutDisableFilters,
246
259
  disableFilters: false,
247
260
  })
@@ -252,7 +265,7 @@ describe('use-lunatic()', () => {
252
265
  });
253
266
  it('should not filter any component when true', function () {
254
267
  const { result } = renderHook(() =>
255
- useLunatic(sourceLogement as any, undefined, {
268
+ useLunatic(sourceLogement, undefined, {
256
269
  ...lunaticConfigurationWithoutDisableFilters,
257
270
  disableFilters: true,
258
271
  })
@@ -269,7 +282,7 @@ describe('use-lunatic()', () => {
269
282
  describe('cleaning', () => {
270
283
  it('should handle cleaning in a loop', () => {
271
284
  const { result } = renderHook(() =>
272
- useLunatic(sourceCleaningLoop as any, undefined)
285
+ useLunatic(sourceCleaningLoop, undefined)
273
286
  );
274
287
  act(() => {
275
288
  result.current.handleChanges([
@@ -284,9 +297,9 @@ describe('use-lunatic()', () => {
284
297
  result.current.goNextPage();
285
298
  });
286
299
  const expectCollectedAgeToEqual = (expectation: unknown[]) => {
287
- expect(
288
- (result.current.getData(false).COLLECTED as any).AGE.COLLECTED
289
- ).toEqual(expectation);
300
+ expect(result.current.getData(false).COLLECTED?.AGE.COLLECTED).toEqual(
301
+ expectation
302
+ );
290
303
  };
291
304
  expectCollectedAgeToEqual([18, 18, 18]);
292
305
  act(() => {
@@ -306,17 +319,17 @@ describe('use-lunatic()', () => {
306
319
  it('should resize after cleaning', () => {
307
320
  const spy = vi.fn();
308
321
  const { result } = renderHook(() =>
309
- useLunatic(sourceCleaningResizing as any, undefined, {
322
+ useLunatic(sourceCleaningResizing, undefined, {
310
323
  onChange: spy,
311
324
  })
312
325
  );
313
326
  act(() => result.current.handleChanges([{ name: 'NB', value: 3 }]));
314
327
  expect(
315
- (result.current.getData(true).COLLECTED?.PRENOMS as any).COLLECTED
328
+ result.current.getData(true).COLLECTED?.PRENOMS?.COLLECTED
316
329
  ).toEqual([null, null, null]);
317
330
  act(() => result.current.handleChanges([{ name: 'NB', value: 2 }]));
318
331
  expect(
319
- (result.current.getData(true).COLLECTED?.PRENOMS as any).COLLECTED
332
+ result.current.getData(true).COLLECTED?.PRENOMS?.COLLECTED
320
333
  ).toEqual([null, null]);
321
334
  });
322
335
  });
@@ -325,7 +338,7 @@ describe('use-lunatic()', () => {
325
338
  let hookRef: { current: ReturnType<typeof useLunatic> };
326
339
  beforeEach(() => {
327
340
  const { result } = renderHook(() =>
328
- useLunatic(sourceSimpsons as any, undefined, {})
341
+ useLunatic(sourceSimpsons, undefined, {})
329
342
  );
330
343
  act(() => {
331
344
  result.current.handleChanges([
@@ -353,7 +366,7 @@ describe('use-lunatic()', () => {
353
366
  let hookRef: { current: ReturnType<typeof useLunatic> };
354
367
  beforeEach(() => {
355
368
  const { result } = renderHook(() =>
356
- useLunatic(sourceSimpsons as any, undefined, { trackChanges: true })
369
+ useLunatic(sourceSimpsons, undefined, { trackChanges: true })
357
370
  );
358
371
  hookRef = result;
359
372
  });
@@ -425,7 +438,7 @@ describe('use-lunatic()', () => {
425
438
  describe('pageHasResponse', () => {
426
439
  it('should detect change on a field', () => {
427
440
  const { result } = renderHook(() =>
428
- useLunatic(sourceCheckboxGroup as any, undefined, {})
441
+ useLunatic(sourceCheckboxGroup, undefined, {})
429
442
  );
430
443
  act(() => {
431
444
  result.current.handleChanges([{ name: 'NATIO1N1', value: true }]);
@@ -434,7 +447,7 @@ describe('use-lunatic()', () => {
434
447
  });
435
448
  it('should not detect unchecked box has a response', () => {
436
449
  const { result } = renderHook(() =>
437
- useLunatic(sourceCheckboxGroup as any, undefined, {})
450
+ useLunatic(sourceCheckboxGroup, undefined, {})
438
451
  );
439
452
  act(() => {
440
453
  result.current.handleChanges([{ name: 'NATIO1N1', value: false }]);
@@ -31,3 +31,16 @@ export function between(n: number, min: number, max: number): number {
31
31
  export function isNumber(n: unknown): n is number {
32
32
  return typeof n === 'number' && Number.isFinite(n);
33
33
  }
34
+
35
+ /**
36
+ * Convert an unknown value into a number
37
+ */
38
+ export function toNumber(v: unknown): number | null {
39
+ if (typeof v === 'number') {
40
+ return v;
41
+ }
42
+ if (typeof v === 'string') {
43
+ return parseInt(v, 10);
44
+ }
45
+ return null;
46
+ }
@@ -1,3 +1,5 @@
1
+ import { LunaticData } from '../use-lunatic/type';
2
+
1
3
  /**
2
4
  * Array.map() but for objet
3
5
  */
@@ -18,6 +20,21 @@ export function objectMap<T extends Record<string, any>, V>(
18
20
  {} as Record<keyof T, V>
19
21
  );
20
22
  }
23
+
24
+ /**
25
+ * Filter items from an object
26
+ */
27
+ export function objectFilter<T extends Record<string, any>>(
28
+ object: T,
29
+ callback: (k: keyof T, v: T[keyof T]) => boolean
30
+ ): T {
31
+ return Object.fromEntries(
32
+ Object.entries(object).filter(([k, v]) => {
33
+ return callback(k, v);
34
+ })
35
+ ) as T;
36
+ }
37
+
21
38
  // Adds the possibility of preserving the original type of the object's keys.
22
39
  // The native function produces an array of strings. (Object.keys)
23
40
  export function objectKeys<T extends Record<string, unknown>>(object: T) {
@@ -33,3 +50,26 @@ export function mergeDefault<
33
50
  return [k, obj[k] ?? defaults[k]] as const;
34
51
  });
35
52
  }
53
+
54
+ export const dataFromObject = (o: Record<string, unknown>): LunaticData => {
55
+ return {
56
+ EXTERNAL: {},
57
+ COLLECTED: Object.keys(o).reduce(
58
+ (acc, k) => ({
59
+ ...acc,
60
+ [k]: {
61
+ COLLECTED: o[k],
62
+ },
63
+ }),
64
+ {}
65
+ ),
66
+ CALCULATED: {},
67
+ };
68
+ };
69
+
70
+ /**
71
+ * isObject function with type narrowing
72
+ */
73
+ export function isObject(v: unknown): v is Record<string, unknown> {
74
+ return typeof v === 'object' && v !== null;
75
+ }