@inseefr/lunatic 3.12.3 → 3.13.1

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.
@@ -7,6 +7,13 @@ import { depth, setAtIndex } from '../../../../utils/array';
7
7
  import { castBool } from '../../../../utils/cast';
8
8
  import { IterationLevel } from '../models';
9
9
 
10
+ type CleaningExpression = {
11
+ expression: string;
12
+ shapeFrom?: string;
13
+ isAggregatorUsed: boolean;
14
+ shouldCheckAllIterations?: boolean;
15
+ };
16
+
10
17
  /**
11
18
  * Implements the cleaning behavior for the variable store.
12
19
  * When a variable changes, this function determines which other variables
@@ -130,13 +137,7 @@ function shouldClean(
130
137
  iteration,
131
138
  isResizing,
132
139
  }: {
133
- expressions:
134
- | string
135
- | {
136
- expression: string;
137
- shapeFrom?: string;
138
- isAggregatorUsed: boolean;
139
- }[];
140
+ expressions: string | CleaningExpression[];
140
141
  iteration?: number[];
141
142
  isResizing: boolean;
142
143
  }
@@ -156,10 +157,21 @@ function shouldClean(
156
157
  expressions = expressions.filter((expr) => expr.isAggregatorUsed);
157
158
  }
158
159
 
159
- // If expressions have shapeFrom and we're at root level, we need to check each iteration individually
160
- if (hasShapeFrom(expressions) && !iteration) {
160
+ // At least one expression requires to compute on each iteration
161
+ if (shouldCheckAtAllIterations(expressions)) {
162
+ const expressionsToCheckAtAllIterations: CleaningExpression[] = [];
163
+ const expressionsNotToCheckAtAllIterations: CleaningExpression[] = [];
164
+
165
+ for (const expression of expressions) {
166
+ if (expression.shouldCheckAllIterations) {
167
+ expressionsToCheckAtAllIterations.push(expression);
168
+ } else {
169
+ expressionsNotToCheckAtAllIterations.push(expression);
170
+ }
171
+ }
172
+
161
173
  const shapeFromVariable = store.get(
162
- expressions[0].shapeFrom as string
174
+ expressionsToCheckAtAllIterations[0].shapeFrom as string
163
175
  ) as Array<unknown>;
164
176
 
165
177
  const shouldCleanArray = new Array(shapeFromVariable.length).fill(
@@ -167,27 +179,69 @@ function shouldClean(
167
179
  ) as Array<boolean>;
168
180
 
169
181
  for (const [iterationIndex] of shouldCleanArray.entries()) {
170
- shouldCleanArray[iterationIndex] = shouldClean(store, {
182
+ shouldCleanArray[iterationIndex] =
183
+ // check if we need to clean each iteration according to expression
184
+ shouldCleanAtIteration(store, {
185
+ expressions: expressionsToCheckAtAllIterations,
186
+ iteration: [iterationIndex],
187
+ }) ||
188
+ shouldCleanAtIteration(store, {
189
+ expressions: expressionsNotToCheckAtAllIterations,
190
+ iteration,
191
+ });
192
+ }
193
+ return shouldCleanArray;
194
+ }
195
+
196
+ // If expressions have shapeFrom and we're at root level, we need to check each iteration individually
197
+ if (expressionsHaveShapeFrom(expressions) && !iteration) {
198
+ const shapeFrom = findFirstExpressionWithShapeFrom(expressions)?.shapeFrom;
199
+ const shapeFromVariable = store.get(shapeFrom as string) as Array<unknown>;
200
+
201
+ const shouldCleanArray = new Array(shapeFromVariable.length).fill(
202
+ false
203
+ ) as Array<boolean>;
204
+
205
+ for (const [iterationIndex] of shouldCleanArray.entries()) {
206
+ shouldCleanArray[iterationIndex] = shouldCleanAtIteration(store, {
171
207
  expressions,
172
208
  iteration: [iterationIndex],
173
- isResizing,
174
- }) as boolean;
209
+ });
175
210
  }
176
211
  return shouldCleanArray;
177
- } else {
178
- // Clean the variable if any condition is false (variable is not visible),
179
- for (const expression of expressions) {
180
- if (
181
- !store.run(expression.expression, {
182
- iteration,
183
- })
184
- )
185
- return true;
186
- }
212
+ }
187
213
 
188
- // All conditions are true, no cleaning needed
189
- return false;
214
+ return shouldCleanAtIteration(store, { expressions, iteration });
215
+ }
216
+
217
+ function shouldCheckAtAllIterations(expressions: CleaningExpression[]) {
218
+ return expressions.some(
219
+ (expression) => expression.shouldCheckAllIterations && expression.shapeFrom
220
+ );
221
+ }
222
+
223
+ function shouldCleanAtIteration(
224
+ store: LunaticVariablesStore,
225
+ {
226
+ expressions,
227
+ iteration,
228
+ }: {
229
+ expressions: CleaningExpression[];
230
+ iteration?: number[];
231
+ }
232
+ ) {
233
+ // Clean the variable if any condition is false (variable is not visible),
234
+ for (const expression of expressions) {
235
+ if (
236
+ !store.run(expression.expression, {
237
+ iteration,
238
+ })
239
+ )
240
+ return true;
190
241
  }
242
+
243
+ // All conditions are true, no cleaning needed
244
+ return false;
191
245
  }
192
246
 
193
247
  /**
@@ -205,6 +259,10 @@ function getValueAtIteration(value: unknown, iteration?: number[]) {
205
259
  return getValueAtIteration(value[iteration[0]], iteration.slice(1));
206
260
  }
207
261
 
262
+ function hasShapeFrom(expression: CleaningExpression) {
263
+ return expression.shapeFrom !== null && expression.shapeFrom !== undefined;
264
+ }
265
+
208
266
  /**
209
267
  * Checks if all expressions in the array have a shapeFrom property
210
268
  *
@@ -213,17 +271,18 @@ function getValueAtIteration(value: unknown, iteration?: number[]) {
213
271
  *
214
272
  * @returns true if all expressions have a non-null shapeFrom property
215
273
  */
216
- function hasShapeFrom(
274
+ function expressionsHaveShapeFrom(
217
275
  expressions: {
218
276
  expression: string;
219
277
  shapeFrom?: string;
220
278
  isAggregatorUsed: boolean;
221
279
  }[]
222
280
  ) {
223
- return expressions.every(
224
- (expression) =>
225
- expression.shapeFrom !== null && expression.shapeFrom !== undefined
226
- );
281
+ return expressions.every((expression) => hasShapeFrom(expression));
282
+ }
283
+
284
+ function findFirstExpressionWithShapeFrom(expressions: CleaningExpression[]) {
285
+ return expressions.find((expression) => hasShapeFrom(expression));
227
286
  }
228
287
 
229
288
  /**
@@ -572,6 +572,91 @@ describe('lunatic-variables-store', () => {
572
572
  variables.set('PRENOM', ['John', 'Jane']);
573
573
  expect(variables.get('AGE')).toEqual([null, null]);
574
574
  });
575
+ it('should clean but not at every iteration when configured from an iterated source change if there is no shapeFrom provided', () => {
576
+ // Given
577
+ variables.set('PRENOM', ['Marc', 'Marc', 'Marc']);
578
+ variables.set('QCU', 'A');
579
+ cleaningBehaviour(
580
+ variables,
581
+ {
582
+ PRENOM: {
583
+ QCU: [
584
+ {
585
+ expression: 'false',
586
+ isAggregatorUsed: false,
587
+ shouldCheckAllIterations: true,
588
+ },
589
+ ],
590
+ },
591
+ },
592
+ { PRENOM: [], QCU: null }
593
+ );
594
+
595
+ // When
596
+ variables.set('PRENOM', 'Patrick', { iteration: [0] });
597
+
598
+ // Then
599
+ expect(variables.get('QCU')).toEqual(null);
600
+ });
601
+ it('should check every iteration when configured from an iterated source change', () => {
602
+ // Given
603
+ variables.set('PRENOM', ['Marc', 'Marc', 'Marc']);
604
+ variables.set('QCU', ['A', 'B', 'B']);
605
+ cleaningBehaviour(
606
+ variables,
607
+ {
608
+ PRENOM: {
609
+ QCU: [
610
+ {
611
+ expression: 'PRENOM <> "Marc"',
612
+ shapeFrom: 'PRENOM',
613
+ isAggregatorUsed: false,
614
+ shouldCheckAllIterations: true,
615
+ },
616
+ ],
617
+ },
618
+ },
619
+ { PRENOM: [], QCU: [null] }
620
+ );
621
+
622
+ // When
623
+ variables.set('PRENOM', 'Patrick', { iteration: [0] });
624
+
625
+ // Then
626
+ expect(variables.get('QCU')).toEqual(['A', null, null]);
627
+ });
628
+ it('should handle simultaneously expressions that should check every iteration and expression that should not ', () => {
629
+ // Given
630
+ variables.set('PRENOM', ['Marc', 'Marc', 'Marc']);
631
+ variables.set('QCU', ['A', 'B', 'B']);
632
+ cleaningBehaviour(
633
+ variables,
634
+ {
635
+ PRENOM: {
636
+ QCU: [
637
+ {
638
+ expression: 'PRENOM <> ""',
639
+ shapeFrom: 'PRENOM',
640
+ isAggregatorUsed: false,
641
+ },
642
+ {
643
+ expression: 'PRENOM <> "Marc"',
644
+ shapeFrom: 'PRENOM',
645
+ isAggregatorUsed: false,
646
+ shouldCheckAllIterations: true,
647
+ },
648
+ ],
649
+ },
650
+ },
651
+ { PRENOM: [], QCU: [null] }
652
+ );
653
+
654
+ // When
655
+ variables.set('PRENOM', '', { iteration: [0] });
656
+
657
+ // Then
658
+ expect(variables.get('QCU')).toEqual([null, null, null]);
659
+ });
575
660
  it('should clean pairwise in two directions', () => {
576
661
  variables.set('LINKS', [
577
662
  [null, '1', '3'],