@openmrs/esm-form-engine-lib 2.1.0-pre.1502 → 2.1.0-pre.1511
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/3a3e1d216bd6470d/3a3e1d216bd6470d.gz +0 -0
- package/__mocks__/forms/rfe-forms/bmi-test-form.json +66 -66
- package/__mocks__/forms/rfe-forms/bsa-test-form.json +66 -66
- package/__mocks__/forms/rfe-forms/edd-test-form.json +87 -87
- package/__mocks__/forms/rfe-forms/external_data_source_form.json +35 -36
- package/__mocks__/forms/rfe-forms/historical-expressions-form.json +1 -1
- package/__mocks__/forms/rfe-forms/labour_and_delivery_test_form.json +1 -1
- package/__mocks__/forms/rfe-forms/months-on-art-form.json +89 -89
- package/__mocks__/forms/rfe-forms/next-visit-test-form.json +77 -77
- package/b3059e748360776a/b3059e748360776a.gz +0 -0
- package/dist/openmrs-esm-form-engine-lib.js +1 -1
- package/package.json +1 -1
- package/src/components/inputs/date/date.test.tsx +107 -0
- package/src/components/inputs/number/number.component.tsx +2 -1
- package/src/components/inputs/ui-select-extended/ui-select-extended.component.tsx +1 -1
- package/src/components/inputs/ui-select-extended/ui-select-extended.test.tsx +5 -0
- package/src/processors/encounter/encounter-form-processor.ts +13 -14
- package/src/types/schema.ts +0 -2
- package/src/utils/common-expression-helpers.test.ts +74 -5
- package/src/utils/common-expression-helpers.ts +21 -26
- package/src/utils/expression-runner.test.ts +156 -69
- package/src/utils/expression-runner.ts +85 -135
- package/aaf8197a12df0c40/aaf8197a12df0c40.gz +0 -0
- package/aba5c979c0dbf1c7/aba5c979c0dbf1c7.gz +0 -0
- package/src/utils/expression-parser.test.ts +0 -308
- package/src/utils/expression-parser.ts +0 -158
- /package/{47245761e3f779c4/47245761e3f779c4.gz → 22ec231da647cd2c/22ec231da647cd2c.gz} +0 -0
- /package/{6f1d94035d69e5e1/6f1d94035d69e5e1.gz → 485e0040f135cee8/485e0040f135cee8.gz} +0 -0
@@ -1,10 +1,17 @@
|
|
1
1
|
import { getRegisteredExpressionHelpers } from '../registry/registry';
|
2
2
|
import { isEmpty } from 'lodash-es';
|
3
3
|
import { type OpenmrsEncounter, type FormField, type FormPage, type FormSection } from '../types';
|
4
|
-
import { CommonExpressionHelpers } from './common-expression-helpers';
|
5
|
-
import { findAndRegisterReferencedFields, linkReferencedFieldValues, parseExpression } from './expression-parser';
|
4
|
+
import { CommonExpressionHelpers, registerDependency, simpleHash } from './common-expression-helpers';
|
6
5
|
import { HistoricalDataSourceService } from '../datasources/historical-data-source';
|
7
|
-
import {
|
6
|
+
import {
|
7
|
+
compile,
|
8
|
+
type DefaultEvaluateReturnType,
|
9
|
+
evaluateAsType,
|
10
|
+
evaluateAsTypeAsync,
|
11
|
+
extractVariableNames,
|
12
|
+
type VariablesMap,
|
13
|
+
type Visit,
|
14
|
+
} from '@openmrs/esm-framework';
|
8
15
|
|
9
16
|
export interface FormNode {
|
10
17
|
value: FormPage | FormSection | FormField;
|
@@ -19,7 +26,21 @@ export interface ExpressionContext {
|
|
19
26
|
visit?: Visit;
|
20
27
|
}
|
21
28
|
|
22
|
-
export
|
29
|
+
export type EvaluateReturnType = DefaultEvaluateReturnType | Record<string, any>;
|
30
|
+
|
31
|
+
export const astCache = new Map();
|
32
|
+
|
33
|
+
function typePredicate(result: unknown): result is EvaluateReturnType {
|
34
|
+
return (
|
35
|
+
typeof result === 'string' ||
|
36
|
+
typeof result === 'number' ||
|
37
|
+
typeof result === 'boolean' ||
|
38
|
+
typeof result === 'undefined' ||
|
39
|
+
typeof result === 'object' || // Support for arbitrary objects
|
40
|
+
result === null ||
|
41
|
+
result === undefined
|
42
|
+
);
|
43
|
+
}
|
23
44
|
|
24
45
|
export function evaluateExpression(
|
25
46
|
expression: string,
|
@@ -31,54 +52,12 @@ export function evaluateExpression(
|
|
31
52
|
if (!expression?.trim()) {
|
32
53
|
return null;
|
33
54
|
}
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
// register dependencies
|
38
|
-
findAndRegisterReferencedFields(node, parts, fields);
|
39
|
-
// setup function scope
|
40
|
-
let { myValue, patient } = context;
|
41
|
-
const { sex, age } = patient && 'sex' in patient && 'age' in patient ? patient : { sex: undefined, age: undefined };
|
42
|
-
|
43
|
-
if (node.type === 'field' && myValue === undefined && node.value) {
|
44
|
-
myValue = fieldValues[node.value['id']];
|
45
|
-
}
|
46
|
-
|
47
|
-
const HD = new HistoricalDataSourceService();
|
48
|
-
|
49
|
-
HD.putObject('prevEnc', {
|
50
|
-
value: context.previousEncounter || { obs: [] },
|
51
|
-
getValue(concept) {
|
52
|
-
return this.value.obs.find((obs) => obs.concept.uuid == concept);
|
53
|
-
},
|
54
|
-
});
|
55
|
-
|
56
|
-
const visitType = context.visit?.visitType || { uuid: '' };
|
57
|
-
const visitTypeUuid = visitType.uuid ?? '';
|
58
|
-
|
59
|
-
const _ = {
|
60
|
-
isEmpty,
|
61
|
-
};
|
62
|
-
|
63
|
-
const expressionContext = {
|
64
|
-
...new CommonExpressionHelpers(node, patient, fields, fieldValues, allFieldsKeys),
|
65
|
-
...getRegisteredExpressionHelpers(),
|
66
|
-
...context,
|
67
|
-
fieldValues,
|
68
|
-
patient,
|
69
|
-
myValue,
|
70
|
-
sex,
|
71
|
-
age,
|
72
|
-
HD,
|
73
|
-
visitType,
|
74
|
-
visitTypeUuid,
|
75
|
-
_,
|
76
|
-
};
|
77
|
-
|
78
|
-
expression = linkReferencedFieldValues(fields, fieldValues, parts);
|
55
|
+
const compiledExpression = getExpressionAst(expression);
|
56
|
+
// track dependencies
|
57
|
+
trackFieldDependencies(compiledExpression, node, fields);
|
79
58
|
|
80
59
|
try {
|
81
|
-
return
|
60
|
+
return evaluateAsType(compiledExpression, getEvaluationContext(node, fields, fieldValues, context), typePredicate);
|
82
61
|
} catch (error) {
|
83
62
|
console.error(`Error: ${error} \n\n failing expression: ${expression}`);
|
84
63
|
}
|
@@ -95,25 +74,35 @@ export async function evaluateAsyncExpression(
|
|
95
74
|
if (!expression?.trim()) {
|
96
75
|
return null;
|
97
76
|
}
|
77
|
+
const compiledExpression = getExpressionAst(expression);
|
78
|
+
// track dependencies
|
79
|
+
trackFieldDependencies(compiledExpression, node, fields);
|
80
|
+
try {
|
81
|
+
return evaluateAsTypeAsync(
|
82
|
+
compiledExpression,
|
83
|
+
getEvaluationContext(node, fields, fieldValues, context),
|
84
|
+
typePredicate,
|
85
|
+
);
|
86
|
+
} catch (error) {
|
87
|
+
console.error(`Error: ${error} \n\n failing expression: ${expression}`);
|
88
|
+
}
|
89
|
+
return null;
|
90
|
+
}
|
98
91
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
// register dependencies
|
106
|
-
findAndRegisterReferencedFields(node, parts, fields);
|
107
|
-
|
108
|
-
// setup function scope
|
92
|
+
function getEvaluationContext(
|
93
|
+
node: FormNode,
|
94
|
+
formFields: FormField[],
|
95
|
+
fieldValues: Record<string, any>,
|
96
|
+
context: ExpressionContext,
|
97
|
+
): VariablesMap {
|
109
98
|
let { myValue, patient } = context;
|
110
|
-
const { sex, age } = patient
|
111
|
-
|
99
|
+
const { sex, age } = patient ?? {};
|
100
|
+
|
101
|
+
if (node.type === 'field' && myValue === undefined && node.value) {
|
112
102
|
myValue = fieldValues[node.value['id']];
|
113
103
|
}
|
114
104
|
|
115
105
|
const HD = new HistoricalDataSourceService();
|
116
|
-
|
117
106
|
HD.putObject('prevEnc', {
|
118
107
|
value: context.previousEncounter || { obs: [] },
|
119
108
|
getValue(concept) {
|
@@ -121,99 +110,60 @@ export async function evaluateAsyncExpression(
|
|
121
110
|
},
|
122
111
|
});
|
123
112
|
|
113
|
+
const visitType = context.visit?.visitType || { uuid: '' };
|
114
|
+
const visitTypeUuid = visitType.uuid ?? '';
|
115
|
+
|
124
116
|
const _ = {
|
125
117
|
isEmpty,
|
126
118
|
};
|
127
119
|
|
128
|
-
|
129
|
-
...new CommonExpressionHelpers(node, patient,
|
120
|
+
return {
|
121
|
+
...new CommonExpressionHelpers(node, patient, formFields, fieldValues),
|
130
122
|
...getRegisteredExpressionHelpers(),
|
131
123
|
...context,
|
132
|
-
fieldValues,
|
124
|
+
...fieldValues,
|
133
125
|
patient,
|
134
126
|
myValue,
|
135
127
|
sex,
|
136
128
|
age,
|
137
|
-
temporaryObjectsMap: {},
|
138
129
|
HD,
|
139
130
|
visitType,
|
140
131
|
visitTypeUuid,
|
141
132
|
_,
|
142
133
|
};
|
143
|
-
|
144
|
-
expression = linkReferencedFieldValues(fields, fieldValues, parts);
|
145
|
-
|
146
|
-
// parts with resolve-able field references
|
147
|
-
parts = parseExpression(expression);
|
148
|
-
const lazyFragments = [];
|
149
|
-
parts.forEach((part, index) => {
|
150
|
-
if (index % 2 == 0) {
|
151
|
-
if (part.startsWith('resolve(')) {
|
152
|
-
const [refinedSubExpression] = checkReferenceToResolvedFragment(part);
|
153
|
-
lazyFragments.push({ expression: refinedSubExpression, index });
|
154
|
-
}
|
155
|
-
}
|
156
|
-
});
|
157
|
-
|
158
|
-
const temporaryObjectsMap = {};
|
159
|
-
// resolve lazy fragments
|
160
|
-
const fragments = await Promise.all(lazyFragments.map(({ expression }) => evaluate(expression, expressionContext)));
|
161
|
-
lazyFragments.forEach((fragment, index) => {
|
162
|
-
if (typeof fragments[index] == 'object') {
|
163
|
-
const objectKey = `obj_${index}`;
|
164
|
-
temporaryObjectsMap[objectKey] = fragments[index];
|
165
|
-
expression = expression.replace(fragment.expression, `temporaryObjectsMap.${objectKey}`);
|
166
|
-
} else {
|
167
|
-
expression = expression.replace(
|
168
|
-
fragment.expression,
|
169
|
-
typeof fragments[index] == 'string' ? `'${fragments[index]}'` : fragments[index],
|
170
|
-
);
|
171
|
-
}
|
172
|
-
});
|
173
|
-
|
174
|
-
expressionContext.temporaryObjectsMap = temporaryObjectsMap;
|
175
|
-
|
176
|
-
try {
|
177
|
-
return evaluate(expression, expressionContext);
|
178
|
-
} catch (error) {
|
179
|
-
console.error(`Error: ${error} \n\n failing expression: ${expression}`);
|
180
|
-
}
|
181
|
-
return null;
|
182
134
|
}
|
183
135
|
|
184
136
|
/**
|
185
|
-
*
|
186
|
-
*
|
187
|
-
* @
|
188
|
-
* @returns An array containing the resolved fragment and the remaining chained reference.
|
137
|
+
* Compiles an expression into an abstract syntax tree (AST) and caches the result.
|
138
|
+
* @param expression - The expression to compile.
|
139
|
+
* @returns The abstract syntax tree (AST) of the compiled expression.
|
189
140
|
*/
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
141
|
+
function getExpressionAst(expression: string): ReturnType<typeof compile> {
|
142
|
+
const hash = simpleHash(expression);
|
143
|
+
if (astCache.has(hash)) {
|
144
|
+
return astCache.get(hash);
|
145
|
+
}
|
146
|
+
const ast = compile(expression);
|
147
|
+
astCache.set(hash, ast);
|
148
|
+
return ast;
|
196
149
|
}
|
197
150
|
|
198
151
|
/**
|
199
|
-
*
|
200
|
-
*
|
201
|
-
*
|
202
|
-
*
|
203
|
-
* ```js
|
204
|
-
* evaluate("myNum + 2", { myNum: 5 }); // 7
|
205
|
-
* ```
|
206
|
-
*
|
207
|
-
* Note that references to variables not included in the `expressionContext` will result at
|
208
|
-
* `undefined` during evaluation.
|
209
|
-
*
|
210
|
-
* @param expression A JS expression to execute
|
211
|
-
* @param expressionContext A JS object consisting of the names to make available in the scope
|
212
|
-
* the expression is executed in.
|
152
|
+
* Extracts all referenced fields in the expression and registers them as dependencies.
|
153
|
+
* @param expression - The expression to track dependencies for.
|
154
|
+
* @param fieldNode - The node representing the field.
|
155
|
+
* @param allFields - The list of all fields in the form.
|
213
156
|
*/
|
214
|
-
function
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
157
|
+
export function trackFieldDependencies(
|
158
|
+
expression: ReturnType<typeof compile>,
|
159
|
+
fieldNode: FormNode,
|
160
|
+
allFields: FormField[],
|
161
|
+
) {
|
162
|
+
const variables = extractVariableNames(expression);
|
163
|
+
for (const variable of variables) {
|
164
|
+
const field = allFields.find((field) => field.id === variable);
|
165
|
+
if (field) {
|
166
|
+
registerDependency(fieldNode, field);
|
167
|
+
}
|
168
|
+
}
|
219
169
|
}
|
Binary file
|
Binary file
|
@@ -1,308 +0,0 @@
|
|
1
|
-
import { type FormField } from '../types';
|
2
|
-
import { ConceptFalse } from '../constants';
|
3
|
-
import {
|
4
|
-
extractArgs,
|
5
|
-
findAndRegisterReferencedFields,
|
6
|
-
hasParentheses,
|
7
|
-
linkReferencedFieldValues,
|
8
|
-
parseExpression,
|
9
|
-
replaceFieldRefWithValuePath,
|
10
|
-
} from './expression-parser';
|
11
|
-
import { testFields } from './expression-runner.test';
|
12
|
-
|
13
|
-
describe('Expression parsing', () => {
|
14
|
-
it('should split expression 1 into parts correctly', () => {
|
15
|
-
const input =
|
16
|
-
"isDateBefore(myValue, '1980-01-01') || myValue < useFieldValue('initiationDate', null) && getOtherValue('arg1', 'arg2')";
|
17
|
-
const expectedOutput = [
|
18
|
-
"isDateBefore(myValue, '1980-01-01')",
|
19
|
-
'||',
|
20
|
-
'myValue',
|
21
|
-
'<',
|
22
|
-
"useFieldValue('initiationDate', null)",
|
23
|
-
'&&',
|
24
|
-
"getOtherValue('arg1', 'arg2')",
|
25
|
-
];
|
26
|
-
|
27
|
-
expect(parseExpression(input)).toEqual(expectedOutput);
|
28
|
-
});
|
29
|
-
|
30
|
-
it('should split expression 2 into parts correctly', () => {
|
31
|
-
const input = "isDateBefore(myValue, '1980-01-01') || myValue < useFieldValue('initiationDate', null)";
|
32
|
-
const expectedOutput = [
|
33
|
-
"isDateBefore(myValue, '1980-01-01')",
|
34
|
-
'||',
|
35
|
-
'myValue',
|
36
|
-
'<',
|
37
|
-
"useFieldValue('initiationDate', null)",
|
38
|
-
];
|
39
|
-
|
40
|
-
expect(parseExpression(input)).toEqual(expectedOutput);
|
41
|
-
});
|
42
|
-
|
43
|
-
it('should split expression 3 into parts correctly', () => {
|
44
|
-
const input =
|
45
|
-
"isDateBefore(myValue, '1980-01-01') != myValue && useFieldValue('initiationDate', null) && getOtherValue('Some string', 'Some other string')";
|
46
|
-
const expectedOutput = [
|
47
|
-
"isDateBefore(myValue, '1980-01-01')",
|
48
|
-
'!=',
|
49
|
-
'myValue',
|
50
|
-
'&&',
|
51
|
-
"useFieldValue('initiationDate', null)",
|
52
|
-
'&&',
|
53
|
-
"getOtherValue('Some string', 'Some other string')",
|
54
|
-
];
|
55
|
-
|
56
|
-
expect(parseExpression(input)).toEqual(expectedOutput);
|
57
|
-
});
|
58
|
-
|
59
|
-
it('should split expression 4 into parts correctly', () => {
|
60
|
-
const input = "getValue('some id') ? 'was truthy' : 'was false'";
|
61
|
-
const expectedOutput = ["getValue('some id')", '?', "'was truthy'", ':', "'was false'"];
|
62
|
-
|
63
|
-
expect(parseExpression(input)).toEqual(expectedOutput);
|
64
|
-
});
|
65
|
-
});
|
66
|
-
|
67
|
-
describe('replaceFieldRefWithValuePath', () => {
|
68
|
-
const field1: FormField = {
|
69
|
-
label: 'Visit Count',
|
70
|
-
type: 'obs',
|
71
|
-
questionOptions: {
|
72
|
-
rendering: 'number',
|
73
|
-
concept: '162576AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
74
|
-
answers: [],
|
75
|
-
},
|
76
|
-
id: 'htsVisitCount',
|
77
|
-
};
|
78
|
-
|
79
|
-
const field2: FormField = {
|
80
|
-
label: 'Notes',
|
81
|
-
type: 'obs',
|
82
|
-
questionOptions: {
|
83
|
-
rendering: 'text',
|
84
|
-
concept: '162576AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
85
|
-
answers: [],
|
86
|
-
},
|
87
|
-
id: 'notes',
|
88
|
-
};
|
89
|
-
|
90
|
-
const field3: FormField = {
|
91
|
-
label: 'Was HIV tested?',
|
92
|
-
type: 'obs',
|
93
|
-
questionOptions: {
|
94
|
-
rendering: 'toggle',
|
95
|
-
concept: '162576AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
96
|
-
answers: [],
|
97
|
-
},
|
98
|
-
id: 'wasHivTested',
|
99
|
-
};
|
100
|
-
|
101
|
-
it("should replace 'htsVisitCount' with value path", () => {
|
102
|
-
// setup
|
103
|
-
const token = "isEmpty('htsVisitCount')";
|
104
|
-
// replay
|
105
|
-
const result = replaceFieldRefWithValuePath(field1, 10, token);
|
106
|
-
// verify
|
107
|
-
expect(result).toEqual('isEmpty(fieldValues.htsVisitCount)');
|
108
|
-
});
|
109
|
-
|
110
|
-
it('should replace "notes" with value path', () => {
|
111
|
-
// setup
|
112
|
-
const token = 'api.getValue(notes)';
|
113
|
-
// replay
|
114
|
-
const result = replaceFieldRefWithValuePath(field2, 'Some notes', token);
|
115
|
-
// verify
|
116
|
-
expect(result).toEqual('api.getValue(fieldValues.notes)');
|
117
|
-
});
|
118
|
-
|
119
|
-
it('should replace "wasHivTested" with the system encoded boolean value for toggle rendering types', () => {
|
120
|
-
const token = "isEmpty('wasHivTested')";
|
121
|
-
const result = replaceFieldRefWithValuePath(field3, false, token);
|
122
|
-
expect(result).toEqual(`isEmpty('${ConceptFalse}')`);
|
123
|
-
});
|
124
|
-
});
|
125
|
-
|
126
|
-
describe('linkReferencedFieldValues', () => {
|
127
|
-
const field1: FormField = {
|
128
|
-
label: 'Visit Count',
|
129
|
-
type: 'obs',
|
130
|
-
questionOptions: {
|
131
|
-
rendering: 'number',
|
132
|
-
concept: '162576AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
133
|
-
answers: [],
|
134
|
-
},
|
135
|
-
id: 'htsVisitCount',
|
136
|
-
};
|
137
|
-
|
138
|
-
const field2: FormField = {
|
139
|
-
label: 'Notes',
|
140
|
-
type: 'obs',
|
141
|
-
questionOptions: {
|
142
|
-
rendering: 'text',
|
143
|
-
concept: '162576AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
144
|
-
answers: [],
|
145
|
-
},
|
146
|
-
id: 'notes',
|
147
|
-
};
|
148
|
-
|
149
|
-
const field3: FormField = {
|
150
|
-
label: 'Was HIV tested?',
|
151
|
-
type: 'obs',
|
152
|
-
questionOptions: {
|
153
|
-
rendering: 'toggle',
|
154
|
-
concept: '162576AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
155
|
-
answers: [],
|
156
|
-
},
|
157
|
-
id: 'wasHivTested',
|
158
|
-
};
|
159
|
-
|
160
|
-
const valuesMap = {
|
161
|
-
htsVisitCount: 10,
|
162
|
-
notes: 'Some notes',
|
163
|
-
wasHivTested: false,
|
164
|
-
};
|
165
|
-
|
166
|
-
it("should replace 'htsVisitCount' with value path", () => {
|
167
|
-
// setup
|
168
|
-
const expression = "htsVisitCount && helpFn1(htsVisitCount) && helpFn2('htsVisitCount')";
|
169
|
-
// replay
|
170
|
-
const result = linkReferencedFieldValues([field1], valuesMap, parseExpression(expression));
|
171
|
-
// verify
|
172
|
-
expect(result).toEqual(
|
173
|
-
'fieldValues.htsVisitCount && helpFn1(fieldValues.htsVisitCount) && helpFn2(fieldValues.htsVisitCount)',
|
174
|
-
);
|
175
|
-
});
|
176
|
-
|
177
|
-
it('should support complex expressions', () => {
|
178
|
-
// setup
|
179
|
-
const expression =
|
180
|
-
'htsVisitCount > 2 ? resolve(api.getByConcept(wasHivTested)) : resolve(api.call2ndApi(wasHivTested, htsVisitCount))';
|
181
|
-
// replay
|
182
|
-
const result = linkReferencedFieldValues([field1, field2, field3], valuesMap, parseExpression(expression));
|
183
|
-
// verify
|
184
|
-
expect(result).toEqual(
|
185
|
-
`fieldValues.htsVisitCount > 2 ? resolve(api.getByConcept('${ConceptFalse}')) : resolve(api.call2ndApi('${ConceptFalse}', fieldValues.htsVisitCount))`,
|
186
|
-
);
|
187
|
-
});
|
188
|
-
|
189
|
-
it('should ignore ref to useFieldValue', () => {
|
190
|
-
// setup
|
191
|
-
const expression =
|
192
|
-
"htsVisitCount > 2 ? resolve(api.getByConcept(useFieldValue('wasHivTested'))) : resolve(api.call2ndApi(wasHivTested, useFieldValue('htsVisitCount')))";
|
193
|
-
// replay
|
194
|
-
const result = linkReferencedFieldValues([field1, field2, field3], valuesMap, parseExpression(expression));
|
195
|
-
// verify
|
196
|
-
expect(result).toEqual(
|
197
|
-
`fieldValues.htsVisitCount > 2 ? resolve(api.getByConcept(useFieldValue('wasHivTested'))) : resolve(api.call2ndApi('${ConceptFalse}', useFieldValue('htsVisitCount')))`,
|
198
|
-
);
|
199
|
-
});
|
200
|
-
});
|
201
|
-
|
202
|
-
describe('findAndRegisterReferencedFields', () => {
|
203
|
-
it('should register field dependents', () => {
|
204
|
-
// setup
|
205
|
-
const expression = "linkedToCare == 'cf82933b-3f3f-45e7-a5ab-5d31aaee3da3' && !isEmpty(htsProviderRemarks)";
|
206
|
-
const patientIdentificationNumberField = testFields.find((f) => f.id === 'patientIdentificationNumber');
|
207
|
-
|
208
|
-
// replay
|
209
|
-
findAndRegisterReferencedFields(
|
210
|
-
{ value: patientIdentificationNumberField, type: 'field' },
|
211
|
-
parseExpression(expression),
|
212
|
-
testFields,
|
213
|
-
);
|
214
|
-
|
215
|
-
// verify
|
216
|
-
const linkedToCare = testFields.find((f) => f.id === 'linkedToCare');
|
217
|
-
const htsProviderRemarks = testFields.find((f) => f.id === 'htsProviderRemarks');
|
218
|
-
expect(linkedToCare.fieldDependents).toStrictEqual(new Set(['patientIdentificationNumber']));
|
219
|
-
expect(htsProviderRemarks.fieldDependents).toStrictEqual(new Set(['patientIdentificationNumber']));
|
220
|
-
});
|
221
|
-
});
|
222
|
-
|
223
|
-
describe('extractArgs', () => {
|
224
|
-
it('should extract single argument correctly', () => {
|
225
|
-
const expression = "('arg1')";
|
226
|
-
const expectedOutput = ['arg1'];
|
227
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
228
|
-
});
|
229
|
-
|
230
|
-
it('should extract multiple arguments correctly', () => {
|
231
|
-
const expression = "('arg1', 'arg2', 'arg3')";
|
232
|
-
const expectedOutput = ['arg1', 'arg2', 'arg3'];
|
233
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
234
|
-
});
|
235
|
-
|
236
|
-
it('should handle arguments with spaces correctly', () => {
|
237
|
-
const expression = "('arg with spaces', 'another arg')";
|
238
|
-
const expectedOutput = ['arg with spaces', 'another arg'];
|
239
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
240
|
-
});
|
241
|
-
|
242
|
-
it('should handle arguments with special characters correctly', () => {
|
243
|
-
const expression = "('arg!@#$', 'another$%^&arg')";
|
244
|
-
const expectedOutput = ['arg!@#$', 'another$%^&arg'];
|
245
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
246
|
-
});
|
247
|
-
|
248
|
-
it('should handle no arguments correctly', () => {
|
249
|
-
const expression = '()';
|
250
|
-
const expectedOutput = [];
|
251
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
252
|
-
});
|
253
|
-
|
254
|
-
it('should handle arguments with escaped quotes correctly', () => {
|
255
|
-
const expression = "('arg\\'with\\'escaped\\'quotes', 'another\\'arg')";
|
256
|
-
const expectedOutput = ["arg'with'escaped'quotes", "another'arg"];
|
257
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
258
|
-
});
|
259
|
-
|
260
|
-
it('should handle complex expressions with various argument types', () => {
|
261
|
-
const expression = "('string', 123, true, 'another string')";
|
262
|
-
const expectedOutput = ['string', '123', 'true', 'another string'];
|
263
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
264
|
-
});
|
265
|
-
|
266
|
-
it('should handle arguments with no quotes correctly', () => {
|
267
|
-
const expression = '(arg1, arg2)';
|
268
|
-
const expectedOutput = ['arg1', 'arg2'];
|
269
|
-
expect(extractArgs(expression)).toEqual(expectedOutput);
|
270
|
-
});
|
271
|
-
});
|
272
|
-
|
273
|
-
describe('hasParentheses', () => {
|
274
|
-
it('should return true for expression with single set of parentheses', () => {
|
275
|
-
const expression = 'myFunction(arg1, arg2)';
|
276
|
-
expect(hasParentheses(expression)).toBe(true);
|
277
|
-
});
|
278
|
-
|
279
|
-
it('should return true for expression with multiple sets of parentheses', () => {
|
280
|
-
const expression = '(arg1 && (arg2 || arg3))';
|
281
|
-
expect(hasParentheses(expression)).toBe(true);
|
282
|
-
});
|
283
|
-
|
284
|
-
it('should return true for expression with nested parentheses', () => {
|
285
|
-
const expression = 'outerFunction(innerFunction(arg1, arg2))';
|
286
|
-
expect(hasParentheses(expression)).toBe(true);
|
287
|
-
});
|
288
|
-
|
289
|
-
it('should return false for expression without parentheses', () => {
|
290
|
-
const expression = 'arg1 && arg2 || arg3';
|
291
|
-
expect(hasParentheses(expression)).toBe(false);
|
292
|
-
});
|
293
|
-
|
294
|
-
it('should return true for expression with parentheses inside quotes', () => {
|
295
|
-
const expression = "myFunction('arg(with)parentheses')";
|
296
|
-
expect(hasParentheses(expression)).toBe(true);
|
297
|
-
});
|
298
|
-
|
299
|
-
it('should return true for expression with mixed characters and parentheses', () => {
|
300
|
-
const expression = 'a + b * (c - d)';
|
301
|
-
expect(hasParentheses(expression)).toBe(true);
|
302
|
-
});
|
303
|
-
|
304
|
-
it('should return true for complex expression with multiple types of parentheses', () => {
|
305
|
-
const expression = 'func1(arg1, (func2(arg2) && func3(arg3)))';
|
306
|
-
expect(hasParentheses(expression)).toBe(true);
|
307
|
-
});
|
308
|
-
});
|