@fogpipe/forma-react 0.12.0-alpha.2 → 0.13.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 +75 -61
- package/dist/index.js +36 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/ErrorBoundary.tsx +14 -7
- package/src/FieldRenderer.tsx +3 -1
- package/src/FormRenderer.tsx +3 -1
- package/src/__tests__/FieldRenderer.test.tsx +128 -1
- package/src/__tests__/FormRenderer.test.tsx +54 -0
- package/src/__tests__/canProceed.test.ts +141 -100
- package/src/__tests__/diabetes-trial-flow.test.ts +235 -66
- package/src/__tests__/null-handling.test.ts +27 -8
- package/src/__tests__/optionVisibility.test.tsx +199 -58
- package/src/__tests__/test-utils.tsx +26 -7
- package/src/__tests__/useForma.test.ts +244 -73
- package/src/context.ts +3 -1
- package/src/index.ts +6 -1
- package/src/types.ts +58 -14
- package/src/useForma.ts +31 -3
|
@@ -34,7 +34,7 @@ describe("Option Visibility", () => {
|
|
|
34
34
|
const props = result.current.getSelectFieldProps("department");
|
|
35
35
|
|
|
36
36
|
expect(props.options).toHaveLength(3);
|
|
37
|
-
expect(props.options.map(o => o.value)).toEqual(["eng", "hr", "sales"]);
|
|
37
|
+
expect(props.options.map((o) => o.value)).toEqual(["eng", "hr", "sales"]);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it("should filter options based on visibleWhen expressions", () => {
|
|
@@ -45,21 +45,33 @@ describe("Option Visibility", () => {
|
|
|
45
45
|
type: "select",
|
|
46
46
|
options: [
|
|
47
47
|
{ value: "intern", label: "Intern" },
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
{
|
|
49
|
+
value: "junior",
|
|
50
|
+
label: "Junior Developer",
|
|
51
|
+
visibleWhen: "experienceYears >= 1",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: "senior",
|
|
55
|
+
label: "Senior Developer",
|
|
56
|
+
visibleWhen: "experienceYears >= 5",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: "lead",
|
|
60
|
+
label: "Tech Lead",
|
|
61
|
+
visibleWhen: "experienceYears >= 8",
|
|
62
|
+
},
|
|
51
63
|
],
|
|
52
64
|
},
|
|
53
65
|
},
|
|
54
66
|
});
|
|
55
67
|
|
|
56
68
|
const { result } = renderHook(() =>
|
|
57
|
-
useForma({ spec, initialData: { experienceYears: 3 } })
|
|
69
|
+
useForma({ spec, initialData: { experienceYears: 3 } }),
|
|
58
70
|
);
|
|
59
71
|
|
|
60
72
|
// With 3 years: intern, junior should be visible
|
|
61
73
|
let props = result.current.getSelectFieldProps("position");
|
|
62
|
-
expect(props.options.map(o => o.value)).toEqual(["intern", "junior"]);
|
|
74
|
+
expect(props.options.map((o) => o.value)).toEqual(["intern", "junior"]);
|
|
63
75
|
|
|
64
76
|
// Change to 6 years: intern, junior, senior should be visible
|
|
65
77
|
act(() => {
|
|
@@ -67,7 +79,11 @@ describe("Option Visibility", () => {
|
|
|
67
79
|
});
|
|
68
80
|
|
|
69
81
|
props = result.current.getSelectFieldProps("position");
|
|
70
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
82
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
83
|
+
"intern",
|
|
84
|
+
"junior",
|
|
85
|
+
"senior",
|
|
86
|
+
]);
|
|
71
87
|
|
|
72
88
|
// Change to 10 years: all should be visible
|
|
73
89
|
act(() => {
|
|
@@ -75,7 +91,12 @@ describe("Option Visibility", () => {
|
|
|
75
91
|
});
|
|
76
92
|
|
|
77
93
|
props = result.current.getSelectFieldProps("position");
|
|
78
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
94
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
95
|
+
"intern",
|
|
96
|
+
"junior",
|
|
97
|
+
"senior",
|
|
98
|
+
"lead",
|
|
99
|
+
]);
|
|
79
100
|
});
|
|
80
101
|
|
|
81
102
|
it("should filter options based on another select field value", () => {
|
|
@@ -91,22 +112,41 @@ describe("Option Visibility", () => {
|
|
|
91
112
|
position: {
|
|
92
113
|
type: "select",
|
|
93
114
|
options: [
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
{
|
|
116
|
+
value: "dev_frontend",
|
|
117
|
+
label: "Frontend Developer",
|
|
118
|
+
visibleWhen: 'department = "eng"',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
value: "dev_backend",
|
|
122
|
+
label: "Backend Developer",
|
|
123
|
+
visibleWhen: 'department = "eng"',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
value: "recruiter",
|
|
127
|
+
label: "Recruiter",
|
|
128
|
+
visibleWhen: 'department = "hr"',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
value: "hr_manager",
|
|
132
|
+
label: "HR Manager",
|
|
133
|
+
visibleWhen: 'department = "hr"',
|
|
134
|
+
},
|
|
98
135
|
],
|
|
99
136
|
},
|
|
100
137
|
},
|
|
101
138
|
});
|
|
102
139
|
|
|
103
140
|
const { result } = renderHook(() =>
|
|
104
|
-
useForma({ spec, initialData: { department: "eng" } })
|
|
141
|
+
useForma({ spec, initialData: { department: "eng" } }),
|
|
105
142
|
);
|
|
106
143
|
|
|
107
144
|
// With Engineering selected
|
|
108
145
|
let props = result.current.getSelectFieldProps("position");
|
|
109
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
146
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
147
|
+
"dev_frontend",
|
|
148
|
+
"dev_backend",
|
|
149
|
+
]);
|
|
110
150
|
|
|
111
151
|
// Switch to HR
|
|
112
152
|
act(() => {
|
|
@@ -114,7 +154,10 @@ describe("Option Visibility", () => {
|
|
|
114
154
|
});
|
|
115
155
|
|
|
116
156
|
props = result.current.getSelectFieldProps("position");
|
|
117
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
157
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
158
|
+
"recruiter",
|
|
159
|
+
"hr_manager",
|
|
160
|
+
]);
|
|
118
161
|
});
|
|
119
162
|
|
|
120
163
|
it("should return empty options when all are hidden", () => {
|
|
@@ -124,15 +167,23 @@ describe("Option Visibility", () => {
|
|
|
124
167
|
premiumFeature: {
|
|
125
168
|
type: "select",
|
|
126
169
|
options: [
|
|
127
|
-
{
|
|
128
|
-
|
|
170
|
+
{
|
|
171
|
+
value: "feature_a",
|
|
172
|
+
label: "Feature A",
|
|
173
|
+
visibleWhen: "isPremium = true",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
value: "feature_b",
|
|
177
|
+
label: "Feature B",
|
|
178
|
+
visibleWhen: "isPremium = true",
|
|
179
|
+
},
|
|
129
180
|
],
|
|
130
181
|
},
|
|
131
182
|
},
|
|
132
183
|
});
|
|
133
184
|
|
|
134
185
|
const { result } = renderHook(() =>
|
|
135
|
-
useForma({ spec, initialData: { isPremium: false } })
|
|
186
|
+
useForma({ spec, initialData: { isPremium: false } }),
|
|
136
187
|
);
|
|
137
188
|
|
|
138
189
|
const props = result.current.getSelectFieldProps("premiumFeature");
|
|
@@ -148,8 +199,16 @@ describe("Option Visibility", () => {
|
|
|
148
199
|
type: "select",
|
|
149
200
|
options: [
|
|
150
201
|
{ value: "standard", label: "Standard Shipping" },
|
|
151
|
-
{
|
|
152
|
-
|
|
202
|
+
{
|
|
203
|
+
value: "express",
|
|
204
|
+
label: "Express Shipping",
|
|
205
|
+
visibleWhen: "computed.orderTotal >= 50",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
value: "overnight",
|
|
209
|
+
label: "Overnight Shipping",
|
|
210
|
+
visibleWhen: "computed.orderTotal >= 100",
|
|
211
|
+
},
|
|
153
212
|
],
|
|
154
213
|
},
|
|
155
214
|
},
|
|
@@ -159,12 +218,12 @@ describe("Option Visibility", () => {
|
|
|
159
218
|
});
|
|
160
219
|
|
|
161
220
|
const { result } = renderHook(() =>
|
|
162
|
-
useForma({ spec, initialData: { quantity: 2, unitPrice: 20 } })
|
|
221
|
+
useForma({ spec, initialData: { quantity: 2, unitPrice: 20 } }),
|
|
163
222
|
);
|
|
164
223
|
|
|
165
224
|
// Total = 40: only standard
|
|
166
225
|
let props = result.current.getSelectFieldProps("shippingMethod");
|
|
167
|
-
expect(props.options.map(o => o.value)).toEqual(["standard"]);
|
|
226
|
+
expect(props.options.map((o) => o.value)).toEqual(["standard"]);
|
|
168
227
|
|
|
169
228
|
// Total = 60: standard and express
|
|
170
229
|
act(() => {
|
|
@@ -172,7 +231,10 @@ describe("Option Visibility", () => {
|
|
|
172
231
|
});
|
|
173
232
|
|
|
174
233
|
props = result.current.getSelectFieldProps("shippingMethod");
|
|
175
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
234
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
235
|
+
"standard",
|
|
236
|
+
"express",
|
|
237
|
+
]);
|
|
176
238
|
|
|
177
239
|
// Total = 120: all options
|
|
178
240
|
act(() => {
|
|
@@ -180,7 +242,11 @@ describe("Option Visibility", () => {
|
|
|
180
242
|
});
|
|
181
243
|
|
|
182
244
|
props = result.current.getSelectFieldProps("shippingMethod");
|
|
183
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
245
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
246
|
+
"standard",
|
|
247
|
+
"express",
|
|
248
|
+
"overnight",
|
|
249
|
+
]);
|
|
184
250
|
});
|
|
185
251
|
});
|
|
186
252
|
|
|
@@ -203,20 +269,28 @@ describe("Option Visibility", () => {
|
|
|
203
269
|
type: "multiselect",
|
|
204
270
|
options: [
|
|
205
271
|
{ value: "basic", label: "Basic Features" },
|
|
206
|
-
{
|
|
207
|
-
|
|
272
|
+
{
|
|
273
|
+
value: "advanced",
|
|
274
|
+
label: "Advanced Features",
|
|
275
|
+
visibleWhen: 'accountType = "paid"',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
value: "enterprise",
|
|
279
|
+
label: "Enterprise Features",
|
|
280
|
+
visibleWhen: 'accountType = "paid"',
|
|
281
|
+
},
|
|
208
282
|
],
|
|
209
283
|
},
|
|
210
284
|
},
|
|
211
285
|
});
|
|
212
286
|
|
|
213
287
|
const { result } = renderHook(() =>
|
|
214
|
-
useForma({ spec, initialData: { accountType: "free" } })
|
|
288
|
+
useForma({ spec, initialData: { accountType: "free" } }),
|
|
215
289
|
);
|
|
216
290
|
|
|
217
291
|
// Free account: only basic
|
|
218
292
|
let props = result.current.getSelectFieldProps("features");
|
|
219
|
-
expect(props.options.map(o => o.value)).toEqual(["basic"]);
|
|
293
|
+
expect(props.options.map((o) => o.value)).toEqual(["basic"]);
|
|
220
294
|
|
|
221
295
|
// Paid account: all features
|
|
222
296
|
act(() => {
|
|
@@ -224,7 +298,11 @@ describe("Option Visibility", () => {
|
|
|
224
298
|
});
|
|
225
299
|
|
|
226
300
|
props = result.current.getSelectFieldProps("features");
|
|
227
|
-
expect(props.options.map(o => o.value)).toEqual([
|
|
301
|
+
expect(props.options.map((o) => o.value)).toEqual([
|
|
302
|
+
"basic",
|
|
303
|
+
"advanced",
|
|
304
|
+
"enterprise",
|
|
305
|
+
]);
|
|
228
306
|
});
|
|
229
307
|
});
|
|
230
308
|
|
|
@@ -249,9 +327,17 @@ describe("Option Visibility", () => {
|
|
|
249
327
|
addon: {
|
|
250
328
|
type: "select",
|
|
251
329
|
options: [
|
|
252
|
-
{
|
|
330
|
+
{
|
|
331
|
+
value: "warranty",
|
|
332
|
+
label: "Extended Warranty",
|
|
333
|
+
visibleWhen: 'item.category = "electronics"',
|
|
334
|
+
},
|
|
253
335
|
{ value: "insurance", label: "Shipping Insurance" },
|
|
254
|
-
{
|
|
336
|
+
{
|
|
337
|
+
value: "giftWrap",
|
|
338
|
+
label: "Gift Wrap",
|
|
339
|
+
visibleWhen: 'item.category = "clothing"',
|
|
340
|
+
},
|
|
255
341
|
],
|
|
256
342
|
},
|
|
257
343
|
},
|
|
@@ -268,18 +354,24 @@ describe("Option Visibility", () => {
|
|
|
268
354
|
{ category: "clothing", addon: null },
|
|
269
355
|
],
|
|
270
356
|
},
|
|
271
|
-
})
|
|
357
|
+
}),
|
|
272
358
|
);
|
|
273
359
|
|
|
274
360
|
const helpers = result.current.getArrayHelpers("orderItems");
|
|
275
361
|
|
|
276
362
|
// First item (electronics): warranty and insurance
|
|
277
363
|
const item0Props = helpers.getItemFieldProps(0, "addon");
|
|
278
|
-
expect(item0Props.options?.map(o => o.value)).toEqual([
|
|
364
|
+
expect(item0Props.options?.map((o) => o.value)).toEqual([
|
|
365
|
+
"warranty",
|
|
366
|
+
"insurance",
|
|
367
|
+
]);
|
|
279
368
|
|
|
280
369
|
// Second item (clothing): insurance and giftWrap
|
|
281
370
|
const item1Props = helpers.getItemFieldProps(1, "addon");
|
|
282
|
-
expect(item1Props.options?.map(o => o.value)).toEqual([
|
|
371
|
+
expect(item1Props.options?.map((o) => o.value)).toEqual([
|
|
372
|
+
"insurance",
|
|
373
|
+
"giftWrap",
|
|
374
|
+
]);
|
|
283
375
|
});
|
|
284
376
|
|
|
285
377
|
it("should update array item options when item data changes", () => {
|
|
@@ -300,7 +392,11 @@ describe("Option Visibility", () => {
|
|
|
300
392
|
options: [
|
|
301
393
|
{ value: "email", label: "Email" },
|
|
302
394
|
{ value: "phone", label: "Phone" },
|
|
303
|
-
{
|
|
395
|
+
{
|
|
396
|
+
value: "fax",
|
|
397
|
+
label: "Fax",
|
|
398
|
+
visibleWhen: 'item.type = "business"',
|
|
399
|
+
},
|
|
304
400
|
],
|
|
305
401
|
},
|
|
306
402
|
},
|
|
@@ -314,13 +410,16 @@ describe("Option Visibility", () => {
|
|
|
314
410
|
initialData: {
|
|
315
411
|
contacts: [{ type: "personal", method: null }],
|
|
316
412
|
},
|
|
317
|
-
})
|
|
413
|
+
}),
|
|
318
414
|
);
|
|
319
415
|
|
|
320
416
|
// Initially personal: email and phone only
|
|
321
417
|
let helpers = result.current.getArrayHelpers("contacts");
|
|
322
418
|
let methodProps = helpers.getItemFieldProps(0, "method");
|
|
323
|
-
expect(methodProps.options?.map(o => o.value)).toEqual([
|
|
419
|
+
expect(methodProps.options?.map((o) => o.value)).toEqual([
|
|
420
|
+
"email",
|
|
421
|
+
"phone",
|
|
422
|
+
]);
|
|
324
423
|
|
|
325
424
|
// Change to business: fax becomes available
|
|
326
425
|
act(() => {
|
|
@@ -329,7 +428,11 @@ describe("Option Visibility", () => {
|
|
|
329
428
|
|
|
330
429
|
helpers = result.current.getArrayHelpers("contacts");
|
|
331
430
|
methodProps = helpers.getItemFieldProps(0, "method");
|
|
332
|
-
expect(methodProps.options?.map(o => o.value)).toEqual([
|
|
431
|
+
expect(methodProps.options?.map((o) => o.value)).toEqual([
|
|
432
|
+
"email",
|
|
433
|
+
"phone",
|
|
434
|
+
"fax",
|
|
435
|
+
]);
|
|
333
436
|
});
|
|
334
437
|
|
|
335
438
|
it("should use itemIndex in option visibleWhen expressions", () => {
|
|
@@ -342,7 +445,11 @@ describe("Option Visibility", () => {
|
|
|
342
445
|
type: "select",
|
|
343
446
|
options: [
|
|
344
447
|
{ value: "member", label: "Team Member" },
|
|
345
|
-
{
|
|
448
|
+
{
|
|
449
|
+
value: "lead",
|
|
450
|
+
label: "Team Lead",
|
|
451
|
+
visibleWhen: "itemIndex = 0",
|
|
452
|
+
},
|
|
346
453
|
],
|
|
347
454
|
},
|
|
348
455
|
},
|
|
@@ -356,22 +463,25 @@ describe("Option Visibility", () => {
|
|
|
356
463
|
initialData: {
|
|
357
464
|
teamMembers: [{ role: null }, { role: null }, { role: null }],
|
|
358
465
|
},
|
|
359
|
-
})
|
|
466
|
+
}),
|
|
360
467
|
);
|
|
361
468
|
|
|
362
469
|
const helpers = result.current.getArrayHelpers("teamMembers");
|
|
363
470
|
|
|
364
471
|
// First item: can be member or lead
|
|
365
472
|
const item0Props = helpers.getItemFieldProps(0, "role");
|
|
366
|
-
expect(item0Props.options?.map(o => o.value)).toEqual([
|
|
473
|
+
expect(item0Props.options?.map((o) => o.value)).toEqual([
|
|
474
|
+
"member",
|
|
475
|
+
"lead",
|
|
476
|
+
]);
|
|
367
477
|
|
|
368
478
|
// Second item: can only be member
|
|
369
479
|
const item1Props = helpers.getItemFieldProps(1, "role");
|
|
370
|
-
expect(item1Props.options?.map(o => o.value)).toEqual(["member"]);
|
|
480
|
+
expect(item1Props.options?.map((o) => o.value)).toEqual(["member"]);
|
|
371
481
|
|
|
372
482
|
// Third item: can only be member
|
|
373
483
|
const item2Props = helpers.getItemFieldProps(2, "role");
|
|
374
|
-
expect(item2Props.options?.map(o => o.value)).toEqual(["member"]);
|
|
484
|
+
expect(item2Props.options?.map((o) => o.value)).toEqual(["member"]);
|
|
375
485
|
});
|
|
376
486
|
|
|
377
487
|
it("should combine form data, computed values, and item context", () => {
|
|
@@ -392,8 +502,17 @@ describe("Option Visibility", () => {
|
|
|
392
502
|
type: "select",
|
|
393
503
|
options: [
|
|
394
504
|
{ value: "standard", label: "Standard" },
|
|
395
|
-
{
|
|
396
|
-
|
|
505
|
+
{
|
|
506
|
+
value: "express",
|
|
507
|
+
label: "Express",
|
|
508
|
+
visibleWhen: "isPremiumOrder = true",
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
value: "priority",
|
|
512
|
+
label: "Priority",
|
|
513
|
+
visibleWhen:
|
|
514
|
+
'isPremiumOrder = true and item.productType = "premium"',
|
|
515
|
+
},
|
|
397
516
|
],
|
|
398
517
|
},
|
|
399
518
|
},
|
|
@@ -411,14 +530,18 @@ describe("Option Visibility", () => {
|
|
|
411
530
|
{ productType: "premium", shipping: null },
|
|
412
531
|
],
|
|
413
532
|
},
|
|
414
|
-
})
|
|
533
|
+
}),
|
|
415
534
|
);
|
|
416
535
|
|
|
417
536
|
let helpers = result.current.getArrayHelpers("lineItems");
|
|
418
537
|
|
|
419
538
|
// Not premium order: only standard shipping for both
|
|
420
|
-
expect(
|
|
421
|
-
|
|
539
|
+
expect(
|
|
540
|
+
helpers.getItemFieldProps(0, "shipping").options?.map((o) => o.value),
|
|
541
|
+
).toEqual(["standard"]);
|
|
542
|
+
expect(
|
|
543
|
+
helpers.getItemFieldProps(1, "shipping").options?.map((o) => o.value),
|
|
544
|
+
).toEqual(["standard"]);
|
|
422
545
|
|
|
423
546
|
// Upgrade to premium order
|
|
424
547
|
act(() => {
|
|
@@ -428,10 +551,14 @@ describe("Option Visibility", () => {
|
|
|
428
551
|
helpers = result.current.getArrayHelpers("lineItems");
|
|
429
552
|
|
|
430
553
|
// Standard product: standard and express
|
|
431
|
-
expect(
|
|
554
|
+
expect(
|
|
555
|
+
helpers.getItemFieldProps(0, "shipping").options?.map((o) => o.value),
|
|
556
|
+
).toEqual(["standard", "express"]);
|
|
432
557
|
|
|
433
558
|
// Premium product: standard, express, and priority
|
|
434
|
-
expect(
|
|
559
|
+
expect(
|
|
560
|
+
helpers.getItemFieldProps(1, "shipping").options?.map((o) => o.value),
|
|
561
|
+
).toEqual(["standard", "express", "priority"]);
|
|
435
562
|
});
|
|
436
563
|
});
|
|
437
564
|
|
|
@@ -460,7 +587,11 @@ describe("Option Visibility", () => {
|
|
|
460
587
|
type: "select",
|
|
461
588
|
options: [
|
|
462
589
|
{ value: "valid", label: "Valid Option" },
|
|
463
|
-
{
|
|
590
|
+
{
|
|
591
|
+
value: "invalid",
|
|
592
|
+
label: "Invalid",
|
|
593
|
+
visibleWhen: "this is not valid FEEL syntax !!!",
|
|
594
|
+
},
|
|
464
595
|
],
|
|
465
596
|
},
|
|
466
597
|
},
|
|
@@ -470,7 +601,7 @@ describe("Option Visibility", () => {
|
|
|
470
601
|
const props = result.current.getSelectFieldProps("status");
|
|
471
602
|
|
|
472
603
|
// Invalid expression should hide the option (treated as false)
|
|
473
|
-
expect(props.options.map(o => o.value)).toEqual(["valid"]);
|
|
604
|
+
expect(props.options.map((o) => o.value)).toEqual(["valid"]);
|
|
474
605
|
});
|
|
475
606
|
|
|
476
607
|
it("should preserve selected value when option becomes hidden", () => {
|
|
@@ -481,20 +612,27 @@ describe("Option Visibility", () => {
|
|
|
481
612
|
type: "select",
|
|
482
613
|
options: [
|
|
483
614
|
{ value: "basic", label: "Basic" },
|
|
484
|
-
{
|
|
615
|
+
{
|
|
616
|
+
value: "advanced",
|
|
617
|
+
label: "Advanced",
|
|
618
|
+
visibleWhen: "level >= 5",
|
|
619
|
+
},
|
|
485
620
|
],
|
|
486
621
|
},
|
|
487
622
|
},
|
|
488
623
|
});
|
|
489
624
|
|
|
490
625
|
const { result } = renderHook(() =>
|
|
491
|
-
useForma({ spec, initialData: { level: 5, feature: "advanced" } })
|
|
626
|
+
useForma({ spec, initialData: { level: 5, feature: "advanced" } }),
|
|
492
627
|
);
|
|
493
628
|
|
|
494
629
|
// Initially advanced is selected and visible
|
|
495
630
|
expect(result.current.data.feature).toBe("advanced");
|
|
496
|
-
expect(
|
|
497
|
-
.
|
|
631
|
+
expect(
|
|
632
|
+
result.current
|
|
633
|
+
.getSelectFieldProps("feature")
|
|
634
|
+
.options.map((o) => o.value),
|
|
635
|
+
).toEqual(["basic", "advanced"]);
|
|
498
636
|
|
|
499
637
|
// Reduce level - advanced option becomes hidden but value is preserved
|
|
500
638
|
act(() => {
|
|
@@ -504,8 +642,11 @@ describe("Option Visibility", () => {
|
|
|
504
642
|
// Value is preserved (validation would catch this if needed)
|
|
505
643
|
expect(result.current.data.feature).toBe("advanced");
|
|
506
644
|
// But option is no longer visible
|
|
507
|
-
expect(
|
|
508
|
-
.
|
|
645
|
+
expect(
|
|
646
|
+
result.current
|
|
647
|
+
.getSelectFieldProps("feature")
|
|
648
|
+
.options.map((o) => o.value),
|
|
649
|
+
).toEqual(["basic"]);
|
|
509
650
|
});
|
|
510
651
|
});
|
|
511
652
|
});
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { render, type RenderOptions } from "@testing-library/react";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
Forma,
|
|
8
|
+
FieldDefinition,
|
|
9
|
+
PageDefinition,
|
|
10
|
+
} from "@fogpipe/forma-core";
|
|
7
11
|
import type {
|
|
8
12
|
ComponentMap,
|
|
9
13
|
TextComponentProps,
|
|
@@ -24,7 +28,7 @@ export function createTestSpec(
|
|
|
24
28
|
computed?: Record<string, { expression: string }>;
|
|
25
29
|
pages?: PageDefinition[];
|
|
26
30
|
referenceData?: Record<string, unknown>;
|
|
27
|
-
} = {}
|
|
31
|
+
} = {},
|
|
28
32
|
): Forma {
|
|
29
33
|
const { fields = {}, fieldOrder, computed, pages, referenceData } = options;
|
|
30
34
|
|
|
@@ -33,10 +37,22 @@ export function createTestSpec(
|
|
|
33
37
|
const schemaRequired: string[] = [];
|
|
34
38
|
|
|
35
39
|
for (const [name, field] of Object.entries(fields)) {
|
|
36
|
-
const {
|
|
40
|
+
const {
|
|
41
|
+
type,
|
|
42
|
+
required,
|
|
43
|
+
options: fieldOptions,
|
|
44
|
+
items,
|
|
45
|
+
...rest
|
|
46
|
+
} = field as Record<string, unknown>;
|
|
37
47
|
|
|
38
48
|
let schemaType = type;
|
|
39
|
-
if (
|
|
49
|
+
if (
|
|
50
|
+
type === "text" ||
|
|
51
|
+
type === "email" ||
|
|
52
|
+
type === "url" ||
|
|
53
|
+
type === "textarea" ||
|
|
54
|
+
type === "password"
|
|
55
|
+
) {
|
|
40
56
|
schemaType = "string";
|
|
41
57
|
}
|
|
42
58
|
if (type === "select") {
|
|
@@ -86,7 +102,8 @@ export function createTestSpec(
|
|
|
86
102
|
void __; // Mark as intentionally unused
|
|
87
103
|
fieldDefs[name] = {
|
|
88
104
|
type: type as FieldDefinition["type"],
|
|
89
|
-
label:
|
|
105
|
+
label:
|
|
106
|
+
(rest.label as string) || name.charAt(0).toUpperCase() + name.slice(1),
|
|
90
107
|
...rest,
|
|
91
108
|
} as FieldDefinition;
|
|
92
109
|
}
|
|
@@ -205,7 +222,9 @@ export function createTestComponentMap(): ComponentMap {
|
|
|
205
222
|
};
|
|
206
223
|
|
|
207
224
|
// Multiselect fields
|
|
208
|
-
const MultiSelectComponent = ({
|
|
225
|
+
const MultiSelectComponent = ({
|
|
226
|
+
field: props,
|
|
227
|
+
}: MultiSelectComponentProps) => {
|
|
209
228
|
const { name, field, value, options, onChange, onBlur, disabled } = props;
|
|
210
229
|
const displayValue = (value || []).join(",");
|
|
211
230
|
return (
|
|
@@ -288,7 +307,7 @@ export function createTestComponentMap(): ComponentMap {
|
|
|
288
307
|
*/
|
|
289
308
|
export function renderWithProviders(
|
|
290
309
|
ui: React.ReactElement,
|
|
291
|
-
options?: Omit<RenderOptions, "wrapper"
|
|
310
|
+
options?: Omit<RenderOptions, "wrapper">,
|
|
292
311
|
) {
|
|
293
312
|
return render(ui, { ...options });
|
|
294
313
|
}
|