@remote-dom/polyfill 1.5.1 → 1.6.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/CHANGELOG.md +88 -0
- package/README.md +51 -3
- package/build/cjs/Attr.cjs +33 -4
- package/build/cjs/CharacterData.cjs +10 -0
- package/build/cjs/ChildNode.cjs +79 -13
- package/build/cjs/Comment.cjs +1 -1
- package/build/cjs/CustomElementRegistry.cjs +27 -2
- package/build/cjs/CustomEvent.cjs +2 -2
- package/build/cjs/Document.cjs +56 -32
- package/build/cjs/DocumentFragment.cjs +6 -2
- package/build/cjs/Element.cjs +68 -8
- package/build/cjs/Event.cjs +29 -27
- package/build/cjs/EventTarget.cjs +128 -58
- package/build/cjs/HTMLTemplateElement.cjs +1 -0
- package/build/cjs/MutationObserver.cjs +167 -1
- package/build/cjs/NamedNodeMap.cjs +82 -45
- package/build/cjs/Node.cjs +47 -11
- package/build/cjs/NodeList.cjs +9 -1
- package/build/cjs/ParentNode.cjs +319 -77
- package/build/cjs/SVGElement.cjs +1 -1
- package/build/cjs/Text.cjs +1 -1
- package/build/cjs/Window.cjs +131 -38
- package/build/cjs/attribute-reactions.cjs +20 -0
- package/build/cjs/constants.cjs +48 -24
- package/build/cjs/custom-element-reactions.cjs +52 -0
- package/build/cjs/dom-exception.cjs +15 -0
- package/build/cjs/hook-effects.cjs +51 -0
- package/build/cjs/names.cjs +61 -0
- package/build/cjs/selectors.cjs +213 -122
- package/build/cjs/serialization.cjs +168 -65
- package/build/cjs/shared.cjs +177 -28
- package/build/esm/Attr.mjs +34 -5
- package/build/esm/CharacterData.mjs +10 -0
- package/build/esm/ChildNode.mjs +76 -15
- package/build/esm/Comment.mjs +2 -2
- package/build/esm/CustomElementRegistry.mjs +27 -2
- package/build/esm/CustomEvent.mjs +2 -2
- package/build/esm/Document.mjs +60 -35
- package/build/esm/DocumentFragment.mjs +7 -3
- package/build/esm/Element.mjs +69 -9
- package/build/esm/Event.mjs +27 -27
- package/build/esm/EventTarget.mjs +130 -60
- package/build/esm/HTMLTemplateElement.mjs +2 -1
- package/build/esm/MutationObserver.mjs +166 -2
- package/build/esm/NamedNodeMap.mjs +83 -46
- package/build/esm/Node.mjs +48 -12
- package/build/esm/NodeList.mjs +9 -1
- package/build/esm/ParentNode.mjs +322 -81
- package/build/esm/SVGElement.mjs +2 -2
- package/build/esm/Text.mjs +2 -2
- package/build/esm/Window.mjs +132 -39
- package/build/esm/attribute-reactions.mjs +16 -0
- package/build/esm/constants.mjs +28 -23
- package/build/esm/custom-element-reactions.mjs +47 -0
- package/build/esm/dom-exception.mjs +11 -0
- package/build/esm/hook-effects.mjs +46 -0
- package/build/esm/names.mjs +54 -0
- package/build/esm/selectors.mjs +202 -124
- package/build/esm/serialization.mjs +169 -66
- package/build/esm/shared.mjs +173 -30
- package/build/esnext/Attr.esnext +45 -5
- package/build/esnext/CharacterData.esnext +10 -0
- package/build/esnext/ChildNode.esnext +84 -15
- package/build/esnext/Comment.esnext +2 -2
- package/build/esnext/CustomElementRegistry.esnext +42 -0
- package/build/esnext/CustomEvent.esnext +2 -2
- package/build/esnext/Document.esnext +69 -32
- package/build/esnext/DocumentFragment.esnext +6 -2
- package/build/esnext/Element.esnext +78 -9
- package/build/esnext/Event.esnext +36 -37
- package/build/esnext/EventTarget.esnext +133 -61
- package/build/esnext/HTMLTemplateElement.esnext +3 -1
- package/build/esnext/MutationObserver.esnext +163 -2
- package/build/esnext/NamedNodeMap.esnext +110 -42
- package/build/esnext/Node.esnext +50 -12
- package/build/esnext/NodeList.esnext +1 -1
- package/build/esnext/ParentNode.esnext +352 -79
- package/build/esnext/SVGElement.esnext +2 -2
- package/build/esnext/Text.esnext +2 -2
- package/build/esnext/Window.esnext +120 -16
- package/build/esnext/attribute-reactions.esnext +21 -0
- package/build/esnext/constants.esnext +34 -21
- package/build/esnext/custom-element-reactions.esnext +49 -0
- package/build/esnext/dom-exception.esnext +11 -0
- package/build/esnext/hook-effects.esnext +46 -0
- package/build/esnext/names.esnext +61 -0
- package/build/esnext/selectors.esnext +202 -100
- package/build/esnext/serialization.esnext +171 -48
- package/build/esnext/shared.esnext +181 -34
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typescript/Attr.d.ts +6 -4
- package/build/typescript/Attr.d.ts.map +1 -1
- package/build/typescript/CharacterData.d.ts.map +1 -1
- package/build/typescript/ChildNode.d.ts +5 -0
- package/build/typescript/ChildNode.d.ts.map +1 -1
- package/build/typescript/ClipboardEvent.d.ts.map +1 -1
- package/build/typescript/Comment.d.ts +2 -2
- package/build/typescript/Comment.d.ts.map +1 -1
- package/build/typescript/CustomElementRegistry.d.ts +1 -0
- package/build/typescript/CustomElementRegistry.d.ts.map +1 -1
- package/build/typescript/CustomEvent.d.ts.map +1 -1
- package/build/typescript/Document.d.ts +10 -5
- package/build/typescript/Document.d.ts.map +1 -1
- package/build/typescript/DocumentFragment.d.ts +2 -1
- package/build/typescript/DocumentFragment.d.ts.map +1 -1
- package/build/typescript/Element.d.ts +16 -7
- package/build/typescript/Element.d.ts.map +1 -1
- package/build/typescript/ErrorEvent.d.ts.map +1 -1
- package/build/typescript/Event.d.ts +9 -10
- package/build/typescript/Event.d.ts.map +1 -1
- package/build/typescript/EventTarget.d.ts +20 -3
- package/build/typescript/EventTarget.d.ts.map +1 -1
- package/build/typescript/FocusEvent.d.ts.map +1 -1
- package/build/typescript/HTMLTemplateElement.d.ts.map +1 -1
- package/build/typescript/MutationObserver.d.ts +42 -0
- package/build/typescript/MutationObserver.d.ts.map +1 -1
- package/build/typescript/NamedNodeMap.d.ts +5 -3
- package/build/typescript/NamedNodeMap.d.ts.map +1 -1
- package/build/typescript/Node.d.ts +4 -3
- package/build/typescript/Node.d.ts.map +1 -1
- package/build/typescript/NodeList.d.ts +3 -2
- package/build/typescript/NodeList.d.ts.map +1 -1
- package/build/typescript/ParentNode.d.ts +29 -10
- package/build/typescript/ParentNode.d.ts.map +1 -1
- package/build/typescript/PromiseRejectionEvent.d.ts.map +1 -1
- package/build/typescript/SVGElement.d.ts +1 -1
- package/build/typescript/SVGElement.d.ts.map +1 -1
- package/build/typescript/Text.d.ts +1 -1
- package/build/typescript/Text.d.ts.map +1 -1
- package/build/typescript/ToggleEvent.d.ts.map +1 -1
- package/build/typescript/Window.d.ts +5 -1
- package/build/typescript/Window.d.ts.map +1 -1
- package/build/typescript/attribute-reactions.d.ts +4 -0
- package/build/typescript/attribute-reactions.d.ts.map +1 -0
- package/build/typescript/constants.d.ts +27 -18
- package/build/typescript/constants.d.ts.map +1 -1
- package/build/typescript/custom-element-reactions.d.ts +6 -0
- package/build/typescript/custom-element-reactions.d.ts.map +1 -0
- package/build/typescript/dom-exception.d.ts +2 -0
- package/build/typescript/dom-exception.d.ts.map +1 -0
- package/build/typescript/extensions.d.ts +4 -0
- package/build/typescript/extensions.d.ts.map +1 -0
- package/build/typescript/hook-effects.d.ts +6 -0
- package/build/typescript/hook-effects.d.ts.map +1 -0
- package/build/typescript/hooks.d.ts +3 -3
- package/build/typescript/hooks.d.ts.map +1 -1
- package/build/typescript/index.d.ts +1 -0
- package/build/typescript/index.d.ts.map +1 -1
- package/build/typescript/names.d.ts +11 -0
- package/build/typescript/names.d.ts.map +1 -0
- package/build/typescript/selectors.d.ts +21 -22
- package/build/typescript/selectors.d.ts.map +1 -1
- package/build/typescript/serialization.d.ts.map +1 -1
- package/build/typescript/shared.d.ts +10 -0
- package/build/typescript/shared.d.ts.map +1 -1
- package/build/typescript/tests/ChildNode.test.d.ts +2 -0
- package/build/typescript/tests/ChildNode.test.d.ts.map +1 -0
- package/build/typescript/tests/MutationObserver.test.d.ts +2 -0
- package/build/typescript/tests/MutationObserver.test.d.ts.map +1 -0
- package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts +2 -0
- package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts.map +1 -0
- package/build/typescript/tests/attr-mutation.test.d.ts +2 -0
- package/build/typescript/tests/attr-mutation.test.d.ts.map +1 -0
- package/build/typescript/tests/child-node-before-after.test.d.ts +2 -0
- package/build/typescript/tests/child-node-before-after.test.d.ts.map +1 -0
- package/build/typescript/tests/child-node-replace-with.test.d.ts +2 -0
- package/build/typescript/tests/child-node-replace-with.test.d.ts.map +1 -0
- package/build/typescript/tests/clone-import-namespaces.test.d.ts +2 -0
- package/build/typescript/tests/clone-import-namespaces.test.d.ts.map +1 -0
- package/build/typescript/tests/constants.test.d.ts +2 -0
- package/build/typescript/tests/constants.test.d.ts.map +1 -0
- package/build/typescript/tests/create-element-namespaces.test.d.ts +2 -0
- package/build/typescript/tests/create-element-namespaces.test.d.ts.map +1 -0
- package/build/typescript/tests/create-text-node.test.d.ts +2 -0
- package/build/typescript/tests/create-text-node.test.d.ts.map +1 -0
- package/build/typescript/tests/custom-element-reactions.test.d.ts +2 -0
- package/build/typescript/tests/custom-element-reactions.test.d.ts.map +1 -0
- package/build/typescript/tests/custom-element-registry.test.d.ts +2 -0
- package/build/typescript/tests/custom-element-registry.test.d.ts.map +1 -0
- package/build/typescript/tests/custom-event.test.d.ts +2 -0
- package/build/typescript/tests/custom-event.test.d.ts.map +1 -0
- package/build/typescript/tests/document-clone-import-contract.test.d.ts +2 -0
- package/build/typescript/tests/document-clone-import-contract.test.d.ts.map +1 -0
- package/build/typescript/tests/document.test.d.ts +2 -0
- package/build/typescript/tests/document.test.d.ts.map +1 -0
- package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts +2 -0
- package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts.map +1 -0
- package/build/typescript/tests/event-dispatch.test.d.ts +2 -0
- package/build/typescript/tests/event-dispatch.test.d.ts.map +1 -0
- package/build/typescript/tests/event-target.test.d.ts +2 -0
- package/build/typescript/tests/event-target.test.d.ts.map +1 -0
- package/build/typescript/tests/event.test.d.ts +2 -0
- package/build/typescript/tests/event.test.d.ts.map +1 -0
- package/build/typescript/tests/extensions.test.d.ts +2 -0
- package/build/typescript/tests/extensions.test.d.ts.map +1 -0
- package/build/typescript/tests/get-elements-by-tag-name.test.d.ts +2 -0
- package/build/typescript/tests/get-elements-by-tag-name.test.d.ts.map +1 -0
- package/build/typescript/tests/getElementsByClassName.test.d.ts +2 -0
- package/build/typescript/tests/getElementsByClassName.test.d.ts.map +1 -0
- package/build/typescript/tests/hooks.test.d.ts +2 -0
- package/build/typescript/tests/hooks.test.d.ts.map +1 -0
- package/build/typescript/tests/inner-html-parsing.test.d.ts +2 -0
- package/build/typescript/tests/inner-html-parsing.test.d.ts.map +1 -0
- package/build/typescript/tests/is-default-namespace.test.d.ts +2 -0
- package/build/typescript/tests/is-default-namespace.test.d.ts.map +1 -0
- package/build/typescript/tests/name-and-namespace-normalization.test.d.ts +2 -0
- package/build/typescript/tests/name-and-namespace-normalization.test.d.ts.map +1 -0
- package/build/typescript/tests/named-node-map.test.d.ts +2 -0
- package/build/typescript/tests/named-node-map.test.d.ts.map +1 -0
- package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts +2 -0
- package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts.map +1 -0
- package/build/typescript/tests/node.test.d.ts +2 -0
- package/build/typescript/tests/node.test.d.ts.map +1 -0
- package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts +2 -0
- package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts.map +1 -0
- package/build/typescript/tests/parent-node-mutation-validation.test.d.ts +2 -0
- package/build/typescript/tests/parent-node-mutation-validation.test.d.ts.map +1 -0
- package/build/typescript/tests/parent-node.test.d.ts +2 -0
- package/build/typescript/tests/parent-node.test.d.ts.map +1 -0
- package/build/typescript/tests/serialization-template.test.d.ts +2 -0
- package/build/typescript/tests/serialization-template.test.d.ts.map +1 -0
- package/build/typescript/tests/slot-attribute.test.d.ts +2 -0
- package/build/typescript/tests/slot-attribute.test.d.ts.map +1 -0
- package/build/typescript/tests/text-content.test.d.ts +2 -0
- package/build/typescript/tests/text-content.test.d.ts.map +1 -0
- package/build/typescript/tests/toggle-attribute.test.d.ts +2 -0
- package/build/typescript/tests/toggle-attribute.test.d.ts.map +1 -0
- package/build/typescript/tests/wide-tree-traversal.test.d.ts +2 -0
- package/build/typescript/tests/wide-tree-traversal.test.d.ts.map +1 -0
- package/build/typescript/tests/window-set-global.test.d.ts +2 -0
- package/build/typescript/tests/window-set-global.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/source/Attr.ts +60 -8
- package/source/CharacterData.ts +13 -0
- package/source/ChildNode.ts +107 -14
- package/source/Comment.ts +2 -2
- package/source/CustomElementRegistry.ts +56 -0
- package/source/CustomEvent.ts +2 -2
- package/source/Document.ts +104 -38
- package/source/DocumentFragment.ts +12 -2
- package/source/Element.ts +129 -14
- package/source/Event.ts +34 -32
- package/source/EventTarget.ts +203 -79
- package/source/HTMLTemplateElement.ts +2 -1
- package/source/MutationObserver.ts +279 -1
- package/source/NamedNodeMap.ts +141 -60
- package/source/Node.ts +70 -14
- package/source/NodeList.ts +13 -3
- package/source/ParentNode.ts +500 -88
- package/source/SVGElement.ts +2 -2
- package/source/Text.ts +2 -2
- package/source/Window.ts +156 -17
- package/source/attribute-reactions.ts +32 -0
- package/source/constants.ts +40 -17
- package/source/custom-element-reactions.ts +64 -0
- package/source/dom-exception.ts +14 -0
- package/source/extensions.ts +4 -0
- package/source/hook-effects.ts +56 -0
- package/source/hooks.ts +11 -3
- package/source/index.ts +1 -0
- package/source/names.ts +94 -0
- package/source/selectors.ts +301 -138
- package/source/serialization.ts +224 -66
- package/source/shared.ts +269 -40
- package/source/tests/ChildNode.test.ts +178 -0
- package/source/tests/MutationObserver.test.ts +303 -0
- package/source/tests/adopt-inserted-subtrees.test.ts +584 -0
- package/source/tests/attr-mutation.test.ts +112 -0
- package/source/tests/child-node-before-after.test.ts +421 -0
- package/source/tests/child-node-replace-with.test.ts +251 -0
- package/source/tests/clone-import-namespaces.test.ts +365 -0
- package/source/tests/constants.test.ts +46 -0
- package/source/tests/create-element-namespaces.test.ts +142 -0
- package/source/tests/create-text-node.test.ts +38 -0
- package/source/tests/custom-element-reactions.test.ts +600 -0
- package/source/tests/custom-element-registry.test.ts +137 -0
- package/source/tests/custom-event.test.ts +26 -0
- package/source/tests/document-clone-import-contract.test.ts +77 -0
- package/source/tests/document.test.ts +122 -0
- package/source/tests/dom-mutation-selector-errors.test.ts +484 -0
- package/source/tests/event-dispatch.test.ts +270 -0
- package/source/tests/event-target.test.ts +208 -0
- package/source/tests/event.test.ts +98 -0
- package/source/tests/extensions.test.ts +159 -0
- package/source/tests/get-elements-by-tag-name.test.ts +79 -0
- package/source/tests/getElementsByClassName.test.ts +109 -0
- package/source/tests/hooks.test.ts +43 -0
- package/source/tests/inner-html-parsing.test.ts +461 -0
- package/source/tests/is-default-namespace.test.ts +111 -0
- package/source/tests/name-and-namespace-normalization.test.ts +319 -0
- package/source/tests/named-node-map.test.ts +122 -0
- package/source/tests/namespaced-attribute-reactions.test.ts +141 -0
- package/source/tests/node.test.ts +56 -0
- package/source/tests/parent-node-mutation-return-values.test.ts +63 -0
- package/source/tests/parent-node-mutation-validation.test.ts +362 -0
- package/source/tests/parent-node.test.ts +169 -0
- package/source/tests/selectors.test.ts +533 -22
- package/source/tests/serialization-template.test.ts +232 -0
- package/source/tests/slot-attribute.test.ts +133 -0
- package/source/tests/text-content.test.ts +440 -0
- package/source/tests/toggle-attribute.test.ts +113 -0
- package/source/tests/wide-tree-traversal.test.ts +699 -0
- package/source/tests/window-set-global.test.ts +321 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {Event} from '../Event.ts';
|
|
4
|
+
import {EventTarget} from '../EventTarget.ts';
|
|
5
|
+
import {Window} from '../Window.ts';
|
|
6
|
+
|
|
7
|
+
const PRE_DISPATCH_STOP_FORMS = [
|
|
8
|
+
['stopPropagation()', (event: Event) => event.stopPropagation()],
|
|
9
|
+
[
|
|
10
|
+
'stopImmediatePropagation()',
|
|
11
|
+
(event: Event) => event.stopImmediatePropagation(),
|
|
12
|
+
],
|
|
13
|
+
['cancelBubble = true', (event: Event) => (event.cancelBubble = true)],
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
describe('EventTarget dispatch lifecycle', () => {
|
|
17
|
+
it('resets stopped propagation before redispatching an event', () => {
|
|
18
|
+
const target = new EventTarget();
|
|
19
|
+
const listener = vi.fn();
|
|
20
|
+
const event = new Event('change');
|
|
21
|
+
|
|
22
|
+
target.addEventListener('change', (event) => event.stopPropagation(), {
|
|
23
|
+
capture: true,
|
|
24
|
+
once: true,
|
|
25
|
+
});
|
|
26
|
+
target.addEventListener('change', listener);
|
|
27
|
+
|
|
28
|
+
target.dispatchEvent(event);
|
|
29
|
+
expect(listener).not.toHaveBeenCalled();
|
|
30
|
+
|
|
31
|
+
target.dispatchEvent(event);
|
|
32
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('resets stopped immediate propagation before redispatching an event', () => {
|
|
36
|
+
const target = new EventTarget();
|
|
37
|
+
const listener = vi.fn();
|
|
38
|
+
const event = new Event('change');
|
|
39
|
+
|
|
40
|
+
target.addEventListener(
|
|
41
|
+
'change',
|
|
42
|
+
(event) => event.stopImmediatePropagation(),
|
|
43
|
+
{once: true},
|
|
44
|
+
);
|
|
45
|
+
target.addEventListener('change', listener);
|
|
46
|
+
|
|
47
|
+
target.dispatchEvent(event);
|
|
48
|
+
expect(listener).not.toHaveBeenCalled();
|
|
49
|
+
|
|
50
|
+
target.dispatchEvent(event);
|
|
51
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe.each(PRE_DISPATCH_STOP_FORMS)(
|
|
55
|
+
'an event stopped with %s before dispatch',
|
|
56
|
+
(_name, stop) => {
|
|
57
|
+
it.each([false, true])(
|
|
58
|
+
'skips capture=%s listeners and resets propagation state',
|
|
59
|
+
(capture) => {
|
|
60
|
+
const target = new EventTarget();
|
|
61
|
+
const listener = vi.fn();
|
|
62
|
+
const event = new Event('change');
|
|
63
|
+
|
|
64
|
+
target.addEventListener('change', listener, {capture});
|
|
65
|
+
stop(event);
|
|
66
|
+
|
|
67
|
+
expect(target.dispatchEvent(event)).toBe(true);
|
|
68
|
+
expect(listener).not.toHaveBeenCalled();
|
|
69
|
+
expect(event.cancelBubble).toBe(false);
|
|
70
|
+
|
|
71
|
+
expect(target.dispatchEvent(event)).toBe(true);
|
|
72
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
it('rejects in-flight redispatch and permits redispatch after completion', () => {
|
|
79
|
+
const target = new EventTarget();
|
|
80
|
+
const event = new Event('change');
|
|
81
|
+
let redispatchError: unknown;
|
|
82
|
+
|
|
83
|
+
target.addEventListener(
|
|
84
|
+
'change',
|
|
85
|
+
() => {
|
|
86
|
+
try {
|
|
87
|
+
target.dispatchEvent(event);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
redispatchError = error;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{once: true},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(target.dispatchEvent(event)).toBe(true);
|
|
96
|
+
expect(redispatchError).toBeInstanceOf(DOMException);
|
|
97
|
+
expect((redispatchError as DOMException).name).toBe('InvalidStateError');
|
|
98
|
+
expect(target.dispatchEvent(event)).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('names in-flight redispatch errors without a global DOMException', () => {
|
|
102
|
+
const target = new EventTarget();
|
|
103
|
+
const event = new Event('change');
|
|
104
|
+
let redispatchError: unknown;
|
|
105
|
+
|
|
106
|
+
vi.stubGlobal('DOMException', undefined);
|
|
107
|
+
try {
|
|
108
|
+
target.addEventListener(
|
|
109
|
+
'change',
|
|
110
|
+
() => {
|
|
111
|
+
try {
|
|
112
|
+
target.dispatchEvent(event);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
redispatchError = error;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{once: true},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
target.dispatchEvent(event);
|
|
121
|
+
} finally {
|
|
122
|
+
vi.unstubAllGlobals();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
expect(redispatchError).toBeInstanceOf(Error);
|
|
126
|
+
expect(redispatchError).toMatchObject({
|
|
127
|
+
name: 'InvalidStateError',
|
|
128
|
+
message: expect.stringContaining('already being dispatched'),
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('defers listeners added during dispatch until the next dispatch', () => {
|
|
133
|
+
const target = new EventTarget();
|
|
134
|
+
const addedListener = vi.fn();
|
|
135
|
+
|
|
136
|
+
target.addEventListener('change', () => {
|
|
137
|
+
target.addEventListener('change', addedListener);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
target.dispatchEvent(new Event('change'));
|
|
141
|
+
expect(addedListener).not.toHaveBeenCalled();
|
|
142
|
+
|
|
143
|
+
target.dispatchEvent(new Event('change'));
|
|
144
|
+
expect(addedListener).toHaveBeenCalledOnce();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('defers a function listener removed and re-added during dispatch', () => {
|
|
148
|
+
const target = new EventTarget();
|
|
149
|
+
const listener = vi.fn();
|
|
150
|
+
let firstDispatch = true;
|
|
151
|
+
|
|
152
|
+
target.addEventListener('change', () => {
|
|
153
|
+
if (!firstDispatch) return;
|
|
154
|
+
firstDispatch = false;
|
|
155
|
+
target.removeEventListener('change', listener);
|
|
156
|
+
target.addEventListener('change', listener);
|
|
157
|
+
});
|
|
158
|
+
target.addEventListener('change', listener);
|
|
159
|
+
|
|
160
|
+
target.dispatchEvent(new Event('change'));
|
|
161
|
+
expect(listener).not.toHaveBeenCalled();
|
|
162
|
+
|
|
163
|
+
target.dispatchEvent(new Event('change'));
|
|
164
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('defers a listener object removed and re-added during dispatch', () => {
|
|
168
|
+
const target = new EventTarget();
|
|
169
|
+
const listener = {handleEvent: vi.fn()};
|
|
170
|
+
let firstDispatch = true;
|
|
171
|
+
|
|
172
|
+
target.addEventListener('change', () => {
|
|
173
|
+
if (!firstDispatch) return;
|
|
174
|
+
firstDispatch = false;
|
|
175
|
+
target.removeEventListener('change', listener);
|
|
176
|
+
target.addEventListener('change', listener);
|
|
177
|
+
});
|
|
178
|
+
target.addEventListener('change', listener);
|
|
179
|
+
|
|
180
|
+
target.dispatchEvent(new Event('change'));
|
|
181
|
+
expect(listener.handleEvent).not.toHaveBeenCalled();
|
|
182
|
+
|
|
183
|
+
target.dispatchEvent(new Event('change'));
|
|
184
|
+
expect(listener.handleEvent).toHaveBeenCalledOnce();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('skips a snapshotted listener whose signal is aborted during dispatch', () => {
|
|
188
|
+
const target = new EventTarget();
|
|
189
|
+
const listener = vi.fn();
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
|
|
192
|
+
target.addEventListener('change', () => controller.abort());
|
|
193
|
+
target.addEventListener('change', listener, {signal: controller.signal});
|
|
194
|
+
|
|
195
|
+
target.dispatchEvent(new Event('change'));
|
|
196
|
+
|
|
197
|
+
expect(listener).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('returns defensive copies that cannot corrupt event traversal', () => {
|
|
201
|
+
const window = new Window();
|
|
202
|
+
const parent = window.document.createElement('div');
|
|
203
|
+
const target = window.document.createElement('button');
|
|
204
|
+
const targetListener = vi.fn((event: globalThis.Event) =>
|
|
205
|
+
event.composedPath(),
|
|
206
|
+
);
|
|
207
|
+
parent.appendChild(target);
|
|
208
|
+
|
|
209
|
+
parent.addEventListener(
|
|
210
|
+
'change',
|
|
211
|
+
(event) => {
|
|
212
|
+
event.composedPath().length = 0;
|
|
213
|
+
},
|
|
214
|
+
{capture: true},
|
|
215
|
+
);
|
|
216
|
+
target.addEventListener('change', targetListener);
|
|
217
|
+
|
|
218
|
+
target.dispatchEvent(new Event('change', {bubbles: true}));
|
|
219
|
+
|
|
220
|
+
expect(targetListener).toHaveBeenCalledOnce();
|
|
221
|
+
expect(targetListener.mock.results[0]!.value).toEqual([target, parent]);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('skips snapshotted listeners removed during dispatch', () => {
|
|
225
|
+
const target = new EventTarget();
|
|
226
|
+
const removedListener = vi.fn();
|
|
227
|
+
|
|
228
|
+
target.addEventListener('change', () => {
|
|
229
|
+
target.removeEventListener('change', removedListener);
|
|
230
|
+
});
|
|
231
|
+
target.addEventListener('change', removedListener);
|
|
232
|
+
|
|
233
|
+
target.dispatchEvent(new Event('change'));
|
|
234
|
+
|
|
235
|
+
expect(removedListener).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('normalizes transient state after dispatch', () => {
|
|
239
|
+
const target = new EventTarget();
|
|
240
|
+
const event = new Event('change');
|
|
241
|
+
let stateDuringDispatch:
|
|
242
|
+
| {
|
|
243
|
+
eventPhase: number;
|
|
244
|
+
currentTarget: globalThis.EventTarget | null;
|
|
245
|
+
path: globalThis.EventTarget[];
|
|
246
|
+
}
|
|
247
|
+
| undefined;
|
|
248
|
+
|
|
249
|
+
target.addEventListener('change', (event) => {
|
|
250
|
+
stateDuringDispatch = {
|
|
251
|
+
eventPhase: event.eventPhase,
|
|
252
|
+
currentTarget: event.currentTarget,
|
|
253
|
+
path: event.composedPath(),
|
|
254
|
+
};
|
|
255
|
+
event.stopPropagation();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
target.dispatchEvent(event);
|
|
259
|
+
|
|
260
|
+
expect(stateDuringDispatch).toEqual({
|
|
261
|
+
eventPhase: Event.AT_TARGET,
|
|
262
|
+
currentTarget: target,
|
|
263
|
+
path: [target],
|
|
264
|
+
});
|
|
265
|
+
expect(event.eventPhase).toBe(Event.NONE);
|
|
266
|
+
expect(event.currentTarget).toBeNull();
|
|
267
|
+
expect(event.composedPath()).toEqual([]);
|
|
268
|
+
expect(event.cancelBubble).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {Event} from '../Event.ts';
|
|
4
|
+
import {EventTarget} from '../EventTarget.ts';
|
|
5
|
+
|
|
6
|
+
describe('EventTarget listener registration', () => {
|
|
7
|
+
it('deduplicates listeners by type, callback, and capture', () => {
|
|
8
|
+
const target = new EventTarget();
|
|
9
|
+
const listener = vi.fn();
|
|
10
|
+
|
|
11
|
+
target.addEventListener('change', listener);
|
|
12
|
+
target.addEventListener('change', listener, false);
|
|
13
|
+
target.addEventListener('change', listener, {capture: false});
|
|
14
|
+
|
|
15
|
+
target.dispatchEvent(new Event('change'));
|
|
16
|
+
|
|
17
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('treats capture and non-capture registrations as distinct', () => {
|
|
21
|
+
const target = new EventTarget();
|
|
22
|
+
const listener = vi.fn();
|
|
23
|
+
|
|
24
|
+
target.addEventListener('change', listener);
|
|
25
|
+
target.addEventListener('change', listener, true);
|
|
26
|
+
target.removeEventListener('change', listener, false);
|
|
27
|
+
|
|
28
|
+
target.dispatchEvent(new Event('change'));
|
|
29
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
30
|
+
|
|
31
|
+
target.removeEventListener('change', listener, true);
|
|
32
|
+
target.dispatchEvent(new Event('change'));
|
|
33
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps colliding event names in separate capture lists', () => {
|
|
37
|
+
const target = new EventTarget();
|
|
38
|
+
const nameListener = vi.fn();
|
|
39
|
+
const nameWithMarkerListener = vi.fn();
|
|
40
|
+
|
|
41
|
+
target.addEventListener('name', nameListener, true);
|
|
42
|
+
target.addEventListener('name@', nameWithMarkerListener);
|
|
43
|
+
|
|
44
|
+
target.dispatchEvent(new Event('name'));
|
|
45
|
+
expect(nameListener).toHaveBeenCalledOnce();
|
|
46
|
+
expect(nameWithMarkerListener).not.toHaveBeenCalled();
|
|
47
|
+
|
|
48
|
+
target.dispatchEvent(new Event('name@'));
|
|
49
|
+
expect(nameListener).toHaveBeenCalledOnce();
|
|
50
|
+
expect(nameWithMarkerListener).toHaveBeenCalledOnce();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('removes colliding event names by type and capture', () => {
|
|
54
|
+
const target = new EventTarget();
|
|
55
|
+
const listener = vi.fn();
|
|
56
|
+
|
|
57
|
+
target.addEventListener('name', listener, true);
|
|
58
|
+
target.addEventListener('name@', listener);
|
|
59
|
+
|
|
60
|
+
target.removeEventListener('name', listener, false);
|
|
61
|
+
target.removeEventListener('name@', listener, true);
|
|
62
|
+
target.dispatchEvent(new Event('name'));
|
|
63
|
+
target.dispatchEvent(new Event('name@'));
|
|
64
|
+
expect(listener).toHaveBeenCalledTimes(2);
|
|
65
|
+
|
|
66
|
+
listener.mockClear();
|
|
67
|
+
target.removeEventListener('name', listener, true);
|
|
68
|
+
target.dispatchEvent(new Event('name@'));
|
|
69
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
70
|
+
|
|
71
|
+
target.removeEventListener('name@', listener);
|
|
72
|
+
target.dispatchEvent(new Event('name@'));
|
|
73
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('does not orphan a once listener when removing the wrong capture mode', () => {
|
|
77
|
+
const target = new EventTarget();
|
|
78
|
+
const listener = vi.fn();
|
|
79
|
+
|
|
80
|
+
target.addEventListener('change', listener, {once: true});
|
|
81
|
+
target.removeEventListener('change', listener, true);
|
|
82
|
+
target.removeEventListener('change', listener);
|
|
83
|
+
|
|
84
|
+
target.dispatchEvent(new Event('change'));
|
|
85
|
+
|
|
86
|
+
expect(listener).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('keeps once listeners distinct across event types', () => {
|
|
90
|
+
const target = new EventTarget();
|
|
91
|
+
const listener = vi.fn((event: globalThis.Event) => event.type);
|
|
92
|
+
|
|
93
|
+
target.addEventListener('start', listener, {once: true});
|
|
94
|
+
target.addEventListener('finish', listener, {once: true});
|
|
95
|
+
|
|
96
|
+
target.dispatchEvent(new Event('start'));
|
|
97
|
+
target.dispatchEvent(new Event('start'));
|
|
98
|
+
target.dispatchEvent(new Event('finish'));
|
|
99
|
+
target.dispatchEvent(new Event('finish'));
|
|
100
|
+
|
|
101
|
+
expect(listener.mock.results.map(({value}) => value)).toEqual([
|
|
102
|
+
'start',
|
|
103
|
+
'finish',
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('deduplicates once listeners and removes them after dispatch', () => {
|
|
108
|
+
const target = new EventTarget();
|
|
109
|
+
const listener = vi.fn();
|
|
110
|
+
|
|
111
|
+
target.addEventListener('change', listener, {once: true});
|
|
112
|
+
target.addEventListener('change', listener, {once: true});
|
|
113
|
+
|
|
114
|
+
target.dispatchEvent(new Event('change'));
|
|
115
|
+
target.dispatchEvent(new Event('change'));
|
|
116
|
+
|
|
117
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('uses the registered capture value when removing a once listener', () => {
|
|
121
|
+
const target = new EventTarget();
|
|
122
|
+
const listener = vi.fn();
|
|
123
|
+
const options = {capture: false, once: true};
|
|
124
|
+
|
|
125
|
+
target.addEventListener('change', listener, options);
|
|
126
|
+
options.capture = true;
|
|
127
|
+
|
|
128
|
+
target.dispatchEvent(new Event('change'));
|
|
129
|
+
target.dispatchEvent(new Event('change'));
|
|
130
|
+
|
|
131
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('does not register a listener with an already-aborted signal', () => {
|
|
135
|
+
const target = new EventTarget();
|
|
136
|
+
const listener = vi.fn();
|
|
137
|
+
const controller = new AbortController();
|
|
138
|
+
controller.abort();
|
|
139
|
+
|
|
140
|
+
target.addEventListener('change', listener, {
|
|
141
|
+
signal: controller.signal,
|
|
142
|
+
});
|
|
143
|
+
target.dispatchEvent(new Event('change'));
|
|
144
|
+
|
|
145
|
+
expect(listener).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('removes a registered listener when its signal aborts', () => {
|
|
149
|
+
const target = new EventTarget();
|
|
150
|
+
const listener = vi.fn();
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
|
|
153
|
+
target.addEventListener('change', listener, {
|
|
154
|
+
signal: controller.signal,
|
|
155
|
+
});
|
|
156
|
+
target.dispatchEvent(new Event('change'));
|
|
157
|
+
controller.abort();
|
|
158
|
+
target.dispatchEvent(new Event('change'));
|
|
159
|
+
|
|
160
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('uses the registered capture value when a signal removes a listener', () => {
|
|
164
|
+
const target = new EventTarget();
|
|
165
|
+
const listener = vi.fn();
|
|
166
|
+
const controller = new AbortController();
|
|
167
|
+
const options = {capture: false, signal: controller.signal};
|
|
168
|
+
|
|
169
|
+
target.addEventListener('change', listener, options);
|
|
170
|
+
options.capture = true;
|
|
171
|
+
controller.abort();
|
|
172
|
+
target.dispatchEvent(new Event('change'));
|
|
173
|
+
|
|
174
|
+
expect(listener).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('does not invoke a signal listener during an earlier abort handler', () => {
|
|
178
|
+
const target = new EventTarget();
|
|
179
|
+
const listener = vi.fn();
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
|
|
182
|
+
controller.signal.addEventListener('abort', () => {
|
|
183
|
+
target.dispatchEvent(new Event('change'));
|
|
184
|
+
});
|
|
185
|
+
target.addEventListener('change', listener, {signal: controller.signal});
|
|
186
|
+
|
|
187
|
+
controller.abort();
|
|
188
|
+
|
|
189
|
+
expect(listener).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('does not let an old signal remove a later registration', () => {
|
|
193
|
+
const target = new EventTarget();
|
|
194
|
+
const listener = vi.fn();
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
|
|
197
|
+
target.addEventListener('change', listener, {
|
|
198
|
+
signal: controller.signal,
|
|
199
|
+
});
|
|
200
|
+
target.removeEventListener('change', listener);
|
|
201
|
+
target.addEventListener('change', listener);
|
|
202
|
+
|
|
203
|
+
controller.abort();
|
|
204
|
+
target.dispatchEvent(new Event('change'));
|
|
205
|
+
|
|
206
|
+
expect(listener).toHaveBeenCalledOnce();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {beforeEach, describe, expect, it} from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {Window} from '../index.ts';
|
|
4
|
+
|
|
5
|
+
let window: Window;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
window = new Window();
|
|
9
|
+
Window.setGlobalThis(window);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('Event cancellation', () => {
|
|
13
|
+
it('returns true when the event is not canceled', () => {
|
|
14
|
+
const target = new window.EventTarget();
|
|
15
|
+
|
|
16
|
+
expect(target.dispatchEvent(new window.Event('event'))).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('returns false when a cancelable event is canceled', () => {
|
|
20
|
+
const target = new window.EventTarget();
|
|
21
|
+
const event = new window.Event('event', {cancelable: true});
|
|
22
|
+
target.addEventListener('event', (event) => event.preventDefault());
|
|
23
|
+
|
|
24
|
+
expect(target.dispatchEvent(event)).toBe(false);
|
|
25
|
+
expect(event.defaultPrevented).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('does not cancel a non-cancelable event', () => {
|
|
29
|
+
const target = new window.EventTarget();
|
|
30
|
+
const event = new window.Event('event');
|
|
31
|
+
target.addEventListener('event', (event) => event.preventDefault());
|
|
32
|
+
|
|
33
|
+
expect(target.dispatchEvent(event)).toBe(true);
|
|
34
|
+
expect(event.defaultPrevented).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('returns the cancellation result when propagation is stopped', () => {
|
|
38
|
+
const target = new window.EventTarget();
|
|
39
|
+
target.addEventListener(
|
|
40
|
+
'event',
|
|
41
|
+
(event) => {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
},
|
|
45
|
+
true,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(
|
|
49
|
+
target.dispatchEvent(new window.Event('event', {cancelable: true})),
|
|
50
|
+
).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns true when propagation is stopped without cancellation', () => {
|
|
54
|
+
const target = new window.EventTarget();
|
|
55
|
+
target.addEventListener('event', (event) => event.stopPropagation());
|
|
56
|
+
|
|
57
|
+
expect(target.dispatchEvent(new window.Event('event'))).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('cancels cancelable events for falsy returnValue assignments', () => {
|
|
61
|
+
for (const value of [false, 0, '', null, undefined]) {
|
|
62
|
+
const event = new window.Event('event', {cancelable: true});
|
|
63
|
+
|
|
64
|
+
(event as any).returnValue = value;
|
|
65
|
+
|
|
66
|
+
expect(event.defaultPrevented).toBe(true);
|
|
67
|
+
expect(event.returnValue).toBe(false);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('does not cancel or uncancel events for truthy returnValue assignments', () => {
|
|
72
|
+
for (const value of [true, 1, 'false', {}]) {
|
|
73
|
+
const event = new window.Event('event', {cancelable: true});
|
|
74
|
+
|
|
75
|
+
(event as any).returnValue = value;
|
|
76
|
+
|
|
77
|
+
expect(event.defaultPrevented).toBe(false);
|
|
78
|
+
expect(event.returnValue).toBe(true);
|
|
79
|
+
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
(event as any).returnValue = value;
|
|
82
|
+
|
|
83
|
+
expect(event.defaultPrevented).toBe(true);
|
|
84
|
+
expect(event.returnValue).toBe(false);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not cancel non-cancelable events through falsy returnValue', () => {
|
|
89
|
+
for (const value of [false, 0, '', null, undefined]) {
|
|
90
|
+
const event = new window.Event('event');
|
|
91
|
+
|
|
92
|
+
(event as any).returnValue = value;
|
|
93
|
+
|
|
94
|
+
expect(event.defaultPrevented).toBe(false);
|
|
95
|
+
expect(event.returnValue).toBe(true);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|