@windstream/react-shared-components 0.2.55 → 0.2.56
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/contentful/index.d.ts +200 -2
- package/dist/contentful/index.esm.js +5 -5
- package/dist/contentful/index.esm.js.map +1 -1
- package/dist/contentful/index.js +4 -4
- package/dist/contentful/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/contentful/blocks/dynamic-form/DynamicForm.stories.tsx +154 -0
- package/src/contentful/blocks/dynamic-form/conditional-fields.test.tsx +108 -0
- package/src/contentful/blocks/dynamic-form/conditional-fields.tsx +103 -0
- package/src/contentful/blocks/dynamic-form/constants.ts +28 -0
- package/src/contentful/blocks/dynamic-form/context.ts +35 -0
- package/src/contentful/blocks/dynamic-form/field-map.test.tsx +421 -0
- package/src/contentful/blocks/dynamic-form/field-map.tsx +486 -0
- package/src/contentful/blocks/dynamic-form/form-field.test.tsx +319 -0
- package/src/contentful/blocks/dynamic-form/form-field.tsx +224 -0
- package/src/contentful/blocks/dynamic-form/index.test.tsx +355 -0
- package/src/contentful/blocks/dynamic-form/index.tsx +552 -0
- package/src/contentful/blocks/dynamic-form/render.test.tsx +116 -0
- package/src/contentful/blocks/dynamic-form/render.tsx +172 -0
- package/src/contentful/blocks/dynamic-form/types.ts +211 -0
- package/src/contentful/blocks/dynamic-form/utils.test.ts +238 -0
- package/src/contentful/blocks/dynamic-form/utils.ts +255 -0
- package/src/contentful/index.ts +20 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { DynamicForm } from "./index";
|
|
3
|
+
import { DynamicFormItem } from "./types";
|
|
4
|
+
|
|
5
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
6
|
+
|
|
7
|
+
const fields: DynamicFormItem[] = [
|
|
8
|
+
{
|
|
9
|
+
itemType: "formField",
|
|
10
|
+
fieldId: "firstName",
|
|
11
|
+
fieldType: "Text",
|
|
12
|
+
displayName: "First Name",
|
|
13
|
+
requiredField: true,
|
|
14
|
+
errorMessage: "First name is required",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
itemType: "formField",
|
|
18
|
+
fieldId: "consent",
|
|
19
|
+
fieldType: "Checkbox",
|
|
20
|
+
displayName: "I agree",
|
|
21
|
+
requiredField: true,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
describe("DynamicForm", () => {
|
|
26
|
+
it("renders the title, fields and submit button", () => {
|
|
27
|
+
render(
|
|
28
|
+
<DynamicForm title="Contact us" fields={fields} submitLabel="Send" />
|
|
29
|
+
);
|
|
30
|
+
expect(screen.getByText("Contact us")).toBeInTheDocument();
|
|
31
|
+
expect(screen.getByText("First Name")).toBeInTheDocument();
|
|
32
|
+
expect(
|
|
33
|
+
screen.getByRole("button", { name: /submit form/i })
|
|
34
|
+
).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("shows a validation message and does not call handlers when required fields are empty", async () => {
|
|
38
|
+
const onEmailSubmit = jest.fn();
|
|
39
|
+
render(
|
|
40
|
+
<DynamicForm
|
|
41
|
+
fields={fields}
|
|
42
|
+
formSubmitOptions={["email"]}
|
|
43
|
+
emailTemplateName="welcome"
|
|
44
|
+
onEmailSubmit={onEmailSubmit}
|
|
45
|
+
validationErrorText="Please fill required fields"
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
fireEvent.click(screen.getByRole("button", { name: /submit form/i }));
|
|
50
|
+
|
|
51
|
+
await waitFor(() =>
|
|
52
|
+
expect(
|
|
53
|
+
screen.getByText("Please fill required fields")
|
|
54
|
+
).toBeInTheDocument()
|
|
55
|
+
);
|
|
56
|
+
expect(onEmailSubmit).not.toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("invokes email, smtp and sms handlers and renders the success message", async () => {
|
|
60
|
+
const onEmailSubmit = jest.fn().mockResolvedValue(undefined);
|
|
61
|
+
const onSmtpSubmit = jest.fn().mockResolvedValue(undefined);
|
|
62
|
+
const onSmsSubmit = jest.fn().mockResolvedValue(undefined);
|
|
63
|
+
const onSubmitSuccess = jest.fn();
|
|
64
|
+
|
|
65
|
+
render(
|
|
66
|
+
<DynamicForm
|
|
67
|
+
fields={fields}
|
|
68
|
+
formSubmitOptions={["email", "sftp", "sms"]}
|
|
69
|
+
emailTemplateName="welcome"
|
|
70
|
+
onEmailSubmit={onEmailSubmit}
|
|
71
|
+
onSmtpSubmit={onSmtpSubmit}
|
|
72
|
+
onSmsSubmit={onSmsSubmit}
|
|
73
|
+
onSubmitSuccess={onSubmitSuccess}
|
|
74
|
+
successMessage={<div>Thanks for reaching out</div>}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
fireEvent.change(screen.getByLabelText("First Name"), {
|
|
79
|
+
target: { value: "Jane", name: "firstName" },
|
|
80
|
+
});
|
|
81
|
+
fireEvent.click(screen.getByRole("checkbox"));
|
|
82
|
+
|
|
83
|
+
fireEvent.click(screen.getByRole("button", { name: /submit form/i }));
|
|
84
|
+
|
|
85
|
+
await waitFor(() =>
|
|
86
|
+
expect(screen.getByText("Thanks for reaching out")).toBeInTheDocument()
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(onEmailSubmit).toHaveBeenCalledTimes(1);
|
|
90
|
+
expect(onSmtpSubmit).toHaveBeenCalledTimes(1);
|
|
91
|
+
expect(onSmsSubmit).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(onSubmitSuccess).toHaveBeenCalledTimes(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("renders the error node when every action fails", async () => {
|
|
96
|
+
const onEmailSubmit = jest.fn().mockRejectedValue(new Error("boom"));
|
|
97
|
+
|
|
98
|
+
render(
|
|
99
|
+
<DynamicForm
|
|
100
|
+
fields={fields}
|
|
101
|
+
formSubmitOptions={["email"]}
|
|
102
|
+
emailTemplateName="welcome"
|
|
103
|
+
onEmailSubmit={onEmailSubmit}
|
|
104
|
+
errorMessage={<div>Something went wrong</div>}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
fireEvent.change(screen.getByLabelText("First Name"), {
|
|
109
|
+
target: { value: "Jane", name: "firstName" },
|
|
110
|
+
});
|
|
111
|
+
fireEvent.click(screen.getByRole("checkbox"));
|
|
112
|
+
fireEvent.click(screen.getByRole("button", { name: /submit form/i }));
|
|
113
|
+
|
|
114
|
+
await waitFor(() =>
|
|
115
|
+
expect(screen.getByText("Something went wrong")).toBeInTheDocument()
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const submit = () =>
|
|
120
|
+
fireEvent.click(screen.getByRole("button", { name: /submit form/i }));
|
|
121
|
+
|
|
122
|
+
it("applies TN/IP preset validation before submitting", async () => {
|
|
123
|
+
const onEmailSubmit = jest.fn().mockResolvedValue(undefined);
|
|
124
|
+
const presetFields: DynamicFormItem[] = [
|
|
125
|
+
{
|
|
126
|
+
itemType: "formField",
|
|
127
|
+
fieldId: "contact",
|
|
128
|
+
fieldType: "Text",
|
|
129
|
+
displayName: "Contact",
|
|
130
|
+
applyPresetValidations: true,
|
|
131
|
+
selectPresetValidationsToApply: "TN-IP-Validation",
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
render(
|
|
135
|
+
<DynamicForm
|
|
136
|
+
fields={presetFields}
|
|
137
|
+
formSubmitOptions={["email"]}
|
|
138
|
+
emailTemplateName="welcome"
|
|
139
|
+
onEmailSubmit={onEmailSubmit}
|
|
140
|
+
successMessage={<div>Done</div>}
|
|
141
|
+
/>
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
fireEvent.change(screen.getByLabelText("Contact"), {
|
|
145
|
+
target: { value: "+1 555-123-4567", name: "contact" },
|
|
146
|
+
});
|
|
147
|
+
submit();
|
|
148
|
+
|
|
149
|
+
await waitFor(() => expect(onEmailSubmit).toHaveBeenCalledTimes(1));
|
|
150
|
+
const body = onEmailSubmit.mock.calls[0][0];
|
|
151
|
+
expect(JSON.parse(body.formData)).toMatchObject({
|
|
152
|
+
form1: { contact: "5551234567" },
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("shows a validation error when a required-one group is entirely empty", async () => {
|
|
157
|
+
const groupFields: DynamicFormItem[] = [
|
|
158
|
+
{
|
|
159
|
+
itemType: "formField",
|
|
160
|
+
fieldId: "opt1",
|
|
161
|
+
fieldType: "Text",
|
|
162
|
+
displayName: "Option 1",
|
|
163
|
+
validationGroupId: "grp",
|
|
164
|
+
validationType: "At least One Required",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
itemType: "formField",
|
|
168
|
+
fieldId: "opt2",
|
|
169
|
+
fieldType: "Text",
|
|
170
|
+
displayName: "Option 2",
|
|
171
|
+
validationGroupId: "grp",
|
|
172
|
+
validationType: "At least One Required",
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
render(
|
|
176
|
+
<DynamicForm
|
|
177
|
+
fields={groupFields}
|
|
178
|
+
formSubmitOptions={["email"]}
|
|
179
|
+
emailTemplateName="welcome"
|
|
180
|
+
onEmailSubmit={jest.fn()}
|
|
181
|
+
validationErrorText="Fill at least one"
|
|
182
|
+
/>
|
|
183
|
+
);
|
|
184
|
+
submit();
|
|
185
|
+
await waitFor(() =>
|
|
186
|
+
expect(screen.getByText("Fill at least one")).toBeInTheDocument()
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("treats a partial action failure as a success", async () => {
|
|
191
|
+
const onEmailSubmit = jest.fn().mockResolvedValue(undefined);
|
|
192
|
+
const onSmsSubmit = jest.fn().mockRejectedValue(new Error("sms down"));
|
|
193
|
+
render(
|
|
194
|
+
<DynamicForm
|
|
195
|
+
fields={fields}
|
|
196
|
+
formSubmitOptions={["email", "sms"]}
|
|
197
|
+
emailTemplateName="welcome"
|
|
198
|
+
onEmailSubmit={onEmailSubmit}
|
|
199
|
+
onSmsSubmit={onSmsSubmit}
|
|
200
|
+
successMessage={<div>Partially sent</div>}
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
fireEvent.change(screen.getByLabelText("First Name"), {
|
|
204
|
+
target: { value: "Jane", name: "firstName" },
|
|
205
|
+
});
|
|
206
|
+
fireEvent.click(screen.getByRole("checkbox"));
|
|
207
|
+
submit();
|
|
208
|
+
await waitFor(() =>
|
|
209
|
+
expect(screen.getByText("Partially sent")).toBeInTheDocument()
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("runs the post handler, buildPayload and analytics tracking", async () => {
|
|
214
|
+
const postHandler = jest.fn().mockResolvedValue(undefined);
|
|
215
|
+
const buildPayload = jest.fn((base: any) => ({ ...base, custom: true }));
|
|
216
|
+
const onTrack = jest.fn();
|
|
217
|
+
render(
|
|
218
|
+
<DynamicForm
|
|
219
|
+
fields={[
|
|
220
|
+
{
|
|
221
|
+
itemType: "formField",
|
|
222
|
+
fieldId: "name",
|
|
223
|
+
fieldType: "Text",
|
|
224
|
+
displayName: "Name",
|
|
225
|
+
},
|
|
226
|
+
]}
|
|
227
|
+
formSubmitOptions={["post"]}
|
|
228
|
+
actionHandlers={{ post: postHandler }}
|
|
229
|
+
buildPayload={buildPayload}
|
|
230
|
+
onTrack={onTrack}
|
|
231
|
+
segmentEventId="evt"
|
|
232
|
+
successMessage={<div>Posted</div>}
|
|
233
|
+
/>
|
|
234
|
+
);
|
|
235
|
+
submit();
|
|
236
|
+
await waitFor(() => expect(postHandler).toHaveBeenCalledTimes(1));
|
|
237
|
+
expect(buildPayload).toHaveBeenCalled();
|
|
238
|
+
expect(onTrack).toHaveBeenCalledWith("evt", expect.any(Object));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("reports an unsupported action and renders the error node", async () => {
|
|
242
|
+
const onError = jest.fn();
|
|
243
|
+
render(
|
|
244
|
+
<DynamicForm
|
|
245
|
+
fields={[
|
|
246
|
+
{
|
|
247
|
+
itemType: "formField",
|
|
248
|
+
fieldId: "name",
|
|
249
|
+
fieldType: "Text",
|
|
250
|
+
displayName: "Name",
|
|
251
|
+
},
|
|
252
|
+
]}
|
|
253
|
+
formSubmitOptions={["voice"]}
|
|
254
|
+
onError={onError}
|
|
255
|
+
errorMessage={<div>Action unavailable</div>}
|
|
256
|
+
/>
|
|
257
|
+
);
|
|
258
|
+
submit();
|
|
259
|
+
await waitFor(() =>
|
|
260
|
+
expect(screen.getByText("Action unavailable")).toBeInTheDocument()
|
|
261
|
+
);
|
|
262
|
+
expect(onError).toHaveBeenCalledWith(
|
|
263
|
+
expect.objectContaining({ type: "unsupported_action" })
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("passes shouldShowPopup=true when a popup option is selected", async () => {
|
|
268
|
+
const onEmailSubmit = jest.fn().mockResolvedValue(undefined);
|
|
269
|
+
const onSubmitSuccess = jest.fn();
|
|
270
|
+
render(
|
|
271
|
+
<DynamicForm
|
|
272
|
+
fields={[
|
|
273
|
+
{
|
|
274
|
+
itemType: "formField",
|
|
275
|
+
fieldId: "topic",
|
|
276
|
+
fieldType: "Radio",
|
|
277
|
+
displayName: "Topic",
|
|
278
|
+
dataSource: [{ label: "Escalate", value: "escalate", popup: true }],
|
|
279
|
+
},
|
|
280
|
+
]}
|
|
281
|
+
formSubmitOptions={["email"]}
|
|
282
|
+
emailTemplateName="welcome"
|
|
283
|
+
onEmailSubmit={onEmailSubmit}
|
|
284
|
+
onSubmitSuccess={onSubmitSuccess}
|
|
285
|
+
successMessage={<div>Sent</div>}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
fireEvent.click(screen.getByText("Escalate"));
|
|
289
|
+
submit();
|
|
290
|
+
await waitFor(() =>
|
|
291
|
+
expect(onSubmitSuccess).toHaveBeenCalledWith(
|
|
292
|
+
expect.objectContaining({ topic: "escalate" }),
|
|
293
|
+
{ shouldShowPopup: true }
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("handles an unexpected error thrown while building the payload", async () => {
|
|
299
|
+
const buildPayload = jest.fn(() => {
|
|
300
|
+
throw new Error("boom");
|
|
301
|
+
});
|
|
302
|
+
render(
|
|
303
|
+
<DynamicForm
|
|
304
|
+
fields={[
|
|
305
|
+
{
|
|
306
|
+
itemType: "formField",
|
|
307
|
+
fieldId: "name",
|
|
308
|
+
fieldType: "Text",
|
|
309
|
+
displayName: "Name",
|
|
310
|
+
},
|
|
311
|
+
]}
|
|
312
|
+
formSubmitOptions={["email"]}
|
|
313
|
+
emailTemplateName="welcome"
|
|
314
|
+
onEmailSubmit={jest.fn()}
|
|
315
|
+
buildPayload={buildPayload}
|
|
316
|
+
errorMessage={<div>Unexpected failure</div>}
|
|
317
|
+
/>
|
|
318
|
+
);
|
|
319
|
+
submit();
|
|
320
|
+
await waitFor(() =>
|
|
321
|
+
expect(screen.getByText("Unexpected failure")).toBeInTheDocument()
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("sends a multipart payload when files are attached", async () => {
|
|
326
|
+
global.URL.createObjectURL = jest.fn(() => "blob:preview");
|
|
327
|
+
global.URL.revokeObjectURL = jest.fn();
|
|
328
|
+
const onEmailSubmit = jest.fn().mockResolvedValue(undefined);
|
|
329
|
+
const { container } = render(
|
|
330
|
+
<DynamicForm
|
|
331
|
+
fields={[
|
|
332
|
+
{
|
|
333
|
+
itemType: "formField",
|
|
334
|
+
fieldId: "attachment",
|
|
335
|
+
fieldType: "Imageupload",
|
|
336
|
+
displayName: "Attachment",
|
|
337
|
+
},
|
|
338
|
+
]}
|
|
339
|
+
formSubmitOptions={["email"]}
|
|
340
|
+
emailTemplateName="welcome"
|
|
341
|
+
onEmailSubmit={onEmailSubmit}
|
|
342
|
+
successMessage={<div>Uploaded</div>}
|
|
343
|
+
/>
|
|
344
|
+
);
|
|
345
|
+
const fileInput = container.querySelector(
|
|
346
|
+
'input[type="file"]'
|
|
347
|
+
) as HTMLInputElement;
|
|
348
|
+
const file = new File(["x"], "pic.png", { type: "image/png" });
|
|
349
|
+
Object.defineProperty(file, "size", { value: 100 });
|
|
350
|
+
fireEvent.change(fileInput, { target: { files: [file] } });
|
|
351
|
+
submit();
|
|
352
|
+
await waitFor(() => expect(onEmailSubmit).toHaveBeenCalledTimes(1));
|
|
353
|
+
expect(onEmailSubmit.mock.calls[0][0]).toBeInstanceOf(FormData);
|
|
354
|
+
});
|
|
355
|
+
});
|