@remote-dom/polyfill 1.5.0 → 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 +94 -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 +7 -3
- 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 +15 -3
- 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,600 @@
|
|
|
1
|
+
import {beforeEach, describe, expect, it} from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {Element} from '../Element.ts';
|
|
4
|
+
import {HOOKS, HTMLElement as PolyfillHTMLElement, Window} from '../index.ts';
|
|
5
|
+
|
|
6
|
+
let polyfillWindow: Window;
|
|
7
|
+
let polyfillDocument: Window['document'];
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
polyfillWindow = new Window();
|
|
11
|
+
Window.setGlobalThis(polyfillWindow);
|
|
12
|
+
polyfillDocument = polyfillWindow.document;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
function defineCustomElement(
|
|
16
|
+
name: string,
|
|
17
|
+
Constructor: new () => PolyfillHTMLElement,
|
|
18
|
+
) {
|
|
19
|
+
polyfillWindow.customElements.define(
|
|
20
|
+
name,
|
|
21
|
+
Constructor as unknown as CustomElementConstructor,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('custom-element tree reactions', () => {
|
|
26
|
+
it('emits insertion before a connected callback removes the element', () => {
|
|
27
|
+
const mutations: string[] = [];
|
|
28
|
+
|
|
29
|
+
class SelfRemovingElement extends PolyfillHTMLElement {
|
|
30
|
+
connectedCallback() {
|
|
31
|
+
this.remove();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
defineCustomElement('self-removing', SelfRemovingElement);
|
|
36
|
+
const element = polyfillDocument.createElement('self-removing');
|
|
37
|
+
|
|
38
|
+
polyfillWindow[HOOKS].insertChild = () => {
|
|
39
|
+
mutations.push('insert');
|
|
40
|
+
};
|
|
41
|
+
polyfillWindow[HOOKS].removeChild = () => {
|
|
42
|
+
mutations.push('remove');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
polyfillDocument.body.appendChild(element);
|
|
46
|
+
|
|
47
|
+
expect(mutations).toEqual(['insert', 'remove']);
|
|
48
|
+
expect(element.parentNode).toBeNull();
|
|
49
|
+
expect(element.isConnected).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('emits removal before a disconnected callback reinserts the element', () => {
|
|
53
|
+
const mutations: string[] = [];
|
|
54
|
+
|
|
55
|
+
class SelfReinsertingElement extends PolyfillHTMLElement {
|
|
56
|
+
disconnectedCallback() {
|
|
57
|
+
polyfillDocument.body.appendChild(this);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
defineCustomElement('self-reinserting', SelfReinsertingElement);
|
|
62
|
+
const element = polyfillDocument.createElement('self-reinserting');
|
|
63
|
+
polyfillDocument.body.appendChild(element);
|
|
64
|
+
|
|
65
|
+
polyfillWindow[HOOKS].insertChild = () => {
|
|
66
|
+
mutations.push('insert');
|
|
67
|
+
};
|
|
68
|
+
polyfillWindow[HOOKS].removeChild = () => {
|
|
69
|
+
mutations.push('remove');
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
polyfillDocument.body.removeChild(element);
|
|
73
|
+
|
|
74
|
+
expect(mutations).toEqual(['remove', 'insert']);
|
|
75
|
+
expect(element.parentNode).toBe(polyfillDocument.body);
|
|
76
|
+
expect(element.isConnected).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('commits connectivity for the full subtree before invoking callbacks', () => {
|
|
80
|
+
const connectivity: boolean[][] = [];
|
|
81
|
+
let child: Element;
|
|
82
|
+
let grandchild: Element;
|
|
83
|
+
|
|
84
|
+
class SubtreeElement extends PolyfillHTMLElement {
|
|
85
|
+
connectedCallback() {
|
|
86
|
+
connectivity.push([
|
|
87
|
+
this.isConnected,
|
|
88
|
+
child.isConnected,
|
|
89
|
+
grandchild.isConnected,
|
|
90
|
+
]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
disconnectedCallback() {
|
|
94
|
+
connectivity.push([
|
|
95
|
+
this.isConnected,
|
|
96
|
+
child.isConnected,
|
|
97
|
+
grandchild.isConnected,
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
defineCustomElement('subtree-element', SubtreeElement);
|
|
103
|
+
const root = polyfillDocument.createElement('subtree-element');
|
|
104
|
+
child = polyfillDocument.createElement('span');
|
|
105
|
+
grandchild = polyfillDocument.createElement('span');
|
|
106
|
+
child.appendChild(grandchild);
|
|
107
|
+
root.appendChild(child);
|
|
108
|
+
|
|
109
|
+
polyfillDocument.body.appendChild(root);
|
|
110
|
+
polyfillDocument.body.removeChild(root);
|
|
111
|
+
|
|
112
|
+
expect(connectivity).toEqual([
|
|
113
|
+
[true, true, true],
|
|
114
|
+
[false, false, false],
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('emits insertion and preserves local state when connectedCallback throws', () => {
|
|
119
|
+
const error = new Error('connected callback failed');
|
|
120
|
+
const insertions: unknown[] = [];
|
|
121
|
+
|
|
122
|
+
class ThrowingElement extends PolyfillHTMLElement {
|
|
123
|
+
connectedCallback() {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
defineCustomElement('throwing-element', ThrowingElement);
|
|
129
|
+
const element = polyfillDocument.createElement('throwing-element');
|
|
130
|
+
polyfillWindow[HOOKS].insertChild = (_parent, node) => {
|
|
131
|
+
insertions.push(node);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
expect(() => polyfillDocument.body.appendChild(element)).toThrow(error);
|
|
135
|
+
expect(insertions).toContain(element);
|
|
136
|
+
expect(element.parentNode).toBe(polyfillDocument.body);
|
|
137
|
+
expect(element.isConnected).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('runs every subtree callback before rethrowing the first error', () => {
|
|
141
|
+
const error = new Error('first callback failed');
|
|
142
|
+
let secondCallbackRan = false;
|
|
143
|
+
|
|
144
|
+
class FirstElement extends PolyfillHTMLElement {
|
|
145
|
+
disconnectedCallback() {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class SecondElement extends PolyfillHTMLElement {
|
|
151
|
+
disconnectedCallback() {
|
|
152
|
+
secondCallbackRan = true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
defineCustomElement('first-callback', FirstElement);
|
|
157
|
+
defineCustomElement('second-callback', SecondElement);
|
|
158
|
+
const root = polyfillDocument.createElement('div');
|
|
159
|
+
const first = polyfillDocument.createElement('first-callback');
|
|
160
|
+
const second = polyfillDocument.createElement('second-callback');
|
|
161
|
+
root.append(first, second);
|
|
162
|
+
polyfillDocument.body.appendChild(root);
|
|
163
|
+
|
|
164
|
+
expect(() => polyfillDocument.body.removeChild(root)).toThrow(error);
|
|
165
|
+
expect(secondCallbackRan).toBe(true);
|
|
166
|
+
expect(root.isConnected).toBe(false);
|
|
167
|
+
expect(first.isConnected).toBe(false);
|
|
168
|
+
expect(second.isConnected).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('finishes a move before removal reactions mutate its target', () => {
|
|
172
|
+
const source = polyfillDocument.createElement('div');
|
|
173
|
+
const target = polyfillDocument.createElement('div');
|
|
174
|
+
const reference = polyfillDocument.createElement('span');
|
|
175
|
+
|
|
176
|
+
class TargetMutatingElement extends PolyfillHTMLElement {
|
|
177
|
+
disconnectedCallback() {
|
|
178
|
+
reference.remove();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
defineCustomElement('target-mutating', TargetMutatingElement);
|
|
183
|
+
const element = polyfillDocument.createElement('target-mutating');
|
|
184
|
+
source.appendChild(element);
|
|
185
|
+
target.appendChild(reference);
|
|
186
|
+
polyfillDocument.body.append(source, target);
|
|
187
|
+
|
|
188
|
+
target.insertBefore(element, reference);
|
|
189
|
+
|
|
190
|
+
expect(element.parentNode).toBe(target);
|
|
191
|
+
expect(element.isConnected).toBe(true);
|
|
192
|
+
expect(reference.parentNode).toBeNull();
|
|
193
|
+
expect(source.childNodes).toHaveLength(0);
|
|
194
|
+
expect([...target.childNodes]).toEqual([element]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('does not duplicate a moved node reinserted by disconnectedCallback', () => {
|
|
198
|
+
const mutations: string[] = [];
|
|
199
|
+
const callbackParents: Array<Element | null> = [];
|
|
200
|
+
const originalParent = polyfillDocument.createElement('div');
|
|
201
|
+
const target = polyfillDocument.createElement('div');
|
|
202
|
+
|
|
203
|
+
class MoveReinsertingElement extends PolyfillHTMLElement {
|
|
204
|
+
disconnectedCallback() {
|
|
205
|
+
callbackParents.push(this.parentNode as Element | null);
|
|
206
|
+
if (this.parentNode !== originalParent)
|
|
207
|
+
originalParent.appendChild(this);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
defineCustomElement('move-reinserting', MoveReinsertingElement);
|
|
212
|
+
const element = polyfillDocument.createElement('move-reinserting');
|
|
213
|
+
originalParent.appendChild(element);
|
|
214
|
+
polyfillDocument.body.append(originalParent, target);
|
|
215
|
+
|
|
216
|
+
polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
|
|
217
|
+
polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
|
|
218
|
+
|
|
219
|
+
target.appendChild(element);
|
|
220
|
+
|
|
221
|
+
expect(mutations).toEqual(['remove', 'insert', 'remove', 'insert']);
|
|
222
|
+
expect(callbackParents).toEqual([target, originalParent]);
|
|
223
|
+
expect([...originalParent.childNodes]).toEqual([element]);
|
|
224
|
+
expect(target.childNodes).toHaveLength(0);
|
|
225
|
+
expect(element.nextSibling).not.toBe(element);
|
|
226
|
+
expect(element.isConnected).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('connects every DocumentFragment child before running callbacks', () => {
|
|
230
|
+
const events: string[] = [];
|
|
231
|
+
let second: Element;
|
|
232
|
+
let firstObservedSecond = false;
|
|
233
|
+
|
|
234
|
+
class FirstFragmentElement extends PolyfillHTMLElement {
|
|
235
|
+
connectedCallback() {
|
|
236
|
+
events.push('callback:first');
|
|
237
|
+
firstObservedSecond =
|
|
238
|
+
second.isConnected && second.parentNode === polyfillDocument.body;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class SecondFragmentElement extends PolyfillHTMLElement {
|
|
243
|
+
connectedCallback() {
|
|
244
|
+
events.push('callback:second');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
defineCustomElement('fragment-first', FirstFragmentElement);
|
|
249
|
+
defineCustomElement('fragment-second', SecondFragmentElement);
|
|
250
|
+
const fragment = polyfillDocument.createDocumentFragment();
|
|
251
|
+
const first = polyfillDocument.createElement('fragment-first');
|
|
252
|
+
second = polyfillDocument.createElement('fragment-second');
|
|
253
|
+
fragment.append(first, second);
|
|
254
|
+
|
|
255
|
+
polyfillWindow[HOOKS].insertChild = (_parent, node) => {
|
|
256
|
+
events.push(`hook:${(node as any).localName}`);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
polyfillDocument.body.appendChild(fragment);
|
|
260
|
+
|
|
261
|
+
expect(events).toEqual([
|
|
262
|
+
'hook:fragment-first',
|
|
263
|
+
'hook:fragment-second',
|
|
264
|
+
'callback:first',
|
|
265
|
+
'callback:second',
|
|
266
|
+
]);
|
|
267
|
+
expect(firstObservedSecond).toBe(true);
|
|
268
|
+
expect(fragment.childNodes).toHaveLength(0);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('flushes a nested attribute reaction before returning to connectedCallback', () => {
|
|
272
|
+
const reactions: string[] = [];
|
|
273
|
+
|
|
274
|
+
class ReadyElement extends PolyfillHTMLElement {
|
|
275
|
+
static observedAttributes = ['state'];
|
|
276
|
+
ready = false;
|
|
277
|
+
|
|
278
|
+
connectedCallback() {
|
|
279
|
+
reactions.push('connected:start');
|
|
280
|
+
this.setAttribute('state', 'ready');
|
|
281
|
+
reactions.push(`connected:ready:${this.ready}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
attributeChangedCallback() {
|
|
285
|
+
this.ready = true;
|
|
286
|
+
reactions.push('attribute');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
defineCustomElement('ready-element', ReadyElement);
|
|
291
|
+
const element = polyfillDocument.createElement('ready-element');
|
|
292
|
+
|
|
293
|
+
polyfillDocument.body.appendChild(element);
|
|
294
|
+
|
|
295
|
+
expect(reactions).toEqual([
|
|
296
|
+
'connected:start',
|
|
297
|
+
'attribute',
|
|
298
|
+
'connected:ready:true',
|
|
299
|
+
]);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('flushes a nested insertion before returning to connectedCallback', () => {
|
|
303
|
+
const reactions: string[] = [];
|
|
304
|
+
let child: Element;
|
|
305
|
+
|
|
306
|
+
class AppendingElement extends PolyfillHTMLElement {
|
|
307
|
+
connectedCallback() {
|
|
308
|
+
reactions.push('parent:start');
|
|
309
|
+
this.appendChild(child);
|
|
310
|
+
reactions.push('parent:after-append');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
class AppendedElement extends PolyfillHTMLElement {
|
|
315
|
+
connectedCallback() {
|
|
316
|
+
reactions.push('child:connected');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
defineCustomElement('appending-element', AppendingElement);
|
|
321
|
+
defineCustomElement('appended-element', AppendedElement);
|
|
322
|
+
const parent = polyfillDocument.createElement('appending-element');
|
|
323
|
+
child = polyfillDocument.createElement('appended-element');
|
|
324
|
+
|
|
325
|
+
polyfillDocument.body.appendChild(parent);
|
|
326
|
+
|
|
327
|
+
expect(reactions).toEqual([
|
|
328
|
+
'parent:start',
|
|
329
|
+
'child:connected',
|
|
330
|
+
'parent:after-append',
|
|
331
|
+
]);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('drains a pending sibling before returning from its nested mutation', () => {
|
|
335
|
+
const reactions: string[] = [];
|
|
336
|
+
let second: Element;
|
|
337
|
+
|
|
338
|
+
class FirstPendingElement extends PolyfillHTMLElement {
|
|
339
|
+
connectedCallback() {
|
|
340
|
+
reactions.push('first:start');
|
|
341
|
+
second.setAttribute('state', 'ready');
|
|
342
|
+
reactions.push('first:after-set');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class SecondPendingElement extends PolyfillHTMLElement {
|
|
347
|
+
static observedAttributes = ['state'];
|
|
348
|
+
|
|
349
|
+
connectedCallback() {
|
|
350
|
+
reactions.push('second:connected');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
attributeChangedCallback() {
|
|
354
|
+
reactions.push('second:attribute');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
defineCustomElement('first-pending', FirstPendingElement);
|
|
359
|
+
defineCustomElement('second-pending', SecondPendingElement);
|
|
360
|
+
const fragment = polyfillDocument.createDocumentFragment();
|
|
361
|
+
const first = polyfillDocument.createElement('first-pending');
|
|
362
|
+
second = polyfillDocument.createElement('second-pending');
|
|
363
|
+
fragment.append(first, second);
|
|
364
|
+
|
|
365
|
+
polyfillDocument.body.appendChild(fragment);
|
|
366
|
+
|
|
367
|
+
expect(reactions).toEqual([
|
|
368
|
+
'first:start',
|
|
369
|
+
'second:connected',
|
|
370
|
+
'second:attribute',
|
|
371
|
+
'first:after-set',
|
|
372
|
+
]);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('keeps nested disconnections behind queued connection reactions', () => {
|
|
376
|
+
const reactions: string[] = [];
|
|
377
|
+
let child: Element;
|
|
378
|
+
|
|
379
|
+
class RemovingAncestorElement extends PolyfillHTMLElement {
|
|
380
|
+
connectedCallback() {
|
|
381
|
+
reactions.push('ancestor:connected');
|
|
382
|
+
child.remove();
|
|
383
|
+
reactions.push('ancestor:after-remove');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
class RemovedDescendantElement extends PolyfillHTMLElement {
|
|
388
|
+
connectedCallback() {
|
|
389
|
+
reactions.push(`descendant:connected:${this.isConnected}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
disconnectedCallback() {
|
|
393
|
+
reactions.push('descendant:disconnected');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
defineCustomElement('removing-ancestor', RemovingAncestorElement);
|
|
398
|
+
defineCustomElement('removed-descendant', RemovedDescendantElement);
|
|
399
|
+
const ancestor = polyfillDocument.createElement('removing-ancestor');
|
|
400
|
+
child = polyfillDocument.createElement('removed-descendant');
|
|
401
|
+
ancestor.appendChild(child);
|
|
402
|
+
|
|
403
|
+
polyfillDocument.body.appendChild(ancestor);
|
|
404
|
+
|
|
405
|
+
expect(reactions).toEqual([
|
|
406
|
+
'ancestor:connected',
|
|
407
|
+
'descendant:connected:false',
|
|
408
|
+
'descendant:disconnected',
|
|
409
|
+
'ancestor:after-remove',
|
|
410
|
+
]);
|
|
411
|
+
expect(child.parentNode).toBeNull();
|
|
412
|
+
expect(child.isConnected).toBe(false);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('runs a captured callback after an earlier reaction replaces it', () => {
|
|
416
|
+
const reactions: string[] = [];
|
|
417
|
+
let descendant: Element;
|
|
418
|
+
|
|
419
|
+
class ReplacingAncestorElement extends PolyfillHTMLElement {
|
|
420
|
+
connectedCallback() {
|
|
421
|
+
reactions.push('ancestor:connected');
|
|
422
|
+
(descendant as any).connectedCallback = () => {
|
|
423
|
+
reactions.push('descendant:replaced');
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
class OriginalDescendantElement extends PolyfillHTMLElement {
|
|
429
|
+
connectedCallback() {
|
|
430
|
+
reactions.push(`descendant:original:${this === descendant}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
defineCustomElement('replacing-ancestor', ReplacingAncestorElement);
|
|
435
|
+
defineCustomElement('original-descendant', OriginalDescendantElement);
|
|
436
|
+
const ancestor = polyfillDocument.createElement('replacing-ancestor');
|
|
437
|
+
descendant = polyfillDocument.createElement('original-descendant');
|
|
438
|
+
ancestor.appendChild(descendant);
|
|
439
|
+
|
|
440
|
+
polyfillDocument.body.appendChild(ancestor);
|
|
441
|
+
|
|
442
|
+
expect(reactions).toEqual([
|
|
443
|
+
'ancestor:connected',
|
|
444
|
+
'descendant:original:true',
|
|
445
|
+
]);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('finishes a replacement before rethrowing a lifecycle error', () => {
|
|
449
|
+
const error = new Error('disconnected callback failed');
|
|
450
|
+
const events: string[] = [];
|
|
451
|
+
|
|
452
|
+
class ThrowingOldElement extends PolyfillHTMLElement {
|
|
453
|
+
disconnectedCallback() {
|
|
454
|
+
events.push('callback:disconnected');
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
class ConnectedReplacementElement extends PolyfillHTMLElement {
|
|
460
|
+
connectedCallback() {
|
|
461
|
+
events.push('callback:connected');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
defineCustomElement('throwing-old', ThrowingOldElement);
|
|
466
|
+
defineCustomElement('connected-replacement', ConnectedReplacementElement);
|
|
467
|
+
const parent = polyfillDocument.createElement('div');
|
|
468
|
+
const oldChild = polyfillDocument.createElement('throwing-old');
|
|
469
|
+
const newChild = polyfillDocument.createElement('connected-replacement');
|
|
470
|
+
parent.appendChild(oldChild);
|
|
471
|
+
polyfillDocument.body.appendChild(parent);
|
|
472
|
+
|
|
473
|
+
polyfillWindow[HOOKS].removeChild = () => events.push('hook:remove');
|
|
474
|
+
polyfillWindow[HOOKS].insertChild = () => events.push('hook:insert');
|
|
475
|
+
|
|
476
|
+
expect(() => parent.replaceChild(newChild, oldChild)).toThrow(error);
|
|
477
|
+
expect(events).toEqual([
|
|
478
|
+
'hook:remove',
|
|
479
|
+
'hook:insert',
|
|
480
|
+
'callback:disconnected',
|
|
481
|
+
'callback:connected',
|
|
482
|
+
]);
|
|
483
|
+
expect([...parent.childNodes]).toEqual([newChild]);
|
|
484
|
+
expect(oldChild.parentNode).toBeNull();
|
|
485
|
+
expect(oldChild.isConnected).toBe(false);
|
|
486
|
+
expect(newChild.parentNode).toBe(parent);
|
|
487
|
+
expect(newChild.isConnected).toBe(true);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe('custom-element attribute reactions', () => {
|
|
492
|
+
it('emits reentrant attribute values in local mutation order', () => {
|
|
493
|
+
const values: string[] = [];
|
|
494
|
+
|
|
495
|
+
class ReentrantAttributeElement extends PolyfillHTMLElement {
|
|
496
|
+
static observedAttributes = ['state'];
|
|
497
|
+
|
|
498
|
+
attributeChangedCallback(
|
|
499
|
+
name: string,
|
|
500
|
+
_oldValue: string | null,
|
|
501
|
+
newValue: string | null,
|
|
502
|
+
) {
|
|
503
|
+
if (newValue === 'outer') this.setAttribute(name, 'inner');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
defineCustomElement('reentrant-attribute', ReentrantAttributeElement);
|
|
508
|
+
const element = polyfillDocument.createElement('reentrant-attribute');
|
|
509
|
+
polyfillWindow[HOOKS].setAttribute = (_element, name, value) => {
|
|
510
|
+
if (name === 'state') values.push(value);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
element.setAttribute('state', 'outer');
|
|
514
|
+
|
|
515
|
+
expect(values).toEqual(['outer', 'inner']);
|
|
516
|
+
expect(element.getAttribute('state')).toBe('inner');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('flushes a nested attribute reaction before returning to its callback', () => {
|
|
520
|
+
const reactions: string[] = [];
|
|
521
|
+
|
|
522
|
+
class NestedAttributeElement extends PolyfillHTMLElement {
|
|
523
|
+
static observedAttributes = ['first', 'second'];
|
|
524
|
+
|
|
525
|
+
attributeChangedCallback(name: string) {
|
|
526
|
+
reactions.push(`${name}:start`);
|
|
527
|
+
if (name === 'first') {
|
|
528
|
+
this.setAttribute('second', 'value');
|
|
529
|
+
reactions.push('first:after-set');
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
defineCustomElement('nested-attribute', NestedAttributeElement);
|
|
535
|
+
const element = polyfillDocument.createElement('nested-attribute');
|
|
536
|
+
|
|
537
|
+
element.setAttribute('first', 'value');
|
|
538
|
+
|
|
539
|
+
expect(reactions).toEqual([
|
|
540
|
+
'first:start',
|
|
541
|
+
'second:start',
|
|
542
|
+
'first:after-set',
|
|
543
|
+
]);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('emits removal before an attribute callback restores the attribute', () => {
|
|
547
|
+
const mutations: string[] = [];
|
|
548
|
+
|
|
549
|
+
class RestoringAttributeElement extends PolyfillHTMLElement {
|
|
550
|
+
static observedAttributes = ['state'];
|
|
551
|
+
|
|
552
|
+
attributeChangedCallback(
|
|
553
|
+
name: string,
|
|
554
|
+
_oldValue: string | null,
|
|
555
|
+
newValue: string | null,
|
|
556
|
+
) {
|
|
557
|
+
if (newValue == null) this.setAttribute(name, 'restored');
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
defineCustomElement('restoring-attribute', RestoringAttributeElement);
|
|
562
|
+
const element = polyfillDocument.createElement('restoring-attribute');
|
|
563
|
+
element.setAttribute('state', 'initial');
|
|
564
|
+
|
|
565
|
+
polyfillWindow[HOOKS].removeAttribute = (_element, name) => {
|
|
566
|
+
if (name === 'state') mutations.push('remove');
|
|
567
|
+
};
|
|
568
|
+
polyfillWindow[HOOKS].setAttribute = (_element, name, value) => {
|
|
569
|
+
if (name === 'state') mutations.push(`set:${value}`);
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
element.removeAttribute('state');
|
|
573
|
+
|
|
574
|
+
expect(mutations).toEqual(['remove', 'set:restored']);
|
|
575
|
+
expect(element.getAttribute('state')).toBe('restored');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('emits the update and preserves local state when a callback throws', () => {
|
|
579
|
+
const error = new Error('attribute callback failed');
|
|
580
|
+
const updates: string[] = [];
|
|
581
|
+
|
|
582
|
+
class ThrowingAttributeElement extends PolyfillHTMLElement {
|
|
583
|
+
static observedAttributes = ['state'];
|
|
584
|
+
|
|
585
|
+
attributeChangedCallback() {
|
|
586
|
+
throw error;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
defineCustomElement('throwing-attribute', ThrowingAttributeElement);
|
|
591
|
+
const element = polyfillDocument.createElement('throwing-attribute');
|
|
592
|
+
polyfillWindow[HOOKS].setAttribute = (_element, name, value) => {
|
|
593
|
+
if (name === 'state') updates.push(value);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
expect(() => element.setAttribute('state', 'updated')).toThrow(error);
|
|
597
|
+
expect(updates).toEqual(['updated']);
|
|
598
|
+
expect(element.getAttribute('state')).toBe('updated');
|
|
599
|
+
});
|
|
600
|
+
});
|