@tkeron/html-parser 0.1.1 → 0.1.3

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.
@@ -0,0 +1,363 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { parseHTML } from "../index";
3
+ import { createDocument } from "../src/dom-simulator";
4
+
5
+ describe("DOM Manipulation - Node Adoption Between Documents", () => {
6
+ describe("Moving nodes between documents with appendChild", () => {
7
+ it("should move element from one document to another", () => {
8
+ const doc1 = parseHTML("<div><span>Moving</span></div>");
9
+ const doc2 = parseHTML("<div></div>");
10
+
11
+ const div1 = doc1.querySelector("div");
12
+ const div2 = doc2.querySelector("div");
13
+ const movingSpan = div1.querySelector("span");
14
+
15
+ div2.appendChild(movingSpan);
16
+
17
+ expect(div1.childNodes.length).toBe(0);
18
+ expect(div2.childNodes.length).toBe(1);
19
+ expect(movingSpan.parentNode).toBe(div2);
20
+ expect(div2.innerHTML).toBe("<span>Moving</span>");
21
+ });
22
+
23
+ it("should move element with nested children between documents", () => {
24
+ const doc1 = parseHTML(
25
+ "<div><section><span>Nested</span><p>Content</p></section></div>"
26
+ );
27
+ const doc2 = parseHTML("<div></div>");
28
+
29
+ const div1 = doc1.querySelector("div");
30
+ const div2 = doc2.querySelector("div");
31
+ const section = div1.querySelector("section");
32
+
33
+ div2.appendChild(section);
34
+
35
+ expect(div1.childNodes.length).toBe(0);
36
+ expect(div2.childNodes.length).toBe(1);
37
+ expect(section.parentNode).toBe(div2);
38
+ expect(section.children.length).toBe(2);
39
+ expect(div2.innerHTML).toBe(
40
+ "<section><span>Nested</span><p>Content</p></section>"
41
+ );
42
+ });
43
+
44
+ it("should move text node between documents", () => {
45
+ const doc1 = parseHTML("<div>Text content</div>");
46
+ const doc2 = parseHTML("<div></div>");
47
+
48
+ const div1 = doc1.querySelector("div");
49
+ const div2 = doc2.querySelector("div");
50
+ const textNode = div1.childNodes[0];
51
+
52
+ div2.appendChild(textNode);
53
+
54
+ expect(div1.childNodes.length).toBe(0);
55
+ expect(div2.childNodes.length).toBe(1);
56
+ expect(textNode.parentNode).toBe(div2);
57
+ expect(div2.textContent).toBe("Text content");
58
+ });
59
+
60
+ it("should update parent references correctly when moving", () => {
61
+ const doc1 = parseHTML("<div><span>Child</span></div>");
62
+ const doc2 = parseHTML("<article></article>");
63
+
64
+ const div = doc1.querySelector("div");
65
+ const article = doc2.querySelector("article");
66
+ const span = div.querySelector("span");
67
+
68
+ article.appendChild(span);
69
+
70
+ expect(span.parentNode).toBe(article);
71
+ expect(span.parentElement).toBe(article);
72
+ expect(div.contains).toBeUndefined(); // or check if defined and works
73
+ });
74
+
75
+ it("should preserve element attributes when moving between documents", () => {
76
+ const doc1 = parseHTML('<div><span class="highlight" id="test">Text</span></div>');
77
+ const doc2 = parseHTML("<div></div>");
78
+
79
+ const div1 = doc1.querySelector("div");
80
+ const div2 = doc2.querySelector("div");
81
+ const span = div1.querySelector("span");
82
+
83
+ div2.appendChild(span);
84
+
85
+ expect(span.getAttribute("class")).toBe("highlight");
86
+ expect(span.getAttribute("id")).toBe("test");
87
+ expect(div2.innerHTML).toBe('<span class="highlight" id="test">Text</span>');
88
+ });
89
+ });
90
+
91
+ describe("Moving nodes between documents with insertBefore", () => {
92
+ it("should move element to another document using insertBefore", () => {
93
+ const doc1 = parseHTML("<div><span>Moving</span></div>");
94
+ const doc2 = parseHTML("<div><span>Existing</span></div>");
95
+
96
+ const div1 = doc1.querySelector("div");
97
+ const div2 = doc2.querySelector("div");
98
+ const movingSpan = div1.querySelector("span");
99
+ const existingSpan = div2.querySelector("span");
100
+
101
+ div2.insertBefore(movingSpan, existingSpan);
102
+
103
+ expect(div1.childNodes.length).toBe(0);
104
+ expect(div2.childNodes.length).toBe(2);
105
+ expect(movingSpan.parentNode).toBe(div2);
106
+ expect(div2.children[0]).toBe(movingSpan);
107
+ expect(div2.children[1]).toBe(existingSpan);
108
+ });
109
+
110
+ it("should update sibling relationships when moving with insertBefore", () => {
111
+ const doc1 = parseHTML("<div><span>Moving</span></div>");
112
+ const doc2 = parseHTML("<div><span>A</span><span>B</span></div>");
113
+
114
+ const div1 = doc1.querySelector("div");
115
+ const div2 = doc2.querySelector("div");
116
+ const movingSpan = div1.querySelector("span");
117
+ const spanB = div2.children[1];
118
+
119
+ div2.insertBefore(movingSpan, spanB);
120
+
121
+ expect(div2.children[1]).toBe(movingSpan);
122
+ expect(movingSpan.previousElementSibling.textContent).toBe("A");
123
+ expect(movingSpan.nextElementSibling).toBe(spanB);
124
+ expect(spanB.previousElementSibling).toBe(movingSpan);
125
+ });
126
+ });
127
+
128
+ describe("Moving nodes between documents with replaceChild", () => {
129
+ it("should move element to another document using replaceChild", () => {
130
+ const doc1 = parseHTML("<div><span>Moving</span></div>");
131
+ const doc2 = parseHTML("<div><span>Old</span></div>");
132
+
133
+ const div1 = doc1.querySelector("div");
134
+ const div2 = doc2.querySelector("div");
135
+ const movingSpan = div1.querySelector("span");
136
+ const oldSpan = div2.querySelector("span");
137
+
138
+ div2.replaceChild(movingSpan, oldSpan);
139
+
140
+ expect(div1.childNodes.length).toBe(0);
141
+ expect(div2.childNodes.length).toBe(1);
142
+ expect(movingSpan.parentNode).toBe(div2);
143
+ expect(div2.children[0]).toBe(movingSpan);
144
+ expect(oldSpan.parentNode).toBeNull();
145
+ });
146
+
147
+ it("should preserve position when replacing with moved node", () => {
148
+ const doc1 = parseHTML("<div><span>Moving</span></div>");
149
+ const doc2 = parseHTML("<div><span>A</span><span>Old</span><span>C</span></div>");
150
+
151
+ const div1 = doc1.querySelector("div");
152
+ const div2 = doc2.querySelector("div");
153
+ const movingSpan = div1.querySelector("span");
154
+ const oldSpan = div2.children[1];
155
+
156
+ div2.replaceChild(movingSpan, oldSpan);
157
+
158
+ expect(div2.children.length).toBe(3);
159
+ expect(div2.children[1]).toBe(movingSpan);
160
+ expect(div2.innerHTML).toBe("<span>A</span><span>Moving</span><span>C</span>");
161
+ });
162
+ });
163
+
164
+ describe("Document isolation and independence", () => {
165
+ it("should keep documents independent after moving nodes", () => {
166
+ const doc1 = parseHTML("<div><span>A</span><span>B</span></div>");
167
+ const doc2 = parseHTML("<article></article>");
168
+
169
+ const div = doc1.querySelector("div");
170
+ const article = doc2.querySelector("article");
171
+ const spanB = div.children[1];
172
+
173
+ article.appendChild(spanB);
174
+
175
+ // Doc1 should only have spanA now
176
+ expect(doc1.querySelector("div").children.length).toBe(1);
177
+ expect(doc1.querySelector("div").children[0].textContent).toBe("A");
178
+
179
+ // Doc2 should have spanB
180
+ expect(doc2.querySelector("article").children.length).toBe(1);
181
+ expect(doc2.querySelector("article").children[0].textContent).toBe("B");
182
+
183
+ // Queries should be independent
184
+ expect(doc1.querySelectorAll("span").length).toBe(1);
185
+ expect(doc2.querySelectorAll("span").length).toBe(1);
186
+ });
187
+
188
+ it("should handle moving root-level elements between documents", () => {
189
+ const doc1 = parseHTML("<div>Doc1</div>");
190
+ const doc2 = createDocument();
191
+
192
+ const div = doc1.querySelector("div");
193
+ doc2.appendChild(div);
194
+
195
+ expect(doc1.querySelectorAll("div").length).toBe(0);
196
+ expect(doc2.querySelectorAll("div").length).toBe(1);
197
+ expect(div.parentNode).toBe(doc2);
198
+ });
199
+ });
200
+
201
+ describe("Complex node adoption scenarios", () => {
202
+ it("should handle moving node with event listeners (structure only)", () => {
203
+ // This test verifies structure is maintained; actual event handling would require
204
+ // event listener implementation
205
+ const doc1 = parseHTML('<div><button id="btn">Click</button></div>');
206
+ const doc2 = parseHTML("<div></div>");
207
+
208
+ const div1 = doc1.querySelector("div");
209
+ const div2 = doc2.querySelector("div");
210
+ const button = doc1.querySelector("#btn");
211
+
212
+ div2.appendChild(button);
213
+
214
+ expect(button.parentNode).toBe(div2);
215
+ expect(button.getAttribute("id")).toBe("btn");
216
+ expect(div2.querySelector("#btn")).toBe(button);
217
+ });
218
+
219
+ it("should prevent circular adoption (HierarchyRequestError)", () => {
220
+ const doc = parseHTML("<div><span></span></div>");
221
+ const div = doc.querySelector("div");
222
+ const span = doc.querySelector("span");
223
+
224
+ // Try to append div to span (which is currently a child of div)
225
+ // This should throw a HierarchyRequestError
226
+ expect(() => {
227
+ span.appendChild(div);
228
+ }).toThrow("HierarchyRequestError");
229
+ });
230
+
231
+ it("should maintain innerHTML synchronization across document boundaries", () => {
232
+ const doc1 = parseHTML("<div><p>Paragraph 1</p><p>Paragraph 2</p></div>");
233
+ const doc2 = parseHTML("<section></section>");
234
+
235
+ const div = doc1.querySelector("div");
236
+ const section = doc2.querySelector("section");
237
+ const p1 = div.children[0];
238
+
239
+ section.appendChild(p1);
240
+
241
+ expect(div.innerHTML).toBe("<p>Paragraph 2</p>");
242
+ expect(section.innerHTML).toBe("<p>Paragraph 1</p>");
243
+ });
244
+
245
+ it("should handle adoption of deeply nested structures", () => {
246
+ const doc1 = parseHTML(`
247
+ <div>
248
+ <section>
249
+ <article>
250
+ <h1>Title</h1>
251
+ <p>Content</p>
252
+ </article>
253
+ </section>
254
+ </div>
255
+ `);
256
+ const doc2 = parseHTML("<main></main>");
257
+
258
+ const section = doc1.querySelector("section");
259
+ const main = doc2.querySelector("main");
260
+
261
+ main.appendChild(section);
262
+
263
+ expect(section.parentNode).toBe(main);
264
+ expect(main.querySelector("h1").textContent).toBe("Title");
265
+ expect(main.querySelector("p").textContent).toBe("Content");
266
+ expect(doc1.querySelector("section")).toBeNull();
267
+ });
268
+ });
269
+
270
+ describe("Node adoption with mixed content", () => {
271
+ it("should move element with mixed text and element children", () => {
272
+ const doc1 = parseHTML("<div>Text <span>element</span> more text</div>");
273
+ const doc2 = parseHTML("<article></article>");
274
+
275
+ const div = doc1.querySelector("div");
276
+ const article = doc2.querySelector("article");
277
+
278
+ article.appendChild(div);
279
+
280
+ expect(div.parentNode).toBe(article);
281
+ expect(div.childNodes.length).toBe(3);
282
+ expect(div.textContent).toBe("Text element more text");
283
+ expect(article.innerHTML).toBe("<div>Text <span>element</span> more text</div>");
284
+ });
285
+
286
+ it("should maintain content structure when moving nodes with attributes", () => {
287
+ const doc1 = parseHTML(`
288
+ <div class="container">
289
+ <span id="item1" class="active">Item 1</span>
290
+ <span id="item2">Item 2</span>
291
+ </div>
292
+ `);
293
+ const doc2 = parseHTML('<section class="new-section"></section>');
294
+
295
+ const container = doc1.querySelector(".container");
296
+ const section = doc2.querySelector("section");
297
+
298
+ section.appendChild(container);
299
+
300
+ expect(container.getAttribute("class")).toBe("container");
301
+ expect(section.querySelector("#item1").getAttribute("class")).toBe("active");
302
+ expect(section.querySelector("#item2")).not.toBeNull();
303
+ });
304
+ });
305
+
306
+ describe("Edge cases in node adoption", () => {
307
+ it("should handle adopting a node back to its original document", () => {
308
+ const doc1 = parseHTML("<div><span>Original</span></div>");
309
+ const doc2 = parseHTML("<article></article>");
310
+
311
+ const div = doc1.querySelector("div");
312
+ const article = doc2.querySelector("article");
313
+ const span = div.querySelector("span");
314
+
315
+ // Move to doc2
316
+ article.appendChild(span);
317
+ expect(span.parentNode).toBe(article);
318
+
319
+ // Move back to doc1
320
+ div.appendChild(span);
321
+ expect(span.parentNode).toBe(div);
322
+ expect(div.innerHTML).toBe("<span>Original</span>");
323
+ expect(article.innerHTML).toBe("");
324
+ });
325
+
326
+ it("should handle moving an entire subtree and then moving it back", () => {
327
+ const doc1 = parseHTML("<div><section><p>A</p><p>B</p></section></div>");
328
+ const doc2 = createDocument();
329
+
330
+ const div = doc1.querySelector("div");
331
+ const section = div.querySelector("section");
332
+
333
+ const originalHTML = div.innerHTML;
334
+
335
+ // Move to doc2
336
+ doc2.appendChild(section);
337
+ expect(div.innerHTML).toBe("");
338
+
339
+ // Move back to doc1
340
+ div.appendChild(section);
341
+ expect(div.innerHTML).toBe(originalHTML);
342
+ });
343
+
344
+ it("should handle adopting multiple nodes sequentially", () => {
345
+ const doc1 = parseHTML("<div><span>1</span><span>2</span><span>3</span></div>");
346
+ const doc2 = parseHTML("<section></section>");
347
+
348
+ const div = doc1.querySelector("div");
349
+ const section = doc2.querySelector("section");
350
+
351
+ // Move all spans one by one
352
+ while (div.children.length > 0) {
353
+ section.appendChild(div.children[0]);
354
+ }
355
+
356
+ expect(div.children.length).toBe(0);
357
+ expect(section.children.length).toBe(3);
358
+ expect(section.children[0].textContent).toBe("1");
359
+ expect(section.children[1].textContent).toBe("2");
360
+ expect(section.children[2].textContent).toBe("3");
361
+ });
362
+ });
363
+ });