@excom/super-form 0.1.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/.rush/temp/chunked-rush-logs/super-form.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/super-form.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/super-form.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_docs/all.log +1 -0
- package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_docs/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +5 -0
- package/index.ts +17 -0
- package/package.json +46 -0
- package/rush-logs/super-form.apply-exports.cache.log +1 -0
- package/rush-logs/super-form.apply-exports.log +1 -0
- package/rush-logs/super-form.build_docs.cache.log +1 -0
- package/rush-logs/super-form.build_docs.log +1 -0
- package/rush-logs/super-form.build_package-metas.cache.log +1 -0
- package/rush-logs/super-form.build_package-metas.log +1 -0
- package/super-form.ts +100 -0
- package/support/custom-elements.json +107 -0
- package/support/demos/external-trigger.html +43 -0
- package/support/demos/simple.html +31 -0
- package/support/dist-docs/super-form.md +212 -0
- package/support/docs/README.md +61 -0
- package/support/package-meta.json +224 -0
- package/support/tests/__snapshots__/external-trigger.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/simple.view.test.ts.snap +23 -0
- package/support/tests/external-trigger.view.test.ts +57 -0
- package/support/tests/simple.view.test.ts +63 -0
- package/support/tests/super-form.test.ts +319 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
fixture,
|
|
6
|
+
it,
|
|
7
|
+
spyFetch,
|
|
8
|
+
vi,
|
|
9
|
+
waitForEvent,
|
|
10
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
11
|
+
import { KitLogger } from "@excom/kit-logger";
|
|
12
|
+
import "../../index";
|
|
13
|
+
|
|
14
|
+
const fetchStubs = {
|
|
15
|
+
success: () => ({
|
|
16
|
+
status: 200,
|
|
17
|
+
body: JSON.stringify({ results: [{ a: 1 }] }),
|
|
18
|
+
}),
|
|
19
|
+
error: () => ({
|
|
20
|
+
status: 500,
|
|
21
|
+
body: JSON.stringify({ message: "something broke" }),
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const formHtml = `
|
|
26
|
+
<form action="/api/submit" method="put">
|
|
27
|
+
<!-- Simple primitives -->
|
|
28
|
+
<input type="text" name="name" value="John Doe">
|
|
29
|
+
<input type="email" name="email" value="j@doe.com">
|
|
30
|
+
<input type="number" name="age" value="50">
|
|
31
|
+
<input type="range" name="volume" min="0" max="100" value="20">
|
|
32
|
+
<input type="tel" name="phone" value="123-456-7890">
|
|
33
|
+
<input type="url" name="website" value="https://example.com">
|
|
34
|
+
<input type="color" name="favoriteColor" value="#ff0000">
|
|
35
|
+
<input type="password" name="password" value="secret">
|
|
36
|
+
|
|
37
|
+
<!-- Checkbox -->
|
|
38
|
+
<input type="checkbox" name="isAdmin" checked>
|
|
39
|
+
<input type="checkbox" name="isModerator">
|
|
40
|
+
|
|
41
|
+
<!-- Checkbox array -->
|
|
42
|
+
<input type="checkbox" name="hobbies" value="reading" checked>
|
|
43
|
+
<input type="checkbox" name="hobbies" value="cooking">
|
|
44
|
+
<input type="checkbox" name="hobbies" value="gaming" checked>
|
|
45
|
+
|
|
46
|
+
<!-- Radio -->
|
|
47
|
+
<input type="radio" name="activity" value="running">
|
|
48
|
+
<input type="radio" name="activity" value="swimming" checked>
|
|
49
|
+
|
|
50
|
+
<!-- Datetimes -->
|
|
51
|
+
<input type="date" name="birthday" value="1999-10-01">
|
|
52
|
+
<input type="time" name="wakeupTime" value="08:00">
|
|
53
|
+
<input type="datetime-local" name="meetingTime" value="2023-10-01T10:00">
|
|
54
|
+
<input type="month" name="birthMonth" value="1999-10">
|
|
55
|
+
<input type="week" name="birthWeek" value="1999-W40">
|
|
56
|
+
|
|
57
|
+
<!-- Select -->
|
|
58
|
+
<select name="country">
|
|
59
|
+
<option value="USA">United States</option>
|
|
60
|
+
<option value="CAN" selected>Canada</option>
|
|
61
|
+
<option value="MEX">Mexico</option>
|
|
62
|
+
</select>
|
|
63
|
+
|
|
64
|
+
<!-- Select multiple -->
|
|
65
|
+
<select name="languages" multiple>
|
|
66
|
+
<option value="english" selected>English</option>
|
|
67
|
+
<option value="french">French</option>
|
|
68
|
+
<option value="spanish" selected>Spanish</option>
|
|
69
|
+
<option value="german">German</option>
|
|
70
|
+
<option value="chinese">Chinese</option>
|
|
71
|
+
</select>
|
|
72
|
+
|
|
73
|
+
<!-- Arrays -->
|
|
74
|
+
<input type="text" name="tags[]" value="smart">
|
|
75
|
+
<input type="text" name="tags[]" value="athletic">
|
|
76
|
+
<input type="text" name="tags[]" value="kind">
|
|
77
|
+
|
|
78
|
+
<!-- Nested -->
|
|
79
|
+
<input type="text" name="address.city" value="Anytown">
|
|
80
|
+
<input type="text" name="address.state" value="CA">
|
|
81
|
+
<input type="number" name="address.zip" value="12345">
|
|
82
|
+
<input type="number" name="address.coordinates[]" value="37.7749">
|
|
83
|
+
<input type="number" name="address.coordinates[]" value="-122.4194">
|
|
84
|
+
|
|
85
|
+
<!-- Disabled -->
|
|
86
|
+
<input type="text" name="disabledValue" value="test" disabled>
|
|
87
|
+
<button type="submit">Submit</button>
|
|
88
|
+
</form>
|
|
89
|
+
`;
|
|
90
|
+
const formJson = {
|
|
91
|
+
name: "John Doe",
|
|
92
|
+
email: "j@doe.com",
|
|
93
|
+
age: 50,
|
|
94
|
+
volume: 20,
|
|
95
|
+
phone: "123-456-7890",
|
|
96
|
+
website: "https://example.com",
|
|
97
|
+
favoriteColor: "#ff0000",
|
|
98
|
+
password: "secret",
|
|
99
|
+
isAdmin: true,
|
|
100
|
+
isModerator: false,
|
|
101
|
+
activity: "swimming",
|
|
102
|
+
hobbies: ["reading", "gaming"],
|
|
103
|
+
birthday: "1999-10-01",
|
|
104
|
+
wakeupTime: "08:00",
|
|
105
|
+
meetingTime: "2023-10-01T10:00",
|
|
106
|
+
birthMonth: "1999-10",
|
|
107
|
+
birthWeek: "1999-W40",
|
|
108
|
+
country: "CAN",
|
|
109
|
+
languages: ["english", "spanish"],
|
|
110
|
+
tags: ["smart", "athletic", "kind"],
|
|
111
|
+
address: {
|
|
112
|
+
city: "Anytown",
|
|
113
|
+
state: "CA",
|
|
114
|
+
zip: 12345,
|
|
115
|
+
coordinates: [37.7749, -122.4194],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const submitForm = (superForm: HTMLSuperFormElement) => {
|
|
120
|
+
superForm
|
|
121
|
+
.getFormElement()!
|
|
122
|
+
.dispatchEvent(new Event("submit", { bubbles: true }));
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
describe("super-form", () => {
|
|
126
|
+
afterEach(() => {
|
|
127
|
+
document.body.innerHTML = "";
|
|
128
|
+
vi.restoreAllMocks();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("sets state attributes and submits properly", async () => {
|
|
132
|
+
const superForm = fixture<HTMLSuperFormElement>(
|
|
133
|
+
`<super-form>${formHtml}</super-form>`,
|
|
134
|
+
);
|
|
135
|
+
const fetchSpy = spyFetch(fetchStubs.success() as any);
|
|
136
|
+
|
|
137
|
+
// Assert loading in the handler: fetch can settle before an awaited
|
|
138
|
+
// `waitForEvent` continuation runs.
|
|
139
|
+
superForm.addEventListener("super-form-loading", () => {
|
|
140
|
+
expect(superForm).dom.to.equalTag(`<super-form is-loading></super-form>`);
|
|
141
|
+
expect(superForm.provision).toEqual(null);
|
|
142
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
143
|
+
"http://localhost:3000/api/submit",
|
|
144
|
+
expect.objectContaining({
|
|
145
|
+
method: "PUT",
|
|
146
|
+
credentials: "include",
|
|
147
|
+
headers: {
|
|
148
|
+
Accept: "application/json",
|
|
149
|
+
"Content-Type": "application/json",
|
|
150
|
+
},
|
|
151
|
+
body: expect.toSatisfy((body: string) => {
|
|
152
|
+
expect(JSON.parse(body)).toEqual(expect.objectContaining(formJson));
|
|
153
|
+
return true;
|
|
154
|
+
}),
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await waitForEvent(superForm, "super-form-success", () => {
|
|
160
|
+
submitForm(superForm);
|
|
161
|
+
});
|
|
162
|
+
expect(superForm).dom.to.equalTag(`<super-form is-success></super-form>`);
|
|
163
|
+
expect(superForm.provision?.body).toEqual({ results: [{ a: 1 }] });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("can be configured to send a body on methods that don't usually send one", async () => {
|
|
167
|
+
const superForm = fixture<HTMLSuperFormElement>(
|
|
168
|
+
`<super-form has-body>${formHtml}</super-form>`,
|
|
169
|
+
);
|
|
170
|
+
const form = superForm.getFormElement()!;
|
|
171
|
+
form.setAttribute("method", "delete");
|
|
172
|
+
const fetchSpy = spyFetch(fetchStubs.success as any);
|
|
173
|
+
|
|
174
|
+
superForm.addEventListener("super-form-loading", () => {
|
|
175
|
+
expect(superForm).dom.to.equalTag(
|
|
176
|
+
`<super-form is-loading has-body></super-form>`,
|
|
177
|
+
);
|
|
178
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
179
|
+
"http://localhost:3000/api/submit",
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
method: "DELETE",
|
|
182
|
+
credentials: "include",
|
|
183
|
+
headers: {
|
|
184
|
+
Accept: "application/json",
|
|
185
|
+
"Content-Type": "application/json",
|
|
186
|
+
},
|
|
187
|
+
body: expect.toSatisfy((body: string) => {
|
|
188
|
+
expect(JSON.parse(body)).toEqual(expect.objectContaining(formJson));
|
|
189
|
+
return true;
|
|
190
|
+
}),
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await waitForEvent(superForm, "super-form-success", () => {
|
|
196
|
+
form.dispatchEvent(new Event("submit", { bubbles: true }));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("converts the form to url params if no body is sent", async () => {
|
|
201
|
+
const superForm = fixture<HTMLSuperFormElement>(
|
|
202
|
+
`<super-form>${formHtml}</super-form>`,
|
|
203
|
+
);
|
|
204
|
+
const form = superForm.getFormElement()!;
|
|
205
|
+
form.setAttribute("method", "get");
|
|
206
|
+
const fetchSpy = spyFetch(fetchStubs.success as any);
|
|
207
|
+
|
|
208
|
+
await waitForEvent(superForm, "super-form-loading", () => {
|
|
209
|
+
form.dispatchEvent(new Event("submit", { bubbles: true }));
|
|
210
|
+
});
|
|
211
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
212
|
+
"http://localhost:3000/api/submit?name=John+Doe&email=j%40doe.com&age=50&volume=20&phone=123-456-7890&website=https%3A%2F%2Fexample.com&favoriteColor=%23ff0000&password=secret&isAdmin=true&hobbies=%5B%22reading%22%2C%22gaming%22%5D&activity=swimming&birthday=1999-10-01&wakeupTime=08%3A00&meetingTime=2023-10-01T10%3A00&birthMonth=1999-10&birthWeek=1999-W40&country=CAN&languages=%5B%22english%22%2C%22spanish%22%5D&tags=%5B%22smart%22%2C%22athletic%22%2C%22kind%22%5D&address=%7B%22city%22%3A%22Anytown%22%2C%22state%22%3A%22CA%22%2C%22zip%22%3A12345%2C%22coordinates%22%3A%5B37.7749%2C-122.4194%5D%7D&isModerator=false",
|
|
213
|
+
expect.objectContaining({
|
|
214
|
+
method: "GET",
|
|
215
|
+
credentials: "include",
|
|
216
|
+
headers: {
|
|
217
|
+
Accept: "application/json",
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("handles fetch error", async () => {
|
|
224
|
+
const superForm = fixture<HTMLSuperFormElement>(
|
|
225
|
+
`<super-form>${formHtml}</super-form>`,
|
|
226
|
+
);
|
|
227
|
+
const fetchSpy = spyFetch(fetchStubs.error as any);
|
|
228
|
+
|
|
229
|
+
KitLogger.suppress();
|
|
230
|
+
superForm.addEventListener("super-form-loading", () => {
|
|
231
|
+
expect(superForm).dom.to.equalTag(`<super-form is-loading></super-form>`);
|
|
232
|
+
expect(superForm.provision).toEqual(null);
|
|
233
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
234
|
+
"http://localhost:3000/api/submit",
|
|
235
|
+
expect.objectContaining({
|
|
236
|
+
method: "PUT",
|
|
237
|
+
credentials: "include",
|
|
238
|
+
headers: {
|
|
239
|
+
Accept: "application/json",
|
|
240
|
+
"Content-Type": "application/json",
|
|
241
|
+
},
|
|
242
|
+
body: expect.toSatisfy((body: string) => {
|
|
243
|
+
expect(JSON.parse(body)).toEqual(expect.objectContaining(formJson));
|
|
244
|
+
return true;
|
|
245
|
+
}),
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await waitForEvent(superForm, "super-form-error", () => {
|
|
251
|
+
submitForm(superForm);
|
|
252
|
+
});
|
|
253
|
+
KitLogger.unsuppress();
|
|
254
|
+
expect(superForm).dom.to.equalTag(`<super-form is-error></super-form>`);
|
|
255
|
+
expect(superForm.provision?.body).toEqual({ message: "something broke" });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("interpolates params from its own attributes and merges them with GET request attributes", async () => {
|
|
259
|
+
const superForm = fixture<HTMLSuperFormElement>(
|
|
260
|
+
`<super-form>${formHtml}</super-form>`,
|
|
261
|
+
);
|
|
262
|
+
const form = superForm.getFormElement()!;
|
|
263
|
+
// will override languages param
|
|
264
|
+
form.setAttribute("action", "/api/users/123?test=abc&languages=what");
|
|
265
|
+
form.setAttribute("method", "get");
|
|
266
|
+
|
|
267
|
+
const fetchSpy = spyFetch(fetchStubs.success as any);
|
|
268
|
+
|
|
269
|
+
superForm.addEventListener("super-form-loading", () => {
|
|
270
|
+
expect(superForm).dom.to.equalTag(`<super-form is-loading></super-form>`);
|
|
271
|
+
expect(superForm.provision).toEqual(null);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await waitForEvent(superForm, "super-form-success", () => {
|
|
275
|
+
submitForm(superForm);
|
|
276
|
+
});
|
|
277
|
+
expect(superForm).dom.to.equalTag(`<super-form is-success></super-form>`);
|
|
278
|
+
expect(superForm.provision?.body).toEqual({ results: [{ a: 1 }] });
|
|
279
|
+
const calledUrl = new URL(fetchSpy.mock.calls[0][0] as URL);
|
|
280
|
+
expect(calledUrl.pathname).toBe("/api/users/123");
|
|
281
|
+
expect(calledUrl.searchParams.get("test")).toBe("abc");
|
|
282
|
+
expect(calledUrl.searchParams.get("email")).toBe("j@doe.com");
|
|
283
|
+
expect(
|
|
284
|
+
JSON.parse(calledUrl.searchParams.get("languages") as string),
|
|
285
|
+
).toEqual(["english", "spanish"]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("aborts existing fetch when new one is triggered", async () => {
|
|
289
|
+
const superForm = fixture<HTMLSuperFormElement>(
|
|
290
|
+
`<super-form>${formHtml}</super-form>`,
|
|
291
|
+
);
|
|
292
|
+
// Small delay so the second submit can abort the first in-flight request
|
|
293
|
+
spyFetch(fetchStubs.success as any, 5);
|
|
294
|
+
|
|
295
|
+
let loadingCount = 0;
|
|
296
|
+
let numberOfSuccesses = 0;
|
|
297
|
+
let numberOfErrors = 0;
|
|
298
|
+
superForm.addEventListener("super-form-loading", () => {
|
|
299
|
+
loadingCount++;
|
|
300
|
+
expect(superForm).dom.to.equalTag(`<super-form is-loading></super-form>`);
|
|
301
|
+
});
|
|
302
|
+
superForm.addEventListener("super-form-success", () => {
|
|
303
|
+
numberOfSuccesses++;
|
|
304
|
+
});
|
|
305
|
+
superForm.addEventListener("super-form-error", () => {
|
|
306
|
+
numberOfErrors++;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const success = waitForEvent(superForm, "super-form-success");
|
|
310
|
+
submitForm(superForm);
|
|
311
|
+
submitForm(superForm);
|
|
312
|
+
await success;
|
|
313
|
+
|
|
314
|
+
expect(loadingCount).toBe(2);
|
|
315
|
+
expect(numberOfSuccesses).toBe(1);
|
|
316
|
+
expect(numberOfErrors).toBe(0);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|