@servicetitan/dte-pdf-editor 1.45.0 → 1.47.0
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/README.md +15 -0
- package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.js +4 -2
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +11 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js +14 -2
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-calculated.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +3 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +2 -2
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts +2 -1
- package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-calculated.js +5 -2
- package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.d.ts +10 -2
- package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js +16 -12
- package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts +2 -2
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +4 -4
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/interface/types.d.ts +2 -1
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/utils/formula/expression.utils.d.ts +13 -0
- package/dist/utils/formula/expression.utils.d.ts.map +1 -1
- package/dist/utils/formula/expression.utils.js +42 -0
- package/dist/utils/formula/expression.utils.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/field-types/date-fields.test.ts +212 -0
- package/src/__tests__/field-types/display-condition-fields.test.ts +340 -0
- package/src/__tests__/field-types/e-sign-fields.test.ts +97 -0
- package/src/__tests__/field-types/fillable-fields.test.ts +180 -0
- package/src/__tests__/field-types/form-fields.test.ts +193 -0
- package/src/__tests__/field-types/formula-fields.test.ts +245 -0
- package/src/__tests__/field-types/merge-tag-fields.test.ts +208 -0
- package/src/components/field-config-panel/field-config-panel.tsx +11 -0
- package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +12 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-calculated.tsx +20 -4
- package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +10 -0
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +5 -1
- package/src/components/pdf-view/pdf-view-calculated.tsx +13 -3
- package/src/components/pdf-view/pdf-view-fillable.tsx +62 -21
- package/src/components/pdf-view/pdf-view.tsx +11 -10
- package/src/interface/types.ts +12 -10
- package/src/styles/generic.css +6 -0
- package/src/styles/inline-editable.css +1 -0
- package/src/utils/conditions/__tests__/evaluate.utils.test.ts +163 -0
- package/src/utils/conditions/__tests__/schema-data-points.utils.test.ts +149 -0
- package/src/utils/data-model/__tests__/extract-fields.utils.test.ts +154 -0
- package/src/utils/data-model/__tests__/resolve-values.utils.test.ts +60 -0
- package/src/utils/field/__tests__/field-background-color.utils.test.ts +26 -0
- package/src/utils/field/__tests__/field-placeholder-text.utils.test.ts +46 -0
- package/src/utils/formula/__tests__/dom.utils.test.ts +33 -0
- package/src/utils/formula/__tests__/evaluate-formula.utils.test.ts +202 -0
- package/src/utils/formula/__tests__/expression.utils.test.ts +119 -0
- package/src/utils/formula/__tests__/form-fields.utils.test.ts +274 -0
- package/src/utils/formula/__tests__/format-calculated-result.utils.test.ts +105 -0
- package/src/utils/formula/__tests__/render-formula.utils.test.ts +43 -0
- package/src/utils/formula/__tests__/validate-formula.utils.test.ts +168 -0
- package/src/utils/formula/expression.utils.ts +44 -0
- package/src/utils/path/__tests__/generate-e-sign-path.test.ts +26 -0
- package/src/utils/path/__tests__/generate-fillable-path.test.ts +17 -0
- package/src/utils/path/__tests__/parse-fillable-path.test.ts +25 -0
- package/src/utils/recipients/__tests__/map-colors.test.ts +40 -0
- package/src/utils/shared/__tests__/date.utils.test.ts +58 -0
- package/src/utils/shared/__tests__/number.utils.test.ts +48 -0
|
@@ -25,6 +25,48 @@ export function tokensToExpression(formula) {
|
|
|
25
25
|
.replace(/\s+/g, ' ')
|
|
26
26
|
.trim();
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert StructuredFormula tokens to a human-readable display string.
|
|
30
|
+
*
|
|
31
|
+
* Field token label resolution order:
|
|
32
|
+
* 1. `pathToLabel[path]` — caller-provided override (fillable / data-model labels).
|
|
33
|
+
* 2. `formSnapshot.fieldName` — embedded form-field label carried on the token.
|
|
34
|
+
* 3. raw `path` — last-resort fallback.
|
|
35
|
+
*
|
|
36
|
+
* Use this for read-only UI surfaces where the formula is shown for context
|
|
37
|
+
* (overlays, previews) — NOT for round-tripping through `parseExpression`,
|
|
38
|
+
* which expects paths.
|
|
39
|
+
*/
|
|
40
|
+
export function formulaToDisplayText(formula, pathToLabel) {
|
|
41
|
+
var _a;
|
|
42
|
+
if (!((_a = formula === null || formula === void 0 ? void 0 : formula.tokens) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
return formula.tokens
|
|
46
|
+
.map(t => {
|
|
47
|
+
var _a;
|
|
48
|
+
if (t.type === 'field') {
|
|
49
|
+
const overrideLabel = pathToLabel === null || pathToLabel === void 0 ? void 0 : pathToLabel.get(t.path);
|
|
50
|
+
if (overrideLabel) {
|
|
51
|
+
return overrideLabel;
|
|
52
|
+
}
|
|
53
|
+
if ((_a = t.formSnapshot) === null || _a === void 0 ? void 0 : _a.fieldName) {
|
|
54
|
+
return t.formSnapshot.fieldName;
|
|
55
|
+
}
|
|
56
|
+
return t.path;
|
|
57
|
+
}
|
|
58
|
+
if (t.type === 'lparen') {
|
|
59
|
+
return '(';
|
|
60
|
+
}
|
|
61
|
+
if (t.type === 'rparen') {
|
|
62
|
+
return ')';
|
|
63
|
+
}
|
|
64
|
+
return t.value;
|
|
65
|
+
})
|
|
66
|
+
.join(' ')
|
|
67
|
+
.replace(/\s+/g, ' ')
|
|
68
|
+
.trim();
|
|
69
|
+
}
|
|
28
70
|
// Field paths may contain hyphens (e.g., fillable_recipient_uuid with UUIDs)
|
|
29
71
|
const EXPRESSION_REGEX = /[A-Za-z_][A-Za-z0-9_.-]*|\d+(?:\.\d+)?|[()+\-*/]|\s+|./g;
|
|
30
72
|
export function tokenizeExpression(expression) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/expression.utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"expression.utils.js","sourceRoot":"","sources":["../../../src/utils/formula/expression.utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CalculatedFormatForDate,
|
|
3
|
+
DisplayConditionState,
|
|
4
|
+
StructuredFormula,
|
|
5
|
+
} from '../../interface/types';
|
|
6
|
+
import {
|
|
7
|
+
dateToUtcZero,
|
|
8
|
+
evaluateDisplayCondition,
|
|
9
|
+
evaluateFormula,
|
|
10
|
+
formatCalculatedResult,
|
|
11
|
+
toDateInputValue,
|
|
12
|
+
} from '../../utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scenario tests for date fields.
|
|
16
|
+
*
|
|
17
|
+
* Validates the full lifecycle of a date value:
|
|
18
|
+
* raw input -> UTC normalization -> display condition evaluation
|
|
19
|
+
* -> business-day formula arithmetic -> calculated-field formatting.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
describe('date field scenarios', () => {
|
|
23
|
+
describe('with input conversion', () => {
|
|
24
|
+
test('normalizes a YYYY-MM-DD input to UTC midnight ISO string', () => {
|
|
25
|
+
expect(dateToUtcZero('2024-06-15')).toBe('2024-06-15T00:00:00.000Z');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('round-trips an ISO date string through toDateInputValue', () => {
|
|
29
|
+
const isoString = dateToUtcZero('2024-06-15');
|
|
30
|
+
expect(toDateInputValue(isoString)).toBe('2024-06-15');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('returns empty string when stored value is empty', () => {
|
|
34
|
+
expect(toDateInputValue('')).toBe('');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('returns empty string when stored value is invalid Date', () => {
|
|
38
|
+
expect(toDateInputValue(new Date('invalid'))).toBe('');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('with display conditions on date fields', () => {
|
|
43
|
+
const subject = (state: DisplayConditionState, data: Record<string, unknown>) =>
|
|
44
|
+
evaluateDisplayCondition(state, data);
|
|
45
|
+
|
|
46
|
+
const dateValue = dateToUtcZero('2024-06-15');
|
|
47
|
+
|
|
48
|
+
test('matches with is_equal_to when stored ISO matches expected ISO', () => {
|
|
49
|
+
const state: DisplayConditionState = {
|
|
50
|
+
behavior: 'show',
|
|
51
|
+
groups: [
|
|
52
|
+
{
|
|
53
|
+
id: 'g',
|
|
54
|
+
conditions: [
|
|
55
|
+
{
|
|
56
|
+
id: 'c',
|
|
57
|
+
dataPointKey: 'signed_at',
|
|
58
|
+
operator: 'is_equal_to',
|
|
59
|
+
value: dateValue,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
expect(subject(state, { signed_at: dateValue })).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('contains operator matches a partial date string', () => {
|
|
69
|
+
const state: DisplayConditionState = {
|
|
70
|
+
behavior: 'show',
|
|
71
|
+
groups: [
|
|
72
|
+
{
|
|
73
|
+
id: 'g',
|
|
74
|
+
conditions: [
|
|
75
|
+
{
|
|
76
|
+
id: 'c',
|
|
77
|
+
dataPointKey: 'signed_at',
|
|
78
|
+
operator: 'contains',
|
|
79
|
+
value: '2024-06',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
expect(subject(state, { signed_at: dateValue })).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('is_empty hides field when date has not been filled', () => {
|
|
89
|
+
const state: DisplayConditionState = {
|
|
90
|
+
behavior: 'hide',
|
|
91
|
+
groups: [
|
|
92
|
+
{
|
|
93
|
+
id: 'g',
|
|
94
|
+
conditions: [
|
|
95
|
+
{
|
|
96
|
+
id: 'c',
|
|
97
|
+
dataPointKey: 'signed_at',
|
|
98
|
+
operator: 'is_empty',
|
|
99
|
+
value: '',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
expect(subject(state, {})).toBe(false);
|
|
106
|
+
expect(subject(state, { signed_at: dateValue })).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('with date fields in formulas', () => {
|
|
111
|
+
const subject = (formula: StructuredFormula, data: Record<string, string>) =>
|
|
112
|
+
evaluateFormula(formula, data);
|
|
113
|
+
|
|
114
|
+
test('adds business days to a date and returns epoch ms of the result', () => {
|
|
115
|
+
// Monday 2024-06-10 + 3 business days = Thursday 2024-06-13
|
|
116
|
+
const formula: StructuredFormula = {
|
|
117
|
+
tokens: [
|
|
118
|
+
{ type: 'field', path: 'start', fieldType: 'date' },
|
|
119
|
+
{ type: 'operator', value: '+' },
|
|
120
|
+
{ type: 'number', value: '3' },
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
const result = subject(formula, { start: '2024-06-10' });
|
|
124
|
+
expect(result).not.toBeNull();
|
|
125
|
+
const resolved = new Date(result as number);
|
|
126
|
+
expect(resolved.getUTCFullYear()).toBe(2024);
|
|
127
|
+
expect(resolved.getUTCMonth()).toBe(5);
|
|
128
|
+
expect(resolved.getUTCDate()).toBe(13);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('skips weekends when adding business days across a Friday boundary', () => {
|
|
132
|
+
// Friday 2024-06-14 + 1 business day = Monday 2024-06-17
|
|
133
|
+
const formula: StructuredFormula = {
|
|
134
|
+
tokens: [
|
|
135
|
+
{ type: 'field', path: 'start', fieldType: 'date' },
|
|
136
|
+
{ type: 'operator', value: '+' },
|
|
137
|
+
{ type: 'number', value: '1' },
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
const result = subject(formula, { start: '2024-06-14' });
|
|
141
|
+
expect(new Date(result as number).getUTCDate()).toBe(17);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('skips weekends and explicit holidays when adding business days', () => {
|
|
145
|
+
const formula: StructuredFormula = {
|
|
146
|
+
tokens: [
|
|
147
|
+
{ type: 'field', path: 'start', fieldType: 'date' },
|
|
148
|
+
{ type: 'operator', value: '+' },
|
|
149
|
+
{ type: 'number', value: '1' },
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
// Mon 2024-06-10 + 1 day, but 2024-06-11 is a holiday => Wed 2024-06-12
|
|
153
|
+
const result = evaluateFormula(formula, { start: '2024-06-10' }, ['2024-06-11']);
|
|
154
|
+
expect(new Date(result as number).getUTCDate()).toBe(12);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('returns null when date field has invalid value', () => {
|
|
158
|
+
const formula: StructuredFormula = {
|
|
159
|
+
tokens: [
|
|
160
|
+
{ type: 'field', path: 'start', fieldType: 'date' },
|
|
161
|
+
{ type: 'operator', value: '+' },
|
|
162
|
+
{ type: 'number', value: '1' },
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
expect(subject(formula, { start: 'not-a-date' })).toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('returns null when date field is missing from data', () => {
|
|
169
|
+
const formula: StructuredFormula = {
|
|
170
|
+
tokens: [
|
|
171
|
+
{ type: 'field', path: 'start', fieldType: 'date' },
|
|
172
|
+
{ type: 'operator', value: '+' },
|
|
173
|
+
{ type: 'number', value: '1' },
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
expect(subject(formula, {})).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('with calculated-field date formatting', () => {
|
|
181
|
+
const today = new Date();
|
|
182
|
+
|
|
183
|
+
test('formats today as MM/DD/YYYY by default', () => {
|
|
184
|
+
const format: CalculatedFormatForDate = {
|
|
185
|
+
resultType: 'date',
|
|
186
|
+
dateFormat: 'MM/DD/YYYY',
|
|
187
|
+
};
|
|
188
|
+
expect(formatCalculatedResult(today.getTime(), format)).toMatch(
|
|
189
|
+
/^\d{2}\/\d{2}\/\d{4}$/,
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('substitutes long month token', () => {
|
|
194
|
+
const format: CalculatedFormatForDate = {
|
|
195
|
+
resultType: 'date',
|
|
196
|
+
dateFormat: 'MMMM YYYY',
|
|
197
|
+
};
|
|
198
|
+
const result = formatCalculatedResult(today.getTime(), format);
|
|
199
|
+
expect(result).toMatch(
|
|
200
|
+
/^(January|February|March|April|May|June|July|August|September|October|November|December) \d{4}$/,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('returns empty string for dates far outside display range', () => {
|
|
205
|
+
const format: CalculatedFormatForDate = {
|
|
206
|
+
resultType: 'date',
|
|
207
|
+
dateFormat: 'YYYY',
|
|
208
|
+
};
|
|
209
|
+
expect(formatCalculatedResult(Date.UTC(1900, 0, 1), format)).toBe('');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataPointOption,
|
|
3
|
+
DisplayConditionState,
|
|
4
|
+
FieldTypeEnum,
|
|
5
|
+
FormFieldsByFormIdI,
|
|
6
|
+
PdfField,
|
|
7
|
+
SchemaObject,
|
|
8
|
+
} from '../../interface/types';
|
|
9
|
+
import {
|
|
10
|
+
buildFormFieldKey,
|
|
11
|
+
evaluateDisplayCondition,
|
|
12
|
+
generateFillablePath,
|
|
13
|
+
getDataPointOptions,
|
|
14
|
+
getDisplayConditionFieldTypeForKey,
|
|
15
|
+
getDocumentFieldsDataPointOptions,
|
|
16
|
+
getSchemaDataPointOptions,
|
|
17
|
+
inferDisplayConditionSourceKind,
|
|
18
|
+
} from '../../utils';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scenario tests for display-condition fields.
|
|
22
|
+
*
|
|
23
|
+
* Display conditions can reference three sources:
|
|
24
|
+
* - data model (merge tags)
|
|
25
|
+
* - fillable fields (filled by recipient)
|
|
26
|
+
* - form submission fields
|
|
27
|
+
*
|
|
28
|
+
* These tests cover source-kind inference, type resolution, and evaluation
|
|
29
|
+
* across each source with realistic AND/OR composition and show/hide behavior.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const schema: SchemaObject = {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
Customer: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
title: 'Customer',
|
|
38
|
+
properties: {
|
|
39
|
+
Name: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
title: 'Name',
|
|
42
|
+
options: { useInConditionals: true },
|
|
43
|
+
},
|
|
44
|
+
Age: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
title: 'Age',
|
|
47
|
+
options: { useInConditionals: true },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const fillableField: PdfField = {
|
|
55
|
+
id: 'fillable-1',
|
|
56
|
+
type: FieldTypeEnum.fillable,
|
|
57
|
+
subType: 'text',
|
|
58
|
+
x: 0,
|
|
59
|
+
y: 0,
|
|
60
|
+
page: 1,
|
|
61
|
+
label: 'Email',
|
|
62
|
+
width: 100,
|
|
63
|
+
height: 25,
|
|
64
|
+
recipient: 'signer1',
|
|
65
|
+
path: generateFillablePath('signer1', 'Email'),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const formFieldsByFormId: FormFieldsByFormIdI = {
|
|
69
|
+
7: [
|
|
70
|
+
{ id: 'abc-123', header: 'Score', itemType: 'number' },
|
|
71
|
+
{ id: 'def-456', header: 'Comment', itemType: 'text' },
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const formScoreKey = buildFormFieldKey(7, 'abc-123');
|
|
76
|
+
const formCommentKey = buildFormFieldKey(7, 'def-456');
|
|
77
|
+
|
|
78
|
+
describe('display-condition field scenarios', () => {
|
|
79
|
+
describe('with source-kind inference', () => {
|
|
80
|
+
const mergeTagOptions: DataPointOption[] = getSchemaDataPointOptions(schema);
|
|
81
|
+
const fillableOptions: DataPointOption[] = getDocumentFieldsDataPointOptions([
|
|
82
|
+
fillableField,
|
|
83
|
+
]);
|
|
84
|
+
const subject = (key: string) =>
|
|
85
|
+
inferDisplayConditionSourceKind(key, mergeTagOptions, fillableOptions);
|
|
86
|
+
|
|
87
|
+
test.each([
|
|
88
|
+
['data model', 'Customer.Name', FieldTypeEnum.dataModel],
|
|
89
|
+
['fillable', fillableField.path!, FieldTypeEnum.fillable],
|
|
90
|
+
['form', formScoreKey, FieldTypeEnum.forms],
|
|
91
|
+
])('infers %s source from key', (_, key, expected) => {
|
|
92
|
+
expect(subject(key)).toBe(expected);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('returns null when key is empty', () => {
|
|
96
|
+
expect(subject('')).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('returns null for unknown key', () => {
|
|
100
|
+
expect(subject('unknown.path')).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('with field-type resolution', () => {
|
|
105
|
+
const mergeTagOptions = getSchemaDataPointOptions(schema);
|
|
106
|
+
const fillableOptions = getDocumentFieldsDataPointOptions([fillableField]);
|
|
107
|
+
|
|
108
|
+
test('returns number for numeric merge tag', () => {
|
|
109
|
+
expect(
|
|
110
|
+
getDisplayConditionFieldTypeForKey(
|
|
111
|
+
'Customer.Age',
|
|
112
|
+
mergeTagOptions,
|
|
113
|
+
fillableOptions,
|
|
114
|
+
formFieldsByFormId,
|
|
115
|
+
),
|
|
116
|
+
).toBe('number');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('returns string for fillable text field', () => {
|
|
120
|
+
expect(
|
|
121
|
+
getDisplayConditionFieldTypeForKey(
|
|
122
|
+
fillableField.path!,
|
|
123
|
+
mergeTagOptions,
|
|
124
|
+
fillableOptions,
|
|
125
|
+
formFieldsByFormId,
|
|
126
|
+
),
|
|
127
|
+
).toBe('string');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('returns number for numeric form field', () => {
|
|
131
|
+
expect(
|
|
132
|
+
getDisplayConditionFieldTypeForKey(
|
|
133
|
+
formScoreKey,
|
|
134
|
+
mergeTagOptions,
|
|
135
|
+
fillableOptions,
|
|
136
|
+
formFieldsByFormId,
|
|
137
|
+
),
|
|
138
|
+
).toBe('number');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('returns string for text form field', () => {
|
|
142
|
+
expect(
|
|
143
|
+
getDisplayConditionFieldTypeForKey(
|
|
144
|
+
formCommentKey,
|
|
145
|
+
mergeTagOptions,
|
|
146
|
+
fillableOptions,
|
|
147
|
+
formFieldsByFormId,
|
|
148
|
+
),
|
|
149
|
+
).toBe('string');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('with options exposed to the condition picker', () => {
|
|
154
|
+
test('includes merge tag and fillable options sorted by title', () => {
|
|
155
|
+
const options = getDataPointOptions(schema, [fillableField]);
|
|
156
|
+
expect(options.map(o => o.title)).toEqual([
|
|
157
|
+
'Customer - Age',
|
|
158
|
+
'Customer - Name',
|
|
159
|
+
'Email',
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('with condition evaluation on merge tag source', () => {
|
|
165
|
+
const subject = (state: DisplayConditionState, data: Record<string, unknown>) =>
|
|
166
|
+
evaluateDisplayCondition(state, data);
|
|
167
|
+
|
|
168
|
+
const state: DisplayConditionState = {
|
|
169
|
+
behavior: 'show',
|
|
170
|
+
groups: [
|
|
171
|
+
{
|
|
172
|
+
id: 'g',
|
|
173
|
+
conditions: [
|
|
174
|
+
{
|
|
175
|
+
id: 'c',
|
|
176
|
+
dataPointKey: 'Customer.Age',
|
|
177
|
+
operator: 'num_gte',
|
|
178
|
+
value: '18',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
test('shows field when numeric merge tag is greater than threshold', () => {
|
|
186
|
+
expect(subject(state, { Customer: { Age: 25 } })).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('hides field when numeric merge tag is below threshold', () => {
|
|
190
|
+
expect(subject(state, { Customer: { Age: 10 } })).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('with condition evaluation on fillable source', () => {
|
|
195
|
+
const subject = (state: DisplayConditionState, data: Record<string, unknown>) =>
|
|
196
|
+
evaluateDisplayCondition(state, data);
|
|
197
|
+
|
|
198
|
+
test('matches fillable value via its fillable_recipient_name path', () => {
|
|
199
|
+
const state: DisplayConditionState = {
|
|
200
|
+
behavior: 'show',
|
|
201
|
+
groups: [
|
|
202
|
+
{
|
|
203
|
+
id: 'g',
|
|
204
|
+
conditions: [
|
|
205
|
+
{
|
|
206
|
+
id: 'c',
|
|
207
|
+
dataPointKey: fillableField.path!,
|
|
208
|
+
operator: 'contains',
|
|
209
|
+
value: '@example.com',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
expect(subject(state, { [fillableField.path!]: 'jane@example.com' })).toBe(true);
|
|
216
|
+
expect(subject(state, { [fillableField.path!]: 'jane@other.com' })).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('with condition evaluation on form-field source', () => {
|
|
221
|
+
const subject = (state: DisplayConditionState, data: Record<string, unknown>) =>
|
|
222
|
+
evaluateDisplayCondition(state, data);
|
|
223
|
+
|
|
224
|
+
test('reads numeric form field via its __submission_fields path', () => {
|
|
225
|
+
const state: DisplayConditionState = {
|
|
226
|
+
behavior: 'show',
|
|
227
|
+
groups: [
|
|
228
|
+
{
|
|
229
|
+
id: 'g',
|
|
230
|
+
conditions: [
|
|
231
|
+
{
|
|
232
|
+
id: 'c',
|
|
233
|
+
dataPointKey: formScoreKey,
|
|
234
|
+
operator: 'num_gt',
|
|
235
|
+
value: '50',
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Form values land at __submission_fields.{formId}.{fieldId}
|
|
243
|
+
const data = {
|
|
244
|
+
__submission_fields: { 7: { abc123: 75 } },
|
|
245
|
+
};
|
|
246
|
+
expect(subject(state, data)).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('with AND/OR composition across sources', () => {
|
|
251
|
+
const subject = (state: DisplayConditionState, data: Record<string, unknown>) =>
|
|
252
|
+
evaluateDisplayCondition(state, data);
|
|
253
|
+
|
|
254
|
+
const state: DisplayConditionState = {
|
|
255
|
+
behavior: 'show',
|
|
256
|
+
groups: [
|
|
257
|
+
{
|
|
258
|
+
id: 'g1',
|
|
259
|
+
conditions: [
|
|
260
|
+
{
|
|
261
|
+
id: 'c1',
|
|
262
|
+
dataPointKey: 'Customer.Age',
|
|
263
|
+
operator: 'num_gte',
|
|
264
|
+
value: '18',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: 'c2',
|
|
268
|
+
logicalOperator: 'and',
|
|
269
|
+
dataPointKey: fillableField.path!,
|
|
270
|
+
operator: 'is_not_empty',
|
|
271
|
+
value: '',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 'g2',
|
|
277
|
+
logicalOperator: 'or',
|
|
278
|
+
conditions: [
|
|
279
|
+
{
|
|
280
|
+
id: 'c3',
|
|
281
|
+
dataPointKey: formScoreKey,
|
|
282
|
+
operator: 'num_gte',
|
|
283
|
+
value: '90',
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
test('matches when first AND group is satisfied', () => {
|
|
291
|
+
const data = {
|
|
292
|
+
Customer: { Age: 25 },
|
|
293
|
+
[fillableField.path!]: 'jane@example.com',
|
|
294
|
+
__submission_fields: { 7: { abc123: 0 } },
|
|
295
|
+
};
|
|
296
|
+
expect(subject(state, data)).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('matches when second OR group is satisfied', () => {
|
|
300
|
+
const data = {
|
|
301
|
+
Customer: { Age: 10 },
|
|
302
|
+
[fillableField.path!]: '',
|
|
303
|
+
__submission_fields: { 7: { abc123: 95 } },
|
|
304
|
+
};
|
|
305
|
+
expect(subject(state, data)).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('hides when neither group is satisfied', () => {
|
|
309
|
+
const data = {
|
|
310
|
+
Customer: { Age: 10 },
|
|
311
|
+
[fillableField.path!]: '',
|
|
312
|
+
__submission_fields: { 7: { abc123: 50 } },
|
|
313
|
+
};
|
|
314
|
+
expect(subject(state, data)).toBe(false);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('with hide behavior', () => {
|
|
319
|
+
test('inverts result to hide when condition matches', () => {
|
|
320
|
+
const state: DisplayConditionState = {
|
|
321
|
+
behavior: 'hide',
|
|
322
|
+
groups: [
|
|
323
|
+
{
|
|
324
|
+
id: 'g',
|
|
325
|
+
conditions: [
|
|
326
|
+
{
|
|
327
|
+
id: 'c',
|
|
328
|
+
dataPointKey: 'Customer.Name',
|
|
329
|
+
operator: 'is_equal_to',
|
|
330
|
+
value: 'Internal',
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
};
|
|
336
|
+
expect(evaluateDisplayCondition(state, { Customer: { Name: 'Internal' } })).toBe(false);
|
|
337
|
+
expect(evaluateDisplayCondition(state, { Customer: { Name: 'External' } })).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|