@inseefr/lunatic 3.5.4 → 3.5.6

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 (73) hide show
  1. package/components/Duration/durationUtils.js +1 -1
  2. package/components/Duration/durationUtils.js.map +1 -1
  3. package/components/Sequence/Sequence.d.ts +1 -1
  4. package/components/Subsequence/Subsequence.d.ts +1 -1
  5. package/components/library.d.ts +2 -2
  6. package/components/type.d.ts +4 -3
  7. package/esm/components/Duration/durationUtils.js +1 -1
  8. package/esm/components/Duration/durationUtils.js.map +1 -1
  9. package/esm/components/Sequence/Sequence.d.ts +1 -1
  10. package/esm/components/Subsequence/Subsequence.d.ts +1 -1
  11. package/esm/components/library.d.ts +2 -2
  12. package/esm/components/type.d.ts +4 -3
  13. package/esm/use-lunatic/actions.d.ts +2 -0
  14. package/esm/use-lunatic/actions.js.map +1 -1
  15. package/esm/use-lunatic/commons/fill-components/fill-component.spec.js +105 -0
  16. package/esm/use-lunatic/commons/fill-components/fill-component.spec.js.map +1 -1
  17. package/esm/use-lunatic/commons/fill-components/fill-components.d.ts +3 -2
  18. package/esm/use-lunatic/commons/fill-components/fill-components.js +12 -4
  19. package/esm/use-lunatic/commons/fill-components/fill-components.js.map +1 -1
  20. package/esm/use-lunatic/commons/variables/lunatic-variables-store.d.ts +7 -2
  21. package/esm/use-lunatic/commons/variables/lunatic-variables-store.js +11 -4
  22. package/esm/use-lunatic/commons/variables/lunatic-variables-store.js.map +1 -1
  23. package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js +11 -0
  24. package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
  25. package/esm/use-lunatic/props/getComponentTypeProps.d.ts +3 -3
  26. package/esm/use-lunatic/props/getComponentTypeProps.js +1 -1
  27. package/esm/use-lunatic/props/getComponentTypeProps.js.map +1 -1
  28. package/esm/use-lunatic/props/propOptions.d.ts +3 -1
  29. package/esm/use-lunatic/props/propOptions.js +22 -24
  30. package/esm/use-lunatic/props/propOptions.js.map +1 -1
  31. package/esm/use-lunatic/props/propOptions.spec.js +46 -0
  32. package/esm/use-lunatic/props/propOptions.spec.js.map +1 -1
  33. package/esm/use-lunatic/reducer/reducer.js +1 -1
  34. package/esm/use-lunatic/reducer/reducer.js.map +1 -1
  35. package/esm/use-lunatic/type.d.ts +1 -0
  36. package/package.json +1 -1
  37. package/src/components/Duration/durationUtils.ts +1 -1
  38. package/src/components/type.ts +9 -2
  39. package/src/use-lunatic/actions.ts +2 -0
  40. package/src/use-lunatic/commons/fill-components/fill-component.spec.ts +126 -0
  41. package/src/use-lunatic/commons/fill-components/fill-components.ts +19 -4
  42. package/src/use-lunatic/commons/variables/lunatic-variables-store.spec.ts +12 -0
  43. package/src/use-lunatic/commons/variables/lunatic-variables-store.ts +20 -5
  44. package/src/use-lunatic/props/getComponentTypeProps.ts +6 -1
  45. package/src/use-lunatic/props/propOptions.spec.ts +64 -0
  46. package/src/use-lunatic/props/propOptions.ts +50 -22
  47. package/src/use-lunatic/reducer/reducer.ts +1 -1
  48. package/src/use-lunatic/type.ts +1 -0
  49. package/src/use-lunatic/use-lunatic.test.ts +39 -0
  50. package/tsconfig.build.tsbuildinfo +1 -1
  51. package/use-lunatic/actions.d.ts +2 -0
  52. package/use-lunatic/actions.js.map +1 -1
  53. package/use-lunatic/commons/fill-components/fill-component.spec.js +105 -0
  54. package/use-lunatic/commons/fill-components/fill-component.spec.js.map +1 -1
  55. package/use-lunatic/commons/fill-components/fill-components.d.ts +3 -2
  56. package/use-lunatic/commons/fill-components/fill-components.js +12 -4
  57. package/use-lunatic/commons/fill-components/fill-components.js.map +1 -1
  58. package/use-lunatic/commons/variables/lunatic-variables-store.d.ts +7 -2
  59. package/use-lunatic/commons/variables/lunatic-variables-store.js +11 -4
  60. package/use-lunatic/commons/variables/lunatic-variables-store.js.map +1 -1
  61. package/use-lunatic/commons/variables/lunatic-variables-store.spec.js +11 -0
  62. package/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
  63. package/use-lunatic/props/getComponentTypeProps.d.ts +3 -3
  64. package/use-lunatic/props/getComponentTypeProps.js +1 -1
  65. package/use-lunatic/props/getComponentTypeProps.js.map +1 -1
  66. package/use-lunatic/props/propOptions.d.ts +3 -1
  67. package/use-lunatic/props/propOptions.js +22 -24
  68. package/use-lunatic/props/propOptions.js.map +1 -1
  69. package/use-lunatic/props/propOptions.spec.js +46 -0
  70. package/use-lunatic/props/propOptions.spec.js.map +1 -1
  71. package/use-lunatic/reducer/reducer.js +1 -1
  72. package/use-lunatic/reducer/reducer.js.map +1 -1
  73. package/use-lunatic/type.d.ts +1 -0
@@ -66,6 +66,7 @@ describe('fillComponents', () => {
66
66
  expect(input.required).toBe(true);
67
67
  expect(input.maxLength).toBe(15);
68
68
  expect(input.conditionFilter).toBe(true);
69
+ expect(input.shouldBeFiltered).toBe(false);
69
70
  });
70
71
 
71
72
  it('should fill a Radio component correctly with options', () => {
@@ -136,6 +137,7 @@ describe('fillComponents', () => {
136
137
  expect(radio.options[0].label).toBe('"oui"');
137
138
  expect(radio.options[1].label).toBe('"non"');
138
139
  expect(radio.conditionFilter).toBe(true);
140
+ expect(radio.shouldBeFiltered).toBe(false);
139
141
  expect(radio.response.name).toBe('TESTRADIO');
140
142
  });
141
143
 
@@ -187,6 +189,7 @@ describe('fillComponents', () => {
187
189
  expect(question.id).toBe('question-m8ilvkbt');
188
190
  expect(question.label).toBe('"Question label"');
189
191
  expect(question.conditionFilter).toBe(true);
192
+ expect(question.shouldBeFiltered).toBe(false);
190
193
  expect(question.components.length).toBe(1);
191
194
 
192
195
  const input = question.components[0];
@@ -258,12 +261,16 @@ describe('fillComponents', () => {
258
261
  expect(input.componentType).toBe('Input');
259
262
  expect(input.response.name).toBe('TESTINPUT');
260
263
  expect(input.maxLength).toBe(100);
264
+ expect(input.conditionFilter).toBe(true);
265
+ expect(input.shouldBeFiltered).toBe(false);
261
266
 
262
267
  const radio = filledComponents[1];
263
268
  expect(radio.componentType).toBe('Radio');
264
269
  expect(radio.response.name).toBe('TESTRADIO');
265
270
  expect(radio.options[0].label).toBe('"Yes"');
266
271
  expect(radio.options[1].label).toBe('"No"');
272
+ expect(radio.conditionFilter).toBe(true);
273
+ expect(radio.shouldBeFiltered).toBe(false);
267
274
  });
268
275
 
269
276
  it('should filter out FilterDescription components if disableFiltersDescription is true', () => {
@@ -453,6 +460,7 @@ describe('fillComponents', () => {
453
460
  const input = filledComponents[0];
454
461
  expect(input.id).toBe('input1');
455
462
  expect(input.conditionFilter).toBe(false);
463
+ expect(input.shouldBeFiltered).toBe(true);
456
464
  });
457
465
 
458
466
  it('should transform components into Text with empty label when conditionFilter is false and parentType is RosterForLoop', () => {
@@ -507,6 +515,8 @@ describe('fillComponents', () => {
507
515
  expect(input.componentType).toBe('Text');
508
516
  expect(input.label).toBe('');
509
517
  expect(input.id).toBe('input1');
518
+ expect(input.conditionFilter).toBe(false);
519
+ expect(input.shouldBeFiltered).toBe(true);
510
520
 
511
521
  // Check the second component that has conditionFilter: true
512
522
  const radio = filledComponents[1];
@@ -569,6 +579,8 @@ describe('fillComponents', () => {
569
579
  // The component should remain unchanged
570
580
  expect(input.componentType).toBe('Input');
571
581
  expect(input.label).toBe('"Input label"');
582
+ expect(input.conditionFilter).toBe(false);
583
+ expect(input.shouldBeFiltered).toBe(true);
572
584
 
573
585
  // Check the second component that has conditionFilter: true
574
586
  const radio = filledComponents[1];
@@ -578,4 +590,118 @@ describe('fillComponents', () => {
578
590
  expect(radio.options[0].label).toBe('"Yes"');
579
591
  expect(radio.options[1].label).toBe('"No"');
580
592
  });
593
+
594
+ it('should tag children components with shouldBeFiltered=true if the parent component should be filtered', () => {
595
+ const components = [
596
+ {
597
+ id: 'question-m8ilvkbt',
598
+ componentType: 'Question',
599
+ page: '1',
600
+ label: {
601
+ value: '"Question label"',
602
+ type: 'VTL|MD',
603
+ },
604
+ conditionFilter: {
605
+ type: 'VTL',
606
+ // value should be string, but did not find how to execute correctly with mocks
607
+ // for having a false conditionFilter at the end
608
+ value: false,
609
+ },
610
+ components: [
611
+ {
612
+ id: 'm8ilvkbt',
613
+ componentType: 'Input',
614
+ page: '1',
615
+ maxLength: 249,
616
+ response: {
617
+ name: 'TESTTEXTE',
618
+ },
619
+ },
620
+ ],
621
+ },
622
+ ];
623
+
624
+ const mockVariables = LunaticVariablesStore.makeFromObject({
625
+ TESTTEXTE: 'some value',
626
+ });
627
+
628
+ const mockState = {
629
+ ...defaultMockState,
630
+ disableFilters: true,
631
+ variables: mockVariables,
632
+ };
633
+
634
+ const filledComponents = fillComponents(
635
+ components as LunaticComponentDefinition[],
636
+ mockState as unknown as FillComponentArgs
637
+ ) as any;
638
+
639
+ const question = filledComponents[0];
640
+
641
+ expect(question.componentType).toBe('Question');
642
+ expect(question.conditionFilter).toBe(false);
643
+ expect(question.shouldBeFiltered).toBe(true);
644
+
645
+ const input = question.components[0];
646
+ expect(input.componentType).toBe('Input');
647
+ expect(input.conditionFilter).toBe(undefined);
648
+ expect(input.shouldBeFiltered).toBe(true);
649
+ });
650
+
651
+ it('should tag options with shouldBeFiltered=true if the component should be filtered', () => {
652
+ const components = [
653
+ {
654
+ id: 'radio1',
655
+ componentType: 'Radio',
656
+ page: '1',
657
+ label: {
658
+ type: 'VTL|MD',
659
+ value: '"Radio label"',
660
+ },
661
+ options: [
662
+ { value: 'yes', label: { value: '"Yes"', type: 'VTL|MD' } },
663
+ { value: 'no', label: { value: '"No"', type: 'VTL|MD' } },
664
+ ],
665
+ response: {
666
+ name: 'TESTRADIO',
667
+ },
668
+ conditionFilter: {
669
+ type: 'VTL',
670
+ // value should be string, but did not find how to execute correctly with mocks
671
+ // for having a false conditionFilter at the end
672
+ value: false,
673
+ },
674
+ },
675
+ ];
676
+
677
+ const mockVariables = LunaticVariablesStore.makeFromObject({
678
+ TESTINPUT: 'Filled input',
679
+ TESTRADIO: 'yes',
680
+ });
681
+
682
+ const mockState = {
683
+ ...defaultMockState,
684
+ disableFilters: true,
685
+ variables: mockVariables,
686
+ };
687
+
688
+ const filledComponents = fillComponents(
689
+ components as LunaticComponentDefinition[],
690
+ mockState as unknown as FillComponentArgs
691
+ ) as any;
692
+
693
+ const radio = filledComponents[0];
694
+
695
+ expect(radio.componentType).toBe('Radio');
696
+ expect(radio.conditionFilter).toBe(false);
697
+ expect(radio.shouldBeFiltered).toBe(true);
698
+
699
+ expect(radio.options[0].label).toBe('"Yes"');
700
+ expect(radio.options[0].conditionFilter).toBe(undefined);
701
+ expect(radio.options[0].shouldBeFiltered).toBe(true);
702
+
703
+ expect(radio.options[1].label).toBe('"No"');
704
+ expect(radio.options[1].conditionFilter).toBe(undefined);
705
+ expect(radio.options[1].shouldBeFiltered).toBe(true);
706
+ });
581
707
  });
@@ -13,6 +13,7 @@ import { getValueProp } from '../../props/propValue';
13
13
  import { getIterationsProp } from '../../props/propIterations';
14
14
  import { getOptionsProp } from '../../props/propOptions';
15
15
  import { LunaticLogger } from '../../logger/type';
16
+ import { VTLScalarExpression } from '../../../type.source';
16
17
 
17
18
  export type FillComponentArgs = {
18
19
  disableFilters?: boolean;
@@ -35,9 +36,20 @@ export type FillComponentArgs = {
35
36
  */
36
37
  export const fillComponent = (
37
38
  component: LunaticComponentDefinition,
38
- state: FillComponentArgs
39
+ state: FillComponentArgs,
40
+ // the given parentConditionFilter is typed as VTLScalarExpression, but it's actually a boolean or undefined
41
+ parentConditionFilter?: any
39
42
  ): LunaticComponentProps & { conditionFilter?: boolean } => {
40
43
  const interpretedProps = fillComponentExpressions(component, state);
44
+
45
+ const shouldParentBeFiltered = parentConditionFilter === false;
46
+
47
+ const shouldBeFiltered =
48
+ shouldParentBeFiltered ||
49
+ ('conditionFilter' in interpretedProps
50
+ ? !interpretedProps.conditionFilter
51
+ : false);
52
+
41
53
  const value = getValueProp(component, state);
42
54
  return {
43
55
  ...interpretedProps,
@@ -48,6 +60,7 @@ export const fillComponent = (
48
60
  shortcut: state.shortcut,
49
61
  goNextPage: state.goNextPage,
50
62
  goPreviousPage: state.goPreviousPage,
63
+ shouldBeFiltered: shouldBeFiltered,
51
64
  iteration: state.pager.iteration,
52
65
  required: 'isMandatory' in component ? component.isMandatory : false,
53
66
  value: value,
@@ -61,7 +74,8 @@ export const fillComponent = (
61
74
  state.pager.iteration,
62
75
  value,
63
76
  state.logger,
64
- state.disableFilters
77
+ state.disableFilters,
78
+ shouldBeFiltered
65
79
  ),
66
80
  ...getComponentTypeProps(interpretedProps, state),
67
81
  // This is too dynamic to be typed correctly, so we allow any here
@@ -74,7 +88,8 @@ export const fillComponent = (
74
88
  export function fillComponents(
75
89
  components: LunaticComponentDefinition[],
76
90
  state: FillComponentArgs,
77
- parentType?: LunaticComponentDefinition['componentType']
91
+ parentType?: LunaticComponentDefinition['componentType'],
92
+ parentConditionFilter?: VTLScalarExpression
78
93
  ): LunaticComponentProps[] {
79
94
  // Flatmap to directly remove FilterDescription components if disableFiltersDescription is true
80
95
  const filledComponents = components.flatMap((component) => {
@@ -85,7 +100,7 @@ export function fillComponents(
85
100
  return [];
86
101
  }
87
102
 
88
- return [fillComponent(component, state)];
103
+ return [fillComponent(component, state, parentConditionFilter)];
89
104
  });
90
105
 
91
106
  if (state.disableFilters) {
@@ -124,6 +124,18 @@ describe('lunatic-variables-store', () => {
124
124
  expect(variables.get('FIRSTNAME')).toEqual('Jane');
125
125
  });
126
126
 
127
+ it('should ignore iteration when updating a scalar value', () => {
128
+ variables.set('FIRSTNAME', 'John'); // Start with a scalar value
129
+ expect(variables.get('FIRSTNAME')).toEqual('John');
130
+ variables.set('FIRSTNAME', 'Jane', {
131
+ iteration: [1],
132
+ ignoreIterationOnScalar: true,
133
+ });
134
+ expect(variables.get('FIRSTNAME')).toEqual('Jane');
135
+ variables.set('FIRSTNAME', 'Marc', { iteration: [1] });
136
+ expect(variables.get('FIRSTNAME')).toEqual([null, 'Marc']);
137
+ });
138
+
127
139
  describe('event listener', () => {
128
140
  it('should trigger onChange', () => {
129
141
  variables.set('FIRSTNAME', 'John');
@@ -35,6 +35,8 @@ export type EventArgs = {
35
35
  iteration?: IterationLevel | undefined;
36
36
  /** What triggered this change. */
37
37
  cause?: 'resizing' | 'cleaning';
38
+ /** Force a vector when an iteration is set and the value was a scalar **/
39
+ ignoreIterationOnScalar?: boolean;
38
40
  /** Extra sent when setting the variable. */
39
41
  [extra: string]: unknown;
40
42
  };
@@ -168,7 +170,10 @@ export class LunaticVariablesStore {
168
170
  public set(
169
171
  name: string,
170
172
  value: unknown,
171
- args: Pick<EventArgs['change'], 'iteration' | 'cause'> = {}
173
+ args: Pick<
174
+ EventArgs['change'],
175
+ 'iteration' | 'cause' | 'ignoreIterationOnScalar'
176
+ > = {}
172
177
  ): LunaticVariable {
173
178
  if (!this.dictionary.has(name)) {
174
179
  this.dictionary.set(
@@ -188,7 +193,7 @@ export class LunaticVariablesStore {
188
193
  );
189
194
  }
190
195
  const variable = this.dictionary.get(name)!;
191
- if (variable.setValue(value, args.iteration)) {
196
+ if (variable.setValue(value, args)) {
192
197
  this.eventTarget.dispatchEvent(
193
198
  new CustomEvent('change', {
194
199
  detail: {
@@ -390,7 +395,9 @@ class LunaticVariable {
390
395
  // this.calculatedCount++;
391
396
  // Remember the value
392
397
  try {
393
- this.setValue(interpretVTL(this.expression, bindings), iteration);
398
+ this.setValue(interpretVTL(this.expression, bindings), {
399
+ iteration: iteration,
400
+ });
394
401
  } catch {
395
402
  throw new VTLInterpretationError(this.expression!, bindings);
396
403
  }
@@ -401,7 +408,11 @@ class LunaticVariable {
401
408
  /**
402
409
  * Set the value and returns true if the variable is touched
403
410
  */
404
- setValue(value: unknown, iteration?: IterationLevel): boolean {
411
+ setValue(
412
+ value: unknown,
413
+ opts: { iteration?: IterationLevel; ignoreIterationOnScalar?: boolean }
414
+ ): boolean {
415
+ const { iteration, ignoreIterationOnScalar } = opts;
405
416
  if (value === this.getSavedValue(iteration)) {
406
417
  return false;
407
418
  }
@@ -411,6 +422,10 @@ class LunaticVariable {
411
422
  }
412
423
  // We want to save a value at a specific iteration, but the value is not an array yet
413
424
  if (iteration !== undefined && !Array.isArray(this.value)) {
425
+ // Ignore the iteration since the value is not an array
426
+ if (ignoreIterationOnScalar) {
427
+ return this.setValue(value, {});
428
+ }
414
429
  this.value = [];
415
430
  }
416
431
  this.value = !Array.isArray(iteration)
@@ -445,7 +460,7 @@ class LunaticVariable {
445
460
  // Update every item of the array and look if we changed one item
446
461
  const oneValueChanged =
447
462
  times(Math.max(oldSize, newSize), (k) =>
448
- this.setValue(value[k], [k])
463
+ this.setValue(value[k], { iteration: [k] })
449
464
  ).find((v) => v) !== undefined;
450
465
  // New array is smaller, shorten the array
451
466
  if (oldSize > newSize && Array.isArray(this.value)) {
@@ -45,7 +45,12 @@ function getChildComponents(
45
45
  state: State
46
46
  ) {
47
47
  return {
48
- components: fillComponents(component.components, state),
48
+ components: fillComponents(
49
+ component.components,
50
+ state,
51
+ undefined,
52
+ component.conditionFilter
53
+ ),
49
54
  };
50
55
  }
51
56
 
@@ -208,6 +208,7 @@ describe('getOptionsProp()', () => {
208
208
 
209
209
  // Ensure the option is not filtered
210
210
  expect(options).toHaveLength(1);
211
+ expect(options[0].shouldBeFiltered).toBe(false);
211
212
  });
212
213
  it('should not filter option (radio) when its conditionFilter evaluation fails', () => {
213
214
  const definition = {
@@ -237,6 +238,7 @@ describe('getOptionsProp()', () => {
237
238
 
238
239
  // Ensure the option is not filtered
239
240
  expect(options).toHaveLength(1);
241
+ expect(options[0].shouldBeFiltered).toBe(false);
240
242
  });
241
243
  it('should not filter any response (CheckboxGroup) when disableFilters is true', () => {
242
244
  const definition = {
@@ -268,6 +270,8 @@ describe('getOptionsProp()', () => {
268
270
 
269
271
  // Ensure the option is not filtered
270
272
  expect(options).toHaveLength(1);
273
+ // the option should would have been filtered if we did not disable filters
274
+ expect(options[0].shouldBeFiltered).toBe(true);
271
275
  });
272
276
  it('should not filter any option (Radio) when disableFilters is true', () => {
273
277
  const definition = {
@@ -296,7 +300,67 @@ describe('getOptionsProp()', () => {
296
300
  true // disableFilters = true
297
301
  );
298
302
 
303
+ // Ensure the option is not filtered
304
+ expect(options).toHaveLength(1);
305
+ // the option should would have been filtered if we did not disable filters
306
+ expect(options[0].shouldBeFiltered).toBe(true);
307
+ });
308
+ it('should set the response (CheckboxGroup) shouldBeFiltered=true when the parent component should be filtered', () => {
309
+ const definition = {
310
+ ...checkboxGroupDefinition,
311
+ responses: [
312
+ {
313
+ label: 'Option 1',
314
+ response: { name: 'O1' },
315
+ id: 'id1',
316
+ conditionFilter: { type: 'VTL', value: 'expression' },
317
+ },
318
+ ],
319
+ } satisfies DeepTranslateExpression<LunaticComponentDefinition>;
320
+
321
+ const options = getOptionsProp(
322
+ definition,
323
+ variables,
324
+ mockChange,
325
+ undefined,
326
+ undefined,
327
+ mockLogger,
328
+ true, // disableFilters = true
329
+ true // parent component should be filtered
330
+ );
331
+
332
+ // Ensure the option is not filtered
333
+ expect(options).toHaveLength(1);
334
+ // the option would have been filtered if we did not disable filters because its parent would
335
+ expect(options[0].shouldBeFiltered).toBe(true);
336
+ });
337
+ it('should set the option (Radio) shouldBeFiltered=true when the parent component should be filtered', () => {
338
+ const definition = {
339
+ ...radioDefinition,
340
+ options: [
341
+ {
342
+ label: 'Option 1',
343
+ value: 'id1',
344
+ conditionFilter: { type: 'VTL', value: 'expression' },
345
+ },
346
+ ],
347
+ } as any as DeepTranslateExpression<LunaticComponentDefinition>;
348
+
349
+ const options = getOptionsProp(
350
+ definition,
351
+ variables,
352
+ mockChange,
353
+ undefined,
354
+ undefined,
355
+ mockLogger,
356
+ true, // disableFilters = true
357
+ true // parent component should be filtered
358
+ );
359
+
360
+ // Ensure the option is not filtered
299
361
  expect(options).toHaveLength(1);
362
+ // the option would have been filtered if we did not disable filters because its parent would
363
+ expect(options[0].shouldBeFiltered).toBe(true);
300
364
  });
301
365
  });
302
366
  });
@@ -8,6 +8,7 @@ import type { DeepTranslateExpression } from '../commons/fill-components/fill-co
8
8
  import { isNumber } from '../../utils/number';
9
9
  import type { LunaticVariablesStore } from '../commons/variables/lunatic-variables-store';
10
10
  import { LunaticLogger } from '../logger/type';
11
+ import { VtlExpression } from '../../components/type';
11
12
 
12
13
  /* Used for radio option and checkbox one option */
13
14
  export type InterpretedOption = {
@@ -32,10 +33,10 @@ export function getOptionsProp(
32
33
  pagerIteration: LunaticState['pager']['iteration'],
33
34
  value: unknown,
34
35
  logger: LunaticLogger,
35
- disableFilters?: boolean
36
+ disableFilters?: boolean,
37
+ shouldParentBeFiltered?: boolean
36
38
  ) {
37
39
  const iteration = isNumber(pagerIteration) ? [pagerIteration] : undefined;
38
- //const iteration = pagerIteration ? [pagerIteration] : undefined;
39
40
 
40
41
  if (definition.componentType === 'CheckboxGroup') {
41
42
  return definition.responses
@@ -43,16 +44,12 @@ export function getOptionsProp(
43
44
  if (disableFilters || !response.conditionFilter) {
44
45
  return true;
45
46
  }
46
- try {
47
- return variables.run(response.conditionFilter.value, { iteration });
48
- } catch (e) {
49
- // If there is an error interpreting a variable, we do not filter
50
- logger({
51
- type: 'ERROR',
52
- error: e as Error,
53
- });
54
- return true;
55
- }
47
+ return !isFilteredOutOption(
48
+ variables,
49
+ iteration,
50
+ logger,
51
+ response.conditionFilter
52
+ );
56
53
  })
57
54
  .map((response) => ({
58
55
  label: response.label,
@@ -74,6 +71,14 @@ export function getOptionsProp(
74
71
  ]);
75
72
  }
76
73
  : undefined,
74
+ shouldBeFiltered:
75
+ shouldParentBeFiltered ||
76
+ isFilteredOutOption(
77
+ variables,
78
+ iteration,
79
+ logger,
80
+ response.conditionFilter
81
+ ),
77
82
  }));
78
83
  }
79
84
 
@@ -90,16 +95,12 @@ export function getOptionsProp(
90
95
  ) {
91
96
  return true;
92
97
  }
93
- try {
94
- return variables.run(option.conditionFilter.value, { iteration });
95
- } catch (e) {
96
- // If there is an error interpreting a variable, we do not filter
97
- logger({
98
- type: 'ERROR',
99
- error: e as Error,
100
- });
101
- return true;
102
- }
98
+ return !isFilteredOutOption(
99
+ variables,
100
+ iteration,
101
+ logger,
102
+ option.conditionFilter
103
+ );
103
104
  })
104
105
  .map((option) => ({
105
106
  label: option.label,
@@ -126,5 +127,32 @@ export function getOptionsProp(
126
127
  handleChanges([{ name: option.detail!.response.name, value }]);
127
128
  }
128
129
  : null,
130
+ shouldBeFiltered:
131
+ shouldParentBeFiltered ||
132
+ ('conditionFilter' in option &&
133
+ isFilteredOutOption(
134
+ variables,
135
+ iteration,
136
+ logger,
137
+ option.conditionFilter
138
+ )),
129
139
  }));
130
140
  }
141
+
142
+ /**
143
+ * Check if an option should be filtered, depending on its conditionFilter.
144
+ */
145
+ function isFilteredOutOption(
146
+ variables: LunaticVariablesStore,
147
+ iteration: number[] | undefined,
148
+ logger: LunaticLogger,
149
+ conditionFilter?: VtlExpression
150
+ ): boolean {
151
+ if (!conditionFilter) return false;
152
+ try {
153
+ return !variables.run(conditionFilter.value, { iteration });
154
+ } catch (e) {
155
+ logger({ type: 'ERROR', error: e as Error });
156
+ return false;
157
+ }
158
+ }
@@ -5,7 +5,7 @@ import { reduceHandleChanges } from './reduce-handle-changes';
5
5
  import { reduceGoPreviousPage } from './reduce-go-previous-page';
6
6
  import { reduceGoToPage } from './reduce-go-to-page';
7
7
 
8
- // Action that trigger a change in the store
8
+ // Actions that trigger a change in the store
9
9
  const commitActions: ActionKind[] = [
10
10
  ActionKind.GO_PREVIOUS_PAGE,
11
11
  ActionKind.GO_NEXT_PAGE,
@@ -366,5 +366,6 @@ export type LunaticChangesHandler = (
366
366
  name: string;
367
367
  value: any;
368
368
  iteration?: number[];
369
+ ignoreIterationOnScalar?: boolean;
369
370
  }[]
370
371
  ) => void;
@@ -59,6 +59,45 @@ describe('use-lunatic()', () => {
59
59
  expect(components[0].id).toBe('kiq5xw5p');
60
60
  });
61
61
 
62
+ describe('handleChange()', () => {
63
+ it('should change variable value', () => {
64
+ const { result } = renderHook(() => useLunatic(...defaultParams));
65
+ act(() => {
66
+ result.current.handleChanges([
67
+ {
68
+ name: 'COMMENT',
69
+ value: 'Mon commentaire',
70
+ },
71
+ ]);
72
+ });
73
+ act(() => {
74
+ expect(
75
+ result.current.getData(false, ['COMMENT']).COLLECTED!.COMMENT
76
+ .COLLECTED
77
+ ).toBe('Mon commentaire');
78
+ });
79
+ });
80
+ it('should ignore iteration for scalar value', () => {
81
+ const { result } = renderHook(() => useLunatic(...defaultParams));
82
+ act(() => {
83
+ result.current.handleChanges([
84
+ {
85
+ name: 'COMMENT',
86
+ value: 'Mon commentaire 2',
87
+ iteration: [1],
88
+ ignoreIterationOnScalar: true,
89
+ },
90
+ ]);
91
+ });
92
+ act(() => {
93
+ expect(
94
+ result.current.getData(false, ['COMMENT']).COLLECTED!.COMMENT
95
+ .COLLECTED
96
+ ).toBe('Mon commentaire 2');
97
+ });
98
+ });
99
+ });
100
+
62
101
  describe('Provider', () => {
63
102
  it('should not generate a new Provider every render', () => {
64
103
  const { result } = renderHook(() => {