@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.
Files changed (52) hide show
  1. package/.rush/temp/chunked-rush-logs/event-handler.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/event-handler.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/event-handler.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/event-handler.ts +178 -0
  16. package/index.css +5 -0
  17. package/index.ts +17 -0
  18. package/package.json +44 -0
  19. package/rush-logs/event-handler.apply-exports.cache.log +1 -0
  20. package/rush-logs/event-handler.apply-exports.log +1 -0
  21. package/rush-logs/event-handler.build_docs.cache.log +1 -0
  22. package/rush-logs/event-handler.build_docs.log +1 -0
  23. package/rush-logs/event-handler.build_package-metas.cache.log +1 -0
  24. package/rush-logs/event-handler.build_package-metas.log +1 -0
  25. package/src/event-handler.css +19 -0
  26. package/support/custom-elements.json +245 -0
  27. package/support/demos/command-dialog.html +9 -0
  28. package/support/demos/debounce.html +21 -0
  29. package/support/demos/enter-close.html +13 -0
  30. package/support/demos/escape-close.html +16 -0
  31. package/support/demos/fire-event.html +13 -0
  32. package/support/demos/prevent-default.html +3 -0
  33. package/support/demos/retarget.html +14 -0
  34. package/support/demos/with-as.html +8 -0
  35. package/support/dist-docs/event-handler.md +277 -0
  36. package/support/docs/INTERNAL.md +1 -0
  37. package/support/docs/README.md +97 -0
  38. package/support/package-meta.json +249 -0
  39. package/support/tests/__snapshots__/debounce.view.test.ts.snap +23 -0
  40. package/support/tests/__snapshots__/fire-event.view.test.ts.snap +23 -0
  41. package/support/tests/__snapshots__/retarget.view.test.ts.snap +23 -0
  42. package/support/tests/command-dialog.view.test.ts +34 -0
  43. package/support/tests/debounce.view.test.ts +43 -0
  44. package/support/tests/enter-close.view.test.ts +35 -0
  45. package/support/tests/escape-close.view.test.ts +44 -0
  46. package/support/tests/event-handler.form-fallback.test.ts +49 -0
  47. package/support/tests/event-handler.test.ts +524 -0
  48. package/support/tests/fire-event.view.test.ts +38 -0
  49. package/support/tests/prevent-default.view.test.ts +21 -0
  50. package/support/tests/retarget.view.test.ts +38 -0
  51. package/support/tests/with-as.view.test.ts +31 -0
  52. 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
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }