@inseefr/lunatic 3.6.1 → 3.6.3
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.
- package/components/PairwiseLinks/PairwiseLinks.js +2 -0
- package/components/PairwiseLinks/PairwiseLinks.js.map +1 -1
- package/components/shared/Checkbox/CheckboxOption.d.ts +1 -0
- package/components/shared/Checkbox/CheckboxOption.js +2 -2
- package/components/shared/Checkbox/CheckboxOption.js.map +1 -1
- package/components/shared/Radio/RadioOption.js +2 -2
- package/components/shared/Radio/RadioOption.js.map +1 -1
- package/components/type.d.ts +4 -1
- package/esm/components/PairwiseLinks/PairwiseLinks.js +2 -0
- package/esm/components/PairwiseLinks/PairwiseLinks.js.map +1 -1
- package/esm/components/shared/Checkbox/CheckboxOption.d.ts +1 -0
- package/esm/components/shared/Checkbox/CheckboxOption.js +2 -2
- package/esm/components/shared/Checkbox/CheckboxOption.js.map +1 -1
- package/esm/components/shared/Radio/RadioOption.js +2 -2
- package/esm/components/shared/Radio/RadioOption.js.map +1 -1
- package/esm/components/type.d.ts +4 -1
- package/esm/type.source.d.ts +3 -0
- package/esm/use-lunatic/commons/variables/behaviours/cleaning-behaviour.d.ts +7 -2
- package/esm/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js +106 -33
- package/esm/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js.map +1 -1
- package/esm/use-lunatic/commons/variables/get-questionnaire-data.js +2 -1
- package/esm/use-lunatic/commons/variables/get-questionnaire-data.js.map +1 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.js +8 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.js.map +1 -1
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js +76 -2
- package/esm/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
- package/esm/use-lunatic/props/getComponentTypeProps.d.ts +4 -1
- package/esm/use-lunatic/props/propOptions.d.ts +6 -2
- package/esm/use-lunatic/props/propOptions.js +9 -7
- package/esm/use-lunatic/props/propOptions.js.map +1 -1
- package/esm/use-lunatic/props/propOptions.spec.js +68 -0
- package/esm/use-lunatic/props/propOptions.spec.js.map +1 -1
- package/package.json +1 -1
- package/src/components/PairwiseLinks/PairwiseLinks.tsx +1 -0
- package/src/components/shared/Checkbox/CheckboxOption.tsx +3 -0
- package/src/components/shared/Radio/RadioOption.tsx +2 -0
- package/src/components/type.ts +4 -1
- package/src/stories/pairwise/source.json +4 -1
- package/src/stories/questionnaires/simpsons/source.json +6 -0
- package/src/type.source.ts +3 -0
- package/src/use-lunatic/commons/variables/behaviours/cleaning-behaviour.ts +120 -30
- package/src/use-lunatic/commons/variables/get-questionnaire-data.ts +4 -1
- package/src/use-lunatic/commons/variables/lunatic-variables-store.spec.ts +87 -2
- package/src/use-lunatic/commons/variables/lunatic-variables-store.ts +9 -1
- package/src/use-lunatic/props/propOptions.spec.ts +88 -0
- package/src/use-lunatic/props/propOptions.ts +10 -5
- package/tsconfig.build.tsbuildinfo +1 -1
- package/type.source.d.ts +3 -0
- package/use-lunatic/commons/variables/behaviours/cleaning-behaviour.d.ts +7 -2
- package/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js +105 -32
- package/use-lunatic/commons/variables/behaviours/cleaning-behaviour.js.map +1 -1
- package/use-lunatic/commons/variables/get-questionnaire-data.js +2 -1
- package/use-lunatic/commons/variables/get-questionnaire-data.js.map +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.js +8 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.js.map +1 -1
- package/use-lunatic/commons/variables/lunatic-variables-store.spec.js +76 -2
- package/use-lunatic/commons/variables/lunatic-variables-store.spec.js.map +1 -1
- package/use-lunatic/props/getComponentTypeProps.d.ts +4 -1
- package/use-lunatic/props/propOptions.d.ts +6 -2
- package/use-lunatic/props/propOptions.js +6 -4
- package/use-lunatic/props/propOptions.js.map +1 -1
- package/use-lunatic/props/propOptions.spec.js +68 -0
- package/use-lunatic/props/propOptions.spec.js.map +1 -1
|
@@ -18,6 +18,7 @@ export type CheckboxOptionProps = {
|
|
|
18
18
|
invalid?: boolean;
|
|
19
19
|
detailAlwaysDisplayed?: boolean;
|
|
20
20
|
detailLabel?: ReactNode;
|
|
21
|
+
detailMaxLength?: number;
|
|
21
22
|
detailValue?: string | null;
|
|
22
23
|
onDetailChange?: (value: string) => void;
|
|
23
24
|
};
|
|
@@ -32,6 +33,7 @@ function LunaticCheckboxOption({
|
|
|
32
33
|
description,
|
|
33
34
|
detailAlwaysDisplayed,
|
|
34
35
|
detailLabel,
|
|
36
|
+
detailMaxLength,
|
|
35
37
|
detailValue,
|
|
36
38
|
onDetailChange,
|
|
37
39
|
codeModality,
|
|
@@ -105,6 +107,7 @@ function LunaticCheckboxOption({
|
|
|
105
107
|
id="detailId"
|
|
106
108
|
label={detailLabel ?? 'Précisez :'}
|
|
107
109
|
value={typeof detailValue === 'string' ? detailValue : ''}
|
|
110
|
+
maxLength={detailMaxLength}
|
|
108
111
|
onChange={onDetailChange}
|
|
109
112
|
disabled={disabled}
|
|
110
113
|
/>
|
|
@@ -38,6 +38,7 @@ function LunaticRadioOption({
|
|
|
38
38
|
onDetailChange,
|
|
39
39
|
detailAlwaysDisplayed,
|
|
40
40
|
detailLabel,
|
|
41
|
+
detailMaxLength,
|
|
41
42
|
detailValue,
|
|
42
43
|
onCheck,
|
|
43
44
|
onUncheck,
|
|
@@ -127,6 +128,7 @@ function LunaticRadioOption({
|
|
|
127
128
|
id="detailId"
|
|
128
129
|
label={detailLabel ?? 'Précisez :'}
|
|
129
130
|
value={typeof detailValue === 'string' ? detailValue : ''}
|
|
131
|
+
maxLength={detailMaxLength}
|
|
130
132
|
onChange={onDetailChange}
|
|
131
133
|
disabled={disabled}
|
|
132
134
|
/>
|
package/src/components/type.ts
CHANGED
|
@@ -195,8 +195,10 @@ export type ComponentPropsByType = {
|
|
|
195
195
|
description?: ReactNode;
|
|
196
196
|
onCheck: (b: boolean) => void;
|
|
197
197
|
onDetailChange?: (v: string) => void;
|
|
198
|
-
detailValue?: string | null;
|
|
199
198
|
detailLabel?: ReactNode;
|
|
199
|
+
detailMaxLength?: number;
|
|
200
|
+
detailValue?: string | null;
|
|
201
|
+
shouldBeFiltered?: boolean;
|
|
200
202
|
}[];
|
|
201
203
|
orientation?: 'horizontal' | 'vertical';
|
|
202
204
|
detailAlwaysDisplayed?: boolean;
|
|
@@ -251,6 +253,7 @@ export type ComponentPropsByType = {
|
|
|
251
253
|
description?: ReactNode;
|
|
252
254
|
label: ReactNode;
|
|
253
255
|
value: string;
|
|
256
|
+
shouldBeFiltered?: boolean;
|
|
254
257
|
}>;
|
|
255
258
|
response: { name: string };
|
|
256
259
|
componentType?: 'Dropdown';
|
|
@@ -90,7 +90,10 @@
|
|
|
90
90
|
{
|
|
91
91
|
"componentType": "Dropdown",
|
|
92
92
|
"id": "dropdown-1",
|
|
93
|
-
"conditionFilter": {
|
|
93
|
+
"conditionFilter": {
|
|
94
|
+
"value": "(nvl(xAxis, \"\") <> \"\") and (nvl(yAxis, \"\") <> \"\")",
|
|
95
|
+
"type": "VTL"
|
|
96
|
+
},
|
|
94
97
|
"label": {
|
|
95
98
|
"value": "\"Qui est \" || yAxis || \" pour \" || xAxis || \" ?\"",
|
|
96
99
|
"type": "VTL|MD"
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
"$schema": "../../../../lunatic-schema.json",
|
|
3
3
|
"componentType": "Questionnaire",
|
|
4
4
|
"variables": [
|
|
5
|
+
{
|
|
6
|
+
"variableType": "CALCULATED",
|
|
7
|
+
"expression": { "type": "VTL", "value": "\"2020\"" },
|
|
8
|
+
"name": "IGNOREDCALC",
|
|
9
|
+
"isIgnoredByLunatic": true
|
|
10
|
+
},
|
|
5
11
|
{
|
|
6
12
|
"variableType": "CALCULATED",
|
|
7
13
|
"expression": { "type": "VTL", "value": "\"2020\"" },
|
package/src/type.source.ts
CHANGED
|
@@ -145,6 +145,7 @@ export type ComponentCheckboxGroupDefinition = ComponentDefinitionBase & {
|
|
|
145
145
|
id: string;
|
|
146
146
|
detail?: {
|
|
147
147
|
label?: VTLExpression;
|
|
148
|
+
maxLength?: number;
|
|
148
149
|
response: ResponseDefinition;
|
|
149
150
|
};
|
|
150
151
|
}[];
|
|
@@ -164,6 +165,7 @@ export type OptionsWithDetail = {
|
|
|
164
165
|
description?: VTLExpression;
|
|
165
166
|
detail?: {
|
|
166
167
|
label: VTLExpression;
|
|
168
|
+
maxLength?: number;
|
|
167
169
|
response: {
|
|
168
170
|
name: string;
|
|
169
171
|
};
|
|
@@ -255,6 +257,7 @@ export type Variable =
|
|
|
255
257
|
variableType: 'CALCULATED';
|
|
256
258
|
name: string;
|
|
257
259
|
expression: VTLExpression;
|
|
260
|
+
isIgnoredByLunatic?: boolean;
|
|
258
261
|
bindingDependencies?: string[];
|
|
259
262
|
shapeFrom?: string[] | string;
|
|
260
263
|
iterationReference?: string;
|
|
@@ -3,17 +3,21 @@ import type {
|
|
|
3
3
|
LunaticVariablesStore,
|
|
4
4
|
} from '../lunatic-variables-store';
|
|
5
5
|
import type { LunaticSource } from '../../../type';
|
|
6
|
-
import { depth } from '../../../../utils/array';
|
|
6
|
+
import { depth, setAtIndex } from '../../../../utils/array';
|
|
7
7
|
import { castBool } from '../../../../utils/cast';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* When a variable changes,
|
|
10
|
+
* Implements the cleaning behavior for the variable store.
|
|
11
|
+
* When a variable changes, this function determines which other variables
|
|
12
|
+
* should be reset based on the cleaning rules defined in the source.
|
|
13
|
+
*
|
|
14
|
+
* @param store - The variables store that manages all variable values
|
|
15
|
+
* @param cleaning - Cleaning rules from the Lunatic source
|
|
16
|
+
* @param sourceValues - Default values from source.json to use when cleaning variables
|
|
12
17
|
*/
|
|
13
18
|
export function cleaningBehaviour(
|
|
14
19
|
store: LunaticVariablesStore,
|
|
15
20
|
cleaning: LunaticSource['cleaning'],
|
|
16
|
-
// Value used as default when cleaning a variable, correspoding to value of variable in source.json (not data)
|
|
17
21
|
sourceValues: Record<string, unknown> = {}
|
|
18
22
|
) {
|
|
19
23
|
if (!cleaning) {
|
|
@@ -37,31 +41,31 @@ export function cleaningBehaviour(
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
//
|
|
44
|
+
// Convert cleaning object to Map for faster lookups
|
|
41
45
|
const cleaningMap = new Map(Object.entries(cleaning));
|
|
42
46
|
|
|
43
47
|
store.on('change', (e) => {
|
|
44
48
|
const cleaningInfo = cleaningMap.get(e.detail.name);
|
|
45
49
|
const iteration = e.detail.iteration;
|
|
46
50
|
|
|
47
|
-
//
|
|
51
|
+
// Skip if the changed variable doesn't have any cleaning rules
|
|
48
52
|
if (!cleaningInfo) {
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
for (const variableName in cleaningInfo) {
|
|
53
57
|
try {
|
|
54
|
-
//
|
|
58
|
+
// Skip if variable is already in a cleaned state
|
|
55
59
|
if (isAlreadyCleaned(store, variableName)) continue;
|
|
56
|
-
// Second: check if variable should be clean i.e one of expressions is true
|
|
57
60
|
|
|
58
|
-
//
|
|
61
|
+
// Determine if the variable needs cleaning based on expressions
|
|
59
62
|
const shouldCleanResult = shouldClean(store, {
|
|
60
63
|
expressions: cleaningInfo[variableName],
|
|
61
64
|
iteration: iteration,
|
|
62
65
|
isResizing: e.detail.cause === 'resizing',
|
|
63
66
|
});
|
|
64
67
|
|
|
68
|
+
// Handle array variables (where we might need to clean specific indexes)
|
|
65
69
|
if (Array.isArray(shouldCleanResult)) {
|
|
66
70
|
cleanArrayVariableAccordingCondition(
|
|
67
71
|
store,
|
|
@@ -71,29 +75,51 @@ export function cleaningBehaviour(
|
|
|
71
75
|
);
|
|
72
76
|
continue;
|
|
73
77
|
}
|
|
78
|
+
|
|
79
|
+
// Clean regular variables if needed
|
|
74
80
|
if (shouldCleanResult)
|
|
75
81
|
cleanVariable(store, sourceValues, variableName, iteration);
|
|
76
82
|
} catch (e) {
|
|
77
|
-
//
|
|
83
|
+
// Log error but continue processing other variables
|
|
78
84
|
console.error(e);
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
});
|
|
82
88
|
}
|
|
83
89
|
|
|
84
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Checks if a variable is already in a cleaned state
|
|
92
|
+
*
|
|
93
|
+
* @param store - The variables store
|
|
94
|
+
* @param variableName - Name of the variable to check
|
|
95
|
+
* @returns true if the variable is null or an array of nulls
|
|
96
|
+
*/
|
|
97
|
+
function isAlreadyCleaned(
|
|
98
|
+
store: LunaticVariablesStore,
|
|
99
|
+
variableName: string
|
|
100
|
+
): boolean {
|
|
85
101
|
const value = store.get(variableName);
|
|
86
102
|
if (Array.isArray(value)) return value.every((v) => v === null);
|
|
87
|
-
|
|
103
|
+
return value === null;
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
/**
|
|
91
|
-
*
|
|
107
|
+
* Determines if a variable needs to be cleaned based on expressions
|
|
108
|
+
*
|
|
109
|
+
* @param store - The variables store
|
|
110
|
+
* @param options - Configuration options
|
|
111
|
+
* @param options.expressions - Conditions that determine if cleaning should occur
|
|
112
|
+
* (string for legacy format or array of expression objects)
|
|
113
|
+
* /!\ Expressions comes from conditionFilter so it evaluates to "true"
|
|
114
|
+
* if the variable is visible, meaning we need to clean if the expression
|
|
115
|
+
* is evaluated to false.
|
|
116
|
+
* @param options.iteration - Current iteration level for the variable
|
|
117
|
+
* @param options.isResizing - Whether the cleaning is triggered by a resize operation
|
|
118
|
+
* @returns Boolean or array of booleans indicating if cleaning should occur
|
|
92
119
|
*/
|
|
93
120
|
function shouldClean(
|
|
94
121
|
store: LunaticVariablesStore,
|
|
95
122
|
{
|
|
96
|
-
// The expressions are a list of condition filter to display the variable, so we should clean if the filter is evaluated to false (false = variable is not visible)
|
|
97
123
|
expressions,
|
|
98
124
|
iteration,
|
|
99
125
|
isResizing,
|
|
@@ -108,8 +134,8 @@ function shouldClean(
|
|
|
108
134
|
iteration?: number[];
|
|
109
135
|
isResizing: boolean;
|
|
110
136
|
}
|
|
111
|
-
) {
|
|
112
|
-
//
|
|
137
|
+
): boolean | boolean[] {
|
|
138
|
+
// Handle legacy format where expressions is a single string
|
|
113
139
|
if (typeof expressions === 'string') {
|
|
114
140
|
return !castBool(
|
|
115
141
|
store.run(expressions, {
|
|
@@ -118,13 +144,13 @@ function shouldClean(
|
|
|
118
144
|
);
|
|
119
145
|
}
|
|
120
146
|
|
|
121
|
-
//
|
|
147
|
+
// Handle a new format with expression objects shaped like this { expression, shapeFrom, isAggregatorUsed }
|
|
122
148
|
if (isResizing) {
|
|
123
|
-
//
|
|
149
|
+
// During resize operations, only consider expressions with aggregators (count, sum...)
|
|
124
150
|
expressions = expressions.filter((expr) => expr.isAggregatorUsed);
|
|
125
151
|
}
|
|
126
152
|
|
|
127
|
-
//
|
|
153
|
+
// If expressions have shapeFrom and we're at root level, we need to check each iteration individually
|
|
128
154
|
if (hasShapeFrom(expressions) && !iteration) {
|
|
129
155
|
const shapeFromVariable = store.get(
|
|
130
156
|
expressions[0].shapeFrom as string
|
|
@@ -143,9 +169,8 @@ function shouldClean(
|
|
|
143
169
|
}
|
|
144
170
|
return shouldCleanArray;
|
|
145
171
|
} else {
|
|
146
|
-
// if
|
|
172
|
+
// Clean the variable if any condition is false (variable is not visible),
|
|
147
173
|
for (const expression of expressions) {
|
|
148
|
-
// Run the expression to check if cleaning should happen
|
|
149
174
|
if (
|
|
150
175
|
!store.run(expression.expression, {
|
|
151
176
|
iteration,
|
|
@@ -154,10 +179,16 @@ function shouldClean(
|
|
|
154
179
|
return true;
|
|
155
180
|
}
|
|
156
181
|
|
|
182
|
+
// All conditions are true, no cleaning needed
|
|
157
183
|
return false;
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Recursively retrieves a value at a specific iteration level in a nested structure
|
|
189
|
+
*
|
|
190
|
+
* @returns The value at the specified iteration path, or null if not found
|
|
191
|
+
*/
|
|
161
192
|
function getValueAtIteration(value: unknown, iteration?: number[]) {
|
|
162
193
|
if (!iteration || iteration.length === 0) {
|
|
163
194
|
return value ?? null;
|
|
@@ -169,12 +200,12 @@ function getValueAtIteration(value: unknown, iteration?: number[]) {
|
|
|
169
200
|
}
|
|
170
201
|
|
|
171
202
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* @returns boolean if all expression has shapeFrom
|
|
203
|
+
* Checks if all expressions in the array have a shapeFrom property
|
|
204
|
+
*
|
|
205
|
+
* In the cleaning model, all expressions either have no shapeFrom
|
|
206
|
+
* or they all have the same shapeFrom value.
|
|
177
207
|
*
|
|
208
|
+
* @returns true if all expressions have a non-null shapeFrom property
|
|
178
209
|
*/
|
|
179
210
|
function hasShapeFrom(
|
|
180
211
|
expressions: {
|
|
@@ -189,6 +220,14 @@ function hasShapeFrom(
|
|
|
189
220
|
);
|
|
190
221
|
}
|
|
191
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Cleans specific elements in an array variable based on a condition array
|
|
225
|
+
*
|
|
226
|
+
* @param store - The variables store
|
|
227
|
+
* @param sourceValues - Default values from source.json
|
|
228
|
+
* @param variableName - Name of the array variable to clean
|
|
229
|
+
* @param shouldClean - Array of booleans indicating which elements should be cleaned
|
|
230
|
+
*/
|
|
192
231
|
function cleanArrayVariableAccordingCondition(
|
|
193
232
|
store: LunaticVariablesStore,
|
|
194
233
|
sourceValues: Record<string, unknown>,
|
|
@@ -202,10 +241,11 @@ function cleanArrayVariableAccordingCondition(
|
|
|
202
241
|
}
|
|
203
242
|
|
|
204
243
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
244
|
+
* Resets a variable to its initial value at a specific iteration level
|
|
245
|
+
*
|
|
246
|
+
* This function retrieves the initial value from sourceValues and sets
|
|
247
|
+
* the variable to that value at the specified iteration. If the variable
|
|
248
|
+
* is a pairwise variable, it uses a special cleaning method to maintain symmetry.
|
|
209
249
|
*/
|
|
210
250
|
function cleanVariable(
|
|
211
251
|
store: LunaticVariablesStore,
|
|
@@ -220,6 +260,9 @@ function cleanVariable(
|
|
|
220
260
|
? undefined
|
|
221
261
|
: iteration?.slice(0, depth(sourceValues[variableName]));
|
|
222
262
|
|
|
263
|
+
if (cleanPairwise(store, variableName, variableIteration)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
223
266
|
store.set(
|
|
224
267
|
variableName,
|
|
225
268
|
getValueAtIteration(sourceValues[variableName], variableIteration),
|
|
@@ -229,3 +272,50 @@ function cleanVariable(
|
|
|
229
272
|
}
|
|
230
273
|
);
|
|
231
274
|
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Cleans a pairwise variable (2D array) at a specific iteration
|
|
278
|
+
*
|
|
279
|
+
* For pairwise variables, cleaning involves setting both the row and column
|
|
280
|
+
* at the specified index to null, maintaining the symmetry of the matrix.
|
|
281
|
+
*
|
|
282
|
+
* @returns true if the variable was a pairwise and was cleaned, false otherwise
|
|
283
|
+
*/
|
|
284
|
+
function cleanPairwise(
|
|
285
|
+
store: LunaticVariablesStore,
|
|
286
|
+
variableName: string,
|
|
287
|
+
iteration?: IterationLevel
|
|
288
|
+
): boolean {
|
|
289
|
+
// We are not trying to clean a pairwise at a specific index
|
|
290
|
+
if (!iteration || iteration.length !== 1) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
const variableValue = store.get(variableName);
|
|
294
|
+
|
|
295
|
+
// The variable is pairwise if it's a 2D array
|
|
296
|
+
if (!Array.isArray(variableValue)) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
const variableDepth = depth(variableValue);
|
|
300
|
+
if (variableDepth !== 2) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Clean the row and the column corresponding to the index
|
|
305
|
+
store.set(
|
|
306
|
+
variableName,
|
|
307
|
+
variableValue.map((value, k) => {
|
|
308
|
+
// The value is not an array, this should not happen so we keep the original value
|
|
309
|
+
if (!Array.isArray(value)) {
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
312
|
+
// Empty the row corresponding to the index being deleted
|
|
313
|
+
if (k === iteration[0]) {
|
|
314
|
+
return value.fill(null);
|
|
315
|
+
}
|
|
316
|
+
// Nullify cells in the column corresponding to the index being deleted
|
|
317
|
+
return setAtIndex(value, iteration, null);
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
@@ -40,7 +40,10 @@ export function getQuestionnaireData(
|
|
|
40
40
|
|
|
41
41
|
for (const variable of variables) {
|
|
42
42
|
// Skip calculated value if necessary
|
|
43
|
-
if (
|
|
43
|
+
if (
|
|
44
|
+
variable.variableType === 'CALCULATED' &&
|
|
45
|
+
(variable.isIgnoredByLunatic || !withCalculated)
|
|
46
|
+
) {
|
|
44
47
|
continue;
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -268,7 +268,7 @@ describe('lunatic-variables-store', () => {
|
|
|
268
268
|
variables.run('"hello"', { iteration: [0] });
|
|
269
269
|
variables.run('"hello"', { iteration: [1] });
|
|
270
270
|
expect(variables.run('"hello"')).toEqual('hello');
|
|
271
|
-
expect(variables.interpretCount).toBe(
|
|
271
|
+
expect(variables.interpretCount).toBe(1); // only 1 interpretation by the engine since optimisation for primitive value
|
|
272
272
|
});
|
|
273
273
|
it('should handle deep refresh', () => {
|
|
274
274
|
variables.set('LIENS', [
|
|
@@ -543,7 +543,7 @@ describe('lunatic-variables-store', () => {
|
|
|
543
543
|
variables.set('READY', [true, true, false]);
|
|
544
544
|
expect(variables.get('PRENOM')).toEqual(['John', 'Jane', null]);
|
|
545
545
|
});
|
|
546
|
-
it('should evaluate cleaning for each iteration at root
|
|
546
|
+
it('should evaluate cleaning for each iteration at root level', () => {
|
|
547
547
|
variables.set('PRENOM', ['John', 'Jane', 'Marc']);
|
|
548
548
|
variables.set('AGE', [18, 21, 23]);
|
|
549
549
|
variables.setCalculated('NB_HAB', 'count(PRENOM)');
|
|
@@ -571,6 +571,45 @@ describe('lunatic-variables-store', () => {
|
|
|
571
571
|
variables.set('PRENOM', ['John', 'Jane']);
|
|
572
572
|
expect(variables.get('AGE')).toEqual([null, null]);
|
|
573
573
|
});
|
|
574
|
+
it('should clean pairwise in two directions', () => {
|
|
575
|
+
variables.set('LINKS', [
|
|
576
|
+
[null, '1', '3'],
|
|
577
|
+
['1', null, '3'],
|
|
578
|
+
['2', '2', null],
|
|
579
|
+
]);
|
|
580
|
+
variables.set('PRENOM', ['Dad', 'Mom', 'Unknow']);
|
|
581
|
+
cleaningBehaviour(
|
|
582
|
+
variables,
|
|
583
|
+
{
|
|
584
|
+
PRENOM: {
|
|
585
|
+
LINKS: [
|
|
586
|
+
{
|
|
587
|
+
expression: 'PRENOM <> ""',
|
|
588
|
+
shapeFrom: 'PRENOM',
|
|
589
|
+
isAggregatorUsed: true,
|
|
590
|
+
},
|
|
591
|
+
],
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
PRENOM: [],
|
|
596
|
+
LINKS: [],
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
variables.set('PRENOM', '', { iteration: [1] });
|
|
600
|
+
expect(variables.get('PRENOM')).toEqual(['Dad', '', 'Unknow']);
|
|
601
|
+
expect(variables.get('LINKS')).toEqual([
|
|
602
|
+
[null, null, '3'],
|
|
603
|
+
[null, null, null],
|
|
604
|
+
['2', null, null],
|
|
605
|
+
]);
|
|
606
|
+
variables.set('PRENOM', '', { iteration: [0] });
|
|
607
|
+
expect(variables.get('LINKS')).toEqual([
|
|
608
|
+
[null, null, null],
|
|
609
|
+
[null, null, null],
|
|
610
|
+
[null, null, null],
|
|
611
|
+
]);
|
|
612
|
+
});
|
|
574
613
|
});
|
|
575
614
|
|
|
576
615
|
describe('missing', () => {
|
|
@@ -657,6 +696,52 @@ describe('lunatic-variables-store', () => {
|
|
|
657
696
|
store.commit();
|
|
658
697
|
expect(store.get('PRENOM')).toEqual('John');
|
|
659
698
|
});
|
|
699
|
+
|
|
700
|
+
it('should handle calculated variables, ignoring them when `isIgnoredByLunatic` is true ', () => {
|
|
701
|
+
const store = LunaticVariablesStore.makeFromSource(
|
|
702
|
+
{
|
|
703
|
+
components: [],
|
|
704
|
+
variables: [
|
|
705
|
+
{
|
|
706
|
+
name: 'calc1',
|
|
707
|
+
dimension: 0,
|
|
708
|
+
expression: {
|
|
709
|
+
type: 'VTL',
|
|
710
|
+
value: '"calculated value"',
|
|
711
|
+
},
|
|
712
|
+
variableType: 'CALCULATED',
|
|
713
|
+
isIgnoredByLunatic: true,
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
name: 'calc2',
|
|
717
|
+
dimension: 0,
|
|
718
|
+
expression: {
|
|
719
|
+
type: 'VTL',
|
|
720
|
+
value: '"calculated value"',
|
|
721
|
+
},
|
|
722
|
+
variableType: 'CALCULATED',
|
|
723
|
+
isIgnoredByLunatic: false,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
name: 'calc3',
|
|
727
|
+
dimension: 0,
|
|
728
|
+
expression: {
|
|
729
|
+
type: 'VTL',
|
|
730
|
+
value: '"calculated value"',
|
|
731
|
+
},
|
|
732
|
+
variableType: 'CALCULATED',
|
|
733
|
+
},
|
|
734
|
+
],
|
|
735
|
+
},
|
|
736
|
+
{},
|
|
737
|
+
{ current: () => {} }
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
expect(store.get('calc1')).toBeNull();
|
|
741
|
+
expect(store.get('calc2')).toBe('calculated value');
|
|
742
|
+
expect(store.get('calc3')).toBe('calculated value');
|
|
743
|
+
});
|
|
744
|
+
|
|
660
745
|
it('should enable cleaning when disableCleaning = false', () => {
|
|
661
746
|
const cleaningSpy = vi.spyOn(cleaningModule, 'cleaningBehaviour');
|
|
662
747
|
LunaticVariablesStore.makeFromSource(
|
|
@@ -88,6 +88,7 @@ export class LunaticVariablesStore {
|
|
|
88
88
|
for (const variable of source.variables) {
|
|
89
89
|
switch (variable.variableType) {
|
|
90
90
|
case 'CALCULATED':
|
|
91
|
+
if (variable.isIgnoredByLunatic) break;
|
|
91
92
|
store.setCalculated(variable.name, variable.expression.value, {
|
|
92
93
|
dependencies: variable.bindingDependencies,
|
|
93
94
|
iterationDepth: getIterationDepth(variable.name),
|
|
@@ -371,9 +372,16 @@ class LunaticVariable {
|
|
|
371
372
|
|
|
372
373
|
// Calculate bindings first to refresh "updatedAt" on calculated dependencies
|
|
373
374
|
const bindings = this.getDependenciesValues(iteration);
|
|
375
|
+
const hasNoBinding = Object.keys(bindings).length === 0;
|
|
376
|
+
|
|
377
|
+
// A static expression should not be reevaluated
|
|
378
|
+
if (hasNoBinding && this.value) {
|
|
379
|
+
return this.value;
|
|
380
|
+
}
|
|
381
|
+
|
|
374
382
|
// A variable without binding is a primitive (string, boolean...)
|
|
375
383
|
// it yields the same results for every iteration, so we can ignore iteration
|
|
376
|
-
if (
|
|
384
|
+
if (hasNoBinding) {
|
|
377
385
|
iteration = undefined;
|
|
378
386
|
}
|
|
379
387
|
if (this.shapeFrom && !this.isOutdated(iteration)) {
|
|
@@ -117,6 +117,94 @@ describe('getOptionsProp()', () => {
|
|
|
117
117
|
{ name: 'O2', value: false },
|
|
118
118
|
]);
|
|
119
119
|
});
|
|
120
|
+
it('should create detail props correctly for checkboxGroup', () => {
|
|
121
|
+
const definition = {
|
|
122
|
+
...checkboxGroupDefinition,
|
|
123
|
+
responses: [
|
|
124
|
+
{
|
|
125
|
+
label: 'Option 1',
|
|
126
|
+
response: { name: 'O1' },
|
|
127
|
+
id: 'id1',
|
|
128
|
+
detail: {
|
|
129
|
+
label: 'Precize:',
|
|
130
|
+
response: { name: 'DETAIL' },
|
|
131
|
+
maxLength: 50,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: 'Option 2',
|
|
136
|
+
response: { name: 'O2' },
|
|
137
|
+
id: 'id2',
|
|
138
|
+
detail: {
|
|
139
|
+
label: 'Precize:',
|
|
140
|
+
response: { name: 'DETAILBIS' },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
} satisfies DeepTranslateExpression<LunaticComponentDefinition>;
|
|
145
|
+
|
|
146
|
+
variables.set('DETAIL', true);
|
|
147
|
+
|
|
148
|
+
const options = getOptionsProp(
|
|
149
|
+
definition,
|
|
150
|
+
variables,
|
|
151
|
+
mockChange,
|
|
152
|
+
undefined,
|
|
153
|
+
undefined,
|
|
154
|
+
mockLogger
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(options).toHaveLength(2);
|
|
158
|
+
expect(options[0].detailLabel).toBe('Precize:');
|
|
159
|
+
expect(options[1].detailLabel).toBe('Precize:');
|
|
160
|
+
expect(options[0].detailValue).toBe(true);
|
|
161
|
+
expect(options[1].detailValue).toBeNull();
|
|
162
|
+
expect(options[0].detailMaxLength).toBe(50);
|
|
163
|
+
expect(options[1].detailMaxLength).toBeUndefined();
|
|
164
|
+
});
|
|
165
|
+
it('should create detail props correctly for Radiogroup', () => {
|
|
166
|
+
const definition = {
|
|
167
|
+
...radioDefinition,
|
|
168
|
+
options: [
|
|
169
|
+
{
|
|
170
|
+
label: 'Option 1',
|
|
171
|
+
value: 'id1',
|
|
172
|
+
detail: {
|
|
173
|
+
label: 'Precize:',
|
|
174
|
+
response: { name: 'DETAIL' },
|
|
175
|
+
maxLength: 50,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
label: 'Option 2',
|
|
180
|
+
value: 'id2',
|
|
181
|
+
detail: {
|
|
182
|
+
label: 'Precize:',
|
|
183
|
+
response: { name: 'DETAILBIS' },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
} satisfies DeepTranslateExpression<LunaticComponentDefinition>;
|
|
188
|
+
|
|
189
|
+
variables.set('DETAIL', true);
|
|
190
|
+
|
|
191
|
+
const options = getOptionsProp(
|
|
192
|
+
definition,
|
|
193
|
+
variables,
|
|
194
|
+
mockChange,
|
|
195
|
+
undefined,
|
|
196
|
+
undefined,
|
|
197
|
+
mockLogger
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(options).toHaveLength(2);
|
|
201
|
+
expect(options[0].detailLabel).toBe('Precize:');
|
|
202
|
+
expect(options[1].detailLabel).toBe('Precize:');
|
|
203
|
+
expect(options[0].detailValue).toBe(true);
|
|
204
|
+
expect(options[1].detailValue).toBeNull();
|
|
205
|
+
expect(options[0].detailMaxLength).toBe(50);
|
|
206
|
+
expect(options[1].detailMaxLength).toBeUndefined();
|
|
207
|
+
});
|
|
120
208
|
it('should filter responses (CheckboxGroup) with conditionFilter evaluated to false', () => {
|
|
121
209
|
const definition = {
|
|
122
210
|
...checkboxGroupDefinition,
|