@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.
Files changed (304) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +51 -3
  3. package/build/cjs/Attr.cjs +33 -4
  4. package/build/cjs/CharacterData.cjs +10 -0
  5. package/build/cjs/ChildNode.cjs +79 -13
  6. package/build/cjs/Comment.cjs +1 -1
  7. package/build/cjs/CustomElementRegistry.cjs +27 -2
  8. package/build/cjs/CustomEvent.cjs +2 -2
  9. package/build/cjs/Document.cjs +56 -32
  10. package/build/cjs/DocumentFragment.cjs +6 -2
  11. package/build/cjs/Element.cjs +68 -8
  12. package/build/cjs/Event.cjs +29 -27
  13. package/build/cjs/EventTarget.cjs +128 -58
  14. package/build/cjs/HTMLTemplateElement.cjs +1 -0
  15. package/build/cjs/MutationObserver.cjs +167 -1
  16. package/build/cjs/NamedNodeMap.cjs +82 -45
  17. package/build/cjs/Node.cjs +47 -11
  18. package/build/cjs/NodeList.cjs +9 -1
  19. package/build/cjs/ParentNode.cjs +319 -77
  20. package/build/cjs/SVGElement.cjs +1 -1
  21. package/build/cjs/Text.cjs +1 -1
  22. package/build/cjs/Window.cjs +131 -38
  23. package/build/cjs/attribute-reactions.cjs +20 -0
  24. package/build/cjs/constants.cjs +48 -24
  25. package/build/cjs/custom-element-reactions.cjs +52 -0
  26. package/build/cjs/dom-exception.cjs +15 -0
  27. package/build/cjs/hook-effects.cjs +51 -0
  28. package/build/cjs/names.cjs +61 -0
  29. package/build/cjs/selectors.cjs +213 -122
  30. package/build/cjs/serialization.cjs +168 -65
  31. package/build/cjs/shared.cjs +177 -28
  32. package/build/esm/Attr.mjs +34 -5
  33. package/build/esm/CharacterData.mjs +10 -0
  34. package/build/esm/ChildNode.mjs +76 -15
  35. package/build/esm/Comment.mjs +2 -2
  36. package/build/esm/CustomElementRegistry.mjs +27 -2
  37. package/build/esm/CustomEvent.mjs +2 -2
  38. package/build/esm/Document.mjs +60 -35
  39. package/build/esm/DocumentFragment.mjs +7 -3
  40. package/build/esm/Element.mjs +69 -9
  41. package/build/esm/Event.mjs +27 -27
  42. package/build/esm/EventTarget.mjs +130 -60
  43. package/build/esm/HTMLTemplateElement.mjs +2 -1
  44. package/build/esm/MutationObserver.mjs +166 -2
  45. package/build/esm/NamedNodeMap.mjs +83 -46
  46. package/build/esm/Node.mjs +48 -12
  47. package/build/esm/NodeList.mjs +9 -1
  48. package/build/esm/ParentNode.mjs +322 -81
  49. package/build/esm/SVGElement.mjs +2 -2
  50. package/build/esm/Text.mjs +2 -2
  51. package/build/esm/Window.mjs +132 -39
  52. package/build/esm/attribute-reactions.mjs +16 -0
  53. package/build/esm/constants.mjs +28 -23
  54. package/build/esm/custom-element-reactions.mjs +47 -0
  55. package/build/esm/dom-exception.mjs +11 -0
  56. package/build/esm/hook-effects.mjs +46 -0
  57. package/build/esm/names.mjs +54 -0
  58. package/build/esm/selectors.mjs +202 -124
  59. package/build/esm/serialization.mjs +169 -66
  60. package/build/esm/shared.mjs +173 -30
  61. package/build/esnext/Attr.esnext +45 -5
  62. package/build/esnext/CharacterData.esnext +10 -0
  63. package/build/esnext/ChildNode.esnext +84 -15
  64. package/build/esnext/Comment.esnext +2 -2
  65. package/build/esnext/CustomElementRegistry.esnext +42 -0
  66. package/build/esnext/CustomEvent.esnext +2 -2
  67. package/build/esnext/Document.esnext +69 -32
  68. package/build/esnext/DocumentFragment.esnext +7 -3
  69. package/build/esnext/Element.esnext +78 -9
  70. package/build/esnext/Event.esnext +36 -37
  71. package/build/esnext/EventTarget.esnext +133 -61
  72. package/build/esnext/HTMLTemplateElement.esnext +3 -1
  73. package/build/esnext/MutationObserver.esnext +163 -2
  74. package/build/esnext/NamedNodeMap.esnext +110 -42
  75. package/build/esnext/Node.esnext +50 -12
  76. package/build/esnext/NodeList.esnext +1 -1
  77. package/build/esnext/ParentNode.esnext +352 -79
  78. package/build/esnext/SVGElement.esnext +2 -2
  79. package/build/esnext/Text.esnext +2 -2
  80. package/build/esnext/Window.esnext +120 -16
  81. package/build/esnext/attribute-reactions.esnext +21 -0
  82. package/build/esnext/constants.esnext +34 -21
  83. package/build/esnext/custom-element-reactions.esnext +49 -0
  84. package/build/esnext/dom-exception.esnext +11 -0
  85. package/build/esnext/hook-effects.esnext +46 -0
  86. package/build/esnext/names.esnext +61 -0
  87. package/build/esnext/selectors.esnext +202 -100
  88. package/build/esnext/serialization.esnext +171 -48
  89. package/build/esnext/shared.esnext +181 -34
  90. package/build/tsconfig.tsbuildinfo +1 -1
  91. package/build/typescript/Attr.d.ts +6 -4
  92. package/build/typescript/Attr.d.ts.map +1 -1
  93. package/build/typescript/CharacterData.d.ts.map +1 -1
  94. package/build/typescript/ChildNode.d.ts +5 -0
  95. package/build/typescript/ChildNode.d.ts.map +1 -1
  96. package/build/typescript/ClipboardEvent.d.ts.map +1 -1
  97. package/build/typescript/Comment.d.ts +2 -2
  98. package/build/typescript/Comment.d.ts.map +1 -1
  99. package/build/typescript/CustomElementRegistry.d.ts +1 -0
  100. package/build/typescript/CustomElementRegistry.d.ts.map +1 -1
  101. package/build/typescript/CustomEvent.d.ts.map +1 -1
  102. package/build/typescript/Document.d.ts +10 -5
  103. package/build/typescript/Document.d.ts.map +1 -1
  104. package/build/typescript/DocumentFragment.d.ts +2 -1
  105. package/build/typescript/DocumentFragment.d.ts.map +1 -1
  106. package/build/typescript/Element.d.ts +16 -7
  107. package/build/typescript/Element.d.ts.map +1 -1
  108. package/build/typescript/ErrorEvent.d.ts.map +1 -1
  109. package/build/typescript/Event.d.ts +9 -10
  110. package/build/typescript/Event.d.ts.map +1 -1
  111. package/build/typescript/EventTarget.d.ts +20 -3
  112. package/build/typescript/EventTarget.d.ts.map +1 -1
  113. package/build/typescript/FocusEvent.d.ts.map +1 -1
  114. package/build/typescript/HTMLTemplateElement.d.ts.map +1 -1
  115. package/build/typescript/MutationObserver.d.ts +42 -0
  116. package/build/typescript/MutationObserver.d.ts.map +1 -1
  117. package/build/typescript/NamedNodeMap.d.ts +5 -3
  118. package/build/typescript/NamedNodeMap.d.ts.map +1 -1
  119. package/build/typescript/Node.d.ts +4 -3
  120. package/build/typescript/Node.d.ts.map +1 -1
  121. package/build/typescript/NodeList.d.ts +3 -2
  122. package/build/typescript/NodeList.d.ts.map +1 -1
  123. package/build/typescript/ParentNode.d.ts +29 -10
  124. package/build/typescript/ParentNode.d.ts.map +1 -1
  125. package/build/typescript/PromiseRejectionEvent.d.ts.map +1 -1
  126. package/build/typescript/SVGElement.d.ts +1 -1
  127. package/build/typescript/SVGElement.d.ts.map +1 -1
  128. package/build/typescript/Text.d.ts +1 -1
  129. package/build/typescript/Text.d.ts.map +1 -1
  130. package/build/typescript/ToggleEvent.d.ts.map +1 -1
  131. package/build/typescript/Window.d.ts +5 -1
  132. package/build/typescript/Window.d.ts.map +1 -1
  133. package/build/typescript/attribute-reactions.d.ts +4 -0
  134. package/build/typescript/attribute-reactions.d.ts.map +1 -0
  135. package/build/typescript/constants.d.ts +27 -18
  136. package/build/typescript/constants.d.ts.map +1 -1
  137. package/build/typescript/custom-element-reactions.d.ts +6 -0
  138. package/build/typescript/custom-element-reactions.d.ts.map +1 -0
  139. package/build/typescript/dom-exception.d.ts +2 -0
  140. package/build/typescript/dom-exception.d.ts.map +1 -0
  141. package/build/typescript/extensions.d.ts +4 -0
  142. package/build/typescript/extensions.d.ts.map +1 -0
  143. package/build/typescript/hook-effects.d.ts +6 -0
  144. package/build/typescript/hook-effects.d.ts.map +1 -0
  145. package/build/typescript/hooks.d.ts +3 -3
  146. package/build/typescript/hooks.d.ts.map +1 -1
  147. package/build/typescript/index.d.ts +1 -0
  148. package/build/typescript/index.d.ts.map +1 -1
  149. package/build/typescript/names.d.ts +11 -0
  150. package/build/typescript/names.d.ts.map +1 -0
  151. package/build/typescript/selectors.d.ts +21 -22
  152. package/build/typescript/selectors.d.ts.map +1 -1
  153. package/build/typescript/serialization.d.ts.map +1 -1
  154. package/build/typescript/shared.d.ts +10 -0
  155. package/build/typescript/shared.d.ts.map +1 -1
  156. package/build/typescript/tests/ChildNode.test.d.ts +2 -0
  157. package/build/typescript/tests/ChildNode.test.d.ts.map +1 -0
  158. package/build/typescript/tests/MutationObserver.test.d.ts +2 -0
  159. package/build/typescript/tests/MutationObserver.test.d.ts.map +1 -0
  160. package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts +2 -0
  161. package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts.map +1 -0
  162. package/build/typescript/tests/attr-mutation.test.d.ts +2 -0
  163. package/build/typescript/tests/attr-mutation.test.d.ts.map +1 -0
  164. package/build/typescript/tests/child-node-before-after.test.d.ts +2 -0
  165. package/build/typescript/tests/child-node-before-after.test.d.ts.map +1 -0
  166. package/build/typescript/tests/child-node-replace-with.test.d.ts +2 -0
  167. package/build/typescript/tests/child-node-replace-with.test.d.ts.map +1 -0
  168. package/build/typescript/tests/clone-import-namespaces.test.d.ts +2 -0
  169. package/build/typescript/tests/clone-import-namespaces.test.d.ts.map +1 -0
  170. package/build/typescript/tests/constants.test.d.ts +2 -0
  171. package/build/typescript/tests/constants.test.d.ts.map +1 -0
  172. package/build/typescript/tests/create-element-namespaces.test.d.ts +2 -0
  173. package/build/typescript/tests/create-element-namespaces.test.d.ts.map +1 -0
  174. package/build/typescript/tests/create-text-node.test.d.ts +2 -0
  175. package/build/typescript/tests/create-text-node.test.d.ts.map +1 -0
  176. package/build/typescript/tests/custom-element-reactions.test.d.ts +2 -0
  177. package/build/typescript/tests/custom-element-reactions.test.d.ts.map +1 -0
  178. package/build/typescript/tests/custom-element-registry.test.d.ts +2 -0
  179. package/build/typescript/tests/custom-element-registry.test.d.ts.map +1 -0
  180. package/build/typescript/tests/custom-event.test.d.ts +2 -0
  181. package/build/typescript/tests/custom-event.test.d.ts.map +1 -0
  182. package/build/typescript/tests/document-clone-import-contract.test.d.ts +2 -0
  183. package/build/typescript/tests/document-clone-import-contract.test.d.ts.map +1 -0
  184. package/build/typescript/tests/document.test.d.ts +2 -0
  185. package/build/typescript/tests/document.test.d.ts.map +1 -0
  186. package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts +2 -0
  187. package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts.map +1 -0
  188. package/build/typescript/tests/event-dispatch.test.d.ts +2 -0
  189. package/build/typescript/tests/event-dispatch.test.d.ts.map +1 -0
  190. package/build/typescript/tests/event-target.test.d.ts +2 -0
  191. package/build/typescript/tests/event-target.test.d.ts.map +1 -0
  192. package/build/typescript/tests/event.test.d.ts +2 -0
  193. package/build/typescript/tests/event.test.d.ts.map +1 -0
  194. package/build/typescript/tests/extensions.test.d.ts +2 -0
  195. package/build/typescript/tests/extensions.test.d.ts.map +1 -0
  196. package/build/typescript/tests/get-elements-by-tag-name.test.d.ts +2 -0
  197. package/build/typescript/tests/get-elements-by-tag-name.test.d.ts.map +1 -0
  198. package/build/typescript/tests/getElementsByClassName.test.d.ts +2 -0
  199. package/build/typescript/tests/getElementsByClassName.test.d.ts.map +1 -0
  200. package/build/typescript/tests/hooks.test.d.ts +2 -0
  201. package/build/typescript/tests/hooks.test.d.ts.map +1 -0
  202. package/build/typescript/tests/inner-html-parsing.test.d.ts +2 -0
  203. package/build/typescript/tests/inner-html-parsing.test.d.ts.map +1 -0
  204. package/build/typescript/tests/is-default-namespace.test.d.ts +2 -0
  205. package/build/typescript/tests/is-default-namespace.test.d.ts.map +1 -0
  206. package/build/typescript/tests/name-and-namespace-normalization.test.d.ts +2 -0
  207. package/build/typescript/tests/name-and-namespace-normalization.test.d.ts.map +1 -0
  208. package/build/typescript/tests/named-node-map.test.d.ts +2 -0
  209. package/build/typescript/tests/named-node-map.test.d.ts.map +1 -0
  210. package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts +2 -0
  211. package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts.map +1 -0
  212. package/build/typescript/tests/node.test.d.ts +2 -0
  213. package/build/typescript/tests/node.test.d.ts.map +1 -0
  214. package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts +2 -0
  215. package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts.map +1 -0
  216. package/build/typescript/tests/parent-node-mutation-validation.test.d.ts +2 -0
  217. package/build/typescript/tests/parent-node-mutation-validation.test.d.ts.map +1 -0
  218. package/build/typescript/tests/parent-node.test.d.ts +2 -0
  219. package/build/typescript/tests/parent-node.test.d.ts.map +1 -0
  220. package/build/typescript/tests/serialization-template.test.d.ts +2 -0
  221. package/build/typescript/tests/serialization-template.test.d.ts.map +1 -0
  222. package/build/typescript/tests/slot-attribute.test.d.ts +2 -0
  223. package/build/typescript/tests/slot-attribute.test.d.ts.map +1 -0
  224. package/build/typescript/tests/text-content.test.d.ts +2 -0
  225. package/build/typescript/tests/text-content.test.d.ts.map +1 -0
  226. package/build/typescript/tests/toggle-attribute.test.d.ts +2 -0
  227. package/build/typescript/tests/toggle-attribute.test.d.ts.map +1 -0
  228. package/build/typescript/tests/wide-tree-traversal.test.d.ts +2 -0
  229. package/build/typescript/tests/wide-tree-traversal.test.d.ts.map +1 -0
  230. package/build/typescript/tests/window-set-global.test.d.ts +2 -0
  231. package/build/typescript/tests/window-set-global.test.d.ts.map +1 -0
  232. package/package.json +1 -1
  233. package/source/Attr.ts +60 -8
  234. package/source/CharacterData.ts +13 -0
  235. package/source/ChildNode.ts +107 -14
  236. package/source/Comment.ts +2 -2
  237. package/source/CustomElementRegistry.ts +56 -0
  238. package/source/CustomEvent.ts +2 -2
  239. package/source/Document.ts +104 -38
  240. package/source/DocumentFragment.ts +15 -3
  241. package/source/Element.ts +129 -14
  242. package/source/Event.ts +34 -32
  243. package/source/EventTarget.ts +203 -79
  244. package/source/HTMLTemplateElement.ts +2 -1
  245. package/source/MutationObserver.ts +279 -1
  246. package/source/NamedNodeMap.ts +141 -60
  247. package/source/Node.ts +70 -14
  248. package/source/NodeList.ts +13 -3
  249. package/source/ParentNode.ts +500 -88
  250. package/source/SVGElement.ts +2 -2
  251. package/source/Text.ts +2 -2
  252. package/source/Window.ts +156 -17
  253. package/source/attribute-reactions.ts +32 -0
  254. package/source/constants.ts +40 -17
  255. package/source/custom-element-reactions.ts +64 -0
  256. package/source/dom-exception.ts +14 -0
  257. package/source/extensions.ts +4 -0
  258. package/source/hook-effects.ts +56 -0
  259. package/source/hooks.ts +11 -3
  260. package/source/index.ts +1 -0
  261. package/source/names.ts +94 -0
  262. package/source/selectors.ts +301 -138
  263. package/source/serialization.ts +224 -66
  264. package/source/shared.ts +269 -40
  265. package/source/tests/ChildNode.test.ts +178 -0
  266. package/source/tests/MutationObserver.test.ts +303 -0
  267. package/source/tests/adopt-inserted-subtrees.test.ts +584 -0
  268. package/source/tests/attr-mutation.test.ts +112 -0
  269. package/source/tests/child-node-before-after.test.ts +421 -0
  270. package/source/tests/child-node-replace-with.test.ts +251 -0
  271. package/source/tests/clone-import-namespaces.test.ts +365 -0
  272. package/source/tests/constants.test.ts +46 -0
  273. package/source/tests/create-element-namespaces.test.ts +142 -0
  274. package/source/tests/create-text-node.test.ts +38 -0
  275. package/source/tests/custom-element-reactions.test.ts +600 -0
  276. package/source/tests/custom-element-registry.test.ts +137 -0
  277. package/source/tests/custom-event.test.ts +26 -0
  278. package/source/tests/document-clone-import-contract.test.ts +77 -0
  279. package/source/tests/document.test.ts +122 -0
  280. package/source/tests/dom-mutation-selector-errors.test.ts +484 -0
  281. package/source/tests/event-dispatch.test.ts +270 -0
  282. package/source/tests/event-target.test.ts +208 -0
  283. package/source/tests/event.test.ts +98 -0
  284. package/source/tests/extensions.test.ts +159 -0
  285. package/source/tests/get-elements-by-tag-name.test.ts +79 -0
  286. package/source/tests/getElementsByClassName.test.ts +109 -0
  287. package/source/tests/hooks.test.ts +43 -0
  288. package/source/tests/inner-html-parsing.test.ts +461 -0
  289. package/source/tests/is-default-namespace.test.ts +111 -0
  290. package/source/tests/name-and-namespace-normalization.test.ts +319 -0
  291. package/source/tests/named-node-map.test.ts +122 -0
  292. package/source/tests/namespaced-attribute-reactions.test.ts +141 -0
  293. package/source/tests/node.test.ts +56 -0
  294. package/source/tests/parent-node-mutation-return-values.test.ts +63 -0
  295. package/source/tests/parent-node-mutation-validation.test.ts +362 -0
  296. package/source/tests/parent-node.test.ts +169 -0
  297. package/source/tests/selectors.test.ts +533 -22
  298. package/source/tests/serialization-template.test.ts +232 -0
  299. package/source/tests/slot-attribute.test.ts +133 -0
  300. package/source/tests/text-content.test.ts +440 -0
  301. package/source/tests/toggle-attribute.test.ts +113 -0
  302. package/source/tests/wide-tree-traversal.test.ts +699 -0
  303. package/source/tests/window-set-global.test.ts +321 -0
  304. package/tsconfig.json +1 -1
@@ -0,0 +1,421 @@
1
+ import {beforeEach, describe, expect, it} from 'vitest';
2
+
3
+ import {HOOKS, HTMLElement as PolyfillHTMLElement, Window} from '../index.ts';
4
+
5
+ let polyfillWindow: Window;
6
+
7
+ beforeEach(() => {
8
+ polyfillWindow = new Window();
9
+ Window.setGlobalThis(polyfillWindow);
10
+ });
11
+
12
+ describe('ChildNode.before', () => {
13
+ it('preserves adjacent siblings and the receiver in argument order', () => {
14
+ const parent = document.createElement('div');
15
+ const leading = document.createElement('span');
16
+ const previous = document.createElement('em');
17
+ const receiver = document.createElement('strong');
18
+ const replacement = document.createElement('i');
19
+ const after = document.createElement('b');
20
+ parent.append(leading, previous, receiver, after);
21
+
22
+ receiver.before(previous, replacement, receiver);
23
+
24
+ expect([...parent.childNodes]).toEqual([
25
+ leading,
26
+ previous,
27
+ replacement,
28
+ receiver,
29
+ after,
30
+ ]);
31
+ expect(leading.nextSibling).toBe(previous);
32
+ expect(previous.nextSibling).toBe(replacement);
33
+ expect(replacement.nextSibling).toBe(receiver);
34
+ expect(receiver.nextSibling).toBe(after);
35
+ });
36
+
37
+ it('commits every argument before running connected callbacks', () => {
38
+ let parent: HTMLElement;
39
+ let observedChildren: unknown[] = [];
40
+
41
+ class BeforeObserver extends PolyfillHTMLElement {
42
+ connectedCallback() {
43
+ observedChildren = [...parent.childNodes];
44
+ }
45
+ }
46
+
47
+ polyfillWindow.customElements.define(
48
+ 'before-observer',
49
+ BeforeObserver as unknown as CustomElementConstructor,
50
+ );
51
+ parent = document.createElement('div');
52
+ const receiver = document.createElement('em');
53
+ const after = document.createElement('strong');
54
+ parent.append(receiver, after);
55
+ document.body.appendChild(parent);
56
+ const first = document.createElement('before-observer');
57
+ const second = document.createElement('span');
58
+
59
+ receiver.before(first, second);
60
+
61
+ expect(observedChildren).toEqual([first, second, receiver, after]);
62
+ expect([...parent.childNodes]).toEqual([first, second, receiver, after]);
63
+ });
64
+
65
+ it('commits arguments before disconnecting a moved custom element', () => {
66
+ const parent = document.createElement('div');
67
+ const receiver = document.createElement('em');
68
+ const trailing = document.createElement('strong');
69
+ const second = document.createElement('span');
70
+ const reactions: string[] = [];
71
+ let observedChildren: unknown[] = [];
72
+
73
+ class MovingBeforeElement extends PolyfillHTMLElement {
74
+ connectedCallback() {
75
+ reactions.push('connected');
76
+ }
77
+
78
+ disconnectedCallback() {
79
+ reactions.push('disconnected');
80
+ observedChildren = [...parent.childNodes];
81
+ }
82
+ }
83
+
84
+ polyfillWindow.customElements.define(
85
+ 'moving-before-element',
86
+ MovingBeforeElement as unknown as CustomElementConstructor,
87
+ );
88
+
89
+ const first = document.createElement('moving-before-element');
90
+ parent.append(receiver, trailing);
91
+ document.body.append(parent, first);
92
+ reactions.length = 0;
93
+
94
+ receiver.before(first, second);
95
+
96
+ expect([...parent.childNodes]).toEqual([first, second, receiver, trailing]);
97
+ expect(observedChildren).toEqual([first, second, receiver, trailing]);
98
+ expect(reactions).toEqual(['disconnected', 'connected']);
99
+ });
100
+
101
+ it('does not move earlier arguments when a later conversion throws', () => {
102
+ let callbacks = 0;
103
+
104
+ class BeforeReplacement extends PolyfillHTMLElement {
105
+ connectedCallback() {
106
+ callbacks += 1;
107
+ }
108
+
109
+ disconnectedCallback() {
110
+ callbacks += 1;
111
+ }
112
+ }
113
+
114
+ polyfillWindow.customElements.define(
115
+ 'before-replacement',
116
+ BeforeReplacement as unknown as CustomElementConstructor,
117
+ );
118
+ const parent = document.createElement('div');
119
+ const receiver = document.createElement('em');
120
+ parent.appendChild(receiver);
121
+ const source = document.createElement('div');
122
+ const replacement = document.createElement('before-replacement');
123
+ source.appendChild(replacement);
124
+ document.body.append(parent, source);
125
+ callbacks = 0;
126
+ const mutations: string[] = [];
127
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
128
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
129
+ const error = new Error('conversion failed');
130
+ const throwingValue = {
131
+ toString() {
132
+ throw error;
133
+ },
134
+ } as unknown as string;
135
+
136
+ expect(() => receiver.before(replacement, throwingValue)).toThrow(error);
137
+
138
+ expect([...document.body.childNodes]).toEqual([parent, source]);
139
+ expect([...parent.childNodes]).toEqual([receiver]);
140
+ expect([...source.childNodes]).toEqual([replacement]);
141
+ expect(replacement.parentNode).toBe(source);
142
+ expect(replacement.isConnected).toBe(true);
143
+ expect(mutations).toEqual([]);
144
+ expect(callbacks).toBe(0);
145
+ });
146
+
147
+ it('commits every argument before rethrowing a lifecycle error', () => {
148
+ const error = new Error('connected callback failed');
149
+ let secondCallbackRan = false;
150
+
151
+ class ThrowingBeforeElement extends PolyfillHTMLElement {
152
+ connectedCallback() {
153
+ throw error;
154
+ }
155
+ }
156
+
157
+ class SecondBeforeElement extends PolyfillHTMLElement {
158
+ connectedCallback() {
159
+ secondCallbackRan = true;
160
+ }
161
+ }
162
+
163
+ polyfillWindow.customElements.define(
164
+ 'throwing-before',
165
+ ThrowingBeforeElement as unknown as CustomElementConstructor,
166
+ );
167
+ polyfillWindow.customElements.define(
168
+ 'second-before',
169
+ SecondBeforeElement as unknown as CustomElementConstructor,
170
+ );
171
+ const parent = document.createElement('div');
172
+ const receiver = document.createElement('em');
173
+ parent.appendChild(receiver);
174
+ document.body.appendChild(parent);
175
+ const first = document.createElement('throwing-before');
176
+ const second = document.createElement('second-before');
177
+
178
+ expect(() => receiver.before(first, second)).toThrow(error);
179
+
180
+ expect([...parent.childNodes]).toEqual([first, second, receiver]);
181
+ expect(secondCallbackRan).toBe(true);
182
+ });
183
+ });
184
+
185
+ describe('ChildNode.after', () => {
186
+ it('preserves the receiver and adjacent siblings in argument order', () => {
187
+ const parent = document.createElement('div');
188
+ const before = document.createElement('span');
189
+ const receiver = document.createElement('em');
190
+ const next = document.createElement('strong');
191
+ const replacement = document.createElement('i');
192
+ const trailing = document.createElement('b');
193
+ parent.append(before, receiver, next, trailing);
194
+
195
+ receiver.after(receiver, replacement, next);
196
+
197
+ expect([...parent.childNodes]).toEqual([
198
+ before,
199
+ receiver,
200
+ replacement,
201
+ next,
202
+ trailing,
203
+ ]);
204
+ expect(before.nextSibling).toBe(receiver);
205
+ expect(receiver.nextSibling).toBe(replacement);
206
+ expect(replacement.nextSibling).toBe(next);
207
+ expect(next.nextSibling).toBe(trailing);
208
+ });
209
+
210
+ it('commits every argument before running connected callbacks', () => {
211
+ let parent: HTMLElement;
212
+ let observedChildren: unknown[] = [];
213
+
214
+ class AfterObserver extends PolyfillHTMLElement {
215
+ connectedCallback() {
216
+ observedChildren = [...parent.childNodes];
217
+ }
218
+ }
219
+
220
+ polyfillWindow.customElements.define(
221
+ 'after-observer',
222
+ AfterObserver as unknown as CustomElementConstructor,
223
+ );
224
+ parent = document.createElement('div');
225
+ const before = document.createElement('span');
226
+ const receiver = document.createElement('em');
227
+ const after = document.createElement('strong');
228
+ parent.append(before, receiver, after);
229
+ document.body.appendChild(parent);
230
+ const first = document.createElement('after-observer');
231
+ const second = document.createElement('i');
232
+
233
+ receiver.after(first, second);
234
+
235
+ expect(observedChildren).toEqual([before, receiver, first, second, after]);
236
+ expect([...parent.childNodes]).toEqual([
237
+ before,
238
+ receiver,
239
+ first,
240
+ second,
241
+ after,
242
+ ]);
243
+ });
244
+
245
+ it('commits arguments before disconnecting a moved custom element', () => {
246
+ const parent = document.createElement('div');
247
+ const receiver = document.createElement('em');
248
+ const trailing = document.createElement('strong');
249
+ const second = document.createElement('span');
250
+ const reactions: string[] = [];
251
+ let observedChildren: unknown[] = [];
252
+
253
+ class MovingAfterElement extends PolyfillHTMLElement {
254
+ connectedCallback() {
255
+ reactions.push('connected');
256
+ }
257
+
258
+ disconnectedCallback() {
259
+ reactions.push('disconnected');
260
+ observedChildren = [...parent.childNodes];
261
+ }
262
+ }
263
+
264
+ polyfillWindow.customElements.define(
265
+ 'moving-after-element',
266
+ MovingAfterElement as unknown as CustomElementConstructor,
267
+ );
268
+
269
+ const first = document.createElement('moving-after-element');
270
+ parent.append(receiver, trailing);
271
+ document.body.append(parent, first);
272
+ reactions.length = 0;
273
+
274
+ receiver.after(first, second);
275
+
276
+ expect([...parent.childNodes]).toEqual([receiver, first, second, trailing]);
277
+ expect(observedChildren).toEqual([receiver, first, second, trailing]);
278
+ expect(reactions).toEqual(['disconnected', 'connected']);
279
+ });
280
+
281
+ it('rejects a cyclic later argument before mutating connected trees', () => {
282
+ let callbacks = 0;
283
+
284
+ class AfterReplacement extends PolyfillHTMLElement {
285
+ connectedCallback() {
286
+ callbacks += 1;
287
+ }
288
+
289
+ disconnectedCallback() {
290
+ callbacks += 1;
291
+ }
292
+ }
293
+
294
+ polyfillWindow.customElements.define(
295
+ 'after-replacement',
296
+ AfterReplacement as unknown as CustomElementConstructor,
297
+ );
298
+ const parent = document.createElement('div');
299
+ const receiver = document.createElement('em');
300
+ const trailing = document.createElement('strong');
301
+ parent.append(receiver, trailing);
302
+ const source = document.createElement('div');
303
+ const replacement = document.createElement('after-replacement');
304
+ source.appendChild(replacement);
305
+ document.body.append(parent, source);
306
+ callbacks = 0;
307
+ const mutations: string[] = [];
308
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
309
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
310
+
311
+ expect(() => receiver.after(replacement, parent)).toThrow();
312
+
313
+ expect([...document.body.childNodes]).toEqual([parent, source]);
314
+ expect([...parent.childNodes]).toEqual([receiver, trailing]);
315
+ expect([...source.childNodes]).toEqual([replacement]);
316
+ expect(replacement.parentNode).toBe(source);
317
+ expect(replacement.isConnected).toBe(true);
318
+ expect(mutations).toEqual([]);
319
+ expect(callbacks).toBe(0);
320
+ });
321
+ });
322
+
323
+ describe.each(['before', 'after', 'replaceWith'] as const)(
324
+ 'ChildNode.%s prevalidation',
325
+ (method) => {
326
+ it('converts arguments for a detached receiver', () => {
327
+ const receiver = document.createElement('span');
328
+ let conversions = 0;
329
+ const value = {
330
+ toString() {
331
+ conversions += 1;
332
+ return 'converted';
333
+ },
334
+ };
335
+
336
+ (receiver as any)[method](value);
337
+
338
+ expect(conversions).toBe(1);
339
+ expect(receiver.parentNode).toBeNull();
340
+ });
341
+
342
+ it('uses the receiver parent after argument conversion', () => {
343
+ const originalParent = document.createElement('div');
344
+ const convertedParent = document.createElement('section');
345
+ const receiver = document.createElement('span');
346
+ originalParent.appendChild(receiver);
347
+ const value = {
348
+ toString() {
349
+ convertedParent.appendChild(receiver);
350
+ return 'converted';
351
+ },
352
+ };
353
+
354
+ (receiver as any)[method](value);
355
+
356
+ expect(originalParent.childNodes).toHaveLength(0);
357
+ if (method === 'before') {
358
+ expect([...convertedParent.childNodes]).toEqual([
359
+ expect.objectContaining({data: 'converted'}),
360
+ receiver,
361
+ ]);
362
+ } else if (method === 'after') {
363
+ expect([...convertedParent.childNodes]).toEqual([
364
+ receiver,
365
+ expect.objectContaining({data: 'converted'}),
366
+ ]);
367
+ } else {
368
+ expect([...convertedParent.childNodes]).toEqual([
369
+ expect.objectContaining({data: 'converted'}),
370
+ ]);
371
+ expect(receiver.parentNode).toBeNull();
372
+ }
373
+ });
374
+
375
+ it('converts earlier arguments before rejecting a later cycle', () => {
376
+ const parent = document.createElement('div');
377
+ const receiver = document.createElement('span');
378
+ parent.appendChild(receiver);
379
+ const conversionError = new TypeError('conversion failed');
380
+ let conversions = 0;
381
+ let thrown: unknown;
382
+ const value = {
383
+ toString() {
384
+ conversions++;
385
+ throw conversionError;
386
+ },
387
+ };
388
+
389
+ try {
390
+ (receiver as any)[method](value, parent);
391
+ } catch (error) {
392
+ thrown = error;
393
+ }
394
+
395
+ expect(conversions).toBe(1);
396
+ expect(thrown).toBe(conversionError);
397
+ expect([...parent.childNodes]).toEqual([receiver]);
398
+ });
399
+
400
+ it('rejects template-host cycles before moving earlier arguments', () => {
401
+ const holder = document.createElement('div');
402
+ const template = document.createElement('template');
403
+ const receiver = document.createElement('span');
404
+ const source = document.createElement('div');
405
+ const movable = document.createElement('em');
406
+ holder.appendChild(template);
407
+ template.content.appendChild(receiver);
408
+ source.appendChild(movable);
409
+ const hooks: string[] = [];
410
+ polyfillWindow[HOOKS].removeChild = () => hooks.push('remove');
411
+ polyfillWindow[HOOKS].insertChild = () => hooks.push('insert');
412
+
413
+ expect(() => (receiver as any)[method](movable, template)).toThrow();
414
+
415
+ expect([...holder.childNodes]).toEqual([template]);
416
+ expect([...template.content.childNodes]).toEqual([receiver]);
417
+ expect([...source.childNodes]).toEqual([movable]);
418
+ expect(hooks).toEqual([]);
419
+ });
420
+ },
421
+ );
@@ -0,0 +1,251 @@
1
+ import {beforeEach, describe, expect, it} from 'vitest';
2
+
3
+ import {HOOKS, HTMLElement as PolyfillHTMLElement, Window} from '../index.ts';
4
+
5
+ let polyfillWindow: Window;
6
+
7
+ beforeEach(() => {
8
+ polyfillWindow = new Window();
9
+ Window.setGlobalThis(polyfillWindow);
10
+ });
11
+
12
+ describe('ChildNode.replaceWith', () => {
13
+ it('inserts multiple nodes and strings in argument order', () => {
14
+ const parent = document.createElement('div');
15
+ const receiver = document.createElement('span');
16
+ const existingNext = document.createElement('em');
17
+ const trailing = document.createElement('strong');
18
+ const first = document.createElement('i');
19
+ const last = document.createElement('b');
20
+ parent.append(receiver, existingNext, trailing);
21
+
22
+ receiver.replaceWith(first, existingNext, 'middle', last);
23
+
24
+ const children = [...parent.childNodes];
25
+ expect(children).toEqual([
26
+ first,
27
+ existingNext,
28
+ expect.objectContaining({data: 'middle'}),
29
+ last,
30
+ trailing,
31
+ ]);
32
+ expect(children[1]!.nextSibling).toBe(children[2]);
33
+ expect(children[2]!.nextSibling).toBe(last);
34
+ expect(receiver.parentNode).toBeNull();
35
+ });
36
+
37
+ it('preserves argument order when the receiver is a replacement', () => {
38
+ const parent = document.createElement('div');
39
+ const receiver = document.createElement('span');
40
+ const replacement = document.createElement('em');
41
+ const after = document.createElement('strong');
42
+ parent.append(receiver, after);
43
+
44
+ receiver.replaceWith(replacement, receiver);
45
+
46
+ expect([...parent.childNodes]).toEqual([replacement, receiver, after]);
47
+ expect(replacement.nextSibling).toBe(receiver);
48
+ expect(receiver.previousSibling).toBe(replacement);
49
+ expect(receiver.nextSibling).toBe(after);
50
+ });
51
+
52
+ it('rejects a cyclic first argument without mutating the tree', () => {
53
+ let reactions = 0;
54
+
55
+ class CyclicReceiver extends PolyfillHTMLElement {
56
+ disconnectedCallback() {
57
+ reactions += 1;
58
+ }
59
+ }
60
+
61
+ polyfillWindow.customElements.define(
62
+ 'cyclic-receiver',
63
+ CyclicReceiver as unknown as CustomElementConstructor,
64
+ );
65
+ const parent = document.createElement('div');
66
+ const before = document.createElement('span');
67
+ const receiver = document.createElement('cyclic-receiver');
68
+ const after = document.createElement('strong');
69
+ parent.append(before, receiver, after);
70
+ document.body.appendChild(parent);
71
+ const mutations: string[] = [];
72
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
73
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
74
+
75
+ expect(() => receiver.replaceWith(parent)).toThrow();
76
+
77
+ expect([...document.body.childNodes]).toEqual([parent]);
78
+ expect([...parent.childNodes]).toEqual([before, receiver, after]);
79
+ expect(before.nextSibling).toBe(receiver);
80
+ expect(receiver.previousSibling).toBe(before);
81
+ expect(receiver.nextSibling).toBe(after);
82
+ expect(after.previousSibling).toBe(receiver);
83
+ expect(mutations).toEqual([]);
84
+ expect(reactions).toBe(0);
85
+ });
86
+
87
+ it('rejects a cyclic later argument before mutating connected trees', () => {
88
+ let callbacks = 0;
89
+
90
+ class ConnectedReplacement extends PolyfillHTMLElement {
91
+ connectedCallback() {
92
+ callbacks += 1;
93
+ }
94
+
95
+ disconnectedCallback() {
96
+ callbacks += 1;
97
+ }
98
+ }
99
+
100
+ polyfillWindow.customElements.define(
101
+ 'connected-replacement',
102
+ ConnectedReplacement as unknown as CustomElementConstructor,
103
+ );
104
+ const parent = document.createElement('div');
105
+ const before = document.createElement('span');
106
+ const receiver = document.createElement('em');
107
+ const after = document.createElement('strong');
108
+ parent.append(before, receiver, after);
109
+ const source = document.createElement('div');
110
+ const replacement = document.createElement('connected-replacement');
111
+ const sourceSibling = document.createElement('i');
112
+ source.append(replacement, sourceSibling);
113
+ document.body.append(parent, source);
114
+ callbacks = 0;
115
+ const mutations: string[] = [];
116
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
117
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
118
+
119
+ expect(() => receiver.replaceWith(replacement, parent)).toThrow();
120
+
121
+ expect(parent.parentNode).toBe(document.body);
122
+ expect([...document.body.childNodes]).toEqual([parent, source]);
123
+ expect([...parent.childNodes]).toEqual([before, receiver, after]);
124
+ expect(before.nextSibling).toBe(receiver);
125
+ expect(receiver.previousSibling).toBe(before);
126
+ expect(receiver.nextSibling).toBe(after);
127
+ expect(after.previousSibling).toBe(receiver);
128
+ expect([...source.childNodes]).toEqual([replacement, sourceSibling]);
129
+ expect(replacement.parentNode).toBe(source);
130
+ expect(replacement.isConnected).toBe(true);
131
+ expect(mutations).toEqual([]);
132
+ expect(callbacks).toBe(0);
133
+ });
134
+
135
+ it('commits replacements before disconnecting a moved custom element', () => {
136
+ const parent = document.createElement('div');
137
+ const receiver = document.createElement('em');
138
+ const second = document.createElement('span');
139
+ const reactions: string[] = [];
140
+ let observedChildren: unknown[] = [];
141
+
142
+ class MovingElement extends PolyfillHTMLElement {
143
+ connectedCallback() {
144
+ reactions.push('connected');
145
+ }
146
+
147
+ disconnectedCallback() {
148
+ reactions.push('disconnected');
149
+ observedChildren = [...parent.childNodes];
150
+ }
151
+ }
152
+
153
+ polyfillWindow.customElements.define(
154
+ 'moving-element',
155
+ MovingElement as unknown as CustomElementConstructor,
156
+ );
157
+
158
+ const first = document.createElement('moving-element');
159
+ parent.appendChild(receiver);
160
+ document.body.append(parent, first);
161
+ reactions.length = 0;
162
+
163
+ receiver.replaceWith(first, second);
164
+
165
+ expect([...parent.childNodes]).toEqual([first, second]);
166
+ expect(observedChildren).toEqual([first, second]);
167
+ expect(reactions).toEqual(['disconnected', 'connected']);
168
+ });
169
+
170
+ it('commits links before removal hooks trigger nested reactions', () => {
171
+ const parent = document.createElement('div');
172
+ const receiver = document.createElement('em');
173
+ const second = document.createElement('span');
174
+ const events: string[] = [];
175
+ let hookObservedChildren: unknown[] = [];
176
+
177
+ class HookMovedElement extends PolyfillHTMLElement {
178
+ static observedAttributes = ['data-state'];
179
+
180
+ connectedCallback() {
181
+ events.push('connected');
182
+ }
183
+
184
+ disconnectedCallback() {
185
+ events.push('disconnected');
186
+ }
187
+
188
+ attributeChangedCallback() {
189
+ events.push('attribute');
190
+ }
191
+ }
192
+
193
+ polyfillWindow.customElements.define(
194
+ 'hook-moved-element',
195
+ HookMovedElement as unknown as CustomElementConstructor,
196
+ );
197
+
198
+ const first = document.createElement('hook-moved-element');
199
+ parent.appendChild(receiver);
200
+ document.body.append(parent, first);
201
+ events.length = 0;
202
+ polyfillWindow[HOOKS].removeChild = (_parent, child) => {
203
+ if (child !== first) return;
204
+ hookObservedChildren = [...parent.childNodes];
205
+ first.setAttribute('data-state', 'moved');
206
+ events.push('hook-return');
207
+ };
208
+
209
+ receiver.replaceWith(first, second);
210
+
211
+ expect(hookObservedChildren).toEqual([first, second]);
212
+ expect(events).toEqual([
213
+ 'disconnected',
214
+ 'connected',
215
+ 'attribute',
216
+ 'hook-return',
217
+ ]);
218
+ });
219
+
220
+ it('commits every replacement before running reentrant reactions', () => {
221
+ let parent: HTMLElement;
222
+ let first: HTMLElement;
223
+ let second: HTMLElement;
224
+ let observedChildren: unknown[] = [];
225
+
226
+ class ReentrantReceiver extends PolyfillHTMLElement {
227
+ disconnectedCallback() {
228
+ observedChildren = [...parent.childNodes];
229
+ second.remove();
230
+ }
231
+ }
232
+
233
+ polyfillWindow.customElements.define(
234
+ 'reentrant-receiver',
235
+ ReentrantReceiver as unknown as CustomElementConstructor,
236
+ );
237
+ parent = document.createElement('div');
238
+ const receiver = document.createElement('reentrant-receiver');
239
+ first = document.createElement('span');
240
+ second = document.createElement('strong');
241
+ parent.appendChild(receiver);
242
+ document.body.appendChild(parent);
243
+
244
+ receiver.replaceWith(first, second);
245
+
246
+ expect(observedChildren).toEqual([first, second]);
247
+ expect([...parent.childNodes]).toEqual([first]);
248
+ expect(receiver.parentNode).toBeNull();
249
+ expect(second.parentNode).toBeNull();
250
+ });
251
+ });