@remote-dom/polyfill 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +88 -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 +6 -2
  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 +12 -2
  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,584 @@
1
+ import {describe, expect, it, vi} from 'vitest';
2
+
3
+ import {HOOKS, Window} from '../index.ts';
4
+ import {CHILD, CONTENT, PARENT} from '../constants.ts';
5
+ import type {HTMLTemplateElement} from '../HTMLTemplateElement.ts';
6
+ import {adoptNodes, collectAdoptionSnapshot} from '../shared.ts';
7
+
8
+ const DEEP_TEMPLATE_CONTENT_DEPTH = 6_000;
9
+
10
+ describe('cross-document insertion', () => {
11
+ it('adopts a nested subtree and its attached attributes', () => {
12
+ const sourceWindow = new Window();
13
+ const destinationWindow = new Window();
14
+ const sourceDocument = sourceWindow.document;
15
+ const destinationDocument = destinationWindow.document;
16
+
17
+ const root = sourceDocument.createElement('section');
18
+ const descendant = sourceDocument.createElement('span');
19
+ const text = sourceDocument.createTextNode('before');
20
+ root.setAttribute('data-root', 'root');
21
+ descendant.setAttribute('data-descendant', 'before');
22
+ descendant.appendChild(text);
23
+ root.appendChild(descendant);
24
+
25
+ const rootAttribute = root.attributes.getNamedItem('data-root')!;
26
+ const descendantAttribute =
27
+ descendant.attributes.getNamedItem('data-descendant')!;
28
+ const sourceSetAttribute = vi.fn();
29
+ const sourceSetText = vi.fn();
30
+ const sourceInsertChild = vi.fn();
31
+ const destinationSetAttribute = vi.fn();
32
+ const destinationSetText = vi.fn();
33
+ const destinationInsertChild = vi.fn();
34
+
35
+ sourceWindow[HOOKS] = {
36
+ setAttribute: sourceSetAttribute,
37
+ setText: sourceSetText,
38
+ insertChild: sourceInsertChild,
39
+ };
40
+ destinationWindow[HOOKS] = {
41
+ setAttribute: destinationSetAttribute,
42
+ setText: destinationSetText,
43
+ insertChild: destinationInsertChild,
44
+ };
45
+
46
+ destinationDocument.body.appendChild(root);
47
+
48
+ expect(root.ownerDocument).toBe(destinationDocument);
49
+ expect(descendant.ownerDocument).toBe(destinationDocument);
50
+ expect(text.ownerDocument).toBe(destinationDocument);
51
+ expect(rootAttribute.ownerDocument).toBe(destinationDocument);
52
+ expect(descendantAttribute.ownerDocument).toBe(destinationDocument);
53
+
54
+ descendant.setAttribute('data-added', 'value');
55
+ descendantAttribute.value = 'after';
56
+ text.data = 'after';
57
+ descendant.appendChild(destinationDocument.createElement('strong'));
58
+
59
+ expect(sourceSetAttribute).not.toHaveBeenCalled();
60
+ expect(sourceSetText).not.toHaveBeenCalled();
61
+ expect(sourceInsertChild).not.toHaveBeenCalled();
62
+ expect(destinationSetAttribute).toHaveBeenCalledTimes(2);
63
+ expect(destinationSetText).toHaveBeenCalledTimes(1);
64
+ expect(destinationInsertChild).toHaveBeenCalledTimes(2);
65
+ });
66
+
67
+ it('adopts initialized template content and routes later mutations through the destination hooks', () => {
68
+ const sourceWindow = new Window();
69
+ const destinationWindow = new Window();
70
+ const sourceDocument = sourceWindow.document;
71
+ const destinationDocument = destinationWindow.document;
72
+ const template = sourceDocument.createElement(
73
+ 'template',
74
+ ) as HTMLTemplateElement;
75
+ const content = template.content;
76
+ const contentElement = sourceDocument.createElement('span');
77
+ const text = sourceDocument.createTextNode('before');
78
+ contentElement.setAttribute('data-content', 'before');
79
+ contentElement.appendChild(text);
80
+ content.appendChild(contentElement);
81
+
82
+ const attribute = contentElement.attributes.getNamedItem('data-content')!;
83
+ const sourceSetAttribute = vi.fn();
84
+ const sourceSetText = vi.fn();
85
+ const sourceInsertChild = vi.fn();
86
+ const destinationSetAttribute = vi.fn();
87
+ const destinationSetText = vi.fn();
88
+ const destinationInsertChild = vi.fn();
89
+
90
+ sourceWindow[HOOKS] = {
91
+ setAttribute: sourceSetAttribute,
92
+ setText: sourceSetText,
93
+ insertChild: sourceInsertChild,
94
+ };
95
+ destinationWindow[HOOKS] = {
96
+ setAttribute: destinationSetAttribute,
97
+ setText: destinationSetText,
98
+ insertChild: destinationInsertChild,
99
+ };
100
+
101
+ destinationDocument.body.appendChild(template);
102
+
103
+ for (const node of [template, content, contentElement, text, attribute]) {
104
+ expect(node.ownerDocument).toBe(destinationDocument);
105
+ }
106
+
107
+ contentElement.setAttribute('data-added', 'value');
108
+ attribute.value = 'after';
109
+ text.data = 'after';
110
+ contentElement.appendChild(destinationDocument.createElement('strong'));
111
+
112
+ expect(sourceSetAttribute).not.toHaveBeenCalled();
113
+ expect(sourceSetText).not.toHaveBeenCalled();
114
+ expect(sourceInsertChild).not.toHaveBeenCalled();
115
+ expect(destinationSetAttribute).toHaveBeenCalledTimes(2);
116
+ expect(destinationSetText).toHaveBeenCalledTimes(1);
117
+ expect(destinationInsertChild).toHaveBeenCalledTimes(2);
118
+ });
119
+
120
+ it('adopts initialized template content with Document.adoptNode', () => {
121
+ const sourceWindow = new Window();
122
+ const destinationWindow = new Window();
123
+ const sourceDocument = sourceWindow.document;
124
+ const destinationDocument = destinationWindow.document;
125
+ const template = sourceDocument.createElement(
126
+ 'template',
127
+ ) as HTMLTemplateElement;
128
+ const content = template.content;
129
+ const contentElement = sourceDocument.createElement('span');
130
+ const text = sourceDocument.createTextNode('before');
131
+ contentElement.setAttribute('data-content', 'before');
132
+ contentElement.appendChild(text);
133
+ content.appendChild(contentElement);
134
+
135
+ const attribute = contentElement.attributes.getNamedItem('data-content')!;
136
+ const sourceSetAttribute = vi.fn();
137
+ const sourceSetText = vi.fn();
138
+ const sourceInsertChild = vi.fn();
139
+ const destinationSetAttribute = vi.fn();
140
+ const destinationSetText = vi.fn();
141
+ const destinationInsertChild = vi.fn();
142
+
143
+ sourceWindow[HOOKS] = {
144
+ setAttribute: sourceSetAttribute,
145
+ setText: sourceSetText,
146
+ insertChild: sourceInsertChild,
147
+ };
148
+ destinationWindow[HOOKS] = {
149
+ setAttribute: destinationSetAttribute,
150
+ setText: destinationSetText,
151
+ insertChild: destinationInsertChild,
152
+ };
153
+
154
+ destinationDocument.adoptNode(template);
155
+
156
+ for (const node of [template, content, contentElement, text, attribute]) {
157
+ expect(node.ownerDocument).toBe(destinationDocument);
158
+ }
159
+
160
+ contentElement.setAttribute('data-added', 'value');
161
+ attribute.value = 'after';
162
+ text.data = 'after';
163
+ contentElement.appendChild(destinationDocument.createElement('strong'));
164
+
165
+ expect(sourceSetAttribute).not.toHaveBeenCalled();
166
+ expect(sourceSetText).not.toHaveBeenCalled();
167
+ expect(sourceInsertChild).not.toHaveBeenCalled();
168
+ expect(destinationSetAttribute).toHaveBeenCalledTimes(2);
169
+ expect(destinationSetText).toHaveBeenCalledTimes(1);
170
+ expect(destinationInsertChild).toHaveBeenCalledTimes(1);
171
+ });
172
+
173
+ it('does not initialize untouched template content during adoption', () => {
174
+ const sourceDocument = new Window().document;
175
+ const destinationDocument = new Window().document;
176
+ const template = sourceDocument.createElement(
177
+ 'template',
178
+ ) as HTMLTemplateElement;
179
+
180
+ expect(template[CONTENT]).toBeUndefined();
181
+
182
+ destinationDocument.body.appendChild(template);
183
+
184
+ expect(template.ownerDocument).toBe(destinationDocument);
185
+ expect(template[CONTENT]).toBeUndefined();
186
+ });
187
+
188
+ it('snapshots and adopts a malformed direct template host cycle once', () => {
189
+ const sourceDocument = new Window().document;
190
+ const destinationDocument = new Window().document;
191
+ const template = sourceDocument.createElement(
192
+ 'template',
193
+ ) as HTMLTemplateElement;
194
+ const content = template.content;
195
+
196
+ content[CHILD] = template;
197
+ template[PARENT] = content;
198
+
199
+ const snapshot = collectAdoptionSnapshot(template);
200
+
201
+ expect(snapshot.treeNodes).toEqual([template]);
202
+ expect(snapshot.nodes).toEqual([template, content]);
203
+ expect(template.ownerDocument).toBe(sourceDocument);
204
+ expect(content.ownerDocument).toBe(sourceDocument);
205
+
206
+ adoptNodes(snapshot.nodes, destinationDocument);
207
+
208
+ expect(template.ownerDocument).toBe(destinationDocument);
209
+ expect(content.ownerDocument).toBe(destinationDocument);
210
+ });
211
+
212
+ it('snapshots and adopts a malformed mutual template host cycle once', () => {
213
+ const sourceDocument = new Window().document;
214
+ const destinationDocument = new Window().document;
215
+ const first = sourceDocument.createElement(
216
+ 'template',
217
+ ) as HTMLTemplateElement;
218
+ const second = sourceDocument.createElement(
219
+ 'template',
220
+ ) as HTMLTemplateElement;
221
+ const firstContent = first.content;
222
+ const secondContent = second.content;
223
+
224
+ firstContent[CHILD] = second;
225
+ second[PARENT] = firstContent;
226
+ secondContent[CHILD] = first;
227
+ first[PARENT] = secondContent;
228
+
229
+ const snapshot = collectAdoptionSnapshot(first);
230
+
231
+ expect(snapshot.treeNodes).toEqual([first]);
232
+ expect(snapshot.nodes).toEqual([
233
+ first,
234
+ firstContent,
235
+ second,
236
+ secondContent,
237
+ ]);
238
+ for (const node of snapshot.nodes) {
239
+ expect(node.ownerDocument).toBe(sourceDocument);
240
+ }
241
+
242
+ adoptNodes(snapshot.nodes, destinationDocument);
243
+
244
+ for (const node of snapshot.nodes) {
245
+ expect(node.ownerDocument).toBe(destinationDocument);
246
+ }
247
+ });
248
+
249
+ it('iteratively adopts deeply nested template content', () => {
250
+ const sourceWindow = new Window();
251
+ const destinationWindow = new Window();
252
+ const sourceDocument = sourceWindow.document;
253
+ const destinationDocument = destinationWindow.document;
254
+ const root = sourceDocument.createElement(
255
+ 'template',
256
+ ) as HTMLTemplateElement;
257
+ const templates = [root];
258
+ const contents = [];
259
+ let leaf = root;
260
+
261
+ for (let depth = 0; depth < DEEP_TEMPLATE_CONTENT_DEPTH; depth++) {
262
+ const content = leaf.content;
263
+ const nested = sourceDocument.createElement(
264
+ 'template',
265
+ ) as HTMLTemplateElement;
266
+ content.appendChild(nested);
267
+ contents.push(content);
268
+ templates.push(nested);
269
+ leaf = nested;
270
+ }
271
+
272
+ const sourceInsertChild = vi.fn();
273
+ const sourceSetAttribute = vi.fn();
274
+ const destinationInsertChild = vi.fn();
275
+ const destinationSetAttribute = vi.fn();
276
+ sourceWindow[HOOKS] = {
277
+ insertChild: sourceInsertChild,
278
+ setAttribute: sourceSetAttribute,
279
+ };
280
+ destinationWindow[HOOKS] = {
281
+ insertChild: destinationInsertChild,
282
+ setAttribute: destinationSetAttribute,
283
+ };
284
+
285
+ expect(leaf[CONTENT]).toBeUndefined();
286
+
287
+ destinationDocument.body.appendChild(root);
288
+
289
+ for (const template of templates) {
290
+ expect(template.ownerDocument).toBe(destinationDocument);
291
+ }
292
+ for (const content of contents) {
293
+ expect(content.ownerDocument).toBe(destinationDocument);
294
+ }
295
+ expect(leaf[CONTENT]).toBeUndefined();
296
+
297
+ leaf.setAttribute('data-deep', 'adopted');
298
+
299
+ expect(sourceInsertChild).not.toHaveBeenCalled();
300
+ expect(sourceSetAttribute).not.toHaveBeenCalled();
301
+ expect(destinationInsertChild).toHaveBeenCalledTimes(1);
302
+ expect(destinationSetAttribute).toHaveBeenCalledTimes(1);
303
+ }, 20_000);
304
+
305
+ it('preflights template content before changing insertion state', () => {
306
+ const sourceWindow = new Window();
307
+ const destinationWindow = new Window();
308
+ const sourceDocument = sourceWindow.document;
309
+ const destinationDocument = destinationWindow.document;
310
+ const source = sourceDocument.createElement('div');
311
+ const destination = destinationDocument.createElement('div');
312
+ const template = sourceDocument.createElement(
313
+ 'template',
314
+ ) as HTMLTemplateElement;
315
+ const content = template.content;
316
+ const contentElement = sourceDocument.createElement('span');
317
+ contentElement.setAttribute('data-content', 'value');
318
+ content.appendChild(contentElement);
319
+ source.appendChild(template);
320
+ sourceDocument.body.appendChild(source);
321
+ destinationDocument.body.appendChild(destination);
322
+
323
+ const attribute = contentElement.attributes.getNamedItem('data-content')!;
324
+ const nodes = [template, content, contentElement, attribute];
325
+ const originalOwnerDocuments = nodes.map((node) => node.ownerDocument);
326
+ const originalConnectivity = nodes.map((node) => node.isConnected);
327
+ const traversalError = new Error('template content traversal failed');
328
+ Object.defineProperty(content, CHILD, {
329
+ get() {
330
+ throw traversalError;
331
+ },
332
+ });
333
+
334
+ const sourceInsertChild = vi.fn();
335
+ const sourceRemoveChild = vi.fn();
336
+ const destinationInsertChild = vi.fn();
337
+ const destinationRemoveChild = vi.fn();
338
+ sourceWindow[HOOKS] = {
339
+ insertChild: sourceInsertChild,
340
+ removeChild: sourceRemoveChild,
341
+ };
342
+ destinationWindow[HOOKS] = {
343
+ insertChild: destinationInsertChild,
344
+ removeChild: destinationRemoveChild,
345
+ };
346
+
347
+ expect(() => destination.appendChild(template)).toThrow(traversalError);
348
+
349
+ expect([...source.childNodes]).toEqual([template]);
350
+ expect(destination.childNodes).toHaveLength(0);
351
+ expect(source.parentNode).toBe(sourceDocument.body);
352
+ expect(destination.parentNode).toBe(destinationDocument.body);
353
+ expect(source.isConnected).toBe(true);
354
+ expect(destination.isConnected).toBe(true);
355
+ expect(template.parentNode).toBe(source);
356
+ expect(template.previousSibling).toBeNull();
357
+ expect(template.nextSibling).toBeNull();
358
+ expect([...content.childNodes]).toEqual([contentElement]);
359
+ expect(contentElement.parentNode).toBe(content);
360
+ nodes.forEach((node, index) => {
361
+ expect(node.ownerDocument).toBe(originalOwnerDocuments[index]);
362
+ expect(node.isConnected).toBe(originalConnectivity[index]);
363
+ });
364
+ expect(sourceInsertChild).not.toHaveBeenCalled();
365
+ expect(sourceRemoveChild).not.toHaveBeenCalled();
366
+ expect(destinationInsertChild).not.toHaveBeenCalled();
367
+ expect(destinationRemoveChild).not.toHaveBeenCalled();
368
+ });
369
+
370
+ it('preflights every compound replacement before moving any argument', () => {
371
+ const sourceWindow = new Window();
372
+ const destinationWindow = new Window();
373
+ const sourceDocument = sourceWindow.document;
374
+ const destinationDocument = destinationWindow.document;
375
+ let reactions = 0;
376
+
377
+ class MovingElement extends sourceWindow.HTMLElement {
378
+ connectedCallback() {
379
+ reactions += 1;
380
+ }
381
+
382
+ disconnectedCallback() {
383
+ reactions += 1;
384
+ }
385
+ }
386
+ sourceWindow.customElements.define(
387
+ 'compound-moving-element',
388
+ MovingElement as unknown as CustomElementConstructor,
389
+ );
390
+
391
+ const source = sourceDocument.createElement('div');
392
+ const first = sourceDocument.createElement('compound-moving-element');
393
+ const template = sourceDocument.createElement(
394
+ 'template',
395
+ ) as HTMLTemplateElement;
396
+ const content = template.content;
397
+ const contentElement = sourceDocument.createElement('span');
398
+ content.appendChild(contentElement);
399
+ source.append(first, template);
400
+ sourceDocument.body.appendChild(source);
401
+
402
+ const destination = destinationDocument.createElement('div');
403
+ const receiver = destinationDocument.createElement('em');
404
+ destination.appendChild(receiver);
405
+ destinationDocument.body.appendChild(destination);
406
+ reactions = 0;
407
+ const nodes = [first, template, content, contentElement];
408
+ const originalOwnerDocuments = nodes.map((node) => node.ownerDocument);
409
+ const originalConnectivity = nodes.map((node) => node.isConnected);
410
+ const traversalError = new Error('compound template traversal failed');
411
+ Object.defineProperty(content, CHILD, {
412
+ get() {
413
+ throw traversalError;
414
+ },
415
+ });
416
+
417
+ const sourceInsertChild = vi.fn();
418
+ const sourceRemoveChild = vi.fn();
419
+ const destinationInsertChild = vi.fn();
420
+ const destinationRemoveChild = vi.fn();
421
+ sourceWindow[HOOKS] = {
422
+ insertChild: sourceInsertChild,
423
+ removeChild: sourceRemoveChild,
424
+ };
425
+ destinationWindow[HOOKS] = {
426
+ insertChild: destinationInsertChild,
427
+ removeChild: destinationRemoveChild,
428
+ };
429
+
430
+ expect(() => receiver.replaceWith(first, template)).toThrow(traversalError);
431
+
432
+ expect([...source.childNodes]).toEqual([first, template]);
433
+ expect([...destination.childNodes]).toEqual([receiver]);
434
+ expect(first.parentNode).toBe(source);
435
+ expect(template.parentNode).toBe(source);
436
+ expect([...content.childNodes]).toEqual([contentElement]);
437
+ expect(contentElement.parentNode).toBe(content);
438
+ nodes.forEach((node, index) => {
439
+ expect(node.ownerDocument).toBe(originalOwnerDocuments[index]);
440
+ expect(node.isConnected).toBe(originalConnectivity[index]);
441
+ });
442
+ expect(sourceInsertChild).not.toHaveBeenCalled();
443
+ expect(sourceRemoveChild).not.toHaveBeenCalled();
444
+ expect(destinationInsertChild).not.toHaveBeenCalled();
445
+ expect(destinationRemoveChild).not.toHaveBeenCalled();
446
+ expect(reactions).toBe(0);
447
+ });
448
+
449
+ it.each(['callback', 'hook'] as const)(
450
+ 'commits adoption before a removal %s appends a source child',
451
+ (source) => {
452
+ const sourceWindow = new Window();
453
+ const destinationWindow = new Window();
454
+ const sourceDocument = sourceWindow.document;
455
+ const destinationDocument = destinationWindow.document;
456
+ let appended: ReturnType<typeof sourceDocument.createElement> | undefined;
457
+ let root: ReturnType<typeof sourceDocument.createElement>;
458
+
459
+ if (source === 'callback') {
460
+ class MovingElement extends sourceWindow.HTMLElement {
461
+ disconnectedCallback() {
462
+ expect(this.ownerDocument).toBe(destinationDocument);
463
+ expect(this.parentNode).toBeNull();
464
+ appended = sourceDocument.createElement('span');
465
+ this.appendChild(appended);
466
+ }
467
+ }
468
+ sourceWindow.customElements.define(
469
+ 'moving-element',
470
+ MovingElement as unknown as CustomElementConstructor,
471
+ );
472
+ root = sourceDocument.createElement('moving-element');
473
+ } else {
474
+ root = sourceDocument.createElement('section');
475
+ }
476
+
477
+ sourceDocument.body.appendChild(root);
478
+ const sourceSetAttribute = vi.fn();
479
+ const destinationSetAttribute = vi.fn();
480
+ sourceWindow[HOOKS] = {
481
+ setAttribute: sourceSetAttribute,
482
+ removeChild: (_parent, child) => {
483
+ if (source !== 'hook' || child !== (root as any)) return;
484
+ expect(root.ownerDocument).toBe(destinationDocument);
485
+ expect(root.parentNode).toBeNull();
486
+ appended = sourceDocument.createElement('span');
487
+ root.appendChild(appended);
488
+ },
489
+ };
490
+ destinationWindow[HOOKS] = {setAttribute: destinationSetAttribute};
491
+
492
+ destinationDocument.adoptNode(root);
493
+
494
+ expect(root.ownerDocument).toBe(destinationDocument);
495
+ expect(appended?.parentNode).toBe(root);
496
+ expect(appended?.ownerDocument).toBe(destinationDocument);
497
+ appended!.setAttribute('data-owner', 'destination');
498
+ expect(sourceSetAttribute).not.toHaveBeenCalled();
499
+ expect(destinationSetAttribute).toHaveBeenCalledOnce();
500
+ },
501
+ );
502
+
503
+ it.each(['callback', 'hook'] as const)(
504
+ 'preserves a third-document transfer from a removal %s',
505
+ (source) => {
506
+ const sourceWindow = new Window();
507
+ const destinationWindow = new Window();
508
+ const thirdWindow = new Window();
509
+ const sourceDocument = sourceWindow.document;
510
+ const destinationDocument = destinationWindow.document;
511
+ const thirdDocument = thirdWindow.document;
512
+ let child: ReturnType<typeof sourceDocument.createElement>;
513
+ let root: ReturnType<typeof sourceDocument.createElement>;
514
+
515
+ if (source === 'callback') {
516
+ class TransferringElement extends sourceWindow.HTMLElement {
517
+ disconnectedCallback() {
518
+ expect(this.ownerDocument).toBe(destinationDocument);
519
+ thirdDocument.body.appendChild(child);
520
+ }
521
+ }
522
+ sourceWindow.customElements.define(
523
+ 'transferring-element',
524
+ TransferringElement as unknown as CustomElementConstructor,
525
+ );
526
+ root = sourceDocument.createElement('transferring-element');
527
+ } else {
528
+ root = sourceDocument.createElement('section');
529
+ }
530
+
531
+ child = sourceDocument.createElement('span');
532
+ root.appendChild(child);
533
+ sourceDocument.body.appendChild(root);
534
+ const sourceSetAttribute = vi.fn();
535
+ const destinationSetAttribute = vi.fn();
536
+ const thirdSetAttribute = vi.fn();
537
+ sourceWindow[HOOKS] = {
538
+ setAttribute: sourceSetAttribute,
539
+ removeChild: (_parent, removed) => {
540
+ if (source === 'hook' && removed === (root as any)) {
541
+ expect(root.ownerDocument).toBe(destinationDocument);
542
+ thirdDocument.body.appendChild(child);
543
+ }
544
+ },
545
+ };
546
+ destinationWindow[HOOKS] = {setAttribute: destinationSetAttribute};
547
+ thirdWindow[HOOKS] = {setAttribute: thirdSetAttribute};
548
+
549
+ destinationDocument.adoptNode(root);
550
+
551
+ expect(root.ownerDocument).toBe(destinationDocument);
552
+ expect(child.parentNode).toBe(thirdDocument.body);
553
+ expect(child.ownerDocument).toBe(thirdDocument);
554
+ child.setAttribute('data-owner', 'third');
555
+ expect(sourceSetAttribute).not.toHaveBeenCalled();
556
+ expect(destinationSetAttribute).not.toHaveBeenCalled();
557
+ expect(thirdSetAttribute).toHaveBeenCalledOnce();
558
+ },
559
+ );
560
+
561
+ it('adopts every subtree inserted from a document fragment', () => {
562
+ const sourceDocument = new Window().document;
563
+ const destinationDocument = new Window().document;
564
+ const fragment = sourceDocument.createDocumentFragment();
565
+ const first = sourceDocument.createElement('div');
566
+ const second = sourceDocument.createElement('section');
567
+ const descendant = sourceDocument.createElement('span');
568
+ const text = sourceDocument.createTextNode('content');
569
+ descendant.setAttribute('data-nested', 'value');
570
+ descendant.appendChild(text);
571
+ second.appendChild(descendant);
572
+ fragment.append(first, second);
573
+
574
+ const attribute = descendant.attributes.getNamedItem('data-nested')!;
575
+
576
+ destinationDocument.body.appendChild(fragment);
577
+
578
+ expect(fragment.ownerDocument).toBe(sourceDocument);
579
+ for (const node of [first, second, descendant, text, attribute]) {
580
+ expect(node.ownerDocument).toBe(destinationDocument);
581
+ }
582
+ expect(fragment.childNodes).toHaveLength(0);
583
+ });
584
+ });
@@ -0,0 +1,112 @@
1
+ import {beforeEach, describe, expect, it, vi} from 'vitest';
2
+
3
+ import {Attr} from '../Attr.ts';
4
+ import {HOOKS} from '../constants.ts';
5
+ import {Window} from '../index.ts';
6
+
7
+ let window: Window;
8
+ let document: Window['document'];
9
+
10
+ beforeEach(() => {
11
+ window = new Window();
12
+ Window.setGlobalThis(window);
13
+ document = window.document;
14
+ });
15
+
16
+ describe('Attr value mutations', () => {
17
+ it.each(['value', 'nodeValue'] as const)(
18
+ 'updates an attached attribute through %s and dispatches its hook',
19
+ (property) => {
20
+ const setAttribute = vi.fn(
21
+ (element: Element, name: string, value: string) => {
22
+ expect(element.getAttribute(name)).toBe(value);
23
+ },
24
+ );
25
+ window[HOOKS].setAttribute = setAttribute;
26
+ const element = document.createElement('div');
27
+ const attribute = new Attr('data-state', 'initial');
28
+ element.attributes.setNamedItem(attribute);
29
+ setAttribute.mockClear();
30
+
31
+ expect(() => {
32
+ attribute[property] = 'updated';
33
+ }).not.toThrow();
34
+
35
+ expect(attribute.value).toBe('updated');
36
+ expect(attribute.nodeValue).toBe('updated');
37
+ expect(attribute.ownerDocument).toBe(document);
38
+ expect(element.getAttribute('data-state')).toBe('updated');
39
+ expect(setAttribute).toHaveBeenCalledOnce();
40
+ expect(setAttribute).toHaveBeenCalledWith(
41
+ element,
42
+ 'data-state',
43
+ 'updated',
44
+ null,
45
+ );
46
+ },
47
+ );
48
+
49
+ it('updates a detached attribute without dispatching a hook', () => {
50
+ const setAttribute = vi.fn();
51
+ window[HOOKS].setAttribute = setAttribute;
52
+ const attribute = new Attr('data-state', 'initial');
53
+
54
+ expect(() => {
55
+ attribute.value = 'detached';
56
+ }).not.toThrow();
57
+
58
+ expect(attribute.value).toBe('detached');
59
+ expect(setAttribute).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('updates a removed attribute without changing its former element', () => {
63
+ const setAttribute = vi.fn();
64
+ window[HOOKS].setAttribute = setAttribute;
65
+ const element = document.createElement('div');
66
+ const attribute = new Attr('data-state', 'initial');
67
+ element.attributes.setNamedItem(attribute);
68
+ element.attributes.removeNamedItem('data-state');
69
+ setAttribute.mockClear();
70
+
71
+ expect(() => {
72
+ attribute.value = 'removed';
73
+ }).not.toThrow();
74
+
75
+ expect(attribute.value).toBe('removed');
76
+ expect(attribute.ownerDocument).toBe(document);
77
+ expect(element.getAttribute('data-state')).toBeNull();
78
+ expect(setAttribute).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('dispatches through the new document after reattachment', () => {
82
+ const firstSetAttribute = vi.fn();
83
+ window[HOOKS].setAttribute = firstSetAttribute;
84
+ const firstElement = document.createElement('div');
85
+ const attribute = new Attr('data-state', 'initial');
86
+ firstElement.attributes.setNamedItem(attribute);
87
+ firstElement.attributes.removeNamedItem('data-state');
88
+
89
+ const secondWindow = new Window();
90
+ const secondSetAttribute = vi.fn();
91
+ secondWindow[HOOKS].setAttribute = secondSetAttribute;
92
+ const secondElement = secondWindow.document.createElement('div');
93
+ secondElement.attributes.setNamedItem(attribute);
94
+ firstSetAttribute.mockClear();
95
+ secondSetAttribute.mockClear();
96
+
97
+ expect(() => {
98
+ attribute.nodeValue = 'reattached';
99
+ }).not.toThrow();
100
+
101
+ expect(attribute.ownerElement).toBe(secondElement);
102
+ expect(attribute.ownerDocument).toBe(secondWindow.document);
103
+ expect(secondElement.getAttribute('data-state')).toBe('reattached');
104
+ expect(firstSetAttribute).not.toHaveBeenCalled();
105
+ expect(secondSetAttribute).toHaveBeenCalledWith(
106
+ secondElement,
107
+ 'data-state',
108
+ 'reattached',
109
+ null,
110
+ );
111
+ });
112
+ });