@excom/listenable-element 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/listenable-element.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/listenable-element.build_docs.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/listenable-element.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 +4 -0
- package/config/rig.json +6 -0
- package/index.ts +282 -0
- package/package.json +45 -0
- package/rush-logs/listenable-element.apply-exports.cache.log +1 -0
- package/rush-logs/listenable-element.apply-exports.log +1 -0
- package/rush-logs/listenable-element.build_docs.cache.log +1 -0
- package/rush-logs/listenable-element.build_docs.log +1 -0
- package/rush-logs/listenable-element.build_package-metas.cache.log +1 -0
- package/rush-logs/listenable-element.build_package-metas.log +1 -0
- package/src/index.css +12 -0
- package/support/custom-elements.json +332 -0
- package/support/dist-docs/listenable-element.md +122 -0
- package/support/docs/README.md +82 -0
- package/support/package-meta.json +148 -0
- package/support/tests/listenable-element.matrix.test.ts +300 -0
- package/support/tests/listenable-element.test.ts +455 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { ListenableElement } from "../../index";
|
|
2
|
+
import {
|
|
3
|
+
afterEach,
|
|
4
|
+
describe,
|
|
5
|
+
wait,
|
|
6
|
+
expect,
|
|
7
|
+
fixture,
|
|
8
|
+
it,
|
|
9
|
+
vi,
|
|
10
|
+
shouldHaveListeners,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
|
|
13
|
+
const TAG = "listenable-element-test";
|
|
14
|
+
if (!customElements.get(TAG)) {
|
|
15
|
+
ListenableElement.define(TAG);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("ListenableElement", () => {
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
document.body.innerHTML = "";
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("invokes actionHandler via handleEvent", () => {
|
|
25
|
+
const el = fixture<any>(`<${TAG}></${TAG}>`);
|
|
26
|
+
const handler = vi.fn();
|
|
27
|
+
el.actionHandler = handler;
|
|
28
|
+
el.handleEvent(new Event("click"));
|
|
29
|
+
expect(handler).toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("calls preventDefault when configured", () => {
|
|
33
|
+
const el = fixture<any>(`<${TAG} prevent-default></${TAG}>`);
|
|
34
|
+
el.actionHandler = vi.fn();
|
|
35
|
+
const event = new Event("click", { cancelable: true });
|
|
36
|
+
vi.spyOn(event, "preventDefault");
|
|
37
|
+
el.handleEvent(event);
|
|
38
|
+
expect(event.preventDefault).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("calls stopPropagation when configured", () => {
|
|
42
|
+
const el = fixture<any>(`<${TAG} stop-propagation></${TAG}>`);
|
|
43
|
+
el.actionHandler = vi.fn();
|
|
44
|
+
const event = new Event("click");
|
|
45
|
+
vi.spyOn(event, "stopPropagation");
|
|
46
|
+
el.handleEvent(event);
|
|
47
|
+
expect(event.stopPropagation).toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("calls stopImmediatePropagation when configured", () => {
|
|
51
|
+
const el = fixture<any>(`<${TAG} stop-immediate-propagation></${TAG}>`);
|
|
52
|
+
el.actionHandler = vi.fn();
|
|
53
|
+
const event = new Event("click");
|
|
54
|
+
vi.spyOn(event, "stopImmediatePropagation");
|
|
55
|
+
el.handleEvent(event);
|
|
56
|
+
expect(event.stopImmediatePropagation).toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("filters by keycode", () => {
|
|
60
|
+
const el = fixture<any>(`<${TAG} keycode-filter="enter"></${TAG}>`);
|
|
61
|
+
const handler = vi.fn();
|
|
62
|
+
el.actionHandler = handler;
|
|
63
|
+
|
|
64
|
+
el.handleEvent(new KeyboardEvent("keydown", { key: "Enter" }));
|
|
65
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
66
|
+
|
|
67
|
+
el.handleEvent(new KeyboardEvent("keydown", { key: "Escape" }));
|
|
68
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("filters by modifier+key chords (order-independent, space = OR)", () => {
|
|
72
|
+
const el = fixture<any>(`<${TAG} keycode-filter="k+shift tab"></${TAG}>`);
|
|
73
|
+
const handler = vi.fn();
|
|
74
|
+
el.actionHandler = handler;
|
|
75
|
+
|
|
76
|
+
el.handleEvent(new KeyboardEvent("keydown", { key: "k", shiftKey: true }));
|
|
77
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
78
|
+
|
|
79
|
+
el.handleEvent(new KeyboardEvent("keydown", { key: "k" }));
|
|
80
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
81
|
+
|
|
82
|
+
el.handleEvent(new KeyboardEvent("keydown", { key: "Tab" }));
|
|
83
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("filters by selector", () => {
|
|
87
|
+
const el = fixture<any>(
|
|
88
|
+
`<${TAG} selector-filter="button"><button></button><div></div></${TAG}>`,
|
|
89
|
+
);
|
|
90
|
+
const handler = vi.fn();
|
|
91
|
+
el.actionHandler = handler;
|
|
92
|
+
|
|
93
|
+
const button = el.querySelector("button")!;
|
|
94
|
+
const buttonEvent = new Event("click", { bubbles: true });
|
|
95
|
+
Object.defineProperty(buttonEvent, "target", { value: button });
|
|
96
|
+
el.handleEvent(buttonEvent);
|
|
97
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
98
|
+
|
|
99
|
+
const div = el.querySelector("div")!;
|
|
100
|
+
const divEvent = new Event("click", { bubbles: true });
|
|
101
|
+
Object.defineProperty(divEvent, "target", { value: div });
|
|
102
|
+
el.handleEvent(divEvent);
|
|
103
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("filters by pathname", () => {
|
|
107
|
+
const el = fixture<any>(`<${TAG} pathname-filter="/allowed"></${TAG}>`);
|
|
108
|
+
const handler = vi.fn();
|
|
109
|
+
el.actionHandler = handler;
|
|
110
|
+
el.handleEvent(new Event("click"));
|
|
111
|
+
expect(handler).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("handles lifecycle string events", () => {
|
|
115
|
+
const el = fixture<any>(`<${TAG}></${TAG}>`);
|
|
116
|
+
const handler = vi.fn();
|
|
117
|
+
el.actionHandler = handler;
|
|
118
|
+
el.handleEvent("connected");
|
|
119
|
+
expect(handler).toHaveBeenCalledWith("connected");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("does not preventDefault or stopPropagation on lifecycle events", () => {
|
|
123
|
+
const el = fixture<any>(
|
|
124
|
+
`<${TAG} prevent-default stop-propagation stop-immediate-propagation></${TAG}>`,
|
|
125
|
+
);
|
|
126
|
+
el.actionHandler = vi.fn();
|
|
127
|
+
el.handleEvent("connected");
|
|
128
|
+
expect(el.actionHandler).toHaveBeenCalledWith("connected");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("calls vibrate when vibrateMs is set", () => {
|
|
132
|
+
const el = fixture<any>(`<${TAG} vibrate-ms="50"></${TAG}>`);
|
|
133
|
+
el.actionHandler = vi.fn();
|
|
134
|
+
const vibrateSpy = vi.fn();
|
|
135
|
+
Object.defineProperty(navigator, "vibrate", {
|
|
136
|
+
value: vibrateSpy,
|
|
137
|
+
writable: true,
|
|
138
|
+
configurable: true,
|
|
139
|
+
});
|
|
140
|
+
el.handleEvent(new Event("click"));
|
|
141
|
+
expect(vibrateSpy).toHaveBeenCalledWith(50);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("uses default 20ms vibration when vibrateMs is 0", () => {
|
|
145
|
+
const el = fixture<any>(`<${TAG} vibrate-ms="0"></${TAG}>`);
|
|
146
|
+
el.actionHandler = vi.fn();
|
|
147
|
+
const vibrateSpy = vi.fn();
|
|
148
|
+
Object.defineProperty(navigator, "vibrate", {
|
|
149
|
+
value: vibrateSpy,
|
|
150
|
+
writable: true,
|
|
151
|
+
configurable: true,
|
|
152
|
+
});
|
|
153
|
+
el.handleEvent(new Event("click"));
|
|
154
|
+
expect(vibrateSpy).toHaveBeenCalledWith(20);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("delays action when delayMs is set", () => {
|
|
158
|
+
vi.useFakeTimers();
|
|
159
|
+
const el = fixture<any>(`<${TAG} delay-ms="100"></${TAG}>`);
|
|
160
|
+
const handler = vi.fn();
|
|
161
|
+
el.actionHandler = handler;
|
|
162
|
+
el.handleEvent(new Event("click"));
|
|
163
|
+
expect(handler).not.toHaveBeenCalled();
|
|
164
|
+
vi.advanceTimersByTime(100);
|
|
165
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
166
|
+
vi.useRealTimers();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("uses debounced handler when debouncedActionHandler and delayMs are set", () => {
|
|
170
|
+
const el = fixture<any>(`<${TAG} delay-ms="100"></${TAG}>`);
|
|
171
|
+
const handler = vi.fn();
|
|
172
|
+
el.actionHandler = handler;
|
|
173
|
+
const debouncedHandler = vi.fn();
|
|
174
|
+
el.debouncedActionHandler = debouncedHandler;
|
|
175
|
+
el.handleEvent(new Event("click"));
|
|
176
|
+
expect(debouncedHandler).toHaveBeenCalledTimes(1);
|
|
177
|
+
expect(handler).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("listens to events once", async () => {
|
|
181
|
+
const el = fixture<typeof ListenableElement.CustomElement>(
|
|
182
|
+
`<${TAG} listen-for="input-event" listen-once></${TAG}>`,
|
|
183
|
+
);
|
|
184
|
+
el.actionHandler = vi.fn();
|
|
185
|
+
el.dispatchEvent(new CustomEvent("input-event"));
|
|
186
|
+
el.dispatchEvent(new CustomEvent("input-event"));
|
|
187
|
+
expect(el.actionHandler).toHaveBeenCalledTimes(1);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("listens to lifecycles once", async () => {
|
|
191
|
+
const el = fixture<typeof ListenableElement.CustomElement>(
|
|
192
|
+
`<${TAG} listen-for-lifecycle="disconnected" listen-once></${TAG}>`,
|
|
193
|
+
);
|
|
194
|
+
el.actionHandler = vi.fn();
|
|
195
|
+
el.remove();
|
|
196
|
+
await wait(0);
|
|
197
|
+
expect(el.actionHandler).toHaveBeenCalledTimes(1);
|
|
198
|
+
expect(el.actionHandler).toHaveBeenCalledWith("disconnected");
|
|
199
|
+
expect(el._lifecycleTracker).toEqual(["disconnected"]);
|
|
200
|
+
document.body.appendChild(el);
|
|
201
|
+
el.remove();
|
|
202
|
+
await wait(0);
|
|
203
|
+
expect(el.actionHandler).toHaveBeenCalledTimes(1);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("listens on a CSS host-ref target", async () => {
|
|
207
|
+
const button = fixture<any>(`<button id="listen-host"></button>`);
|
|
208
|
+
expect(button).toContainListeners({ click: 0 });
|
|
209
|
+
const el = fixture<any>(
|
|
210
|
+
`<${TAG} host-ref="#listen-host" listen-for="click"></${TAG}>`,
|
|
211
|
+
);
|
|
212
|
+
expect(button).toContainListeners({ click: 1 });
|
|
213
|
+
const handler = vi.fn();
|
|
214
|
+
el.actionHandler = handler;
|
|
215
|
+
|
|
216
|
+
button.click();
|
|
217
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
218
|
+
el.click();
|
|
219
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
220
|
+
el.remove();
|
|
221
|
+
await wait(0);
|
|
222
|
+
expect(button).toContainListeners({ click: 0 });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("listens on window via host-ref for global key events", async () => {
|
|
226
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
227
|
+
const el = fixture<any>(
|
|
228
|
+
`<${TAG} host-ref="window" listen-for="keydown" keycode-filter="escape"></${TAG}>`,
|
|
229
|
+
);
|
|
230
|
+
expect(window).toContainListeners({ keydown: 1 });
|
|
231
|
+
const handler = vi.fn();
|
|
232
|
+
el.actionHandler = handler;
|
|
233
|
+
|
|
234
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape" }));
|
|
235
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
236
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
|
|
237
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
238
|
+
|
|
239
|
+
el.remove();
|
|
240
|
+
await wait(0);
|
|
241
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("listens on document via host-ref", async () => {
|
|
245
|
+
expect(document).toContainListeners({ "demo-doc-event": 0 });
|
|
246
|
+
const el = fixture<any>(
|
|
247
|
+
`<${TAG} host-ref="document" listen-for="demo-doc-event"></${TAG}>`,
|
|
248
|
+
);
|
|
249
|
+
expect(document).toContainListeners({ "demo-doc-event": 1 });
|
|
250
|
+
const handler = vi.fn();
|
|
251
|
+
el.actionHandler = handler;
|
|
252
|
+
|
|
253
|
+
document.dispatchEvent(new CustomEvent("demo-doc-event"));
|
|
254
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
255
|
+
el.dispatchEvent(new CustomEvent("demo-doc-event"));
|
|
256
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
257
|
+
|
|
258
|
+
el.remove();
|
|
259
|
+
await wait(0);
|
|
260
|
+
expect(document).toContainListeners({ "demo-doc-event": 0 });
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("cleans up previous host listeners when host-ref is cleared", async () => {
|
|
264
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
265
|
+
const el = fixture<any>(
|
|
266
|
+
`<${TAG} host-ref="window" listen-for="keydown"></${TAG}>`,
|
|
267
|
+
);
|
|
268
|
+
expect(window).toContainListeners({ keydown: 1 });
|
|
269
|
+
expect(el).toContainListeners({ keydown: 0 });
|
|
270
|
+
const handler = vi.fn();
|
|
271
|
+
el.actionHandler = handler;
|
|
272
|
+
|
|
273
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
274
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
275
|
+
|
|
276
|
+
el.removeAttribute("host-ref");
|
|
277
|
+
await wait(0);
|
|
278
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
279
|
+
expect(el).toContainListeners({ keydown: 1 });
|
|
280
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
281
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
282
|
+
|
|
283
|
+
// listen-for is unchanged, so the element itself is now the host
|
|
284
|
+
el.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
285
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("moves listeners when host-ref changes between hosts", async () => {
|
|
289
|
+
const button = fixture<any>(`<button id="host-a"></button>`);
|
|
290
|
+
expect(button).toContainListeners({ click: 0 });
|
|
291
|
+
expect(window).toContainListeners({ click: 0 });
|
|
292
|
+
|
|
293
|
+
const el = fixture<any>(
|
|
294
|
+
`<${TAG} host-ref="#host-a" listen-for="click"></${TAG}>`,
|
|
295
|
+
);
|
|
296
|
+
expect(button).toContainListeners({ click: 1 });
|
|
297
|
+
expect(window).toContainListeners({ click: 0 });
|
|
298
|
+
const handler = vi.fn();
|
|
299
|
+
el.actionHandler = handler;
|
|
300
|
+
|
|
301
|
+
button.dispatchEvent(new MouseEvent("click", { bubbles: false }));
|
|
302
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
303
|
+
|
|
304
|
+
el.hostRef = "window";
|
|
305
|
+
await wait(0);
|
|
306
|
+
expect(button).toContainListeners({ click: 0 });
|
|
307
|
+
expect(window).toContainListeners({ click: 1 });
|
|
308
|
+
|
|
309
|
+
// non-bubbling so a window listener can't be mistaken for the button's
|
|
310
|
+
button.dispatchEvent(new MouseEvent("click", { bubbles: false }));
|
|
311
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
312
|
+
window.dispatchEvent(new MouseEvent("click"));
|
|
313
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
314
|
+
|
|
315
|
+
Object.assign(el, {
|
|
316
|
+
hostRef: "#host-a",
|
|
317
|
+
listenFor: ["test-event", "b-event"],
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
button.remove();
|
|
321
|
+
await wait(0);
|
|
322
|
+
expect(button).toContainListeners({ 'test-event': 1, 'b-event': 1, click: 0 });
|
|
323
|
+
document.body.appendChild(button);
|
|
324
|
+
await wait(0);
|
|
325
|
+
|
|
326
|
+
expect(button).toContainListeners({ 'test-event': 1, 'b-event': 1, click: 0 });
|
|
327
|
+
expect(window).toContainListeners({ 'test-event': 0, 'b-event': 0, click: 0 });
|
|
328
|
+
button.dispatchEvent(new CustomEvent("test-event"));
|
|
329
|
+
expect(handler).toHaveBeenCalledTimes(3);
|
|
330
|
+
button.dispatchEvent(new CustomEvent("b-event"));
|
|
331
|
+
expect(handler).toHaveBeenCalledTimes(4);
|
|
332
|
+
|
|
333
|
+
Object.assign(el, {
|
|
334
|
+
hostRef: "window",
|
|
335
|
+
listenFor: ["test-event", "b-event"],
|
|
336
|
+
})
|
|
337
|
+
el.listenFor = ["test-event", "c-event"];
|
|
338
|
+
|
|
339
|
+
expect(button).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 0, click: 0 });
|
|
340
|
+
expect(window).toContainListeners({ 'test-event': 1, 'b-event': 0, 'c-event': 1, click: 0 });
|
|
341
|
+
window.dispatchEvent(new CustomEvent("test-event"));
|
|
342
|
+
expect(handler).toHaveBeenCalledTimes(5);
|
|
343
|
+
window.dispatchEvent(new CustomEvent("b-event"));
|
|
344
|
+
expect(handler).toHaveBeenCalledTimes(5);
|
|
345
|
+
button.dispatchEvent(new CustomEvent("test-event"));
|
|
346
|
+
button.dispatchEvent(new CustomEvent("c-event"));
|
|
347
|
+
window.dispatchEvent(new CustomEvent("c-event"));
|
|
348
|
+
expect(handler).toHaveBeenCalledTimes(6);
|
|
349
|
+
window.dispatchEvent(new CustomEvent("test-event"));
|
|
350
|
+
expect(handler).toHaveBeenCalledTimes(7);
|
|
351
|
+
|
|
352
|
+
el.remove();
|
|
353
|
+
await wait(0);
|
|
354
|
+
expect(button).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 0, click: 0 });
|
|
355
|
+
expect(window).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 0, click: 0 });
|
|
356
|
+
document.body.appendChild(el);
|
|
357
|
+
await wait(0);
|
|
358
|
+
expect(button).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 0, click: 0 });
|
|
359
|
+
expect(window).toContainListeners({ 'test-event': 1, 'b-event': 0, 'c-event': 1, click: 0 });
|
|
360
|
+
|
|
361
|
+
Object.assign(el, {
|
|
362
|
+
hostRef: null,
|
|
363
|
+
listenFor: ["c-event", "click"],
|
|
364
|
+
})
|
|
365
|
+
expect(button).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 0, click: 0 });
|
|
366
|
+
expect(window).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 0, click: 0 });
|
|
367
|
+
expect(el).toContainListeners({ 'test-event': 0, 'b-event': 0, 'c-event': 1, click: 1 });
|
|
368
|
+
button.dispatchEvent(new CustomEvent("click"));
|
|
369
|
+
window.dispatchEvent(new CustomEvent("click"));
|
|
370
|
+
el.dispatchEvent(new CustomEvent("click"));
|
|
371
|
+
expect(handler).toHaveBeenCalledTimes(8);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("cleans up host listeners when the element is disconnected", async () => {
|
|
375
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
376
|
+
const el = fixture<any>(
|
|
377
|
+
`<${TAG} host-ref="window" listen-for="keydown"></${TAG}>`,
|
|
378
|
+
);
|
|
379
|
+
expect(window).toContainListeners({ keydown: 1 });
|
|
380
|
+
const handler = vi.fn();
|
|
381
|
+
el.actionHandler = handler;
|
|
382
|
+
|
|
383
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
384
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
385
|
+
|
|
386
|
+
el.remove();
|
|
387
|
+
await wait(0);
|
|
388
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
389
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
390
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("switches listen-for events on a host-ref", async () => {
|
|
394
|
+
expect(window).toContainListeners({ keydown: 0, keyup: 0 });
|
|
395
|
+
const el = fixture<any>(
|
|
396
|
+
`<${TAG} host-ref="window" listen-for="keydown"></${TAG}>`,
|
|
397
|
+
);
|
|
398
|
+
expect(window).toContainListeners({ keydown: 1, keyup: 0 });
|
|
399
|
+
const handler = vi.fn();
|
|
400
|
+
el.actionHandler = handler;
|
|
401
|
+
|
|
402
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
403
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
404
|
+
|
|
405
|
+
el.listenFor = ["keyup"];
|
|
406
|
+
await wait(0);
|
|
407
|
+
expect(window).toContainListeners({ keydown: 0, keyup: 1 });
|
|
408
|
+
|
|
409
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
410
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
411
|
+
window.dispatchEvent(new KeyboardEvent("keyup", { key: "a" }));
|
|
412
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
413
|
+
|
|
414
|
+
el.remove();
|
|
415
|
+
await wait(0);
|
|
416
|
+
expect(window).toContainListeners({ keydown: 0, keyup: 0 });
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("listen-once on a host-ref fires only once", async () => {
|
|
420
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
421
|
+
const el = fixture<any>(
|
|
422
|
+
`<${TAG} host-ref="window" listen-for="keydown" listen-once></${TAG}>`,
|
|
423
|
+
);
|
|
424
|
+
expect(window).toContainListeners({ keydown: 1 });
|
|
425
|
+
const handler = vi.fn();
|
|
426
|
+
el.actionHandler = handler;
|
|
427
|
+
|
|
428
|
+
expect(window).toContainListeners({ keydown: 1 });
|
|
429
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
430
|
+
window.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
431
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
432
|
+
// this doesn't work. would need to shim the listener, if once: true, remove this from the collection. It works natively
|
|
433
|
+
// expect(window).toContainListeners({ keydown: 0 });
|
|
434
|
+
|
|
435
|
+
el.hostRef = null;
|
|
436
|
+
expect(el).toContainListeners({ keydown: 1 });
|
|
437
|
+
el.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
438
|
+
el.dispatchEvent(new KeyboardEvent("keydown", { key: "a" }));
|
|
439
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
440
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
441
|
+
// expect(el).toContainListeners({ keydown: 0 });
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
el.remove();
|
|
445
|
+
await wait(0);
|
|
446
|
+
expect(window).toContainListeners({ keydown: 0 });
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("throws when host-ref matches nothing", () => {
|
|
450
|
+
const el = fixture<any>(`<${TAG}></${TAG}>`);
|
|
451
|
+
expect(() => {
|
|
452
|
+
el.hostRef = "#does-not-exist";
|
|
453
|
+
}).toThrow(/Host element not found/);
|
|
454
|
+
});
|
|
455
|
+
});
|