@inseefr/lunatic 3.5.6 → 3.5.8

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 (245) hide show
  1. package/components/CheckboxOne/CheckboxOne.spec.js +3 -2
  2. package/components/CheckboxOne/CheckboxOne.spec.js.map +1 -1
  3. package/components/shared/Combobox/Combobox.stories.d.ts +3 -2
  4. package/components/shared/Combobox/Combobox.stories.js +26 -21
  5. package/components/shared/Combobox/Combobox.stories.js.map +1 -1
  6. package/components/type.d.ts +3 -0
  7. package/esm/components/CheckboxOne/CheckboxOne.spec.js +3 -2
  8. package/esm/components/CheckboxOne/CheckboxOne.spec.js.map +1 -1
  9. package/esm/components/shared/Combobox/Combobox.stories.d.ts +3 -2
  10. package/esm/components/shared/Combobox/Combobox.stories.js +28 -24
  11. package/esm/components/shared/Combobox/Combobox.stories.js.map +1 -1
  12. package/esm/components/type.d.ts +3 -0
  13. package/esm/hooks/useLocalStorage.d.ts +1 -0
  14. package/esm/hooks/useLocalStorage.js +31 -0
  15. package/esm/hooks/useLocalStorage.js.map +1 -0
  16. package/esm/type.source.d.ts +6 -0
  17. package/esm/use-lunatic/commons/compile-controls.js +22 -14
  18. package/esm/use-lunatic/commons/compile-controls.js.map +1 -1
  19. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.js +4 -1
  20. package/esm/use-lunatic/commons/fill-components/fill-component-expressions.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.d.ts +3 -0
  26. package/esm/utils/number.d.ts +4 -0
  27. package/esm/utils/number.js +12 -0
  28. package/esm/utils/number.js.map +1 -1
  29. package/esm/utils/object.d.ts +10 -0
  30. package/esm/utils/object.js +26 -0
  31. package/esm/utils/object.js.map +1 -1
  32. package/hooks/useLocalStorage.d.ts +1 -0
  33. package/hooks/useLocalStorage.js +34 -0
  34. package/hooks/useLocalStorage.js.map +1 -0
  35. package/package.json +76 -137
  36. package/src/components/CheckboxOne/CheckboxOne.spec.tsx +3 -0
  37. package/src/components/shared/Combobox/Combobox.stories.tsx +48 -49
  38. package/src/components/type.ts +1 -0
  39. package/src/hooks/useLocalStorage.ts +37 -0
  40. package/src/json.d.ts +16 -0
  41. package/src/stories/accordion/accordion.stories.tsx +21 -0
  42. package/src/stories/behaviour/cleaning/cleaning.stories.tsx +40 -0
  43. package/src/stories/behaviour/controls/controls.stories.tsx +51 -0
  44. package/src/stories/behaviour/disabled/disabled.stories.tsx +29 -0
  45. package/src/stories/behaviour/filter/filter.stories.tsx +34 -0
  46. package/src/stories/behaviour/missing/missing.stories.tsx +31 -0
  47. package/src/stories/behaviour/overview/overview.stories.tsx +61 -0
  48. package/src/stories/{overview/sourceWithHierarchy.json → behaviour/overview/source.json} +1 -1
  49. package/src/stories/{overview → behaviour/overview}/sourceLoop.json +1 -1
  50. package/src/stories/behaviour/performance/performance.stories.tsx +29 -0
  51. package/src/stories/behaviour/resizing/resizing.stories.tsx +34 -0
  52. package/src/stories/behaviour/slots.stories.tsx +34 -0
  53. package/src/stories/checkbox/checkbox.stories.tsx +75 -0
  54. package/src/stories/{checkbox-group/sourceLoop.json → checkbox/sourceGroupLoop.json} +1 -1
  55. package/src/stories/datepicker/datepicker.stories.tsx +21 -0
  56. package/src/stories/declaration/declaration.stories.tsx +27 -0
  57. package/src/stories/dropdown/dropdown.stories.tsx +27 -0
  58. package/src/stories/duration/duration.stories.tsx +27 -0
  59. package/src/stories/filter-description/filter-description.stories.tsx +28 -0
  60. package/src/stories/input/input.stories.tsx +21 -0
  61. package/src/stories/input-number/input-number.stories.tsx +35 -0
  62. package/src/stories/loop/loop.stories.tsx +35 -0
  63. package/src/stories/loop/roster-for-loop.stories.tsx +59 -0
  64. package/src/stories/pairwise/pairwise.stories.tsx +30 -0
  65. package/src/stories/paste.stories.tsx +85 -0
  66. package/src/stories/question/question.stories.tsx +21 -0
  67. package/src/stories/questionnaires/logement/logement.stories.tsx +26 -0
  68. package/src/stories/questionnaires/recensement/recensement.stories.tsx +28 -0
  69. package/src/stories/questionnaires/rp/rp.stories.tsx +21 -0
  70. package/src/stories/questionnaires/simpsons/simpsons.stories.tsx +31 -0
  71. package/src/stories/radio/radio.stories.tsx +53 -0
  72. package/src/stories/roundabout/roundabout.stories.tsx +37 -0
  73. package/src/stories/roundabout/source.json +1 -0
  74. package/src/stories/sequence/sequence.stories.tsx +28 -0
  75. package/src/stories/suggester/source-option-responses.json +1 -1
  76. package/src/stories/suggester/suggester.stories.tsx +68 -0
  77. package/src/stories/summary/source.json +22 -1
  78. package/src/stories/summary/summary.stories.tsx +31 -0
  79. package/src/stories/table/table.stories.tsx +35 -0
  80. package/src/stories/text/text.stories.tsx +38 -0
  81. package/src/stories/textarea/textarea.stories.tsx +21 -0
  82. package/src/stories/utils/Orchestrator.tsx +310 -0
  83. package/src/stories/utils/OrchestratorData.tsx +176 -0
  84. package/src/stories/utils/OrchestratorOverview.tsx +70 -0
  85. package/src/stories/utils/OrchestratorSidebar.tsx +119 -0
  86. package/src/stories/utils/SchemaValidator.tsx +29 -0
  87. package/src/stories/utils/referentiel.ts +9 -0
  88. package/src/type.source.ts +6 -0
  89. package/src/use-lunatic/commons/compile-controls.ts +36 -18
  90. package/src/use-lunatic/commons/fill-components/fill-component-expressions.ts +4 -1
  91. package/src/use-lunatic/hooks/use-page-has-response.ts +1 -1
  92. package/src/use-lunatic/hooks/useOverview.spec.ts +3 -2
  93. package/src/use-lunatic/use-lunatic.test.ts +53 -40
  94. package/src/utils/number.ts +13 -0
  95. package/src/utils/object.ts +40 -0
  96. package/tsconfig.build.tsbuildinfo +1 -1
  97. package/type.source.d.ts +6 -0
  98. package/use-lunatic/commons/compile-controls.js +21 -13
  99. package/use-lunatic/commons/compile-controls.js.map +1 -1
  100. package/use-lunatic/commons/fill-components/fill-component-expressions.js +5 -2
  101. package/use-lunatic/commons/fill-components/fill-component-expressions.js.map +1 -1
  102. package/use-lunatic/hooks/use-page-has-response.js +2 -2
  103. package/use-lunatic/hooks/use-page-has-response.js.map +1 -1
  104. package/use-lunatic/hooks/useOverview.spec.js +8 -8
  105. package/use-lunatic/hooks/useOverview.spec.js.map +1 -1
  106. package/use-lunatic/props/getComponentTypeProps.d.ts +3 -0
  107. package/utils/number.d.ts +4 -0
  108. package/utils/number.js +13 -0
  109. package/utils/number.js.map +1 -1
  110. package/utils/object.d.ts +10 -0
  111. package/utils/object.js +30 -0
  112. package/utils/object.js.map +1 -1
  113. package/esm/stories/overview/sourceWithHierarchy.json +0 -5151
  114. package/esm/tests/utils/lunatic.d.ts +0 -15
  115. package/esm/tests/utils/lunatic.js +0 -27
  116. package/esm/tests/utils/lunatic.js.map +0 -1
  117. package/esm/use-lunatic/replace-component-sequence.d.ts +0 -36
  118. package/esm/use-lunatic/replace-component-sequence.js +0 -19
  119. package/esm/use-lunatic/replace-component-sequence.js.map +0 -1
  120. package/esm/use-lunatic/test.utils.d.ts +0 -2
  121. package/esm/use-lunatic/test.utils.js +0 -13
  122. package/esm/use-lunatic/test.utils.js.map +0 -1
  123. package/esm/utils/is-object.d.ts +0 -4
  124. package/esm/utils/is-object.js +0 -7
  125. package/esm/utils/is-object.js.map +0 -1
  126. package/esm/utils/to-number.d.ts +0 -4
  127. package/esm/utils/to-number.js +0 -13
  128. package/esm/utils/to-number.js.map +0 -1
  129. package/src/stories/accordion/accordion.stories.jsx +0 -17
  130. package/src/stories/behaviour/cleaning/cleaning.stories.jsx +0 -69
  131. package/src/stories/behaviour/controls/controls.stories.jsx +0 -81
  132. package/src/stories/behaviour/filter/dataLoop.json +0 -14
  133. package/src/stories/behaviour/filter/filter.stories.jsx +0 -36
  134. package/src/stories/behaviour/missing/missing.stories.jsx +0 -69
  135. package/src/stories/behaviour/others/V2_DeclarationsSimples.json +0 -908
  136. package/src/stories/behaviour/others/V2_MinMaxSum_Boucles.json +0 -489
  137. package/src/stories/behaviour/others/V2_QuestSimple_Boucles.json +0 -3919
  138. package/src/stories/behaviour/others/V2_TCMRallyeGames.json +0 -2760
  139. package/src/stories/behaviour/others/test-dylan.json +0 -538
  140. package/src/stories/behaviour/others/test.stories.jsx +0 -78
  141. package/src/stories/behaviour/paste/source.json +0 -32
  142. package/src/stories/behaviour/paste/test.stories.jsx +0 -62
  143. package/src/stories/behaviour/performance/performance.stories.jsx +0 -26
  144. package/src/stories/behaviour/resizing/resizing.stories.jsx +0 -60
  145. package/src/stories/behaviour/slots.stories.jsx +0 -32
  146. package/src/stories/checkbox-boolean/checkboxBoolean.stories.jsx +0 -17
  147. package/src/stories/checkbox-group/checkbox-group.stories.jsx +0 -77
  148. package/src/stories/checkbox-one/checkboxOne.stories.jsx +0 -53
  149. package/src/stories/date-picker/data.json +0 -3
  150. package/src/stories/date-picker/datepicker.stories.jsx +0 -26
  151. package/src/stories/declaration/data.json +0 -1
  152. package/src/stories/declaration/input.stories.jsx +0 -18
  153. package/src/stories/disabled/data.json +0 -16
  154. package/src/stories/disabled/disabled.stories.jsx +0 -18
  155. package/src/stories/dropdown/data.json +0 -8
  156. package/src/stories/dropdown/dropdown.stories.jsx +0 -25
  157. package/src/stories/duration/duration.stories.jsx +0 -25
  158. package/src/stories/filter-description/filter-description.stories.jsx +0 -37
  159. package/src/stories/input/data.json +0 -1
  160. package/src/stories/input/input.stories.jsx +0 -18
  161. package/src/stories/input-number/input-number.stories.jsx +0 -23
  162. package/src/stories/loop/loop.stories.jsx +0 -29
  163. package/src/stories/loop/roster-for-loop.stories.jsx +0 -46
  164. package/src/stories/markdown/markdown.stories.jsx +0 -20
  165. package/src/stories/overview/data.json +0 -1
  166. package/src/stories/overview/dataLoop.json +0 -93
  167. package/src/stories/overview/overview.stories.jsx +0 -44
  168. package/src/stories/overview/source.json +0 -25
  169. package/src/stories/pairwise/data.json +0 -12
  170. package/src/stories/pairwise/pairwise-links.stories.jsx +0 -48
  171. package/src/stories/question/question.stories.jsx +0 -16
  172. package/src/stories/questionnaires/logement/logement.stories.jsx +0 -59
  173. package/src/stories/questionnaires/recensement/data.json +0 -12
  174. package/src/stories/questionnaires/recensement/recensement.stories.jsx +0 -35
  175. package/src/stories/questionnaires/rp/data.json +0 -5
  176. package/src/stories/questionnaires/rp/rp.stories.jsx +0 -23
  177. package/src/stories/questionnaires/simpsons/simpsons.stories.jsx +0 -246
  178. package/src/stories/radio/radio.stories.jsx +0 -78
  179. package/src/stories/roundabout/data1.json +0 -13
  180. package/src/stories/roundabout/data2.json +0 -16
  181. package/src/stories/roundabout/roundabout.stories.jsx +0 -32
  182. package/src/stories/sequence/sequence.stories.jsx +0 -29
  183. package/src/stories/suggester/suggester.stories.jsx +0 -71
  184. package/src/stories/summary/data.json +0 -16
  185. package/src/stories/summary/summary.stories.jsx +0 -23
  186. package/src/stories/switch/README.md +0 -29
  187. package/src/stories/switch/data-forced.json +0 -40
  188. package/src/stories/switch/source.json +0 -64
  189. package/src/stories/switch/switch.stories.jsx +0 -17
  190. package/src/stories/table/data.json +0 -1
  191. package/src/stories/table/table.stories.jsx +0 -30
  192. package/src/stories/text/data-roster.json +0 -5
  193. package/src/stories/text/text.stories.jsx +0 -20
  194. package/src/stories/textarea/data.json +0 -1
  195. package/src/stories/textarea/textarea.stories.jsx +0 -18
  196. package/src/stories/utils/SchemaValidator.jsx +0 -40
  197. package/src/stories/utils/default-arg-types.js +0 -39
  198. package/src/stories/utils/default-args.js +0 -3
  199. package/src/stories/utils/options.js +0 -19
  200. package/src/stories/utils/orchestrator.jsx +0 -267
  201. package/src/stories/utils/orchestrator.scss +0 -66
  202. package/src/stories/utils/overview.jsx +0 -39
  203. package/src/stories/utils/overview.scss +0 -37
  204. package/src/stories/utils/referentiel.js +0 -7
  205. package/src/tests/utils/e2e.js +0 -91
  206. package/src/tests/utils/lunatic.ts +0 -33
  207. package/src/use-lunatic/replace-component-sequence.ts +0 -25
  208. package/src/use-lunatic/test.utils.ts +0 -17
  209. package/src/utils/is-object.ts +0 -6
  210. package/src/utils/to-number.ts +0 -12
  211. package/stories/overview/sourceWithHierarchy.json +0 -5151
  212. package/tests/utils/lunatic.d.ts +0 -15
  213. package/tests/utils/lunatic.js +0 -31
  214. package/tests/utils/lunatic.js.map +0 -1
  215. package/use-lunatic/replace-component-sequence.d.ts +0 -36
  216. package/use-lunatic/replace-component-sequence.js +0 -22
  217. package/use-lunatic/replace-component-sequence.js.map +0 -1
  218. package/use-lunatic/test.utils.d.ts +0 -2
  219. package/use-lunatic/test.utils.js +0 -17
  220. package/use-lunatic/test.utils.js.map +0 -1
  221. package/utils/is-object.d.ts +0 -4
  222. package/utils/is-object.js +0 -10
  223. package/utils/is-object.js.map +0 -1
  224. package/utils/to-number.d.ts +0 -4
  225. package/utils/to-number.js +0 -16
  226. package/utils/to-number.js.map +0 -1
  227. /package/src/stories/behaviour/cleaning/{loop.json → source-loop-scopes.json} +0 -0
  228. /package/src/stories/behaviour/controls/{boucles-n.json → source-boucles-n.json} +0 -0
  229. /package/src/stories/behaviour/controls/{loop.json → source-loop.json} +0 -0
  230. /package/src/stories/behaviour/controls/{roundabout.json → source-roundabout.json} +0 -0
  231. /package/src/stories/behaviour/controls/{simple-numeric.json → source-simple-numeric.json} +0 -0
  232. /package/src/stories/behaviour/controls/{simple.json → source-simple.json} +0 -0
  233. /package/src/stories/{disabled → behaviour/disabled}/source.json +0 -0
  234. /package/src/stories/{checkbox-boolean → checkbox}/source.json +0 -0
  235. /package/src/stories/{checkbox-group/source.json → checkbox/sourceGroup.json} +0 -0
  236. /package/src/stories/{checkbox-group/sourceCondition.json → checkbox/sourceGroupCondition.json} +0 -0
  237. /package/src/stories/{checkbox-group/sourceDetail.json → checkbox/sourceGroupDetail.json} +0 -0
  238. /package/src/stories/{checkbox-one/source.json → checkbox/sourceOne.json} +0 -0
  239. /package/src/stories/{checkbox-one/sourceDetail.json → checkbox/sourceOneDetail.json} +0 -0
  240. /package/src/stories/{date-picker → datepicker}/source.json +0 -0
  241. /package/src/stories/{markdown/source.json → declaration/sourceMarkdown.json} +0 -0
  242. /package/src/stories/duration/{mois.json → sourceMonths.json} +0 -0
  243. /package/src/stories/duration/{time.json → sourceTime.json} +0 -0
  244. /package/src/stories/filter-description/{source-options.json → sourceOptions.json} +0 -0
  245. /package/src/stories/table/{table-dynamique.json → source-dynamic.json} +0 -0
@@ -0,0 +1,119 @@
1
+ import type { useLunatic } from '../../use-lunatic/use-lunatic';
2
+ import { objectKeys } from '../../utils/object';
3
+ import type { PropsWithChildren } from 'react';
4
+
5
+ type Props = PropsWithChildren<
6
+ Pick<
7
+ ReturnType<typeof useLunatic>,
8
+ | 'goPreviousPage'
9
+ | 'goNextPage'
10
+ | 'goToPage'
11
+ | 'isLastPage'
12
+ | 'isFirstPage'
13
+ | 'pageTag'
14
+ | 'pager'
15
+ >
16
+ > & {
17
+ hasPageResponse: unknown;
18
+ onLogData: () => void;
19
+ onLogComponents: () => void;
20
+ };
21
+
22
+ export function OrchestratorSidebar({
23
+ goPreviousPage,
24
+ goNextPage,
25
+ goToPage,
26
+ isLastPage,
27
+ isFirstPage,
28
+ pageTag,
29
+ pager,
30
+ children,
31
+ hasPageResponse,
32
+ onLogData,
33
+ onLogComponents,
34
+ }: Props) {
35
+ return (
36
+ <aside className="space-y-4 card card-border border-base-300 p-2">
37
+ <div className="space-y-4">
38
+ {/* Next / Prev button */}
39
+ <div>
40
+ <div className="join mb-2 w-full">
41
+ <button
42
+ className="btn join-item btn-block shrink btn-primary"
43
+ onClick={goPreviousPage}
44
+ disabled={isFirstPage}
45
+ >
46
+ Previous
47
+ </button>
48
+ <button
49
+ className="btn join-item btn-block shrink btn-primary"
50
+ onClick={goNextPage}
51
+ disabled={isLastPage}
52
+ >
53
+ Next
54
+ </button>
55
+ </div>
56
+ <div className="textarea-sm text-base-content/70 text-center">
57
+ You can use PgDown / PgUp shortcut
58
+ </div>
59
+ </div>
60
+
61
+ {/* Reach a specific page */}
62
+ <form
63
+ className="join w-full"
64
+ onSubmit={(e) => {
65
+ e.preventDefault();
66
+ goToPage({
67
+ page: e.currentTarget.querySelector('input')!.valueAsNumber,
68
+ });
69
+ }}
70
+ >
71
+ <label className="input join-item">
72
+ <span className="label">Page</span>
73
+ <input type="number" placeholder="1" />
74
+ </label>
75
+ <button type="submit" className="join-item btn btn-neutral">
76
+ Reach
77
+ </button>
78
+ </form>
79
+
80
+ {/* Pager informations */}
81
+ <div>
82
+ <h3 className="text-lg font-bold mb-4">Pager</h3>
83
+ <ul className="flex flex-col">
84
+ <li className="flex gap-2">
85
+ <span>PageTag</span>
86
+ <span className="dot-leader">{JSON.stringify(pageTag)}</span>
87
+ </li>
88
+ {objectKeys(pager).map((key) => (
89
+ <li className="flex gap-2" key={key}>
90
+ <span>{key}</span>{' '}
91
+ <span className="dot-leader">{JSON.stringify(pager[key])}</span>
92
+ </li>
93
+ ))}
94
+ </ul>
95
+ </div>
96
+ </div>
97
+ <div className="flex gap-2 w-full">
98
+ <button className="btn btn-warning grow" onClick={onLogComponents}>
99
+ Log components
100
+ </button>
101
+ <button className="btn btn-warning grow" onClick={onLogData}>
102
+ Log data
103
+ </button>
104
+ </div>
105
+ <div>
106
+ <h3 className="text-lg font-bold mb-2">Misc</h3>
107
+ <ul className="flex flex-col">
108
+ <li className="flex gap-2">
109
+ <span>pageHasResponse:</span>{' '}
110
+ <span className="dot-leader">
111
+ {JSON.stringify(hasPageResponse)}
112
+ </span>
113
+ </li>
114
+ </ul>
115
+ </div>
116
+ {children}
117
+ </aside>
118
+ );
119
+ }
@@ -0,0 +1,29 @@
1
+ import type { ErrorObject } from 'ajv';
2
+
3
+ type Props = {
4
+ errors: ErrorObject<string, Record<string, any>, unknown>[];
5
+ };
6
+
7
+ export function SchemaValidator({ errors }: Readonly<Props>) {
8
+ if (!errors) {
9
+ return null;
10
+ }
11
+
12
+ return (
13
+ <div className="alert alert-error alert-outline text-xs">
14
+ <div>
15
+ <h4 className="mb-2">
16
+ <strong>{errors.length}</strong> erreurs
17
+ </h4>
18
+ <ul>
19
+ {errors.map((err) => (
20
+ <li key={err.instancePath}>
21
+ <strong>{err.instancePath}</strong> : {err.message}{' '}
22
+ <small>({err.schemaPath})</small>
23
+ </li>
24
+ ))}
25
+ </ul>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { IndexEntry } from '../../utils/search/SearchInterface';
2
+
3
+ export const getReferentiel = async (name: string) => {
4
+ return fetch(`./${name}.json`)
5
+ .then((r) => r.json())
6
+ .catch(() => {
7
+ throw new Error(`Unknown référentiel ${name}`);
8
+ }) as Promise<IndexEntry[]>;
9
+ };
@@ -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;
@@ -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],
@@ -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
 
@@ -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)', () => {
@@ -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
+ }