@tkeron/html-parser 1.5.2 → 1.5.5

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,312 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { parseHTML } from "../index";
3
+
4
+ describe("DOM Manipulation - removeChild", () => {
5
+ describe("Basic removeChild functionality", () => {
6
+ it("should remove a child node", () => {
7
+ const doc = parseHTML("<div><span>First</span><span>Second</span></div>");
8
+ const div = doc.querySelector("div");
9
+ const firstSpan = div!.childNodes[0]!;
10
+
11
+ const removed = div!.removeChild(firstSpan);
12
+
13
+ expect(removed.textContent).toBe("First");
14
+ expect(div!.childNodes.length).toBe(1);
15
+ expect(div!.childNodes[0]!.textContent).toBe("Second");
16
+ expect(firstSpan.parentNode).toBe(null);
17
+ });
18
+
19
+ it("should throw error when removing non-child node", () => {
20
+ const doc = parseHTML("<div><span></span></div><p><span></span></p>");
21
+ const div = doc.querySelector("div");
22
+ const pSpan = doc.querySelector("p span");
23
+
24
+ expect(() => div!.removeChild(pSpan!)).toThrow();
25
+ });
26
+
27
+ it("should remove the last child", () => {
28
+ const doc = parseHTML("<div><span>Only</span></div>");
29
+ const div = doc.querySelector("div");
30
+ const span = div!.childNodes[0]!;
31
+
32
+ const removed = div!.removeChild(span);
33
+
34
+ expect(removed.textContent).toBe("Only");
35
+ expect(div!.childNodes.length).toBe(0);
36
+ expect(span.parentNode).toBe(null);
37
+ });
38
+
39
+ it("should update sibling references", () => {
40
+ const doc = parseHTML(
41
+ "<div><span>A</span><span>B</span><span>C</span></div>",
42
+ );
43
+ const div = doc.querySelector("div");
44
+ const spanB = div!.childNodes[1]!;
45
+
46
+ div!.removeChild(spanB);
47
+
48
+ expect(div!.childNodes.length).toBe(2);
49
+ expect(div!.childNodes[0]!.textContent).toBe("A");
50
+ expect(div!.childNodes[1]!.textContent).toBe("C");
51
+ expect(div!.childNodes[0]!.nextSibling!.textContent).toBe("C");
52
+ expect(div!.childNodes[1]!.previousSibling!.textContent).toBe("A");
53
+ });
54
+
55
+ it("should return the removed node", () => {
56
+ const doc = parseHTML("<div><span>Child</span></div>");
57
+ const div = doc.querySelector("div");
58
+ const span = div!.childNodes[0]!;
59
+
60
+ const result = div!.removeChild(span);
61
+
62
+ expect(result).toBe(span);
63
+ });
64
+
65
+ it("should update firstChild when removing first", () => {
66
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
67
+ const div = doc.querySelector("div");
68
+ const spanA = div!.childNodes[0]!;
69
+ const spanB = div!.childNodes[1]!;
70
+
71
+ div!.removeChild(spanA);
72
+
73
+ expect(div!.firstChild).toBe(spanB);
74
+ });
75
+
76
+ it("should update lastChild when removing last", () => {
77
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
78
+ const div = doc.querySelector("div");
79
+ const spanA = div!.childNodes[0]!;
80
+ const spanB = div!.childNodes[1]!;
81
+
82
+ div!.removeChild(spanB);
83
+
84
+ expect(div!.lastChild).toBe(spanA);
85
+ });
86
+
87
+ it("should set firstChild and lastChild to null when empty", () => {
88
+ const doc = parseHTML("<div><span>Only</span></div>");
89
+ const div = doc.querySelector("div");
90
+ const span = div!.childNodes[0]!;
91
+
92
+ div!.removeChild(span);
93
+
94
+ expect(div!.firstChild).toBe(null);
95
+ expect(div!.lastChild).toBe(null);
96
+ });
97
+ });
98
+
99
+ describe("Removing different node types", () => {
100
+ it("should remove text nodes", () => {
101
+ const doc = parseHTML("<div>Hello World</div>");
102
+ const div = doc.querySelector("div");
103
+ const textNode = div!.childNodes[0]!;
104
+
105
+ div!.removeChild(textNode);
106
+
107
+ expect(div!.childNodes.length).toBe(0);
108
+ expect(div!.textContent).toBe("");
109
+ });
110
+
111
+ it("should remove comment nodes", () => {
112
+ const doc = parseHTML("<div><!-- comment --><span>Content</span></div>");
113
+ const div = doc.querySelector("div");
114
+ const comment = div!.childNodes[0]!;
115
+
116
+ expect(comment.nodeType).toBe(8);
117
+ div!.removeChild(comment);
118
+
119
+ expect(div!.childNodes.length).toBe(1);
120
+ expect((div!.childNodes[0] as Element).tagName).toBe("SPAN");
121
+ });
122
+
123
+ it("should remove whitespace text nodes", () => {
124
+ const doc = parseHTML("<div> </div>");
125
+ const div = doc.querySelector("div");
126
+ const whitespace = div!.childNodes[0]!;
127
+
128
+ div!.removeChild(whitespace);
129
+
130
+ expect(div!.childNodes.length).toBe(0);
131
+ });
132
+ });
133
+
134
+ describe("Element sibling references", () => {
135
+ it("should update nextElementSibling", () => {
136
+ const doc = parseHTML(
137
+ "<div><span>A</span><span>B</span><span>C</span></div>",
138
+ );
139
+ const div = doc.querySelector("div");
140
+ const spanA = div!.children[0]!;
141
+ const spanB = div!.children[1]!;
142
+ const spanC = div!.children[2]!;
143
+
144
+ div!.removeChild(spanB);
145
+
146
+ expect(spanA.nextElementSibling).toBe(spanC);
147
+ });
148
+
149
+ it("should update previousElementSibling", () => {
150
+ const doc = parseHTML(
151
+ "<div><span>A</span><span>B</span><span>C</span></div>",
152
+ );
153
+ const div = doc.querySelector("div");
154
+ const spanA = div!.children[0]!;
155
+ const spanB = div!.children[1]!;
156
+ const spanC = div!.children[2]!;
157
+
158
+ div!.removeChild(spanB);
159
+
160
+ expect(spanC.previousElementSibling).toBe(spanA);
161
+ });
162
+
163
+ it("should update firstElementChild", () => {
164
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
165
+ const div = doc.querySelector("div");
166
+ const spanA = div!.children[0]!;
167
+ const spanB = div!.children[1]!;
168
+
169
+ div!.removeChild(spanA);
170
+
171
+ expect(div!.firstElementChild).toBe(spanB);
172
+ });
173
+
174
+ it("should update lastElementChild", () => {
175
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
176
+ const div = doc.querySelector("div");
177
+ const spanA = div!.children[0]!;
178
+ const spanB = div!.children[1]!;
179
+
180
+ div!.removeChild(spanB);
181
+
182
+ expect(div!.lastElementChild).toBe(spanA);
183
+ });
184
+
185
+ it("should update children array", () => {
186
+ const doc = parseHTML(
187
+ "<div><span>A</span><span>B</span><span>C</span></div>",
188
+ );
189
+ const div = doc.querySelector("div");
190
+ const spanB = div!.children[1]!;
191
+
192
+ div!.removeChild(spanB);
193
+
194
+ expect(div!.children.length).toBe(2);
195
+ expect(div!.children[0]!.textContent).toBe("A");
196
+ expect(div!.children[1]!.textContent).toBe("C");
197
+ });
198
+ });
199
+
200
+ describe("Removed node state", () => {
201
+ it("should clear parentNode", () => {
202
+ const doc = parseHTML("<div><span>Child</span></div>");
203
+ const div = doc.querySelector("div");
204
+ const span = div!.childNodes[0]!;
205
+
206
+ div!.removeChild(span);
207
+
208
+ expect(span.parentNode).toBe(null);
209
+ });
210
+
211
+ it("should clear parentElement for elements", () => {
212
+ const doc = parseHTML("<div><span>Child</span></div>");
213
+ const div = doc.querySelector("div");
214
+ const span = doc.querySelector("span");
215
+
216
+ div!.removeChild(span!);
217
+
218
+ expect(span!.parentElement).toBe(null);
219
+ });
220
+
221
+ it("should clear sibling references", () => {
222
+ const doc = parseHTML(
223
+ "<div><span>A</span><span>B</span><span>C</span></div>",
224
+ );
225
+ const div = doc.querySelector("div");
226
+ const spanB = div!.childNodes[1]!;
227
+
228
+ div!.removeChild(spanB);
229
+
230
+ expect(spanB.previousSibling).toBe(null);
231
+ expect(spanB.nextSibling).toBe(null);
232
+ });
233
+
234
+ it("should clear element sibling references", () => {
235
+ const doc = parseHTML(
236
+ "<div><span>A</span><span>B</span><span>C</span></div>",
237
+ );
238
+ const div = doc.querySelector("div");
239
+ const spanB = div!.children[1]!;
240
+
241
+ div!.removeChild(spanB);
242
+
243
+ expect(spanB.previousElementSibling).toBe(null);
244
+ expect(spanB.nextElementSibling).toBe(null);
245
+ });
246
+
247
+ it("should preserve removed node children", () => {
248
+ const doc = parseHTML("<div><span><em>Deep</em></span></div>");
249
+ const div = doc.querySelector("div");
250
+ const span = doc.querySelector("span");
251
+
252
+ div!.removeChild(span!);
253
+
254
+ expect(span!.childNodes.length).toBe(1);
255
+ expect((span!.childNodes[0] as Element).tagName).toBe("EM");
256
+ });
257
+ });
258
+
259
+ describe("Edge cases", () => {
260
+ it("should handle removing from deeply nested structure", () => {
261
+ const doc = parseHTML(
262
+ "<div><span><em><strong>Deep</strong></em></span></div>",
263
+ );
264
+ const em = doc.querySelector("em");
265
+ const strong = doc.querySelector("strong");
266
+
267
+ em!.removeChild(strong!);
268
+
269
+ expect(em!.childNodes.length).toBe(0);
270
+ });
271
+
272
+ it("should allow re-appending removed node", () => {
273
+ const doc = parseHTML("<div><span>Child</span></div><p></p>");
274
+ const div = doc.querySelector("div");
275
+ const p = doc.querySelector("p");
276
+ const span = doc.querySelector("span");
277
+
278
+ div!.removeChild(span!);
279
+ p!.appendChild(span!);
280
+
281
+ expect(div!.childNodes.length).toBe(0);
282
+ expect(p!.childNodes.length).toBe(1);
283
+ expect(span!.parentNode).toBe(p);
284
+ });
285
+
286
+ it("should handle consecutive removes", () => {
287
+ const doc = parseHTML(
288
+ "<div><span>A</span><span>B</span><span>C</span></div>",
289
+ );
290
+ const div = doc.querySelector("div");
291
+
292
+ while (div!.childNodes.length > 0) {
293
+ div!.removeChild(div!.childNodes[0]!);
294
+ }
295
+
296
+ expect(div!.childNodes.length).toBe(0);
297
+ });
298
+
299
+ it("should handle removing all children in reverse order", () => {
300
+ const doc = parseHTML(
301
+ "<div><span>A</span><span>B</span><span>C</span></div>",
302
+ );
303
+ const div = doc.querySelector("div");
304
+
305
+ while (div!.childNodes.length > 0) {
306
+ div!.removeChild(div!.lastChild!);
307
+ }
308
+
309
+ expect(div!.childNodes.length).toBe(0);
310
+ });
311
+ });
312
+ });
@@ -0,0 +1,375 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { parseHTML } from "../index";
3
+
4
+ describe("DOM Manipulation - replaceChild", () => {
5
+ describe("Basic replaceChild functionality", () => {
6
+ it("should replace a child node", () => {
7
+ const doc = parseHTML("<div><span>Old</span></div>");
8
+ const div = doc.querySelector("div");
9
+ const oldSpan = div!.childNodes[0]!;
10
+ const newSpan = doc.createElement("span");
11
+ newSpan.textContent = "New";
12
+
13
+ const replaced = div!.replaceChild(newSpan, oldSpan);
14
+
15
+ expect(replaced.textContent).toBe("Old");
16
+ expect(div!.childNodes.length).toBe(1);
17
+ expect(div!.childNodes[0]!.textContent).toBe("New");
18
+ expect((newSpan.parentNode as Element).tagName).toBe("DIV");
19
+ expect(oldSpan.parentNode).toBe(null);
20
+ });
21
+
22
+ it("should replace the only child", () => {
23
+ const doc = parseHTML("<div><span>Only</span></div>");
24
+ const div = doc.querySelector("div");
25
+ const oldSpan = div!.childNodes[0]!;
26
+ const newSpan = doc.createElement("span");
27
+ newSpan.textContent = "New";
28
+
29
+ div!.replaceChild(newSpan, oldSpan);
30
+
31
+ expect(div!.childNodes.length).toBe(1);
32
+ expect(div!.childNodes[0]!.textContent).toBe("New");
33
+ });
34
+
35
+ it("should update sibling references", () => {
36
+ const doc = parseHTML(
37
+ "<div><span>A</span><span>B</span><span>C</span></div>",
38
+ );
39
+ const div = doc.querySelector("div");
40
+ const spanB = div!.childNodes[1]!;
41
+ const newSpan = doc.createElement("span");
42
+ newSpan.textContent = "New";
43
+
44
+ div!.replaceChild(newSpan, spanB);
45
+
46
+ expect(div!.childNodes.length).toBe(3);
47
+ expect(div!.childNodes[1]!.textContent).toBe("New");
48
+ expect(div!.childNodes[0]!.nextSibling!.textContent).toBe("New");
49
+ expect(div!.childNodes[2]!.previousSibling!.textContent).toBe("New");
50
+ });
51
+
52
+ it("should throw error when old child is not a child", () => {
53
+ const doc = parseHTML("<div></div><p><span></span></p>");
54
+ const div = doc.querySelector("div");
55
+ const pSpan = doc.querySelector("span");
56
+ const newSpan = doc.createElement("span");
57
+
58
+ expect(() => div!.replaceChild(newSpan, pSpan!)).toThrow();
59
+ });
60
+
61
+ it("should handle replacing with the same node", () => {
62
+ const doc = parseHTML("<div><span>Test</span></div>");
63
+ const div = doc.querySelector("div");
64
+ const span = div!.childNodes[0]!;
65
+
66
+ const replaced = div!.replaceChild(span, span);
67
+
68
+ expect(replaced.textContent).toBe("Test");
69
+ expect(div!.childNodes.length).toBe(1);
70
+ expect(div!.childNodes[0]!.textContent).toBe("Test");
71
+ });
72
+
73
+ it("should return the replaced node", () => {
74
+ const doc = parseHTML("<div><span>Old</span></div>");
75
+ const div = doc.querySelector("div");
76
+ const oldSpan = div!.childNodes[0]!;
77
+ const newSpan = doc.createElement("span");
78
+
79
+ const result = div!.replaceChild(newSpan, oldSpan);
80
+
81
+ expect(result).toBe(oldSpan);
82
+ });
83
+ });
84
+
85
+ describe("Replacing different node types", () => {
86
+ it("should replace element with text node", () => {
87
+ const doc = parseHTML("<div><span>Old</span></div>");
88
+ const div = doc.querySelector("div");
89
+ const span = div!.childNodes[0]!;
90
+ const textNode = doc.createTextNode("New text");
91
+
92
+ div!.replaceChild(textNode, span);
93
+
94
+ expect(div!.childNodes.length).toBe(1);
95
+ expect(div!.childNodes[0]!.nodeType).toBe(3);
96
+ expect(div!.textContent).toBe("New text");
97
+ });
98
+
99
+ it("should replace text node with element", () => {
100
+ const doc = parseHTML("<div>Old text</div>");
101
+ const div = doc.querySelector("div");
102
+ const textNode = div!.childNodes[0]!;
103
+ const span = doc.createElement("span");
104
+ span.textContent = "New";
105
+
106
+ div!.replaceChild(span, textNode);
107
+
108
+ expect(div!.childNodes.length).toBe(1);
109
+ expect((div!.childNodes[0] as Element).tagName).toBe("SPAN");
110
+ });
111
+
112
+ it("should replace element with parsed comment", () => {
113
+ const doc = parseHTML("<div><span>Old</span></div>");
114
+ const commentDoc = parseHTML("<p><!-- replaced --></p>");
115
+ const div = doc.querySelector("div");
116
+ const span = div!.childNodes[0]!;
117
+ const comment = commentDoc.querySelector("p")!.childNodes[0]!;
118
+
119
+ div!.replaceChild(comment, span);
120
+
121
+ expect(div!.childNodes.length).toBe(1);
122
+ expect(div!.childNodes[0]!.nodeType).toBe(8);
123
+ });
124
+
125
+ it("should replace comment with element", () => {
126
+ const doc = parseHTML("<div><!-- old --></div>");
127
+ const div = doc.querySelector("div");
128
+ const comment = div!.childNodes[0]!;
129
+ const span = doc.createElement("span");
130
+ span.textContent = "New";
131
+
132
+ div!.replaceChild(span, comment);
133
+
134
+ expect(div!.childNodes.length).toBe(1);
135
+ expect((div!.childNodes[0] as Element).tagName).toBe("SPAN");
136
+ });
137
+ });
138
+
139
+ describe("Element sibling references", () => {
140
+ it("should update nextElementSibling when replacing", () => {
141
+ const doc = parseHTML(
142
+ "<div><span>A</span><span>B</span><span>C</span></div>",
143
+ );
144
+ const div = doc.querySelector("div");
145
+ const spanA = div!.children[0]!;
146
+ const spanB = div!.children[1]!;
147
+ const spanC = div!.children[2]!;
148
+ const newSpan = doc.createElement("span");
149
+ newSpan.textContent = "New";
150
+
151
+ div!.replaceChild(newSpan, spanB);
152
+
153
+ expect(spanA.nextElementSibling).toBe(newSpan);
154
+ expect(newSpan.nextElementSibling).toBe(spanC);
155
+ });
156
+
157
+ it("should update previousElementSibling when replacing", () => {
158
+ const doc = parseHTML(
159
+ "<div><span>A</span><span>B</span><span>C</span></div>",
160
+ );
161
+ const div = doc.querySelector("div");
162
+ const spanA = div!.children[0]!;
163
+ const spanB = div!.children[1]!;
164
+ const spanC = div!.children[2]!;
165
+ const newSpan = doc.createElement("span");
166
+
167
+ div!.replaceChild(newSpan, spanB);
168
+
169
+ expect(newSpan.previousElementSibling).toBe(spanA);
170
+ expect(spanC.previousElementSibling).toBe(newSpan);
171
+ });
172
+
173
+ it("should update firstElementChild when replacing first", () => {
174
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
175
+ const div = doc.querySelector("div");
176
+ const spanA = div!.children[0]!;
177
+ const newSpan = doc.createElement("span");
178
+ newSpan.textContent = "New";
179
+
180
+ div!.replaceChild(newSpan, spanA);
181
+
182
+ expect(div!.firstElementChild).toBe(newSpan);
183
+ });
184
+
185
+ it("should update lastElementChild when replacing last", () => {
186
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
187
+ const div = doc.querySelector("div");
188
+ const spanB = div!.children[1]!;
189
+ const newSpan = doc.createElement("span");
190
+ newSpan.textContent = "New";
191
+
192
+ div!.replaceChild(newSpan, spanB);
193
+
194
+ expect(div!.lastElementChild).toBe(newSpan);
195
+ });
196
+
197
+ it("should update children array", () => {
198
+ const doc = parseHTML(
199
+ "<div><span>A</span><span>B</span><span>C</span></div>",
200
+ );
201
+ const div = doc.querySelector("div");
202
+ const spanB = div!.children[1]!;
203
+ const newSpan = doc.createElement("span");
204
+ newSpan.textContent = "New";
205
+
206
+ div!.replaceChild(newSpan, spanB);
207
+
208
+ expect(div!.children.length).toBe(3);
209
+ expect(div!.children[1]).toBe(newSpan);
210
+ });
211
+
212
+ it("should remove from children when replacing element with non-element", () => {
213
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
214
+ const div = doc.querySelector("div");
215
+ const spanB = div!.children[1]!;
216
+ const textNode = doc.createTextNode("text");
217
+
218
+ div!.replaceChild(textNode, spanB);
219
+
220
+ expect(div!.children.length).toBe(1);
221
+ expect(div!.childNodes.length).toBe(2);
222
+ });
223
+ });
224
+
225
+ describe("Hierarchy validation", () => {
226
+ it("should throw when replacing with ancestor", () => {
227
+ const doc = parseHTML("<div><span><em></em></span></div>");
228
+ const div = doc.querySelector("div");
229
+ const span = doc.querySelector("span");
230
+ const em = doc.querySelector("em");
231
+
232
+ expect(() => span!.replaceChild(div!, em!)).toThrow();
233
+ });
234
+
235
+ it("should throw when creating circular reference", () => {
236
+ const doc = parseHTML("<div><span></span></div>");
237
+ const div = doc.querySelector("div");
238
+ const span = doc.querySelector("span");
239
+
240
+ expect(() => span!.replaceChild(div!, span!)).toThrow();
241
+ });
242
+ });
243
+
244
+ describe("Moving nodes during replace", () => {
245
+ it("should move new node from its current parent", () => {
246
+ const doc = parseHTML(
247
+ "<div><span>Child</span></div><p><em>To move</em></p>",
248
+ );
249
+ const div = doc.querySelector("div");
250
+ const p = doc.querySelector("p");
251
+ const span = doc.querySelector("span");
252
+ const em = doc.querySelector("em");
253
+
254
+ div!.replaceChild(em!, span!);
255
+
256
+ expect(p!.childNodes.length).toBe(0);
257
+ expect(div!.childNodes.length).toBe(1);
258
+ expect(div!.childNodes[0] ?? null).toBe(em);
259
+ });
260
+
261
+ it("should handle replacing with node from different parent", () => {
262
+ const doc = parseHTML("<div><span>A</span></div><p><em>B</em></p>");
263
+ const div = doc.querySelector("div");
264
+ const p = doc.querySelector("p");
265
+ const span = div!.childNodes[0]!;
266
+ const em = p!.childNodes[0]!;
267
+
268
+ div!.replaceChild(em, span);
269
+
270
+ expect(div!.childNodes.length).toBe(1);
271
+ expect(div!.childNodes[0]).toBe(em);
272
+ expect(p!.childNodes.length).toBe(0);
273
+ });
274
+ });
275
+
276
+ describe("Replaced node state", () => {
277
+ it("should clear parentNode of replaced node", () => {
278
+ const doc = parseHTML("<div><span>Old</span></div>");
279
+ const div = doc.querySelector("div");
280
+ const span = div!.childNodes[0]!;
281
+ const newSpan = doc.createElement("span");
282
+
283
+ div!.replaceChild(newSpan, span);
284
+
285
+ expect(span.parentNode).toBe(null);
286
+ });
287
+
288
+ it("should clear sibling references of replaced node", () => {
289
+ const doc = parseHTML(
290
+ "<div><span>A</span><span>B</span><span>C</span></div>",
291
+ );
292
+ const div = doc.querySelector("div");
293
+ const spanB = div!.childNodes[1]!;
294
+ const newSpan = doc.createElement("span");
295
+
296
+ div!.replaceChild(newSpan, spanB);
297
+
298
+ expect(spanB.previousSibling).toBe(null);
299
+ expect(spanB.nextSibling).toBe(null);
300
+ });
301
+
302
+ it("should preserve replaced node children", () => {
303
+ const doc = parseHTML("<div><span><em>Deep</em></span></div>");
304
+ const div = doc.querySelector("div");
305
+ const span = doc.querySelector("span");
306
+ const newSpan = doc.createElement("span");
307
+
308
+ div!.replaceChild(newSpan, span!);
309
+
310
+ expect(span!.childNodes.length).toBe(1);
311
+ expect((span!.childNodes[0] as Element).tagName).toBe("EM");
312
+ });
313
+ });
314
+
315
+ describe("Edge cases", () => {
316
+ it("should handle replacing first child", () => {
317
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
318
+ const div = doc.querySelector("div");
319
+ const spanA = div!.childNodes[0]!;
320
+ const spanB = div!.childNodes[1]!;
321
+ const newSpan = doc.createElement("span");
322
+ newSpan.textContent = "New";
323
+
324
+ div!.replaceChild(newSpan, spanA);
325
+
326
+ expect(div!.firstChild).toBe(newSpan);
327
+ expect(newSpan.previousSibling).toBe(null);
328
+ expect(newSpan.nextSibling).toBe(spanB);
329
+ });
330
+
331
+ it("should handle replacing last child", () => {
332
+ const doc = parseHTML("<div><span>A</span><span>B</span></div>");
333
+ const div = doc.querySelector("div");
334
+ const spanA = div!.childNodes[0]!;
335
+ const spanB = div!.childNodes[1]!;
336
+ const newSpan = doc.createElement("span");
337
+ newSpan.textContent = "New";
338
+
339
+ div!.replaceChild(newSpan, spanB);
340
+
341
+ expect(div!.lastChild).toBe(newSpan);
342
+ expect(newSpan.nextSibling).toBe(null);
343
+ expect(newSpan.previousSibling).toBe(spanA);
344
+ });
345
+
346
+ it("should allow re-using replaced node", () => {
347
+ const doc = parseHTML("<div><span>A</span></div><p></p>");
348
+ const div = doc.querySelector("div");
349
+ const p = doc.querySelector("p");
350
+ const span = doc.querySelector("span");
351
+ const newSpan = doc.createElement("span");
352
+ newSpan.textContent = "New";
353
+
354
+ div!.replaceChild(newSpan, span!);
355
+ p!.appendChild(span!);
356
+
357
+ expect(p!.childNodes[0] ?? null).toBe(span);
358
+ expect(span!.parentNode).toBe(p);
359
+ });
360
+
361
+ it("should handle deeply nested replacement", () => {
362
+ const doc = parseHTML(
363
+ "<div><span><em><strong>Deep</strong></em></span></div>",
364
+ );
365
+ const em = doc.querySelector("em");
366
+ const strong = doc.querySelector("strong");
367
+ const newStrong = doc.createElement("strong");
368
+ newStrong.textContent = "New";
369
+
370
+ em!.replaceChild(newStrong, strong!);
371
+
372
+ expect(em!.childNodes[0]).toBe(newStrong);
373
+ });
374
+ });
375
+ });