@fogpipe/forma-react 0.12.0 → 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
|
@@ -38,14 +38,143 @@ describe("useForma", () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
const initialData = { name: "John", age: 25 };
|
|
41
|
-
const { result } = renderHook(() =>
|
|
42
|
-
useForma({ spec, initialData })
|
|
43
|
-
);
|
|
41
|
+
const { result } = renderHook(() => useForma({ spec, initialData }));
|
|
44
42
|
|
|
45
43
|
expect(result.current.data).toEqual(initialData);
|
|
46
44
|
expect(result.current.isDirty).toBe(false);
|
|
47
45
|
});
|
|
48
46
|
|
|
47
|
+
it("should initialize with defaultValue from field definitions", () => {
|
|
48
|
+
const spec = createTestSpec({
|
|
49
|
+
fields: {
|
|
50
|
+
country: {
|
|
51
|
+
type: "select",
|
|
52
|
+
defaultValue: "us",
|
|
53
|
+
options: [
|
|
54
|
+
{ value: "us", label: "US" },
|
|
55
|
+
{ value: "ca", label: "CA" },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
age: { type: "number", defaultValue: 25 },
|
|
59
|
+
name: { type: "text" },
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const { result } = renderHook(() => useForma({ spec }));
|
|
64
|
+
|
|
65
|
+
expect(result.current.data.country).toBe("us");
|
|
66
|
+
expect(result.current.data.age).toBe(25);
|
|
67
|
+
expect(result.current.data.name).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should let initialData override defaultValue", () => {
|
|
71
|
+
const spec = createTestSpec({
|
|
72
|
+
fields: {
|
|
73
|
+
country: {
|
|
74
|
+
type: "select",
|
|
75
|
+
defaultValue: "us",
|
|
76
|
+
options: [
|
|
77
|
+
{ value: "us", label: "US" },
|
|
78
|
+
{ value: "ca", label: "CA" },
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const { result } = renderHook(() =>
|
|
85
|
+
useForma({ spec, initialData: { country: "ca" } }),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result.current.data.country).toBe("ca");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should let defaultValue=true override implicit boolean false", () => {
|
|
92
|
+
const spec = createTestSpec({
|
|
93
|
+
fields: {
|
|
94
|
+
agree: { type: "boolean", defaultValue: true },
|
|
95
|
+
optIn: { type: "boolean" },
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const { result } = renderHook(() => useForma({ spec }));
|
|
100
|
+
|
|
101
|
+
expect(result.current.data.agree).toBe(true);
|
|
102
|
+
expect(result.current.data.optIn).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should apply defaultValue for number and string fields", () => {
|
|
106
|
+
const spec = createTestSpec({
|
|
107
|
+
fields: {
|
|
108
|
+
quantity: { type: "integer", defaultValue: 5 },
|
|
109
|
+
price: { type: "number", defaultValue: 9.99 },
|
|
110
|
+
notes: { type: "textarea", defaultValue: "N/A" },
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const { result } = renderHook(() => useForma({ spec }));
|
|
115
|
+
|
|
116
|
+
expect(result.current.data.quantity).toBe(5);
|
|
117
|
+
expect(result.current.data.price).toBe(9.99);
|
|
118
|
+
expect(result.current.data.notes).toBe("N/A");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should not apply defaultValue for display fields in data", () => {
|
|
122
|
+
const spec = createTestSpec({
|
|
123
|
+
fields: {
|
|
124
|
+
name: { type: "text", defaultValue: "John" },
|
|
125
|
+
header: { type: "display", content: "Header" },
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const { result } = renderHook(() => useForma({ spec }));
|
|
130
|
+
|
|
131
|
+
expect(result.current.data.name).toBe("John");
|
|
132
|
+
// display fields don't produce data entries
|
|
133
|
+
expect(result.current.data.header).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should preserve defaultValue after resetForm", () => {
|
|
137
|
+
const spec = createTestSpec({
|
|
138
|
+
fields: {
|
|
139
|
+
name: { type: "text", defaultValue: "Default" },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const { result } = renderHook(() => useForma({ spec }));
|
|
144
|
+
|
|
145
|
+
expect(result.current.data.name).toBe("Default");
|
|
146
|
+
|
|
147
|
+
act(() => {
|
|
148
|
+
result.current.setFieldValue("name", "Changed");
|
|
149
|
+
});
|
|
150
|
+
expect(result.current.data.name).toBe("Changed");
|
|
151
|
+
|
|
152
|
+
act(() => {
|
|
153
|
+
result.current.resetForm();
|
|
154
|
+
});
|
|
155
|
+
expect(result.current.data.name).toBe("Default");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should apply defaultValue for multiselect as array", () => {
|
|
159
|
+
const spec = createTestSpec({
|
|
160
|
+
fields: {
|
|
161
|
+
tags: {
|
|
162
|
+
type: "multiselect",
|
|
163
|
+
defaultValue: ["a", "b"],
|
|
164
|
+
options: [
|
|
165
|
+
{ value: "a", label: "A" },
|
|
166
|
+
{ value: "b", label: "B" },
|
|
167
|
+
{ value: "c", label: "C" },
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const { result } = renderHook(() => useForma({ spec }));
|
|
174
|
+
|
|
175
|
+
expect(result.current.data.tags).toEqual(["a", "b"]);
|
|
176
|
+
});
|
|
177
|
+
|
|
49
178
|
it("should merge referenceData from options with spec", () => {
|
|
50
179
|
const spec = createTestSpec({
|
|
51
180
|
fields: { value: { type: "number" } },
|
|
@@ -56,7 +185,7 @@ describe("useForma", () => {
|
|
|
56
185
|
useForma({
|
|
57
186
|
spec,
|
|
58
187
|
referenceData: { added: { b: 2 } },
|
|
59
|
-
})
|
|
188
|
+
}),
|
|
60
189
|
);
|
|
61
190
|
|
|
62
191
|
expect(result.current.spec.referenceData).toEqual({
|
|
@@ -127,7 +256,7 @@ describe("useForma", () => {
|
|
|
127
256
|
});
|
|
128
257
|
|
|
129
258
|
const { result } = renderHook(() =>
|
|
130
|
-
useForma({ spec, initialData: { items: [{ name: "A" }] } })
|
|
259
|
+
useForma({ spec, initialData: { items: [{ name: "A" }] } }),
|
|
131
260
|
);
|
|
132
261
|
|
|
133
262
|
act(() => {
|
|
@@ -172,7 +301,7 @@ describe("useForma", () => {
|
|
|
172
301
|
});
|
|
173
302
|
|
|
174
303
|
const { result } = renderHook(() =>
|
|
175
|
-
useForma({ spec, initialData: { name: "Test" } })
|
|
304
|
+
useForma({ spec, initialData: { name: "Test" } }),
|
|
176
305
|
);
|
|
177
306
|
|
|
178
307
|
const props = result.current.getFieldProps("name");
|
|
@@ -295,7 +424,7 @@ describe("useForma", () => {
|
|
|
295
424
|
});
|
|
296
425
|
|
|
297
426
|
const { result } = renderHook(() =>
|
|
298
|
-
useForma({ spec, initialData: { tags: ["a", "b"] } })
|
|
427
|
+
useForma({ spec, initialData: { tags: ["a", "b"] } }),
|
|
299
428
|
);
|
|
300
429
|
|
|
301
430
|
const props = result.current.getSelectFieldProps("tags");
|
|
@@ -322,7 +451,7 @@ describe("useForma", () => {
|
|
|
322
451
|
});
|
|
323
452
|
|
|
324
453
|
const { result } = renderHook(() =>
|
|
325
|
-
useForma({ spec, initialData: { hasLicense: false } })
|
|
454
|
+
useForma({ spec, initialData: { hasLicense: false } }),
|
|
326
455
|
);
|
|
327
456
|
|
|
328
457
|
// Initially hidden
|
|
@@ -348,7 +477,7 @@ describe("useForma", () => {
|
|
|
348
477
|
});
|
|
349
478
|
|
|
350
479
|
const { result } = renderHook(() =>
|
|
351
|
-
useForma({ spec, initialData: { age: 14 } })
|
|
480
|
+
useForma({ spec, initialData: { age: 14 } }),
|
|
352
481
|
);
|
|
353
482
|
|
|
354
483
|
expect(result.current.visibility.canDrive).toBe(false);
|
|
@@ -378,7 +507,7 @@ describe("useForma", () => {
|
|
|
378
507
|
});
|
|
379
508
|
|
|
380
509
|
const { result } = renderHook(() =>
|
|
381
|
-
useForma({ spec, initialData: { employmentStatus: "unemployed" } })
|
|
510
|
+
useForma({ spec, initialData: { employmentStatus: "unemployed" } }),
|
|
382
511
|
);
|
|
383
512
|
|
|
384
513
|
expect(result.current.getFieldProps("employerName").required).toBe(false);
|
|
@@ -408,7 +537,7 @@ describe("useForma", () => {
|
|
|
408
537
|
});
|
|
409
538
|
|
|
410
539
|
const { result } = renderHook(() =>
|
|
411
|
-
useForma({ spec, initialData: { isEditable: false } })
|
|
540
|
+
useForma({ spec, initialData: { isEditable: false } }),
|
|
412
541
|
);
|
|
413
542
|
|
|
414
543
|
expect(result.current.getFieldProps("notes").enabled).toBe(false);
|
|
@@ -438,7 +567,7 @@ describe("useForma", () => {
|
|
|
438
567
|
});
|
|
439
568
|
|
|
440
569
|
const { result } = renderHook(() =>
|
|
441
|
-
useForma({ spec, initialData: { quantity: 5, price: 10 } })
|
|
570
|
+
useForma({ spec, initialData: { quantity: 5, price: 10 } }),
|
|
442
571
|
);
|
|
443
572
|
|
|
444
573
|
expect(result.current.computed.total).toBe(50);
|
|
@@ -456,7 +585,7 @@ describe("useForma", () => {
|
|
|
456
585
|
});
|
|
457
586
|
|
|
458
587
|
const { result } = renderHook(() =>
|
|
459
|
-
useForma({ spec, initialData: { a: 1, b: 2 } })
|
|
588
|
+
useForma({ spec, initialData: { a: 1, b: 2 } }),
|
|
460
589
|
);
|
|
461
590
|
|
|
462
591
|
expect(result.current.computed.sum).toBe(3);
|
|
@@ -504,7 +633,7 @@ describe("useForma", () => {
|
|
|
504
633
|
});
|
|
505
634
|
|
|
506
635
|
const { result } = renderHook(() =>
|
|
507
|
-
useForma({ spec, validateOn: "blur" })
|
|
636
|
+
useForma({ spec, validateOn: "blur" }),
|
|
508
637
|
);
|
|
509
638
|
|
|
510
639
|
// Errors exist but not shown
|
|
@@ -520,11 +649,13 @@ describe("useForma", () => {
|
|
|
520
649
|
});
|
|
521
650
|
|
|
522
651
|
const { result } = renderHook(() =>
|
|
523
|
-
useForma({ spec, validateOn: "change" })
|
|
652
|
+
useForma({ spec, validateOn: "change" }),
|
|
524
653
|
);
|
|
525
654
|
|
|
526
655
|
// Errors shown immediately
|
|
527
|
-
expect(
|
|
656
|
+
expect(
|
|
657
|
+
result.current.getFieldProps("name").errors.length,
|
|
658
|
+
).toBeGreaterThan(0);
|
|
528
659
|
});
|
|
529
660
|
|
|
530
661
|
it("should validate custom validation rules", () => {
|
|
@@ -540,7 +671,7 @@ describe("useForma", () => {
|
|
|
540
671
|
});
|
|
541
672
|
|
|
542
673
|
const { result } = renderHook(() =>
|
|
543
|
-
useForma({ spec, initialData: { age: 16 } })
|
|
674
|
+
useForma({ spec, initialData: { age: 16 } }),
|
|
544
675
|
);
|
|
545
676
|
|
|
546
677
|
// Touch the field to show errors
|
|
@@ -549,7 +680,9 @@ describe("useForma", () => {
|
|
|
549
680
|
});
|
|
550
681
|
|
|
551
682
|
const errors = result.current.getFieldProps("age").errors;
|
|
552
|
-
expect(errors.some((e) => e.message === "Must be 18 or older")).toBe(
|
|
683
|
+
expect(errors.some((e) => e.message === "Must be 18 or older")).toBe(
|
|
684
|
+
true,
|
|
685
|
+
);
|
|
553
686
|
});
|
|
554
687
|
|
|
555
688
|
it("should clear isSubmitted when data changes", () => {
|
|
@@ -583,7 +716,7 @@ describe("useForma", () => {
|
|
|
583
716
|
});
|
|
584
717
|
|
|
585
718
|
const { result } = renderHook(() =>
|
|
586
|
-
useForma({ spec, validationDebounceMs: 100 })
|
|
719
|
+
useForma({ spec, validationDebounceMs: 100 }),
|
|
587
720
|
);
|
|
588
721
|
|
|
589
722
|
// Initially invalid (required field empty)
|
|
@@ -619,7 +752,7 @@ describe("useForma", () => {
|
|
|
619
752
|
});
|
|
620
753
|
|
|
621
754
|
const { result } = renderHook(() =>
|
|
622
|
-
useForma({ spec, validationDebounceMs: 500, onSubmit })
|
|
755
|
+
useForma({ spec, validationDebounceMs: 500, onSubmit }),
|
|
623
756
|
);
|
|
624
757
|
|
|
625
758
|
// Fill the field
|
|
@@ -647,7 +780,7 @@ describe("useForma", () => {
|
|
|
647
780
|
});
|
|
648
781
|
|
|
649
782
|
const { result } = renderHook(() =>
|
|
650
|
-
useForma({ spec, validationDebounceMs: 100, onSubmit })
|
|
783
|
+
useForma({ spec, validationDebounceMs: 100, onSubmit }),
|
|
651
784
|
);
|
|
652
785
|
|
|
653
786
|
// Submit without filling required field
|
|
@@ -667,7 +800,7 @@ describe("useForma", () => {
|
|
|
667
800
|
});
|
|
668
801
|
|
|
669
802
|
const { result } = renderHook(() =>
|
|
670
|
-
useForma({ spec, validationDebounceMs: 0 })
|
|
803
|
+
useForma({ spec, validationDebounceMs: 0 }),
|
|
671
804
|
);
|
|
672
805
|
|
|
673
806
|
expect(result.current.isValid).toBe(false);
|
|
@@ -698,7 +831,7 @@ describe("useForma", () => {
|
|
|
698
831
|
});
|
|
699
832
|
|
|
700
833
|
const { result } = renderHook(() =>
|
|
701
|
-
useForma({ spec, initialData: { items: [] } })
|
|
834
|
+
useForma({ spec, initialData: { items: [] } }),
|
|
702
835
|
);
|
|
703
836
|
|
|
704
837
|
const helpers = result.current.getArrayHelpers("items");
|
|
@@ -718,7 +851,7 @@ describe("useForma", () => {
|
|
|
718
851
|
});
|
|
719
852
|
|
|
720
853
|
const { result } = renderHook(() =>
|
|
721
|
-
useForma({ spec, initialData: { items: [] } })
|
|
854
|
+
useForma({ spec, initialData: { items: [] } }),
|
|
722
855
|
);
|
|
723
856
|
|
|
724
857
|
act(() => {
|
|
@@ -737,7 +870,7 @@ describe("useForma", () => {
|
|
|
737
870
|
useForma({
|
|
738
871
|
spec,
|
|
739
872
|
initialData: { items: [{ name: "A" }, { name: "B" }, { name: "C" }] },
|
|
740
|
-
})
|
|
873
|
+
}),
|
|
741
874
|
);
|
|
742
875
|
|
|
743
876
|
act(() => {
|
|
@@ -756,7 +889,7 @@ describe("useForma", () => {
|
|
|
756
889
|
useForma({
|
|
757
890
|
spec,
|
|
758
891
|
initialData: { items: [{ name: "A" }, { name: "B" }, { name: "C" }] },
|
|
759
|
-
})
|
|
892
|
+
}),
|
|
760
893
|
);
|
|
761
894
|
|
|
762
895
|
act(() => {
|
|
@@ -779,7 +912,7 @@ describe("useForma", () => {
|
|
|
779
912
|
useForma({
|
|
780
913
|
spec,
|
|
781
914
|
initialData: { items: [{ name: "A" }, { name: "B" }] },
|
|
782
|
-
})
|
|
915
|
+
}),
|
|
783
916
|
);
|
|
784
917
|
|
|
785
918
|
act(() => {
|
|
@@ -798,7 +931,7 @@ describe("useForma", () => {
|
|
|
798
931
|
useForma({
|
|
799
932
|
spec,
|
|
800
933
|
initialData: { items: [{ name: "A" }, { name: "C" }] },
|
|
801
|
-
})
|
|
934
|
+
}),
|
|
802
935
|
);
|
|
803
936
|
|
|
804
937
|
act(() => {
|
|
@@ -823,7 +956,7 @@ describe("useForma", () => {
|
|
|
823
956
|
useForma({
|
|
824
957
|
spec,
|
|
825
958
|
initialData: { items: [{ name: "Only" }] },
|
|
826
|
-
})
|
|
959
|
+
}),
|
|
827
960
|
);
|
|
828
961
|
|
|
829
962
|
const helpers = result.current.getArrayHelpers("items");
|
|
@@ -849,7 +982,7 @@ describe("useForma", () => {
|
|
|
849
982
|
useForma({
|
|
850
983
|
spec,
|
|
851
984
|
initialData: { items: [{ name: "A" }, { name: "B" }] },
|
|
852
|
-
})
|
|
985
|
+
}),
|
|
853
986
|
);
|
|
854
987
|
|
|
855
988
|
const helpers = result.current.getArrayHelpers("items");
|
|
@@ -880,10 +1013,12 @@ describe("useForma", () => {
|
|
|
880
1013
|
useForma({
|
|
881
1014
|
spec,
|
|
882
1015
|
initialData: { items: [{ name: "Test Item" }] },
|
|
883
|
-
})
|
|
1016
|
+
}),
|
|
884
1017
|
);
|
|
885
1018
|
|
|
886
|
-
const itemProps = result.current
|
|
1019
|
+
const itemProps = result.current
|
|
1020
|
+
.getArrayHelpers("items")
|
|
1021
|
+
.getItemFieldProps(0, "name");
|
|
887
1022
|
|
|
888
1023
|
expect(itemProps.name).toBe("items[0].name");
|
|
889
1024
|
expect(itemProps.value).toBe("Test Item");
|
|
@@ -1022,12 +1157,17 @@ describe("useForma", () => {
|
|
|
1022
1157
|
},
|
|
1023
1158
|
pages: [
|
|
1024
1159
|
{ id: "page1", title: "Step 1", fields: ["showPage2", "field1"] },
|
|
1025
|
-
{
|
|
1160
|
+
{
|
|
1161
|
+
id: "page2",
|
|
1162
|
+
title: "Step 2",
|
|
1163
|
+
fields: ["field2"],
|
|
1164
|
+
visibleWhen: "showPage2 = true",
|
|
1165
|
+
},
|
|
1026
1166
|
],
|
|
1027
1167
|
});
|
|
1028
1168
|
|
|
1029
1169
|
const { result } = renderHook(() =>
|
|
1030
|
-
useForma({ spec, initialData: { showPage2: false } })
|
|
1170
|
+
useForma({ spec, initialData: { showPage2: false } }),
|
|
1031
1171
|
);
|
|
1032
1172
|
|
|
1033
1173
|
// Page 2 should be hidden
|
|
@@ -1039,7 +1179,9 @@ describe("useForma", () => {
|
|
|
1039
1179
|
result.current.setFieldValue("showPage2", true);
|
|
1040
1180
|
});
|
|
1041
1181
|
|
|
1042
|
-
const page2After = result.current.wizard?.pages.find(
|
|
1182
|
+
const page2After = result.current.wizard?.pages.find(
|
|
1183
|
+
(p) => p.id === "page2",
|
|
1184
|
+
);
|
|
1043
1185
|
expect(page2After?.visible).toBe(true);
|
|
1044
1186
|
});
|
|
1045
1187
|
|
|
@@ -1058,7 +1200,7 @@ describe("useForma", () => {
|
|
|
1058
1200
|
});
|
|
1059
1201
|
|
|
1060
1202
|
const { result } = renderHook(() =>
|
|
1061
|
-
useForma({ spec, initialData: { name: "Test" }, onSubmit })
|
|
1203
|
+
useForma({ spec, initialData: { name: "Test" }, onSubmit }),
|
|
1062
1204
|
);
|
|
1063
1205
|
|
|
1064
1206
|
await act(async () => {
|
|
@@ -1074,9 +1216,7 @@ describe("useForma", () => {
|
|
|
1074
1216
|
fields: { name: { type: "text", required: true } },
|
|
1075
1217
|
});
|
|
1076
1218
|
|
|
1077
|
-
const { result } = renderHook(() =>
|
|
1078
|
-
useForma({ spec, onSubmit })
|
|
1079
|
-
);
|
|
1219
|
+
const { result } = renderHook(() => useForma({ spec, onSubmit }));
|
|
1080
1220
|
|
|
1081
1221
|
await act(async () => {
|
|
1082
1222
|
await result.current.submitForm();
|
|
@@ -1088,14 +1228,14 @@ describe("useForma", () => {
|
|
|
1088
1228
|
|
|
1089
1229
|
it("should track isSubmitting state during async submit", async () => {
|
|
1090
1230
|
const onSubmit = vi.fn(
|
|
1091
|
-
(): Promise<void> => new Promise((resolve) => setTimeout(resolve, 100))
|
|
1231
|
+
(): Promise<void> => new Promise((resolve) => setTimeout(resolve, 100)),
|
|
1092
1232
|
);
|
|
1093
1233
|
const spec = createTestSpec({
|
|
1094
1234
|
fields: { name: { type: "text" } },
|
|
1095
1235
|
});
|
|
1096
1236
|
|
|
1097
1237
|
const { result } = renderHook(() =>
|
|
1098
|
-
useForma({ spec, initialData: { name: "Test" }, onSubmit })
|
|
1238
|
+
useForma({ spec, initialData: { name: "Test" }, onSubmit }),
|
|
1099
1239
|
);
|
|
1100
1240
|
|
|
1101
1241
|
let submitPromise: Promise<void>;
|
|
@@ -1118,7 +1258,7 @@ describe("useForma", () => {
|
|
|
1118
1258
|
});
|
|
1119
1259
|
|
|
1120
1260
|
const { result } = renderHook(() =>
|
|
1121
|
-
useForma({ spec, initialData: { name: "Original" } })
|
|
1261
|
+
useForma({ spec, initialData: { name: "Original" } }),
|
|
1122
1262
|
);
|
|
1123
1263
|
|
|
1124
1264
|
act(() => {
|
|
@@ -1144,9 +1284,7 @@ describe("useForma", () => {
|
|
|
1144
1284
|
fields: { name: { type: "text" } },
|
|
1145
1285
|
});
|
|
1146
1286
|
|
|
1147
|
-
const { result } = renderHook(() =>
|
|
1148
|
-
useForma({ spec, onChange })
|
|
1149
|
-
);
|
|
1287
|
+
const { result } = renderHook(() => useForma({ spec, onChange }));
|
|
1150
1288
|
|
|
1151
1289
|
act(() => {
|
|
1152
1290
|
result.current.setFieldValue("name", "New Value");
|
|
@@ -1154,7 +1292,7 @@ describe("useForma", () => {
|
|
|
1154
1292
|
|
|
1155
1293
|
expect(onChange).toHaveBeenCalledWith(
|
|
1156
1294
|
{ name: "New Value" },
|
|
1157
|
-
expect.any(Object) // computed values
|
|
1295
|
+
expect.any(Object), // computed values
|
|
1158
1296
|
);
|
|
1159
1297
|
});
|
|
1160
1298
|
|
|
@@ -1165,7 +1303,7 @@ describe("useForma", () => {
|
|
|
1165
1303
|
});
|
|
1166
1304
|
|
|
1167
1305
|
renderHook(() =>
|
|
1168
|
-
useForma({ spec, initialData: { name: "Initial" }, onChange })
|
|
1306
|
+
useForma({ spec, initialData: { name: "Initial" }, onChange }),
|
|
1169
1307
|
);
|
|
1170
1308
|
|
|
1171
1309
|
expect(onChange).not.toHaveBeenCalled();
|
|
@@ -1197,7 +1335,7 @@ describe("useForma", () => {
|
|
|
1197
1335
|
});
|
|
1198
1336
|
|
|
1199
1337
|
const { result } = renderHook(() =>
|
|
1200
|
-
useForma({ spec, initialData: { acceptTerms: true } })
|
|
1338
|
+
useForma({ spec, initialData: { acceptTerms: true } }),
|
|
1201
1339
|
);
|
|
1202
1340
|
|
|
1203
1341
|
expect(result.current.data.acceptTerms).toBe(true);
|
|
@@ -1226,7 +1364,9 @@ describe("useForma", () => {
|
|
|
1226
1364
|
type: "boolean",
|
|
1227
1365
|
label: "I accept the terms",
|
|
1228
1366
|
required: true,
|
|
1229
|
-
validations: [
|
|
1367
|
+
validations: [
|
|
1368
|
+
{ rule: "value = true", message: "You must accept the terms" },
|
|
1369
|
+
],
|
|
1230
1370
|
},
|
|
1231
1371
|
},
|
|
1232
1372
|
});
|
|
@@ -1332,12 +1472,14 @@ describe("useForma", () => {
|
|
|
1332
1472
|
});
|
|
1333
1473
|
|
|
1334
1474
|
const { result } = renderHook(() =>
|
|
1335
|
-
useForma({ spec, initialData: { age: -5 } })
|
|
1475
|
+
useForma({ spec, initialData: { age: -5 } }),
|
|
1336
1476
|
);
|
|
1337
1477
|
|
|
1338
1478
|
const fieldErrors = result.current.validateField("age");
|
|
1339
1479
|
|
|
1340
|
-
expect(fieldErrors.some((e) => e.message === "Must be positive")).toBe(
|
|
1480
|
+
expect(fieldErrors.some((e) => e.message === "Must be positive")).toBe(
|
|
1481
|
+
true,
|
|
1482
|
+
);
|
|
1341
1483
|
});
|
|
1342
1484
|
});
|
|
1343
1485
|
|
|
@@ -1360,7 +1502,7 @@ describe("useForma", () => {
|
|
|
1360
1502
|
});
|
|
1361
1503
|
|
|
1362
1504
|
const { result } = renderHook(() =>
|
|
1363
|
-
useForma({ spec, initialData: { name: "", items: [{ title: "" }] } })
|
|
1505
|
+
useForma({ spec, initialData: { name: "", items: [{ title: "" }] } }),
|
|
1364
1506
|
);
|
|
1365
1507
|
|
|
1366
1508
|
// Edit root field
|
|
@@ -1378,7 +1520,9 @@ describe("useForma", () => {
|
|
|
1378
1520
|
|
|
1379
1521
|
// Both values should be preserved
|
|
1380
1522
|
expect(result.current.data.name).toBe("John");
|
|
1381
|
-
expect(
|
|
1523
|
+
expect(
|
|
1524
|
+
(result.current.data.items as Array<{ title: string }>)[0].title,
|
|
1525
|
+
).toBe("First Item");
|
|
1382
1526
|
});
|
|
1383
1527
|
|
|
1384
1528
|
it("should preserve array item changes when editing root fields", () => {
|
|
@@ -1393,7 +1537,7 @@ describe("useForma", () => {
|
|
|
1393
1537
|
});
|
|
1394
1538
|
|
|
1395
1539
|
const { result } = renderHook(() =>
|
|
1396
|
-
useForma({ spec, initialData: { name: "", items: [{ title: "" }] } })
|
|
1540
|
+
useForma({ spec, initialData: { name: "", items: [{ title: "" }] } }),
|
|
1397
1541
|
);
|
|
1398
1542
|
|
|
1399
1543
|
// Edit array item field first
|
|
@@ -1402,7 +1546,9 @@ describe("useForma", () => {
|
|
|
1402
1546
|
const itemProps = helpers.getItemFieldProps(0, "title");
|
|
1403
1547
|
itemProps.onChange("First Item");
|
|
1404
1548
|
});
|
|
1405
|
-
expect(
|
|
1549
|
+
expect(
|
|
1550
|
+
(result.current.data.items as Array<{ title: string }>)[0].title,
|
|
1551
|
+
).toBe("First Item");
|
|
1406
1552
|
|
|
1407
1553
|
// Now edit root field
|
|
1408
1554
|
act(() => {
|
|
@@ -1411,7 +1557,9 @@ describe("useForma", () => {
|
|
|
1411
1557
|
|
|
1412
1558
|
// Both values should be preserved
|
|
1413
1559
|
expect(result.current.data.name).toBe("John");
|
|
1414
|
-
expect(
|
|
1560
|
+
expect(
|
|
1561
|
+
(result.current.data.items as Array<{ title: string }>)[0].title,
|
|
1562
|
+
).toBe("First Item");
|
|
1415
1563
|
});
|
|
1416
1564
|
|
|
1417
1565
|
it("should preserve data when alternating between root and array item edits", () => {
|
|
@@ -1435,9 +1583,12 @@ describe("useForma", () => {
|
|
|
1435
1583
|
initialData: {
|
|
1436
1584
|
name: "",
|
|
1437
1585
|
email: "",
|
|
1438
|
-
items: [
|
|
1586
|
+
items: [
|
|
1587
|
+
{ title: "", value: null },
|
|
1588
|
+
{ title: "", value: null },
|
|
1589
|
+
],
|
|
1439
1590
|
},
|
|
1440
|
-
})
|
|
1591
|
+
}),
|
|
1441
1592
|
);
|
|
1442
1593
|
|
|
1443
1594
|
// Sequence of edits alternating between root and array fields
|
|
@@ -1468,9 +1619,15 @@ describe("useForma", () => {
|
|
|
1468
1619
|
const data = result.current.data;
|
|
1469
1620
|
expect(data.name).toBe("Alice");
|
|
1470
1621
|
expect(data.email).toBe("alice@example.com");
|
|
1471
|
-
expect(
|
|
1472
|
-
|
|
1473
|
-
|
|
1622
|
+
expect(
|
|
1623
|
+
(data.items as Array<{ title: string; value: number }>)[0].title,
|
|
1624
|
+
).toBe("Item 1");
|
|
1625
|
+
expect(
|
|
1626
|
+
(data.items as Array<{ title: string; value: number }>)[0].value,
|
|
1627
|
+
).toBe(100);
|
|
1628
|
+
expect(
|
|
1629
|
+
(data.items as Array<{ title: string; value: number }>)[1].title,
|
|
1630
|
+
).toBe("Item 2");
|
|
1474
1631
|
});
|
|
1475
1632
|
|
|
1476
1633
|
it("should preserve item data when adding new items", () => {
|
|
@@ -1484,7 +1641,7 @@ describe("useForma", () => {
|
|
|
1484
1641
|
});
|
|
1485
1642
|
|
|
1486
1643
|
const { result } = renderHook(() =>
|
|
1487
|
-
useForma({ spec, initialData: { items: [] } })
|
|
1644
|
+
useForma({ spec, initialData: { items: [] } }),
|
|
1488
1645
|
);
|
|
1489
1646
|
|
|
1490
1647
|
// Add first item
|
|
@@ -1498,7 +1655,9 @@ describe("useForma", () => {
|
|
|
1498
1655
|
const helpers = result.current.getArrayHelpers("items");
|
|
1499
1656
|
helpers.getItemFieldProps(0, "name").onChange("First");
|
|
1500
1657
|
});
|
|
1501
|
-
expect(
|
|
1658
|
+
expect(
|
|
1659
|
+
(result.current.data.items as Array<{ name: string }>)[0].name,
|
|
1660
|
+
).toBe("First");
|
|
1502
1661
|
|
|
1503
1662
|
// Add second item - first item should keep its value
|
|
1504
1663
|
act(() => {
|
|
@@ -1506,8 +1665,12 @@ describe("useForma", () => {
|
|
|
1506
1665
|
helpers.push({ name: "" });
|
|
1507
1666
|
});
|
|
1508
1667
|
|
|
1509
|
-
expect(
|
|
1510
|
-
|
|
1668
|
+
expect(
|
|
1669
|
+
(result.current.data.items as Array<{ name: string }>)[0].name,
|
|
1670
|
+
).toBe("First");
|
|
1671
|
+
expect(
|
|
1672
|
+
(result.current.data.items as Array<{ name: string }>).length,
|
|
1673
|
+
).toBe(2);
|
|
1511
1674
|
|
|
1512
1675
|
// Edit second item - first item should still keep its value
|
|
1513
1676
|
act(() => {
|
|
@@ -1515,8 +1678,12 @@ describe("useForma", () => {
|
|
|
1515
1678
|
helpers.getItemFieldProps(1, "name").onChange("Second");
|
|
1516
1679
|
});
|
|
1517
1680
|
|
|
1518
|
-
expect(
|
|
1519
|
-
|
|
1681
|
+
expect(
|
|
1682
|
+
(result.current.data.items as Array<{ name: string }>)[0].name,
|
|
1683
|
+
).toBe("First");
|
|
1684
|
+
expect(
|
|
1685
|
+
(result.current.data.items as Array<{ name: string }>)[1].name,
|
|
1686
|
+
).toBe("Second");
|
|
1520
1687
|
});
|
|
1521
1688
|
|
|
1522
1689
|
it("should preserve data when editing different array items in sequence", () => {
|
|
@@ -1535,7 +1702,7 @@ describe("useForma", () => {
|
|
|
1535
1702
|
initialData: {
|
|
1536
1703
|
items: [{ name: "" }, { name: "" }, { name: "" }],
|
|
1537
1704
|
},
|
|
1538
|
-
})
|
|
1705
|
+
}),
|
|
1539
1706
|
);
|
|
1540
1707
|
|
|
1541
1708
|
// Edit items in sequence: 0, 2, 1, 0 again
|
|
@@ -1581,7 +1748,7 @@ describe("useForma", () => {
|
|
|
1581
1748
|
initialData: {
|
|
1582
1749
|
items: [{ name: "First" }, { name: "Second" }, { name: "Third" }],
|
|
1583
1750
|
},
|
|
1584
|
-
})
|
|
1751
|
+
}),
|
|
1585
1752
|
);
|
|
1586
1753
|
|
|
1587
1754
|
// Remove middle item
|
|
@@ -1618,7 +1785,7 @@ describe("useForma", () => {
|
|
|
1618
1785
|
});
|
|
1619
1786
|
|
|
1620
1787
|
const { result } = renderHook(() =>
|
|
1621
|
-
useForma({ spec, initialData: { items: [{ name: "" }] } })
|
|
1788
|
+
useForma({ spec, initialData: { items: [{ name: "" }] } }),
|
|
1622
1789
|
);
|
|
1623
1790
|
|
|
1624
1791
|
// Rapidly edit the same field multiple times
|
|
@@ -1642,7 +1809,9 @@ describe("useForma", () => {
|
|
|
1642
1809
|
helpers.getItemFieldProps(0, "name").onChange("abcd");
|
|
1643
1810
|
});
|
|
1644
1811
|
|
|
1645
|
-
expect(
|
|
1812
|
+
expect(
|
|
1813
|
+
(result.current.data.items as Array<{ name: string }>)[0].name,
|
|
1814
|
+
).toBe("abcd");
|
|
1646
1815
|
});
|
|
1647
1816
|
|
|
1648
1817
|
it("should preserve multiple root fields when editing array items", () => {
|
|
@@ -1667,7 +1836,7 @@ describe("useForma", () => {
|
|
|
1667
1836
|
email: "john@example.com",
|
|
1668
1837
|
items: [{ name: "" }],
|
|
1669
1838
|
},
|
|
1670
|
-
})
|
|
1839
|
+
}),
|
|
1671
1840
|
);
|
|
1672
1841
|
|
|
1673
1842
|
// Edit array item
|
|
@@ -1680,7 +1849,9 @@ describe("useForma", () => {
|
|
|
1680
1849
|
expect(result.current.data.firstName).toBe("John");
|
|
1681
1850
|
expect(result.current.data.lastName).toBe("Doe");
|
|
1682
1851
|
expect(result.current.data.email).toBe("john@example.com");
|
|
1683
|
-
expect(
|
|
1852
|
+
expect(
|
|
1853
|
+
(result.current.data.items as Array<{ name: string }>)[0].name,
|
|
1854
|
+
).toBe("Item Name");
|
|
1684
1855
|
});
|
|
1685
1856
|
});
|
|
1686
1857
|
});
|