@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,484 @@
1
+ import {beforeEach, describe, expect, it} from 'vitest';
2
+
3
+ import {HOOKS, Window} from '../index.ts';
4
+ import {PARENT} from '../constants.ts';
5
+ import {createDOMException} from '../dom-exception.ts';
6
+ import {parseSelector} from '../selectors.ts';
7
+
8
+ let polyfillWindow: Window;
9
+
10
+ beforeEach(() => {
11
+ polyfillWindow = new Window();
12
+ Window.setGlobalThis(polyfillWindow);
13
+ });
14
+
15
+ function expectDOMError(operation: () => unknown, name: string) {
16
+ try {
17
+ operation();
18
+ } catch (error) {
19
+ expect(error).toBeInstanceOf(Error);
20
+ expect(error).toMatchObject({name});
21
+ return;
22
+ }
23
+
24
+ throw new Error(`Expected ${name}`);
25
+ }
26
+
27
+ describe('DOM mutation errors', () => {
28
+ it('reports invalid child and reference nodes as NotFoundError without mutations', () => {
29
+ const parent = document.createElement('div');
30
+ const child = document.createElement('span');
31
+ const foreignParent = document.createElement('section');
32
+ const foreignChild = document.createElement('em');
33
+ parent.appendChild(child);
34
+ foreignParent.appendChild(foreignChild);
35
+ const mutations: string[] = [];
36
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
37
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
38
+
39
+ expectDOMError(() => parent.removeChild(foreignChild), 'NotFoundError');
40
+ expectDOMError(
41
+ () => parent.insertBefore(foreignChild, foreignChild),
42
+ 'NotFoundError',
43
+ );
44
+ expectDOMError(
45
+ () => parent.replaceChild(foreignChild, foreignChild),
46
+ 'NotFoundError',
47
+ );
48
+
49
+ expect([...parent.childNodes]).toEqual([child]);
50
+ expect([...foreignParent.childNodes]).toEqual([foreignChild]);
51
+ expect(child.parentNode).toBe(parent);
52
+ expect(foreignChild.parentNode).toBe(foreignParent);
53
+ expect(mutations).toEqual([]);
54
+ });
55
+
56
+ it('reports hierarchy violations as HierarchyRequestError without mutations', () => {
57
+ const ancestor = document.createElement('section');
58
+ const parent = document.createElement('div');
59
+ const child = document.createElement('span');
60
+ ancestor.appendChild(parent);
61
+ parent.appendChild(child);
62
+ const mutations: string[] = [];
63
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
64
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
65
+
66
+ expectDOMError(
67
+ () => parent.replaceChild(ancestor, child),
68
+ 'HierarchyRequestError',
69
+ );
70
+
71
+ expect([...ancestor.childNodes]).toEqual([parent]);
72
+ expect([...parent.childNodes]).toEqual([child]);
73
+ expect(parent.parentNode).toBe(ancestor);
74
+ expect(child.parentNode).toBe(parent);
75
+ expect(mutations).toEqual([]);
76
+ });
77
+
78
+ it('reports hierarchy errors before invalid reference errors', () => {
79
+ const parent = document.createElement('div');
80
+ const child = document.createElement('span');
81
+ const foreignParent = document.createElement('section');
82
+ const foreignChild = document.createElement('em');
83
+ parent.appendChild(child);
84
+ foreignParent.appendChild(foreignChild);
85
+ const mutations: string[] = [];
86
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
87
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
88
+
89
+ expectDOMError(
90
+ () => parent.insertBefore(parent, foreignChild),
91
+ 'HierarchyRequestError',
92
+ );
93
+ expectDOMError(
94
+ () => parent.replaceChild(parent, foreignChild),
95
+ 'HierarchyRequestError',
96
+ );
97
+
98
+ expect([...parent.childNodes]).toEqual([child]);
99
+ expect([...foreignParent.childNodes]).toEqual([foreignChild]);
100
+ expect(parent.parentNode).toBeNull();
101
+ expect(child.parentNode).toBe(parent);
102
+ expect(foreignChild.parentNode).toBe(foreignParent);
103
+ expect(mutations).toEqual([]);
104
+ });
105
+
106
+ it('validates host-including hierarchy before references and variadic commits', () => {
107
+ const sourceWindow = new Window();
108
+ const sourceDocument = sourceWindow.document;
109
+ const destinationDocument = polyfillWindow.document;
110
+ const holder = destinationDocument.createElement('div');
111
+ const template = destinationDocument.createElement('template');
112
+ const content = template.content;
113
+ const foreignParent = destinationDocument.createElement('section');
114
+ const foreignReference = destinationDocument.createElement('span');
115
+ const sourceParent = sourceDocument.createElement('div');
116
+ const movable = sourceDocument.createElement('atomic-host-node');
117
+ let reactions = 0;
118
+ (movable as any).connectedCallback = () => reactions++;
119
+ (movable as any).disconnectedCallback = () => reactions++;
120
+ holder.appendChild(template);
121
+ foreignParent.appendChild(foreignReference);
122
+ sourceDocument.body.appendChild(sourceParent);
123
+ sourceParent.appendChild(movable);
124
+ reactions = 0;
125
+
126
+ const sourceHooks: string[] = [];
127
+ const destinationHooks: string[] = [];
128
+ sourceWindow[HOOKS] = {
129
+ insertChild: () => sourceHooks.push('insert'),
130
+ removeChild: () => sourceHooks.push('remove'),
131
+ };
132
+ polyfillWindow[HOOKS] = {
133
+ insertChild: () => destinationHooks.push('insert'),
134
+ removeChild: () => destinationHooks.push('remove'),
135
+ };
136
+
137
+ expectDOMError(
138
+ () => content.insertBefore(template, foreignReference),
139
+ 'HierarchyRequestError',
140
+ );
141
+ expectDOMError(
142
+ () => content.append(movable, template),
143
+ 'HierarchyRequestError',
144
+ );
145
+
146
+ expect([...holder.childNodes]).toEqual([template]);
147
+ expect([...content.childNodes]).toEqual([]);
148
+ expect([...foreignParent.childNodes]).toEqual([foreignReference]);
149
+ expect([...sourceParent.childNodes]).toEqual([movable]);
150
+ expect(template.parentNode).toBe(holder);
151
+ expect(movable.parentNode).toBe(sourceParent);
152
+ expect(movable.ownerDocument).toBe(sourceDocument);
153
+ expect(sourceHooks).toEqual([]);
154
+ expect(destinationHooks).toEqual([]);
155
+ expect(reactions).toBe(0);
156
+ });
157
+
158
+ it.each([
159
+ [
160
+ 'append',
161
+ (parent: Element, child: Element) => parent.append(child, parent),
162
+ ],
163
+ [
164
+ 'prepend',
165
+ (parent: Element, child: Element) => parent.prepend(child, parent),
166
+ ],
167
+ [
168
+ 'replaceChildren',
169
+ (parent: Element, child: Element) =>
170
+ parent.replaceChildren(child, parent),
171
+ ],
172
+ [
173
+ 'before',
174
+ (parent: Element, child: Element) =>
175
+ parent.firstElementChild!.before(child, parent),
176
+ ],
177
+ [
178
+ 'after',
179
+ (parent: Element, child: Element) =>
180
+ parent.firstElementChild!.after(child, parent),
181
+ ],
182
+ [
183
+ 'replaceWith',
184
+ (parent: Element, child: Element) =>
185
+ parent.firstElementChild!.replaceWith(child, parent),
186
+ ],
187
+ ])('validates every %s argument before changing state', (_name, mutate) => {
188
+ const parent = document.createElement('div');
189
+ const existing = document.createElement('span');
190
+ const source = document.createElement('section');
191
+ const movable = document.createElement('em');
192
+ parent.appendChild(existing);
193
+ source.appendChild(movable);
194
+ const mutations: string[] = [];
195
+ polyfillWindow[HOOKS].insertChild = () => mutations.push('insert');
196
+ polyfillWindow[HOOKS].removeChild = () => mutations.push('remove');
197
+
198
+ expectDOMError(() => mutate(parent, movable), 'HierarchyRequestError');
199
+
200
+ expect([...parent.childNodes]).toEqual([existing]);
201
+ expect([...source.childNodes]).toEqual([movable]);
202
+ expect(existing.parentNode).toBe(parent);
203
+ expect(movable.parentNode).toBe(source);
204
+ expect(mutations).toEqual([]);
205
+ });
206
+
207
+ it.each([
208
+ [
209
+ 'append',
210
+ (parent: Element, child: Element) => parent.append(child),
211
+ (parent: Element, child: Element) => parent.appendChild(child),
212
+ ],
213
+ [
214
+ 'prepend',
215
+ (parent: Element, child: Element) => parent.prepend(child),
216
+ (parent: Element, child: Element) =>
217
+ parent.insertBefore(child, parent.firstChild),
218
+ ],
219
+ ])(
220
+ 'does not add a second hierarchy walk for single-node %s',
221
+ (_name, mutate, directMutation) => {
222
+ const countParentReads = (
223
+ operation: (parent: Element, child: Element) => unknown,
224
+ ) => {
225
+ const parent = document.createElement('div');
226
+ const child = document.createElement('span');
227
+ let parentReads = 0;
228
+ Object.defineProperty(parent, PARENT, {
229
+ configurable: true,
230
+ get() {
231
+ parentReads += 1;
232
+ return null;
233
+ },
234
+ });
235
+
236
+ operation(parent, child);
237
+ expect(parent.firstElementChild).toBe(child);
238
+ return parentReads;
239
+ };
240
+
241
+ expect(countParentReads(mutate)).toBe(countParentReads(directMutation));
242
+ },
243
+ );
244
+
245
+ it.each([
246
+ [
247
+ 'append',
248
+ (parent: any, child: any, invalid: any) => parent.append(child, invalid),
249
+ ],
250
+ [
251
+ 'prepend',
252
+ (parent: any, child: any, invalid: any) => parent.prepend(child, invalid),
253
+ ],
254
+ [
255
+ 'replaceChildren',
256
+ (parent: any, child: any, invalid: any) =>
257
+ parent.replaceChildren(child, invalid),
258
+ ],
259
+ [
260
+ 'before',
261
+ (parent: any, child: any, invalid: any) =>
262
+ parent.firstElementChild!.before(child, invalid),
263
+ ],
264
+ [
265
+ 'after',
266
+ (parent: any, child: any, invalid: any) =>
267
+ parent.firstElementChild!.after(child, invalid),
268
+ ],
269
+ [
270
+ 'replaceWith',
271
+ (parent: any, child: any, invalid: any) =>
272
+ parent.firstElementChild!.replaceWith(child, invalid),
273
+ ],
274
+ ])(
275
+ 'converts every %s argument before moving cross-document nodes',
276
+ (_name, mutate) => {
277
+ const sourceWindow = new Window();
278
+ const sourceDocument = sourceWindow.document;
279
+ const destinationDocument = polyfillWindow.document;
280
+ const sourceParent = sourceDocument.createElement('section');
281
+ const movable = sourceDocument.createElement('atomic-node');
282
+ const parent = destinationDocument.createElement('div');
283
+ const existing = destinationDocument.createElement('span');
284
+ let reactions = 0;
285
+ (movable as any).connectedCallback = () => reactions++;
286
+ (movable as any).disconnectedCallback = () => reactions++;
287
+ sourceDocument.body.appendChild(sourceParent);
288
+ sourceParent.appendChild(movable);
289
+ destinationDocument.body.appendChild(parent);
290
+ parent.appendChild(existing);
291
+ reactions = 0;
292
+
293
+ const sourceHooks: string[] = [];
294
+ const destinationHooks: string[] = [];
295
+ sourceWindow[HOOKS] = {
296
+ createText: () => sourceHooks.push('createText'),
297
+ insertChild: () => sourceHooks.push('insert'),
298
+ removeChild: () => sourceHooks.push('remove'),
299
+ };
300
+ polyfillWindow[HOOKS] = {
301
+ createText: () => destinationHooks.push('createText'),
302
+ insertChild: () => destinationHooks.push('insert'),
303
+ removeChild: () => destinationHooks.push('remove'),
304
+ };
305
+ const conversionError = new Error('conversion failed');
306
+ const invalid = {
307
+ toString() {
308
+ throw conversionError;
309
+ },
310
+ };
311
+
312
+ expect(() => mutate(parent, movable, invalid)).toThrow(conversionError);
313
+
314
+ expect([...parent.childNodes]).toEqual([existing]);
315
+ expect([...sourceParent.childNodes]).toEqual([movable]);
316
+ expect(existing.parentNode).toBe(parent);
317
+ expect(movable.parentNode).toBe(sourceParent);
318
+ expect(movable.ownerDocument).toBe(sourceDocument);
319
+ expect(sourceHooks).toEqual([]);
320
+ expect(destinationHooks).toEqual([]);
321
+ expect(reactions).toBe(0);
322
+ },
323
+ );
324
+ });
325
+
326
+ describe('selector syntax errors and EOF recovery', () => {
327
+ it.each(['div:has(span', 'div:not(.missing', 'div:has(> span'])(
328
+ 'recovers supported function selector %j at EOF',
329
+ (selector) => {
330
+ const root = document.createElement('section');
331
+ root.innerHTML = '<div data-kind="item"><span></span></div>';
332
+ const expected = root.firstElementChild;
333
+
334
+ expect(() => parseSelector(selector)).not.toThrow();
335
+ expect(root.querySelector(selector)).toBe(expected);
336
+ expect(root.querySelectorAll(selector)).toEqual([expected]);
337
+ },
338
+ );
339
+
340
+ it.each([
341
+ ['[data-open', 'data-open', '', false],
342
+ ['[data-kind=item', 'data-kind', 'item', false],
343
+ ['[data-kind="item', 'data-kind', 'item', false],
344
+ ['[data-kind="item]', 'data-kind', 'item]', false],
345
+ ['[data-kind=\'item"]', 'data-kind', 'item"]', false],
346
+ [':has([data-kind="item])', 'data-kind', 'item])', true],
347
+ [':has([data-kind=\'item"])', 'data-kind', 'item"])', true],
348
+ ['article:has([data-kind="item]) span', 'data-kind', 'item]) span', true],
349
+ ])(
350
+ 'recovers attribute selector %j at EOF',
351
+ (selector, attribute, value, nested) => {
352
+ const root = document.createElement('section');
353
+ const wrapper = document.createElement('article');
354
+ const child = document.createElement('div');
355
+ child.setAttribute(attribute as string, value);
356
+ wrapper.appendChild(child);
357
+ root.appendChild(wrapper);
358
+ const expected = nested ? wrapper : child;
359
+
360
+ expect(() => parseSelector(selector as string)).not.toThrow();
361
+ expect(root.querySelector(selector as string)).toBe(expected);
362
+ expect(root.querySelectorAll(selector as string)).toEqual([expected]);
363
+ },
364
+ );
365
+
366
+ it('recovers an EOF attribute after a complete compound member', () => {
367
+ const root = document.createElement('section');
368
+ const child = document.createElement('div');
369
+ child.setAttribute('data-kind', 'a');
370
+ child.setAttribute('other', 'b');
371
+ root.appendChild(child);
372
+ const selector = '[data-kind="a"][other="b';
373
+
374
+ expect(() => parseSelector(selector)).not.toThrow();
375
+ expect(root.querySelector(selector)).toBe(child);
376
+ expect(root.querySelectorAll(selector)).toEqual([child]);
377
+ });
378
+
379
+ it.each([
380
+ '',
381
+ ' ',
382
+ 'div)',
383
+ 'div, span',
384
+ ':hover',
385
+ ':HOVER',
386
+ ':matches(div)',
387
+ ':has(:hover)',
388
+ 'div >',
389
+ 'div >> span',
390
+ 'div:has()',
391
+ 'div:has(span))',
392
+ '[data-kind^=item]',
393
+ '[data-kind=item"value]',
394
+ '[data-kind==item]',
395
+ '[data-kind="item" junk]',
396
+ '[data-kind=#item]',
397
+ '[data-kind]div',
398
+ ':not(.missing)div',
399
+ ])('reports malformed or unsupported %j as SyntaxError', (selector) => {
400
+ const parent = document.createElement('div');
401
+ parent.appendChild(document.createElement('span'));
402
+
403
+ expectDOMError(() => parseSelector(selector), 'SyntaxError');
404
+ expectDOMError(() => parent.querySelector(selector), 'SyntaxError');
405
+ expectDOMError(() => parent.querySelectorAll(selector), 'SyntaxError');
406
+ });
407
+ });
408
+
409
+ describe('DOMException fallback', () => {
410
+ it('creates a named Error when the DOMException global is unavailable', () => {
411
+ const descriptor = Object.getOwnPropertyDescriptor(
412
+ globalThis,
413
+ 'DOMException',
414
+ );
415
+ Object.defineProperty(globalThis, 'DOMException', {
416
+ configurable: true,
417
+ value: undefined,
418
+ writable: true,
419
+ });
420
+
421
+ try {
422
+ const error = createDOMException('Invalid tree', 'HierarchyRequestError');
423
+ expect(error).toBeInstanceOf(Error);
424
+ expect(error).toMatchObject({
425
+ message: 'Invalid tree',
426
+ name: 'HierarchyRequestError',
427
+ });
428
+ } finally {
429
+ if (descriptor) {
430
+ Object.defineProperty(globalThis, 'DOMException', descriptor);
431
+ } else {
432
+ delete (globalThis as {DOMException?: typeof DOMException})
433
+ .DOMException;
434
+ }
435
+ }
436
+ });
437
+
438
+ it('preserves public error names without the DOMException global', () => {
439
+ const descriptor = Object.getOwnPropertyDescriptor(
440
+ globalThis,
441
+ 'DOMException',
442
+ );
443
+ Object.defineProperty(globalThis, 'DOMException', {
444
+ configurable: true,
445
+ value: undefined,
446
+ writable: true,
447
+ });
448
+
449
+ try {
450
+ const parent = document.createElement('div');
451
+ const foreignChild = document.createElement('span');
452
+ const firstElement = document.createElement('section');
453
+ const secondElement = document.createElement('section');
454
+ firstElement.setAttribute('shared', 'value');
455
+ const sharedAttribute = firstElement.attributes.item(0)!;
456
+ class InvalidElement extends HTMLElement {}
457
+
458
+ const operations: [() => unknown, string][] = [
459
+ [() => parent.append(parent), 'HierarchyRequestError'],
460
+ [() => parent.removeChild(foreignChild), 'NotFoundError'],
461
+ [() => parent.querySelector(''), 'SyntaxError'],
462
+ [() => document.createElement('invalid name'), 'InvalidCharacterError'],
463
+ [() => document.createElementNS(null, 'p:name'), 'NamespaceError'],
464
+ [() => customElements.define('invalid', InvalidElement), 'SyntaxError'],
465
+ [() => document.importNode(document), 'NotSupportedError'],
466
+ [
467
+ () => secondElement.attributes.setNamedItem(sharedAttribute),
468
+ 'InUseAttributeError',
469
+ ],
470
+ ];
471
+
472
+ for (const [operation, name] of operations) {
473
+ expectDOMError(operation, name);
474
+ }
475
+ } finally {
476
+ if (descriptor) {
477
+ Object.defineProperty(globalThis, 'DOMException', descriptor);
478
+ } else {
479
+ delete (globalThis as {DOMException?: typeof DOMException})
480
+ .DOMException;
481
+ }
482
+ }
483
+ });
484
+ });