@fogpipe/forma-react 0.11.2 → 0.12.0-alpha.2
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/dist/index.d.ts +45 -3
- package/dist/index.js +553 -323
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/src/FieldRenderer.tsx +107 -20
- package/src/FormRenderer.tsx +321 -157
- package/src/__tests__/FieldRenderer.test.tsx +136 -20
- package/src/__tests__/FormRenderer.test.tsx +264 -85
- package/src/index.ts +2 -0
- package/src/types.ts +44 -1
- package/src/useForma.ts +392 -235
|
@@ -29,10 +29,7 @@ describe("FormRenderer", () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
render(
|
|
32
|
-
<FormRenderer
|
|
33
|
-
spec={spec}
|
|
34
|
-
components={createTestComponentMap()}
|
|
35
|
-
/>
|
|
32
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
36
33
|
);
|
|
37
34
|
|
|
38
35
|
expect(screen.getByTestId("field-name")).toBeInTheDocument();
|
|
@@ -50,10 +47,7 @@ describe("FormRenderer", () => {
|
|
|
50
47
|
});
|
|
51
48
|
|
|
52
49
|
const { container } = render(
|
|
53
|
-
<FormRenderer
|
|
54
|
-
spec={spec}
|
|
55
|
-
components={createTestComponentMap()}
|
|
56
|
-
/>
|
|
50
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
57
51
|
);
|
|
58
52
|
|
|
59
53
|
const fields = container.querySelectorAll("[data-testid^='field-']");
|
|
@@ -74,7 +68,7 @@ describe("FormRenderer", () => {
|
|
|
74
68
|
spec={spec}
|
|
75
69
|
initialData={{ name: "John Doe" }}
|
|
76
70
|
components={createTestComponentMap()}
|
|
77
|
-
|
|
71
|
+
/>,
|
|
78
72
|
);
|
|
79
73
|
|
|
80
74
|
const input = screen.getByRole("textbox");
|
|
@@ -95,7 +89,7 @@ describe("FormRenderer", () => {
|
|
|
95
89
|
spec={spec}
|
|
96
90
|
components={createTestComponentMap()}
|
|
97
91
|
layout={CustomLayout}
|
|
98
|
-
|
|
92
|
+
/>,
|
|
99
93
|
);
|
|
100
94
|
|
|
101
95
|
expect(screen.getByTestId("custom-layout")).toBeInTheDocument();
|
|
@@ -113,10 +107,7 @@ describe("FormRenderer", () => {
|
|
|
113
107
|
});
|
|
114
108
|
|
|
115
109
|
render(
|
|
116
|
-
<FormRenderer
|
|
117
|
-
spec={spec}
|
|
118
|
-
components={createTestComponentMap()}
|
|
119
|
-
/>
|
|
110
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
120
111
|
);
|
|
121
112
|
|
|
122
113
|
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
@@ -128,10 +119,7 @@ describe("FormRenderer", () => {
|
|
|
128
119
|
});
|
|
129
120
|
|
|
130
121
|
render(
|
|
131
|
-
<FormRenderer
|
|
132
|
-
spec={spec}
|
|
133
|
-
components={createTestComponentMap()}
|
|
134
|
-
/>
|
|
122
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
135
123
|
);
|
|
136
124
|
|
|
137
125
|
expect(screen.getByRole("spinbutton")).toBeInTheDocument();
|
|
@@ -143,10 +131,7 @@ describe("FormRenderer", () => {
|
|
|
143
131
|
});
|
|
144
132
|
|
|
145
133
|
render(
|
|
146
|
-
<FormRenderer
|
|
147
|
-
spec={spec}
|
|
148
|
-
components={createTestComponentMap()}
|
|
149
|
-
/>
|
|
134
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
150
135
|
);
|
|
151
136
|
|
|
152
137
|
expect(screen.getByRole("checkbox")).toBeInTheDocument();
|
|
@@ -166,10 +151,7 @@ describe("FormRenderer", () => {
|
|
|
166
151
|
});
|
|
167
152
|
|
|
168
153
|
render(
|
|
169
|
-
<FormRenderer
|
|
170
|
-
spec={spec}
|
|
171
|
-
components={createTestComponentMap()}
|
|
172
|
-
/>
|
|
154
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
173
155
|
);
|
|
174
156
|
|
|
175
157
|
expect(screen.getByRole("combobox")).toBeInTheDocument();
|
|
@@ -193,7 +175,7 @@ describe("FormRenderer", () => {
|
|
|
193
175
|
spec={spec}
|
|
194
176
|
initialData={{ items: [{ name: "Item 1" }] }}
|
|
195
177
|
components={createTestComponentMap()}
|
|
196
|
-
|
|
178
|
+
/>,
|
|
197
179
|
);
|
|
198
180
|
|
|
199
181
|
expect(screen.getByTestId("field-items")).toBeInTheDocument();
|
|
@@ -218,7 +200,7 @@ describe("FormRenderer", () => {
|
|
|
218
200
|
spec={spec}
|
|
219
201
|
components={createTestComponentMap()}
|
|
220
202
|
onChange={onChange}
|
|
221
|
-
|
|
203
|
+
/>,
|
|
222
204
|
);
|
|
223
205
|
|
|
224
206
|
const input = screen.getByRole("textbox");
|
|
@@ -239,7 +221,7 @@ describe("FormRenderer", () => {
|
|
|
239
221
|
spec={spec}
|
|
240
222
|
initialData={{ agree: false }}
|
|
241
223
|
components={createTestComponentMap()}
|
|
242
|
-
|
|
224
|
+
/>,
|
|
243
225
|
);
|
|
244
226
|
|
|
245
227
|
const checkbox = screen.getByRole("checkbox");
|
|
@@ -264,10 +246,7 @@ describe("FormRenderer", () => {
|
|
|
264
246
|
});
|
|
265
247
|
|
|
266
248
|
render(
|
|
267
|
-
<FormRenderer
|
|
268
|
-
spec={spec}
|
|
269
|
-
components={createTestComponentMap()}
|
|
270
|
-
/>
|
|
249
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
271
250
|
);
|
|
272
251
|
|
|
273
252
|
const select = screen.getByRole("combobox");
|
|
@@ -295,7 +274,7 @@ describe("FormRenderer", () => {
|
|
|
295
274
|
initialData={{ name: "John" }}
|
|
296
275
|
onSubmit={onSubmit}
|
|
297
276
|
components={createTestComponentMap()}
|
|
298
|
-
|
|
277
|
+
/>,
|
|
299
278
|
);
|
|
300
279
|
|
|
301
280
|
const submitButton = screen.getByRole("button", { name: /submit/i });
|
|
@@ -316,7 +295,7 @@ describe("FormRenderer", () => {
|
|
|
316
295
|
spec={spec}
|
|
317
296
|
onSubmit={onSubmit}
|
|
318
297
|
components={createTestComponentMap()}
|
|
319
|
-
|
|
298
|
+
/>,
|
|
320
299
|
);
|
|
321
300
|
|
|
322
301
|
const submitButton = screen.getByRole("button", { name: /submit/i });
|
|
@@ -332,10 +311,7 @@ describe("FormRenderer", () => {
|
|
|
332
311
|
});
|
|
333
312
|
|
|
334
313
|
render(
|
|
335
|
-
<FormRenderer
|
|
336
|
-
spec={spec}
|
|
337
|
-
components={createTestComponentMap()}
|
|
338
|
-
/>
|
|
314
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
339
315
|
);
|
|
340
316
|
|
|
341
317
|
const submitButton = screen.getByRole("button", { name: /submit/i });
|
|
@@ -350,7 +326,7 @@ describe("FormRenderer", () => {
|
|
|
350
326
|
it("should disable submit button while submitting", async () => {
|
|
351
327
|
const user = userEvent.setup();
|
|
352
328
|
const onSubmit = vi.fn(
|
|
353
|
-
(): Promise<void> => new Promise((resolve) => setTimeout(resolve, 100))
|
|
329
|
+
(): Promise<void> => new Promise((resolve) => setTimeout(resolve, 100)),
|
|
354
330
|
);
|
|
355
331
|
const spec = createTestSpec({
|
|
356
332
|
fields: { name: { type: "text" } },
|
|
@@ -362,7 +338,7 @@ describe("FormRenderer", () => {
|
|
|
362
338
|
initialData={{ name: "John" }}
|
|
363
339
|
onSubmit={onSubmit}
|
|
364
340
|
components={createTestComponentMap()}
|
|
365
|
-
|
|
341
|
+
/>,
|
|
366
342
|
);
|
|
367
343
|
|
|
368
344
|
const submitButton = screen.getByRole("button", { name: /submit/i });
|
|
@@ -594,7 +570,7 @@ describe("FormRenderer", () => {
|
|
|
594
570
|
spec={spec}
|
|
595
571
|
initialData={{ name: "Test" }}
|
|
596
572
|
components={components}
|
|
597
|
-
|
|
573
|
+
/>,
|
|
598
574
|
);
|
|
599
575
|
|
|
600
576
|
expect(screen.getByTestId("consumer")).toBeInTheDocument();
|
|
@@ -609,7 +585,7 @@ describe("FormRenderer", () => {
|
|
|
609
585
|
}
|
|
610
586
|
|
|
611
587
|
expect(() => render(<BadComponent />)).toThrow(
|
|
612
|
-
/useFormaContext must be used within
|
|
588
|
+
/useFormaContext must be used within/,
|
|
613
589
|
);
|
|
614
590
|
});
|
|
615
591
|
});
|
|
@@ -632,10 +608,7 @@ describe("FormRenderer", () => {
|
|
|
632
608
|
});
|
|
633
609
|
|
|
634
610
|
render(
|
|
635
|
-
<FormRenderer
|
|
636
|
-
spec={spec}
|
|
637
|
-
components={createTestComponentMap()}
|
|
638
|
-
/>
|
|
611
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
639
612
|
);
|
|
640
613
|
|
|
641
614
|
// First page should be visible
|
|
@@ -662,7 +635,7 @@ describe("FormRenderer", () => {
|
|
|
662
635
|
spec={spec}
|
|
663
636
|
components={createTestComponentMap()}
|
|
664
637
|
pageWrapper={CustomPageWrapper}
|
|
665
|
-
|
|
638
|
+
/>,
|
|
666
639
|
);
|
|
667
640
|
|
|
668
641
|
const pageWrapper = screen.getByTestId("custom-page");
|
|
@@ -693,7 +666,7 @@ describe("FormRenderer", () => {
|
|
|
693
666
|
spec={spec}
|
|
694
667
|
initialData={{ showDetails: false }}
|
|
695
668
|
components={createTestComponentMap()}
|
|
696
|
-
|
|
669
|
+
/>,
|
|
697
670
|
);
|
|
698
671
|
|
|
699
672
|
// Details field should not be rendered when hidden
|
|
@@ -718,7 +691,7 @@ describe("FormRenderer", () => {
|
|
|
718
691
|
spec={spec}
|
|
719
692
|
initialData={{ showDetails: false }}
|
|
720
693
|
components={createTestComponentMap()}
|
|
721
|
-
|
|
694
|
+
/>,
|
|
722
695
|
);
|
|
723
696
|
|
|
724
697
|
// Initially hidden
|
|
@@ -756,29 +729,46 @@ describe("FormRenderer", () => {
|
|
|
756
729
|
type: "select",
|
|
757
730
|
label: "Position",
|
|
758
731
|
options: [
|
|
759
|
-
{
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
732
|
+
{
|
|
733
|
+
value: "dev",
|
|
734
|
+
label: "Developer",
|
|
735
|
+
visibleWhen: 'department = "engineering"',
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
value: "qa",
|
|
739
|
+
label: "QA Engineer",
|
|
740
|
+
visibleWhen: 'department = "engineering"',
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
value: "rep",
|
|
744
|
+
label: "Sales Rep",
|
|
745
|
+
visibleWhen: 'department = "sales"',
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
value: "mgr",
|
|
749
|
+
label: "Sales Manager",
|
|
750
|
+
visibleWhen: 'department = "sales"',
|
|
751
|
+
},
|
|
763
752
|
],
|
|
764
753
|
},
|
|
765
754
|
},
|
|
766
755
|
});
|
|
767
756
|
|
|
768
757
|
render(
|
|
769
|
-
<FormRenderer
|
|
770
|
-
spec={spec}
|
|
771
|
-
components={createTestComponentMap()}
|
|
772
|
-
/>
|
|
758
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
773
759
|
);
|
|
774
760
|
|
|
775
761
|
// Initially no department selected - no position options should show
|
|
776
|
-
const positionSelect = screen
|
|
762
|
+
const positionSelect = screen
|
|
763
|
+
.getByTestId("field-position")
|
|
764
|
+
.querySelector("select")!;
|
|
777
765
|
// Only the placeholder "Select..." option should be present
|
|
778
766
|
expect(positionSelect.querySelectorAll("option")).toHaveLength(1);
|
|
779
767
|
|
|
780
768
|
// Select Engineering department
|
|
781
|
-
const departmentSelect = screen
|
|
769
|
+
const departmentSelect = screen
|
|
770
|
+
.getByTestId("field-department")
|
|
771
|
+
.querySelector("select")!;
|
|
782
772
|
await user.selectOptions(departmentSelect, "engineering");
|
|
783
773
|
|
|
784
774
|
// Now only engineering positions should show
|
|
@@ -823,10 +813,7 @@ describe("FormRenderer", () => {
|
|
|
823
813
|
});
|
|
824
814
|
|
|
825
815
|
render(
|
|
826
|
-
<FormRenderer
|
|
827
|
-
spec={spec}
|
|
828
|
-
components={createTestComponentMap()}
|
|
829
|
-
/>
|
|
816
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
830
817
|
);
|
|
831
818
|
|
|
832
819
|
expect(screen.getByText("Red")).toBeInTheDocument();
|
|
@@ -851,22 +838,29 @@ describe("FormRenderer", () => {
|
|
|
851
838
|
label: "Features",
|
|
852
839
|
options: [
|
|
853
840
|
{ value: "email", label: "Email Support" },
|
|
854
|
-
{
|
|
855
|
-
|
|
841
|
+
{
|
|
842
|
+
value: "phone",
|
|
843
|
+
label: "Phone Support",
|
|
844
|
+
visibleWhen: 'tier = "premium"',
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
value: "priority",
|
|
848
|
+
label: "Priority Queue",
|
|
849
|
+
visibleWhen: 'tier = "premium"',
|
|
850
|
+
},
|
|
856
851
|
],
|
|
857
852
|
},
|
|
858
853
|
},
|
|
859
854
|
});
|
|
860
855
|
|
|
861
856
|
render(
|
|
862
|
-
<FormRenderer
|
|
863
|
-
spec={spec}
|
|
864
|
-
components={createTestComponentMap()}
|
|
865
|
-
/>
|
|
857
|
+
<FormRenderer spec={spec} components={createTestComponentMap()} />,
|
|
866
858
|
);
|
|
867
859
|
|
|
868
860
|
// Initially no tier selected - only non-conditional option visible
|
|
869
|
-
const featuresSelect = screen
|
|
861
|
+
const featuresSelect = screen
|
|
862
|
+
.getByTestId("field-features")
|
|
863
|
+
.querySelector("select")!;
|
|
870
864
|
await waitFor(() => {
|
|
871
865
|
// placeholder + 1 option without visibleWhen
|
|
872
866
|
expect(featuresSelect.querySelectorAll("option")).toHaveLength(2);
|
|
@@ -875,7 +869,9 @@ describe("FormRenderer", () => {
|
|
|
875
869
|
});
|
|
876
870
|
|
|
877
871
|
// Select Premium tier
|
|
878
|
-
const tierSelect = screen
|
|
872
|
+
const tierSelect = screen
|
|
873
|
+
.getByTestId("field-tier")
|
|
874
|
+
.querySelector("select")!;
|
|
879
875
|
await user.selectOptions(tierSelect, "premium");
|
|
880
876
|
|
|
881
877
|
// All options should now show
|
|
@@ -895,8 +891,8 @@ describe("FormRenderer", () => {
|
|
|
895
891
|
type: "select",
|
|
896
892
|
label: "Category",
|
|
897
893
|
options: [
|
|
898
|
-
{ value: "a", label: "Option A", visibleWhen:
|
|
899
|
-
{ value: "b", label: "Option B", visibleWhen:
|
|
894
|
+
{ value: "a", label: "Option A", visibleWhen: "toggle = true" },
|
|
895
|
+
{ value: "b", label: "Option B", visibleWhen: "toggle = true" },
|
|
900
896
|
],
|
|
901
897
|
},
|
|
902
898
|
toggle: {
|
|
@@ -911,11 +907,13 @@ describe("FormRenderer", () => {
|
|
|
911
907
|
spec={spec}
|
|
912
908
|
initialData={{ toggle: false }}
|
|
913
909
|
components={createTestComponentMap()}
|
|
914
|
-
|
|
910
|
+
/>,
|
|
915
911
|
);
|
|
916
912
|
|
|
917
913
|
// All options have visibleWhen that evaluates to false
|
|
918
|
-
const categorySelect = screen
|
|
914
|
+
const categorySelect = screen
|
|
915
|
+
.getByTestId("field-category")
|
|
916
|
+
.querySelector("select")!;
|
|
919
917
|
// Only placeholder
|
|
920
918
|
expect(categorySelect.querySelectorAll("option")).toHaveLength(1);
|
|
921
919
|
});
|
|
@@ -943,7 +941,7 @@ describe("FormRenderer", () => {
|
|
|
943
941
|
spec={spec}
|
|
944
942
|
initialData={{ items: [] }}
|
|
945
943
|
components={createTestComponentMap()}
|
|
946
|
-
|
|
944
|
+
/>,
|
|
947
945
|
);
|
|
948
946
|
|
|
949
947
|
const addButton = screen.getByTestId("add-items");
|
|
@@ -971,7 +969,7 @@ describe("FormRenderer", () => {
|
|
|
971
969
|
spec={spec}
|
|
972
970
|
initialData={{ items: [{ name: "Item 1" }, { name: "Item 2" }] }}
|
|
973
971
|
components={createTestComponentMap()}
|
|
974
|
-
|
|
972
|
+
/>,
|
|
975
973
|
);
|
|
976
974
|
|
|
977
975
|
expect(screen.getByTestId("array-item-items-0")).toBeInTheDocument();
|
|
@@ -982,7 +980,9 @@ describe("FormRenderer", () => {
|
|
|
982
980
|
|
|
983
981
|
await waitFor(() => {
|
|
984
982
|
// After removing first item, we should have only 1 item (at index 0)
|
|
985
|
-
expect(
|
|
983
|
+
expect(
|
|
984
|
+
screen.queryByTestId("array-item-items-1"),
|
|
985
|
+
).not.toBeInTheDocument();
|
|
986
986
|
});
|
|
987
987
|
});
|
|
988
988
|
|
|
@@ -1006,7 +1006,7 @@ describe("FormRenderer", () => {
|
|
|
1006
1006
|
spec={spec}
|
|
1007
1007
|
initialData={{ items: [] }}
|
|
1008
1008
|
components={createTestComponentMap()}
|
|
1009
|
-
|
|
1009
|
+
/>,
|
|
1010
1010
|
);
|
|
1011
1011
|
|
|
1012
1012
|
const addButton = screen.getByTestId("add-items");
|
|
@@ -1050,7 +1050,7 @@ describe("FormRenderer", () => {
|
|
|
1050
1050
|
spec={spec}
|
|
1051
1051
|
initialData={{ items: [] }}
|
|
1052
1052
|
components={createTestComponentMap()}
|
|
1053
|
-
|
|
1053
|
+
/>,
|
|
1054
1054
|
);
|
|
1055
1055
|
|
|
1056
1056
|
const addButton = screen.getByTestId("add-items");
|
|
@@ -1072,7 +1072,9 @@ describe("FormRenderer", () => {
|
|
|
1072
1072
|
// Should have 2 items remaining (indices 0 and 1)
|
|
1073
1073
|
expect(screen.getByTestId("array-item-items-0")).toBeInTheDocument();
|
|
1074
1074
|
expect(screen.getByTestId("array-item-items-1")).toBeInTheDocument();
|
|
1075
|
-
expect(
|
|
1075
|
+
expect(
|
|
1076
|
+
screen.queryByTestId("array-item-items-2"),
|
|
1077
|
+
).not.toBeInTheDocument();
|
|
1076
1078
|
});
|
|
1077
1079
|
});
|
|
1078
1080
|
|
|
@@ -1095,7 +1097,7 @@ describe("FormRenderer", () => {
|
|
|
1095
1097
|
initialData={{ items: [{ name: "Existing Item" }] }}
|
|
1096
1098
|
components={createTestComponentMap()}
|
|
1097
1099
|
onChange={onChange}
|
|
1098
|
-
|
|
1100
|
+
/>,
|
|
1099
1101
|
);
|
|
1100
1102
|
|
|
1101
1103
|
// Verify initial state
|
|
@@ -1134,7 +1136,7 @@ describe("FormRenderer", () => {
|
|
|
1134
1136
|
spec={spec}
|
|
1135
1137
|
initialData={{ items: [] }}
|
|
1136
1138
|
components={createTestComponentMap()}
|
|
1137
|
-
|
|
1139
|
+
/>,
|
|
1138
1140
|
);
|
|
1139
1141
|
|
|
1140
1142
|
const addButton = screen.getByTestId("add-items");
|
|
@@ -1147,10 +1149,187 @@ describe("FormRenderer", () => {
|
|
|
1147
1149
|
// All 5 items should be present
|
|
1148
1150
|
await waitFor(() => {
|
|
1149
1151
|
for (let i = 0; i < 5; i++) {
|
|
1150
|
-
expect(
|
|
1152
|
+
expect(
|
|
1153
|
+
screen.getByTestId(`array-item-items-${i}`),
|
|
1154
|
+
).toBeInTheDocument();
|
|
1151
1155
|
}
|
|
1152
1156
|
});
|
|
1153
1157
|
});
|
|
1154
1158
|
});
|
|
1155
1159
|
});
|
|
1160
|
+
|
|
1161
|
+
// ============================================================================
|
|
1162
|
+
// Visibility Wrapper Stability
|
|
1163
|
+
// ============================================================================
|
|
1164
|
+
|
|
1165
|
+
describe("visibility wrapper stability", () => {
|
|
1166
|
+
it("should render a hidden wrapper div when field is invisible", () => {
|
|
1167
|
+
const spec = createTestSpec({
|
|
1168
|
+
fields: {
|
|
1169
|
+
toggle: { type: "boolean", label: "Toggle" },
|
|
1170
|
+
details: {
|
|
1171
|
+
type: "text",
|
|
1172
|
+
label: "Details",
|
|
1173
|
+
visibleWhen: "toggle = true",
|
|
1174
|
+
},
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
|
|
1178
|
+
const { container } = render(
|
|
1179
|
+
<FormRenderer
|
|
1180
|
+
spec={spec}
|
|
1181
|
+
initialData={{ toggle: false }}
|
|
1182
|
+
components={createTestComponentMap()}
|
|
1183
|
+
/>,
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
// The wrapper div should exist with hidden attribute
|
|
1187
|
+
const wrapper = container.querySelector('[data-field-path="details"]');
|
|
1188
|
+
expect(wrapper).toBeInTheDocument();
|
|
1189
|
+
expect(wrapper).toHaveAttribute("hidden");
|
|
1190
|
+
// Should have no children (field content not rendered)
|
|
1191
|
+
expect(wrapper!.children).toHaveLength(0);
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it("should remove hidden attribute when field becomes visible", async () => {
|
|
1195
|
+
const user = userEvent.setup();
|
|
1196
|
+
const spec = createTestSpec({
|
|
1197
|
+
fields: {
|
|
1198
|
+
toggle: { type: "boolean", label: "Toggle" },
|
|
1199
|
+
details: {
|
|
1200
|
+
type: "text",
|
|
1201
|
+
label: "Details",
|
|
1202
|
+
visibleWhen: "toggle = true",
|
|
1203
|
+
},
|
|
1204
|
+
},
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
const { container } = render(
|
|
1208
|
+
<FormRenderer
|
|
1209
|
+
spec={spec}
|
|
1210
|
+
initialData={{ toggle: false }}
|
|
1211
|
+
components={createTestComponentMap()}
|
|
1212
|
+
/>,
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
// Initially hidden
|
|
1216
|
+
const wrapper = container.querySelector('[data-field-path="details"]');
|
|
1217
|
+
expect(wrapper).toHaveAttribute("hidden");
|
|
1218
|
+
|
|
1219
|
+
// Toggle visibility
|
|
1220
|
+
const checkbox = screen.getByRole("checkbox");
|
|
1221
|
+
await user.click(checkbox);
|
|
1222
|
+
|
|
1223
|
+
// Now visible - wrapper should not have hidden attribute
|
|
1224
|
+
await waitFor(() => {
|
|
1225
|
+
const visibleWrapper = container.querySelector(
|
|
1226
|
+
'[data-field-path="details"]',
|
|
1227
|
+
);
|
|
1228
|
+
expect(visibleWrapper).toBeInTheDocument();
|
|
1229
|
+
expect(visibleWrapper).not.toHaveAttribute("hidden");
|
|
1230
|
+
});
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it("should reuse the same DOM node when toggling visibility", async () => {
|
|
1234
|
+
const user = userEvent.setup();
|
|
1235
|
+
const spec = createTestSpec({
|
|
1236
|
+
fields: {
|
|
1237
|
+
toggle: { type: "boolean", label: "Toggle" },
|
|
1238
|
+
details: {
|
|
1239
|
+
type: "text",
|
|
1240
|
+
label: "Details",
|
|
1241
|
+
visibleWhen: "toggle = true",
|
|
1242
|
+
},
|
|
1243
|
+
},
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
const { container } = render(
|
|
1247
|
+
<FormRenderer
|
|
1248
|
+
spec={spec}
|
|
1249
|
+
initialData={{ toggle: false }}
|
|
1250
|
+
components={createTestComponentMap()}
|
|
1251
|
+
/>,
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
// Get reference to the wrapper DOM node
|
|
1255
|
+
const wrapperBefore = container.querySelector(
|
|
1256
|
+
'[data-field-path="details"]',
|
|
1257
|
+
);
|
|
1258
|
+
expect(wrapperBefore).toHaveAttribute("hidden");
|
|
1259
|
+
|
|
1260
|
+
// Toggle to visible
|
|
1261
|
+
const checkbox = screen.getByRole("checkbox");
|
|
1262
|
+
await user.click(checkbox);
|
|
1263
|
+
|
|
1264
|
+
await waitFor(() => {
|
|
1265
|
+
const wrapperAfter = container.querySelector(
|
|
1266
|
+
'[data-field-path="details"]',
|
|
1267
|
+
);
|
|
1268
|
+
expect(wrapperAfter).not.toHaveAttribute("hidden");
|
|
1269
|
+
// Same DOM node should be reused (React reconciliation with same key + element type)
|
|
1270
|
+
expect(wrapperAfter).toBe(wrapperBefore);
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
it("should not render field content inside hidden wrapper", () => {
|
|
1275
|
+
const spec = createTestSpec({
|
|
1276
|
+
fields: {
|
|
1277
|
+
toggle: { type: "boolean", label: "Toggle" },
|
|
1278
|
+
details: {
|
|
1279
|
+
type: "text",
|
|
1280
|
+
label: "Details",
|
|
1281
|
+
visibleWhen: "toggle = true",
|
|
1282
|
+
},
|
|
1283
|
+
},
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
const { container } = render(
|
|
1287
|
+
<FormRenderer
|
|
1288
|
+
spec={spec}
|
|
1289
|
+
initialData={{ toggle: false }}
|
|
1290
|
+
components={createTestComponentMap()}
|
|
1291
|
+
/>,
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
const wrapper = container.querySelector('[data-field-path="details"]');
|
|
1295
|
+
expect(wrapper).toHaveAttribute("hidden");
|
|
1296
|
+
// The test component renders data-testid="field-details" - should not exist
|
|
1297
|
+
expect(screen.queryByTestId("field-details")).not.toBeInTheDocument();
|
|
1298
|
+
// Wrapper should be empty
|
|
1299
|
+
expect(wrapper!.innerHTML).toBe("");
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
it("should render field content inside visible wrapper", async () => {
|
|
1303
|
+
const user = userEvent.setup();
|
|
1304
|
+
const spec = createTestSpec({
|
|
1305
|
+
fields: {
|
|
1306
|
+
toggle: { type: "boolean", label: "Toggle" },
|
|
1307
|
+
details: {
|
|
1308
|
+
type: "text",
|
|
1309
|
+
label: "Details",
|
|
1310
|
+
visibleWhen: "toggle = true",
|
|
1311
|
+
},
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
const { container } = render(
|
|
1316
|
+
<FormRenderer
|
|
1317
|
+
spec={spec}
|
|
1318
|
+
initialData={{ toggle: false }}
|
|
1319
|
+
components={createTestComponentMap()}
|
|
1320
|
+
/>,
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
// Toggle to visible
|
|
1324
|
+
await user.click(screen.getByRole("checkbox"));
|
|
1325
|
+
|
|
1326
|
+
await waitFor(() => {
|
|
1327
|
+
const wrapper = container.querySelector('[data-field-path="details"]');
|
|
1328
|
+
expect(wrapper).not.toHaveAttribute("hidden");
|
|
1329
|
+
// Field content should be rendered inside
|
|
1330
|
+
expect(wrapper!.children.length).toBeGreaterThan(0);
|
|
1331
|
+
expect(screen.getByTestId("field-details")).toBeInTheDocument();
|
|
1332
|
+
});
|
|
1333
|
+
});
|
|
1334
|
+
});
|
|
1156
1335
|
});
|