@servicetitan/dte-pdf-editor 1.45.0 → 1.46.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.
Files changed (45) hide show
  1. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  2. package/dist/components/field-config-panel/field-config-panel.js +4 -2
  3. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  4. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
  5. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +3 -0
  6. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
  7. package/dist/components/pdf-view/pdf-view-fillable.d.ts +8 -0
  8. package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
  9. package/dist/components/pdf-view/pdf-view-fillable.js +14 -6
  10. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  11. package/dist/interface/types.d.ts +1 -0
  12. package/dist/interface/types.d.ts.map +1 -1
  13. package/dist/interface/types.js.map +1 -1
  14. package/package.json +1 -1
  15. package/src/__tests__/field-types/date-fields.test.ts +212 -0
  16. package/src/__tests__/field-types/display-condition-fields.test.ts +340 -0
  17. package/src/__tests__/field-types/e-sign-fields.test.ts +97 -0
  18. package/src/__tests__/field-types/fillable-fields.test.ts +180 -0
  19. package/src/__tests__/field-types/form-fields.test.ts +193 -0
  20. package/src/__tests__/field-types/formula-fields.test.ts +245 -0
  21. package/src/__tests__/field-types/merge-tag-fields.test.ts +208 -0
  22. package/src/components/field-config-panel/field-config-panel.tsx +11 -0
  23. package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +10 -0
  24. package/src/components/pdf-view/pdf-view-fillable.tsx +58 -13
  25. package/src/interface/types.ts +1 -0
  26. package/src/styles/inline-editable.css +1 -0
  27. package/src/utils/conditions/__tests__/evaluate.utils.test.ts +163 -0
  28. package/src/utils/conditions/__tests__/schema-data-points.utils.test.ts +149 -0
  29. package/src/utils/data-model/__tests__/extract-fields.utils.test.ts +154 -0
  30. package/src/utils/data-model/__tests__/resolve-values.utils.test.ts +60 -0
  31. package/src/utils/field/__tests__/field-background-color.utils.test.ts +26 -0
  32. package/src/utils/field/__tests__/field-placeholder-text.utils.test.ts +46 -0
  33. package/src/utils/formula/__tests__/dom.utils.test.ts +33 -0
  34. package/src/utils/formula/__tests__/evaluate-formula.utils.test.ts +202 -0
  35. package/src/utils/formula/__tests__/expression.utils.test.ts +119 -0
  36. package/src/utils/formula/__tests__/form-fields.utils.test.ts +274 -0
  37. package/src/utils/formula/__tests__/format-calculated-result.utils.test.ts +105 -0
  38. package/src/utils/formula/__tests__/render-formula.utils.test.ts +43 -0
  39. package/src/utils/formula/__tests__/validate-formula.utils.test.ts +168 -0
  40. package/src/utils/path/__tests__/generate-e-sign-path.test.ts +26 -0
  41. package/src/utils/path/__tests__/generate-fillable-path.test.ts +17 -0
  42. package/src/utils/path/__tests__/parse-fillable-path.test.ts +25 -0
  43. package/src/utils/recipients/__tests__/map-colors.test.ts +40 -0
  44. package/src/utils/shared/__tests__/date.utils.test.ts +58 -0
  45. package/src/utils/shared/__tests__/number.utils.test.ts +48 -0
@@ -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
+ });
@@ -0,0 +1,97 @@
1
+ import {
2
+ ESignFieldType,
3
+ FieldTypeEnum,
4
+ PdfField,
5
+ RecipientInfo,
6
+ } from '../../interface/types';
7
+ import {
8
+ generateESignPath,
9
+ getDocumentFieldsDataPointOptions,
10
+ getFieldBackgroundColor,
11
+ getFieldPlaceholderText,
12
+ mapColorsToRecipients,
13
+ } from '../../utils';
14
+
15
+ /**
16
+ * Scenario tests for e-sign fields.
17
+ *
18
+ * E-sign fields belong to a recipient and use one of four subtypes:
19
+ * signature, initials, dateSigned, fullName. They are excluded from the
20
+ * display-condition source list since their values are not user-typed data.
21
+ */
22
+
23
+ const buildESignField = (overrides: Partial<PdfField> = {}): PdfField => ({
24
+ id: 'esign-1',
25
+ type: FieldTypeEnum.eSign,
26
+ subType: ESignFieldType.signature,
27
+ x: 0,
28
+ y: 0,
29
+ page: 1,
30
+ label: 'Signature',
31
+ width: 200,
32
+ height: 50,
33
+ recipient: 'signer1',
34
+ path: generateESignPath('signer1', ESignFieldType.signature),
35
+ ...overrides,
36
+ });
37
+
38
+ describe('e-sign field scenarios', () => {
39
+ describe('with path generation per subtype', () => {
40
+ test.each([
41
+ [ESignFieldType.signature, 'esign_signer1_signature'],
42
+ [ESignFieldType.initials, 'esign_signer1_initials'],
43
+ [ESignFieldType.dateSigned, 'esign_signer1_dateSigned'],
44
+ [ESignFieldType.fullName, 'esign_signer1_fullName'],
45
+ ])('generates "%s" path for sub-type', (subType, expected) => {
46
+ expect(generateESignPath('signer1', subType)).toBe(expected);
47
+ });
48
+
49
+ test('keeps recipient and subtype distinct in the path', () => {
50
+ expect(generateESignPath('signer2', ESignFieldType.initials)).toBe(
51
+ 'esign_signer2_initials',
52
+ );
53
+ });
54
+ });
55
+
56
+ describe('with recipient color resolution', () => {
57
+ const recipients: RecipientInfo[] = [
58
+ { id: 1, name: 'signer1', displayName: 'Signer 1' },
59
+ { id: 2, name: 'signer2', displayName: 'Signer 2' },
60
+ ];
61
+ const colors = mapColorsToRecipients(recipients);
62
+
63
+ test('assigns a background color for each recipient', () => {
64
+ const field = buildESignField({ recipient: 'signer1' });
65
+ expect(getFieldBackgroundColor(field.recipient, colors)).toBeDefined();
66
+ });
67
+
68
+ test('returns "none" for an unknown recipient', () => {
69
+ expect(getFieldBackgroundColor('unknown', colors)).toBe('none');
70
+ });
71
+ });
72
+
73
+ describe('with placeholder text', () => {
74
+ test('uses subtype-friendly label without required marker', () => {
75
+ const field = buildESignField({ label: 'Signature' });
76
+ expect(getFieldPlaceholderText(field)).toBe('Signature');
77
+ });
78
+
79
+ test('adds required marker when field is required', () => {
80
+ const field = buildESignField({ label: 'Initials', required: true });
81
+ expect(getFieldPlaceholderText(field)).toBe('Initials *');
82
+ });
83
+ });
84
+
85
+ describe('with display-condition options', () => {
86
+ test('excludes e-sign fields from document field options', () => {
87
+ const fields: PdfField[] = [
88
+ buildESignField({ id: '1', subType: ESignFieldType.signature }),
89
+ buildESignField({ id: '2', subType: ESignFieldType.initials }),
90
+ buildESignField({ id: '3', subType: ESignFieldType.dateSigned }),
91
+ buildESignField({ id: '4', subType: ESignFieldType.fullName }),
92
+ ];
93
+
94
+ expect(getDocumentFieldsDataPointOptions(fields)).toEqual([]);
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,180 @@
1
+ import { FieldTypeEnum, PdfField, RecipientInfo } from '../../interface/types';
2
+ import {
3
+ generateFillablePath,
4
+ getDocumentFieldsDataPointOptions,
5
+ getFieldBackgroundColor,
6
+ getFieldPlaceholderText,
7
+ mapColorsToRecipients,
8
+ parseFillablePathName,
9
+ } from '../../utils';
10
+
11
+ /**
12
+ * Scenario tests for fillable fields.
13
+ *
14
+ * Fillable fields support five sub-types: text, number, date, checkbox, radio.
15
+ * These tests validate the full flow: path generation -> parsing -> placeholder
16
+ * -> color resolution -> data-point option export for downstream conditions.
17
+ */
18
+
19
+ const buildFillableField = (overrides: Partial<PdfField> = {}): PdfField => ({
20
+ id: 'field-1',
21
+ type: FieldTypeEnum.fillable,
22
+ subType: 'text',
23
+ x: 0,
24
+ y: 0,
25
+ page: 1,
26
+ label: 'Customer Name',
27
+ width: 200,
28
+ height: 25,
29
+ recipient: 'signer1',
30
+ path: generateFillablePath('signer1', 'CustomerName'),
31
+ ...overrides,
32
+ });
33
+
34
+ const recipients: RecipientInfo[] = [
35
+ { id: 1, name: 'signer1', displayName: 'Signer 1' },
36
+ { id: 2, name: 'signer2', displayName: 'Signer 2' },
37
+ ];
38
+
39
+ describe('fillable field scenarios', () => {
40
+ describe('with text sub-type', () => {
41
+ const field = buildFillableField();
42
+
43
+ test('generates path with recipient and field name', () => {
44
+ expect(field.path).toBe('fillable_signer1_CustomerName');
45
+ });
46
+
47
+ test('parses original name back from path', () => {
48
+ expect(parseFillablePathName(field.path)).toBe('CustomerName');
49
+ });
50
+
51
+ test('builds placeholder text from label', () => {
52
+ expect(getFieldPlaceholderText(field)).toBe('Customer Name');
53
+ });
54
+
55
+ test('marks placeholder as required when required is true', () => {
56
+ expect(getFieldPlaceholderText({ ...field, required: true })).toBe(
57
+ 'Customer Name *',
58
+ );
59
+ });
60
+
61
+ test('exports as string data point option', () => {
62
+ const options = getDocumentFieldsDataPointOptions([field]);
63
+ expect(options).toEqual([
64
+ {
65
+ fieldType: 'string',
66
+ fullKey: 'fillable_signer1_CustomerName',
67
+ title: 'Customer Name',
68
+ },
69
+ ]);
70
+ });
71
+ });
72
+
73
+ describe('with number sub-type', () => {
74
+ const field = buildFillableField({
75
+ subType: 'number',
76
+ label: 'Amount',
77
+ path: generateFillablePath('signer1', 'Amount'),
78
+ });
79
+
80
+ test('exports as number data point option', () => {
81
+ const options = getDocumentFieldsDataPointOptions([field]);
82
+ expect(options[0].fieldType).toBe('number');
83
+ });
84
+
85
+ test('keeps path format consistent with other sub-types', () => {
86
+ expect(field.path).toBe('fillable_signer1_Amount');
87
+ });
88
+ });
89
+
90
+ describe('with date sub-type', () => {
91
+ const field = buildFillableField({
92
+ subType: 'date',
93
+ label: 'Effective Date',
94
+ path: generateFillablePath('signer1', 'EffectiveDate'),
95
+ });
96
+
97
+ test('exports as string data point (dates are evaluated by string comparison)', () => {
98
+ const options = getDocumentFieldsDataPointOptions([field]);
99
+ expect(options[0].fieldType).toBe('string');
100
+ });
101
+
102
+ test('preserves field metadata when extracted as option', () => {
103
+ const options = getDocumentFieldsDataPointOptions([field]);
104
+ expect(options[0].title).toBe('Effective Date');
105
+ expect(options[0].fullKey).toBe('fillable_signer1_EffectiveDate');
106
+ });
107
+ });
108
+
109
+ describe('with checkbox sub-type', () => {
110
+ const field = buildFillableField({
111
+ subType: 'checkbox',
112
+ label: 'Agree to terms',
113
+ path: generateFillablePath('signer1', 'AgreeToTerms'),
114
+ });
115
+
116
+ test('exports as string data point', () => {
117
+ const options = getDocumentFieldsDataPointOptions([field]);
118
+ expect(options[0].fieldType).toBe('string');
119
+ });
120
+ });
121
+
122
+ describe('with radio sub-type', () => {
123
+ const field = buildFillableField({
124
+ subType: 'radio',
125
+ label: 'Yes',
126
+ path: generateFillablePath('signer1', 'Choice'),
127
+ });
128
+
129
+ test('exports as string data point', () => {
130
+ const options = getDocumentFieldsDataPointOptions([field]);
131
+ expect(options[0].fieldType).toBe('string');
132
+ });
133
+ });
134
+
135
+ describe('with multi-recipient color resolution', () => {
136
+ const colors = mapColorsToRecipients(recipients);
137
+ const signer1Field = buildFillableField({ recipient: 'signer1' });
138
+ const signer2Field = buildFillableField({ id: 'f2', recipient: 'signer2' });
139
+
140
+ test('returns distinct background colors per recipient', () => {
141
+ expect(getFieldBackgroundColor(signer1Field.recipient, colors)).not.toBe(
142
+ getFieldBackgroundColor(signer2Field.recipient, colors),
143
+ );
144
+ });
145
+
146
+ test('returns "none" when field has no recipient', () => {
147
+ expect(getFieldBackgroundColor(undefined, colors)).toBe('none');
148
+ });
149
+ });
150
+
151
+ describe('with mixed field list', () => {
152
+ const fields: PdfField[] = [
153
+ buildFillableField({
154
+ id: 'a',
155
+ label: 'Z Last',
156
+ path: generateFillablePath('signer1', 'ZLast'),
157
+ }),
158
+ buildFillableField({
159
+ id: 'b',
160
+ label: 'A First',
161
+ path: generateFillablePath('signer1', 'AFirst'),
162
+ }),
163
+ buildFillableField({
164
+ id: 'c',
165
+ label: 'No Path',
166
+ path: undefined,
167
+ }),
168
+ {
169
+ ...buildFillableField({ id: 'd', label: 'Sig' }),
170
+ type: FieldTypeEnum.eSign,
171
+ path: 'esign_signer1_signature',
172
+ },
173
+ ];
174
+
175
+ test('returns only fillable fields with a path, sorted by title', () => {
176
+ const options = getDocumentFieldsDataPointOptions(fields);
177
+ expect(options.map(o => o.title)).toEqual(['A First', 'Z Last']);
178
+ });
179
+ });
180
+ });