@excom/event-handler 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/event-handler.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/event-handler.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/event-handler.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/event-handler.ts +178 -0
- package/index.css +5 -0
- package/index.ts +17 -0
- package/package.json +44 -0
- package/rush-logs/event-handler.apply-exports.cache.log +1 -0
- package/rush-logs/event-handler.apply-exports.log +1 -0
- package/rush-logs/event-handler.build_docs.cache.log +1 -0
- package/rush-logs/event-handler.build_docs.log +1 -0
- package/rush-logs/event-handler.build_package-metas.cache.log +1 -0
- package/rush-logs/event-handler.build_package-metas.log +1 -0
- package/src/event-handler.css +19 -0
- package/support/custom-elements.json +245 -0
- package/support/demos/command-dialog.html +9 -0
- package/support/demos/debounce.html +21 -0
- package/support/demos/enter-close.html +13 -0
- package/support/demos/escape-close.html +16 -0
- package/support/demos/fire-event.html +13 -0
- package/support/demos/prevent-default.html +3 -0
- package/support/demos/retarget.html +14 -0
- package/support/demos/with-as.html +8 -0
- package/support/dist-docs/event-handler.md +277 -0
- package/support/docs/INTERNAL.md +1 -0
- package/support/docs/README.md +97 -0
- package/support/package-meta.json +249 -0
- package/support/tests/__snapshots__/debounce.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/fire-event.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/retarget.view.test.ts.snap +23 -0
- package/support/tests/command-dialog.view.test.ts +34 -0
- package/support/tests/debounce.view.test.ts +43 -0
- package/support/tests/enter-close.view.test.ts +35 -0
- package/support/tests/escape-close.view.test.ts +44 -0
- package/support/tests/event-handler.form-fallback.test.ts +49 -0
- package/support/tests/event-handler.test.ts +524 -0
- package/support/tests/fire-event.view.test.ts +38 -0
- package/support/tests/prevent-default.view.test.ts +21 -0
- package/support/tests/retarget.view.test.ts +38 -0
- package/support/tests/with-as.view.test.ts +31 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
fixture,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
wait,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
import "../../index";
|
|
11
|
+
|
|
12
|
+
if (!navigator.vibrate) {
|
|
13
|
+
// @ts-ignore shim
|
|
14
|
+
navigator.vibrate = () => {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("event-handler", () => {
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.innerHTML = "";
|
|
20
|
+
vi.restoreAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handlers event properly", async () => {
|
|
24
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
25
|
+
`<p></p>
|
|
26
|
+
<event-handler fire-event="test-event">
|
|
27
|
+
<section></section>
|
|
28
|
+
<aside></aside>
|
|
29
|
+
<div></div>
|
|
30
|
+
</event-handler>
|
|
31
|
+
<span></span>`,
|
|
32
|
+
);
|
|
33
|
+
// fixture returns first element (p), grab event-handler as next sibling
|
|
34
|
+
const p = eventHandler;
|
|
35
|
+
const handler = p.nextElementSibling as HTMLEventHandlerElement;
|
|
36
|
+
const cb = vi.fn((e) => {
|
|
37
|
+
expect(e.type).toBe("test-event");
|
|
38
|
+
});
|
|
39
|
+
document.body.addEventListener("test-event", cb);
|
|
40
|
+
handler.dispatchEvent(new Event("click"));
|
|
41
|
+
expect(cb).toHaveBeenCalled();
|
|
42
|
+
|
|
43
|
+
// replace click with custom event
|
|
44
|
+
handler.listenFor = ["foo-custom-event"];
|
|
45
|
+
handler.dispatchEvent(new Event("click"));
|
|
46
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
47
|
+
handler.dispatchEvent(new CustomEvent("foo-custom-event"));
|
|
48
|
+
expect(cb).toHaveBeenCalledTimes(2);
|
|
49
|
+
|
|
50
|
+
// replace click with custom event
|
|
51
|
+
handler.listenFor = ["bar-custom-event", "baz-custom-event"];
|
|
52
|
+
handler.dispatchEvent(new Event("click"));
|
|
53
|
+
expect(cb).toHaveBeenCalledTimes(2);
|
|
54
|
+
handler.dispatchEvent(new CustomEvent("foo-custom-event"));
|
|
55
|
+
expect(cb).toHaveBeenCalledTimes(2);
|
|
56
|
+
handler.dispatchEvent(new CustomEvent("bar-custom-event"));
|
|
57
|
+
expect(cb).toHaveBeenCalledTimes(3);
|
|
58
|
+
handler.dispatchEvent(new CustomEvent("baz-custom-event"));
|
|
59
|
+
expect(cb).toHaveBeenCalledTimes(4);
|
|
60
|
+
|
|
61
|
+
// target previous
|
|
62
|
+
document.body.removeEventListener("test-event", cb);
|
|
63
|
+
p.addEventListener("test-event", cb);
|
|
64
|
+
handler.targetRef = "p:has(+:scope)";
|
|
65
|
+
handler.dispatchEvent(new CustomEvent("baz-custom-event"));
|
|
66
|
+
expect(cb).toHaveBeenCalledTimes(5);
|
|
67
|
+
|
|
68
|
+
// target span
|
|
69
|
+
const spanTag = handler.nextElementSibling!;
|
|
70
|
+
spanTag.addEventListener("test-event", cb);
|
|
71
|
+
handler.targetRef = "span";
|
|
72
|
+
handler.dispatchEvent(new CustomEvent("baz-custom-event"));
|
|
73
|
+
expect(cb).toHaveBeenCalledTimes(6);
|
|
74
|
+
|
|
75
|
+
// turn off bubbling
|
|
76
|
+
spanTag.removeEventListener("test-event", cb);
|
|
77
|
+
document.body.addEventListener("test-event", cb);
|
|
78
|
+
handler.notBubbles = true;
|
|
79
|
+
handler.dispatchEvent(new CustomEvent("baz-custom-event"));
|
|
80
|
+
expect(cb).toHaveBeenCalledTimes(6);
|
|
81
|
+
handler.notBubbles = false;
|
|
82
|
+
|
|
83
|
+
// turn off cancelable
|
|
84
|
+
handler.notCancelable = true;
|
|
85
|
+
handler.dispatchEvent(new CustomEvent("baz-custom-event"));
|
|
86
|
+
expect(cb).toHaveBeenCalledTimes(7);
|
|
87
|
+
expect(cb.mock.calls[6][0].cancelable).toBe(false);
|
|
88
|
+
handler.notCancelable = false;
|
|
89
|
+
|
|
90
|
+
// turn off composed
|
|
91
|
+
handler.notComposed = true;
|
|
92
|
+
handler.dispatchEvent(new CustomEvent("baz-custom-event"));
|
|
93
|
+
expect(cb).toHaveBeenCalledTimes(8);
|
|
94
|
+
expect(cb.mock.calls[7][0].composed).toBe(false);
|
|
95
|
+
handler.notComposed = false;
|
|
96
|
+
|
|
97
|
+
// test keycode filter
|
|
98
|
+
handler.listenFor = ["keydown"];
|
|
99
|
+
handler.keycodeFilter = ["a", "b"];
|
|
100
|
+
handler.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
101
|
+
expect(cb).toHaveBeenCalledTimes(9);
|
|
102
|
+
handler.dispatchEvent(new KeyboardEvent("keydown", { key: "c" }));
|
|
103
|
+
expect(cb).toHaveBeenCalledTimes(9);
|
|
104
|
+
|
|
105
|
+
// test element filter
|
|
106
|
+
handler.listenFor = ["foo-event"];
|
|
107
|
+
handler.keycodeFilter = [];
|
|
108
|
+
handler.selectorFilter = "section,aside";
|
|
109
|
+
const section = handler.querySelector("section")!;
|
|
110
|
+
const aside = handler.querySelector("aside")!;
|
|
111
|
+
const div = handler.querySelector("div")!;
|
|
112
|
+
section.dispatchEvent(new CustomEvent("foo-event", { bubbles: true }));
|
|
113
|
+
expect(cb).toHaveBeenCalledTimes(10);
|
|
114
|
+
aside.dispatchEvent(new CustomEvent("foo-event", { bubbles: true }));
|
|
115
|
+
expect(cb).toHaveBeenCalledTimes(11);
|
|
116
|
+
div.dispatchEvent(new CustomEvent("foo-event", { bubbles: true }));
|
|
117
|
+
expect(cb).toHaveBeenCalledTimes(11);
|
|
118
|
+
handler.selectorFilter = "";
|
|
119
|
+
|
|
120
|
+
// prevents default and stops propagation
|
|
121
|
+
handler.preventDefault = true;
|
|
122
|
+
const event1 = new CustomEvent("foo-event", { bubbles: true });
|
|
123
|
+
event1.preventDefault = vi.fn();
|
|
124
|
+
event1.stopPropagation = vi.fn();
|
|
125
|
+
handler.dispatchEvent(event1);
|
|
126
|
+
expect(event1.preventDefault).toHaveBeenCalled();
|
|
127
|
+
handler.stopPropagation = true;
|
|
128
|
+
handler.dispatchEvent(event1);
|
|
129
|
+
expect(event1.stopPropagation).toHaveBeenCalled();
|
|
130
|
+
handler.stopPropagation = false;
|
|
131
|
+
handler.preventDefault = false;
|
|
132
|
+
|
|
133
|
+
// test vibrate
|
|
134
|
+
handler.vibrateMs = 100;
|
|
135
|
+
const vibrateSpy = vi.spyOn(navigator, "vibrate");
|
|
136
|
+
handler.dispatchEvent(new CustomEvent("foo-event", { bubbles: true }));
|
|
137
|
+
expect(vibrateSpy).toHaveBeenCalledWith(100);
|
|
138
|
+
handler.vibrateMs = 0;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("includes detail from attrs", () => {
|
|
142
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
143
|
+
`<event-handler
|
|
144
|
+
listen-for="my-event"
|
|
145
|
+
fire-event="test-event"
|
|
146
|
+
detail-foo="bar"
|
|
147
|
+
detail-baz="qux">
|
|
148
|
+
</event-handler>`,
|
|
149
|
+
);
|
|
150
|
+
const cb = vi.fn((e) => {
|
|
151
|
+
expect(e.type).toBe("test-event");
|
|
152
|
+
});
|
|
153
|
+
document.body.addEventListener("test-event", cb);
|
|
154
|
+
|
|
155
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
156
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
157
|
+
expect(cb.mock.calls[0][0].detail).toEqual({
|
|
158
|
+
foo: "bar",
|
|
159
|
+
baz: "qux",
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("includes detail from form and attrs", () => {
|
|
164
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
165
|
+
`<event-handler
|
|
166
|
+
listen-for="my-event"
|
|
167
|
+
fire-event="test-event"
|
|
168
|
+
detail-foo="bar"
|
|
169
|
+
detail-baz="qux"
|
|
170
|
+
form-ref="form">
|
|
171
|
+
<form>
|
|
172
|
+
<input name="detail.myProp" value="abc" type="text">
|
|
173
|
+
<input name="detail.baz" value="nope" type="text">
|
|
174
|
+
<input name="bubbles" type="checkbox">
|
|
175
|
+
</form>
|
|
176
|
+
</event-handler>`,
|
|
177
|
+
);
|
|
178
|
+
const cb = vi.fn((e) => {
|
|
179
|
+
expect(e.type).toBe("test-event");
|
|
180
|
+
});
|
|
181
|
+
eventHandler.addEventListener("test-event", cb);
|
|
182
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
183
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
184
|
+
expect(cb.mock.calls[0][0].bubbles).toBe(false);
|
|
185
|
+
expect(cb.mock.calls[0][0].detail).toEqual({
|
|
186
|
+
foo: "bar",
|
|
187
|
+
myProp: "abc",
|
|
188
|
+
baz: "qux",
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("fires the event when form-ref matches nothing", () => {
|
|
193
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
194
|
+
`<event-handler
|
|
195
|
+
listen-for="my-event"
|
|
196
|
+
fire-event="test-event"
|
|
197
|
+
detail-foo="bar"
|
|
198
|
+
form-ref="#no-such-form">
|
|
199
|
+
</event-handler>`,
|
|
200
|
+
);
|
|
201
|
+
const cb = vi.fn();
|
|
202
|
+
document.body.addEventListener("test-event", cb);
|
|
203
|
+
expect(() =>
|
|
204
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event")),
|
|
205
|
+
).not.toThrow();
|
|
206
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
207
|
+
// only the detail-* attributes, no form data
|
|
208
|
+
expect(cb.mock.calls[0][0].detail).toEqual({ foo: "bar" });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("fires the event when form-ref matches a non-form element", () => {
|
|
212
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
213
|
+
`<event-handler
|
|
214
|
+
listen-for="my-event"
|
|
215
|
+
fire-event="test-event"
|
|
216
|
+
detail-foo="bar"
|
|
217
|
+
form-ref="#not-a-form">
|
|
218
|
+
<div id="not-a-form">
|
|
219
|
+
<input name="detail.myProp" value="abc" type="text">
|
|
220
|
+
</div>
|
|
221
|
+
</event-handler>`,
|
|
222
|
+
);
|
|
223
|
+
const cb = vi.fn();
|
|
224
|
+
document.body.addEventListener("test-event", cb);
|
|
225
|
+
expect(() =>
|
|
226
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event")),
|
|
227
|
+
).not.toThrow();
|
|
228
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(cb.mock.calls[0][0].detail).toEqual({ foo: "bar" });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("mutates target from attrs and form", () => {
|
|
233
|
+
fixture<HTMLDivElement>(
|
|
234
|
+
`<div id="div-to-mutate"></div>
|
|
235
|
+
<event-handler
|
|
236
|
+
mutate-target
|
|
237
|
+
target-ref="#div-to-mutate"
|
|
238
|
+
listen-for="my-event"
|
|
239
|
+
attr-foo="bar"
|
|
240
|
+
attr-baz="qux"
|
|
241
|
+
form-ref="form">
|
|
242
|
+
<form>
|
|
243
|
+
<input name="my-attr" value="abc" type="text">
|
|
244
|
+
<input name="baz" value="nope" type="text">
|
|
245
|
+
</form>
|
|
246
|
+
</event-handler>`,
|
|
247
|
+
);
|
|
248
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
249
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
250
|
+
wait(1);
|
|
251
|
+
const div = document.querySelector("#div-to-mutate")!;
|
|
252
|
+
expect(div.getAttribute("foo")).toBe("bar");
|
|
253
|
+
expect(div.getAttribute("my-attr")).toBe("abc");
|
|
254
|
+
expect(div.getAttribute("baz")).toBe("qux");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("mutates target from attrs only when no form-ref is set", () => {
|
|
258
|
+
fixture<HTMLDivElement>(
|
|
259
|
+
`<div id="mutate-attrs-only"></div>
|
|
260
|
+
<event-handler
|
|
261
|
+
mutate-target
|
|
262
|
+
target-ref="#mutate-attrs-only"
|
|
263
|
+
listen-for="my-event"
|
|
264
|
+
attr-data-state="open">
|
|
265
|
+
</event-handler>`,
|
|
266
|
+
);
|
|
267
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
268
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
269
|
+
const div = document.querySelector("#mutate-attrs-only")!;
|
|
270
|
+
expect(div.getAttribute("data-state")).toBe("open");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("converts typed form fields into attributes on the target", () => {
|
|
274
|
+
fixture<HTMLDivElement>(
|
|
275
|
+
`<div id="mutate-typed" is-closed="" data-count="0"></div>
|
|
276
|
+
<event-handler
|
|
277
|
+
mutate-target
|
|
278
|
+
target-ref="#mutate-typed"
|
|
279
|
+
listen-for="my-event"
|
|
280
|
+
form-ref="form">
|
|
281
|
+
<form>
|
|
282
|
+
<input name="data-count" value="3" type="number">
|
|
283
|
+
<input name="is-open" type="checkbox" checked>
|
|
284
|
+
<input name="is-closed" type="checkbox">
|
|
285
|
+
<input name="data-tag" type="checkbox" value="alpha" checked>
|
|
286
|
+
</form>
|
|
287
|
+
</event-handler>`,
|
|
288
|
+
);
|
|
289
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
290
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
291
|
+
const div = document.querySelector("#mutate-typed")!;
|
|
292
|
+
// number → string attribute
|
|
293
|
+
expect(div.getAttribute("data-count")).toBe("3");
|
|
294
|
+
// true → boolean attribute present
|
|
295
|
+
expect(div.getAttribute("is-open")).toBe("");
|
|
296
|
+
// false → attribute removed
|
|
297
|
+
expect(div.hasAttribute("is-closed")).toBe(false);
|
|
298
|
+
// valued checkbox → list, written verbatim
|
|
299
|
+
expect(div.getAttribute("data-tag")).toBe("alpha");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("mutates from attr-* alone when form-ref matches nothing", () => {
|
|
303
|
+
fixture<HTMLDivElement>(
|
|
304
|
+
`<div id="mutate-missing-form"></div>
|
|
305
|
+
<event-handler
|
|
306
|
+
mutate-target
|
|
307
|
+
target-ref="#mutate-missing-form"
|
|
308
|
+
listen-for="my-event"
|
|
309
|
+
form-ref="#no-such-form"
|
|
310
|
+
attr-data-state="open">
|
|
311
|
+
</event-handler>`,
|
|
312
|
+
);
|
|
313
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
314
|
+
expect(() =>
|
|
315
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event")),
|
|
316
|
+
).not.toThrow();
|
|
317
|
+
const div = document.querySelector("#mutate-missing-form")!;
|
|
318
|
+
expect(div.getAttribute("data-state")).toBe("open");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("mutates from attr-* alone when form-ref matches a non-form element", () => {
|
|
322
|
+
fixture<HTMLDivElement>(
|
|
323
|
+
`<div id="mutate-non-form"></div>
|
|
324
|
+
<event-handler
|
|
325
|
+
mutate-target
|
|
326
|
+
target-ref="#mutate-non-form"
|
|
327
|
+
listen-for="my-event"
|
|
328
|
+
form-ref="#not-a-form"
|
|
329
|
+
attr-data-state="open">
|
|
330
|
+
<div id="not-a-form">
|
|
331
|
+
<input name="data-ignored" value="x" type="text">
|
|
332
|
+
</div>
|
|
333
|
+
</event-handler>`,
|
|
334
|
+
);
|
|
335
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
336
|
+
expect(() =>
|
|
337
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event")),
|
|
338
|
+
).not.toThrow();
|
|
339
|
+
const div = document.querySelector("#mutate-non-form")!;
|
|
340
|
+
expect(div.getAttribute("data-state")).toBe("open");
|
|
341
|
+
expect(div.hasAttribute("data-ignored")).toBe(false);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("does not mutate itself or a missing target", () => {
|
|
345
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
346
|
+
`<event-handler
|
|
347
|
+
mutate-target
|
|
348
|
+
listen-for="my-event"
|
|
349
|
+
fire-event="test-event"
|
|
350
|
+
attr-data-state="open">
|
|
351
|
+
</event-handler>`,
|
|
352
|
+
);
|
|
353
|
+
const cb = vi.fn();
|
|
354
|
+
document.body.addEventListener("test-event", cb);
|
|
355
|
+
|
|
356
|
+
// no target-ref → the target is the element itself → no mutation, but
|
|
357
|
+
// fire-event still runs
|
|
358
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
359
|
+
expect(eventHandler.hasAttribute("data-state")).toBe(false);
|
|
360
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
361
|
+
|
|
362
|
+
// unresolved target-ref → nothing to mutate; the event falls back to
|
|
363
|
+
// dispatching from the element itself
|
|
364
|
+
eventHandler.targetRef = "#does-not-exist";
|
|
365
|
+
expect(() =>
|
|
366
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event")),
|
|
367
|
+
).not.toThrow();
|
|
368
|
+
expect(eventHandler.hasAttribute("data-state")).toBe(false);
|
|
369
|
+
expect(cb).toHaveBeenCalledTimes(2);
|
|
370
|
+
expect(cb.mock.calls[1][0].target).toBe(eventHandler);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("does nothing without fire-event, command-name or mutate-target", () => {
|
|
374
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
375
|
+
`<event-handler listen-for="my-event" detail-foo="bar"></event-handler>`,
|
|
376
|
+
);
|
|
377
|
+
const cb = vi.fn();
|
|
378
|
+
document.body.addEventListener("test-event", cb);
|
|
379
|
+
expect(() =>
|
|
380
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event")),
|
|
381
|
+
).not.toThrow();
|
|
382
|
+
expect(cb).not.toHaveBeenCalled();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("invokes built-in command-names on the target through a proxy button", () => {
|
|
386
|
+
const invoked: Array<{ command: string; target: Element }> = [];
|
|
387
|
+
Object.defineProperty(HTMLButtonElement.prototype, "commandForElement", {
|
|
388
|
+
configurable: true,
|
|
389
|
+
writable: true,
|
|
390
|
+
value: null,
|
|
391
|
+
});
|
|
392
|
+
vi.spyOn(HTMLButtonElement.prototype, "click").mockImplementation(
|
|
393
|
+
function (this: any) {
|
|
394
|
+
invoked.push({ command: this.command, target: this.commandForElement });
|
|
395
|
+
},
|
|
396
|
+
);
|
|
397
|
+
try {
|
|
398
|
+
fixture<HTMLDialogElement>(
|
|
399
|
+
`<dialog id="command-target"></dialog>
|
|
400
|
+
<event-handler
|
|
401
|
+
listen-for="my-event"
|
|
402
|
+
target-ref="#command-target"
|
|
403
|
+
command-name="show-modal close">
|
|
404
|
+
</event-handler>`,
|
|
405
|
+
);
|
|
406
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
407
|
+
const dialog = document.querySelector("#command-target")!;
|
|
408
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
409
|
+
expect(invoked).toEqual([
|
|
410
|
+
{ command: "show-modal", target: dialog },
|
|
411
|
+
{ command: "close", target: dialog },
|
|
412
|
+
]);
|
|
413
|
+
// the proxy button is removed again
|
|
414
|
+
expect(document.querySelector("button")).toBeNull();
|
|
415
|
+
} finally {
|
|
416
|
+
delete (HTMLButtonElement.prototype as any).commandForElement;
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("dispatches custom --commands at the target with itself as source", () => {
|
|
421
|
+
fixture<HTMLDivElement>(
|
|
422
|
+
`<div>
|
|
423
|
+
<section id="command-custom"></section>
|
|
424
|
+
<event-handler listen-for="my-event" target-ref="#command-custom" command-name="--open --close"></event-handler>
|
|
425
|
+
</div>`,
|
|
426
|
+
);
|
|
427
|
+
const eventHandler = document.querySelector("event-handler")!;
|
|
428
|
+
const section = document.querySelector("#command-custom")!;
|
|
429
|
+
const seen: Array<[string, Element | null, boolean]> = [];
|
|
430
|
+
section.addEventListener("command", (e: any) =>
|
|
431
|
+
seen.push([e.command, e.source, e.bubbles]),
|
|
432
|
+
);
|
|
433
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
434
|
+
expect(seen).toEqual([
|
|
435
|
+
["--open", eventHandler, false],
|
|
436
|
+
["--close", eventHandler, false],
|
|
437
|
+
]);
|
|
438
|
+
expect(document.querySelector("button")).toBeNull();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("fires several events with the same detail", () => {
|
|
442
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
443
|
+
`<event-handler
|
|
444
|
+
listen-for="my-event"
|
|
445
|
+
fire-event="first-event second-event"
|
|
446
|
+
detail-foo="bar">
|
|
447
|
+
</event-handler>`,
|
|
448
|
+
);
|
|
449
|
+
const seen: string[] = [];
|
|
450
|
+
const cb = vi.fn((e: CustomEvent) => {
|
|
451
|
+
seen.push(e.type);
|
|
452
|
+
expect(e.detail).toEqual({ foo: "bar" });
|
|
453
|
+
});
|
|
454
|
+
document.body.addEventListener("first-event", cb);
|
|
455
|
+
document.body.addEventListener("second-event", cb);
|
|
456
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
457
|
+
expect(seen).toEqual(["first-event", "second-event"]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("delays events", async () => {
|
|
461
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
462
|
+
`<event-handler
|
|
463
|
+
listen-for="my-event"
|
|
464
|
+
fire-event="test-event"
|
|
465
|
+
delay-ms="100">
|
|
466
|
+
</event-handler>`,
|
|
467
|
+
);
|
|
468
|
+
const cb = vi.fn((e) => {
|
|
469
|
+
expect(e.type).toBe("test-event");
|
|
470
|
+
});
|
|
471
|
+
document.body.addEventListener("test-event", cb);
|
|
472
|
+
|
|
473
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
474
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
475
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
476
|
+
expect(cb).toHaveBeenCalledTimes(0);
|
|
477
|
+
await wait(105);
|
|
478
|
+
expect(cb).toHaveBeenCalledTimes(3);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("debounces", async () => {
|
|
482
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
483
|
+
`<event-handler
|
|
484
|
+
listen-for="my-event"
|
|
485
|
+
fire-event="test-event"
|
|
486
|
+
delay-ms="100"
|
|
487
|
+
is-debounced>
|
|
488
|
+
</event-handler>`,
|
|
489
|
+
);
|
|
490
|
+
const cb = vi.fn((e) => {
|
|
491
|
+
expect(e.type).toBe("test-event");
|
|
492
|
+
});
|
|
493
|
+
document.body.addEventListener("test-event", cb);
|
|
494
|
+
|
|
495
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
496
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
497
|
+
eventHandler.dispatchEvent(new CustomEvent("my-event"));
|
|
498
|
+
expect(cb).toHaveBeenCalledTimes(0);
|
|
499
|
+
await wait(105);
|
|
500
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("handlers on configured lifecycles", async () => {
|
|
504
|
+
const cb = vi.fn((e) => {
|
|
505
|
+
expect(e.type).toBe("test-event");
|
|
506
|
+
});
|
|
507
|
+
document.body.addEventListener("test-event", cb);
|
|
508
|
+
const eventHandler = fixture<HTMLEventHandlerElement>(
|
|
509
|
+
`<event-handler
|
|
510
|
+
listen-for="click"
|
|
511
|
+
listen-for-lifecycle="connected disconnected"
|
|
512
|
+
fire-event="test-event">
|
|
513
|
+
</event-handler>`,
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
517
|
+
eventHandler.dispatchEvent(new Event("click"));
|
|
518
|
+
expect(cb).toHaveBeenCalledTimes(2);
|
|
519
|
+
eventHandler.addEventListener("test-event", cb);
|
|
520
|
+
eventHandler.remove();
|
|
521
|
+
await wait(0);
|
|
522
|
+
expect(cb).toHaveBeenCalledTimes(3);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "@excom/quark-sheet";
|
|
2
|
+
import "../../index";
|
|
3
|
+
import {
|
|
4
|
+
afterEach,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
import {
|
|
11
|
+
click,
|
|
12
|
+
expectComplexity,
|
|
13
|
+
flush,
|
|
14
|
+
installDemoModules,
|
|
15
|
+
measureComplexity,
|
|
16
|
+
mountView,
|
|
17
|
+
readDemo,
|
|
18
|
+
restoreDemoModules,
|
|
19
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
20
|
+
|
|
21
|
+
describe("fire-event view", () => {
|
|
22
|
+
beforeEach(() => installDemoModules());
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.body.innerHTML = "";
|
|
25
|
+
restoreDemoModules();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("writes cart-add detail to output", async () => {
|
|
29
|
+
const { root, quark } = await mountView(readDemo(import.meta.url, "fire-event"));
|
|
30
|
+
const meter = measureComplexity(quark!);
|
|
31
|
+
click(root.querySelector("event-handler"));
|
|
32
|
+
await flush();
|
|
33
|
+
const budget = meter.take();
|
|
34
|
+
meter.stop();
|
|
35
|
+
expect(root.querySelector("output")?.textContent).toMatch(/sku-1/);
|
|
36
|
+
expectComplexity(budget);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
8
|
+
import { mountView, readDemo } from "@excom/quark/support/tests/view-helpers";
|
|
9
|
+
|
|
10
|
+
describe("prevent-default view", () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.innerHTML = "";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("cancels the native link click", async () => {
|
|
16
|
+
const { root } = await mountView(readDemo(import.meta.url, "prevent-default"));
|
|
17
|
+
const ev = new MouseEvent("click", { bubbles: true, cancelable: true });
|
|
18
|
+
root.querySelector("a")!.dispatchEvent(ev);
|
|
19
|
+
expect(ev.defaultPrevented).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "@excom/quark-sheet";
|
|
2
|
+
import "../../index";
|
|
3
|
+
import {
|
|
4
|
+
afterEach,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
it,
|
|
9
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
10
|
+
import {
|
|
11
|
+
click,
|
|
12
|
+
expectComplexity,
|
|
13
|
+
flush,
|
|
14
|
+
installDemoModules,
|
|
15
|
+
measureComplexity,
|
|
16
|
+
mountView,
|
|
17
|
+
readDemo,
|
|
18
|
+
restoreDemoModules,
|
|
19
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
20
|
+
|
|
21
|
+
describe("retarget view", () => {
|
|
22
|
+
beforeEach(() => installDemoModules());
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.body.innerHTML = "";
|
|
25
|
+
restoreDemoModules();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("pings the previous output", async () => {
|
|
29
|
+
const { root, quark } = await mountView(readDemo(import.meta.url, "retarget"));
|
|
30
|
+
const meter = measureComplexity(quark!);
|
|
31
|
+
click(root.querySelector("event-handler"));
|
|
32
|
+
await flush();
|
|
33
|
+
const budget = meter.take();
|
|
34
|
+
meter.stop();
|
|
35
|
+
expect(root.querySelector("output")?.textContent).toMatch(/pong/);
|
|
36
|
+
expectComplexity(budget);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "@excom/content-drawer";
|
|
2
|
+
import "../../index";
|
|
3
|
+
import {
|
|
4
|
+
afterEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
it,
|
|
8
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
9
|
+
import {
|
|
10
|
+
click,
|
|
11
|
+
flush,
|
|
12
|
+
mountView,
|
|
13
|
+
readDemo,
|
|
14
|
+
} from "@excom/quark/support/tests/view-helpers";
|
|
15
|
+
|
|
16
|
+
describe("with-as view", () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
document.body.innerHTML = "";
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("toggles the drawer", async () => {
|
|
22
|
+
const { root } = await mountView(readDemo(import.meta.url, "with-as"));
|
|
23
|
+
const drawer = root.querySelector("content-drawer")!;
|
|
24
|
+
click(root.querySelector("event-handler"));
|
|
25
|
+
await flush();
|
|
26
|
+
expect(drawer.hasAttribute("is-open")).toBe(true);
|
|
27
|
+
click(root.querySelector("event-handler"));
|
|
28
|
+
await flush();
|
|
29
|
+
expect(drawer.hasAttribute("is-open")).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|