@tkeron/html-parser 1.5.1 → 1.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tkeron/html-parser",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "A fast and lightweight HTML parser for Bun",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
@@ -48,9 +48,11 @@ export const updateElementContent = (element: any): void => {
48
48
  configurable: true,
49
49
  });
50
50
 
51
- if (element.parentElement) {
52
- element.parentElement._internalOuterHTML = undefined;
53
- element.parentElement._internalInnerHTML = undefined;
54
- element.parentElement._internalTextContent = undefined;
51
+ let ancestor = element.parentElement || element.parentNode;
52
+ while (ancestor) {
53
+ ancestor._internalOuterHTML = undefined;
54
+ ancestor._internalInnerHTML = undefined;
55
+ ancestor._internalTextContent = undefined;
56
+ ancestor = ancestor.parentElement || ancestor.parentNode;
55
57
  }
56
58
  };
@@ -0,0 +1,246 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { parseHTML } from "../index";
3
+
4
+ describe("DOM Manipulation - append", () => {
5
+ describe("Basic append functionality", () => {
6
+ it("should append a node to an element", () => {
7
+ const doc = parseHTML("<div><span>Existing</span></div>");
8
+ const div = doc.querySelector("div");
9
+ const newSpan = doc.createElement("span");
10
+ newSpan.textContent = "New";
11
+
12
+ div!.append(newSpan);
13
+
14
+ expect(div!.childNodes.length).toBe(2);
15
+ expect(div!.childNodes[0]!.textContent).toBe("Existing");
16
+ expect(div!.childNodes[1]!.textContent).toBe("New");
17
+ });
18
+
19
+ it("should append multiple nodes", () => {
20
+ const doc = parseHTML("<div><span>Existing</span></div>");
21
+ const div = doc.querySelector("div");
22
+ const span1 = doc.createElement("span");
23
+ span1.textContent = "1";
24
+ const span2 = doc.createElement("span");
25
+ span2.textContent = "2";
26
+
27
+ div!.append(span1, span2);
28
+
29
+ expect(div!.childNodes.length).toBe(3);
30
+ expect(div!.childNodes[0]!.textContent).toBe("Existing");
31
+ expect(div!.childNodes[1]!.textContent).toBe("1");
32
+ expect(div!.childNodes[2]!.textContent).toBe("2");
33
+ });
34
+
35
+ it("should append to empty element", () => {
36
+ const doc = parseHTML("<div></div>");
37
+ const div = doc.querySelector("div");
38
+ const newSpan = doc.createElement("span");
39
+ newSpan.textContent = "First";
40
+
41
+ div!.append(newSpan);
42
+
43
+ expect(div!.childNodes.length).toBe(1);
44
+ expect(div!.childNodes[0]!.textContent).toBe("First");
45
+ });
46
+
47
+ it("should update parent reference of appended node", () => {
48
+ const doc = parseHTML("<div></div>");
49
+ const div = doc.querySelector("div");
50
+ const span = doc.createElement("span");
51
+
52
+ div!.append(span);
53
+
54
+ expect(span.parentNode).toBe(div);
55
+ });
56
+
57
+ it("should update lastChild reference", () => {
58
+ const doc = parseHTML("<div><span>First</span></div>");
59
+ const div = doc.querySelector("div");
60
+ const newSpan = doc.createElement("span");
61
+ newSpan.textContent = "Last";
62
+
63
+ div!.append(newSpan);
64
+
65
+ expect(div!.lastChild).toBe(newSpan);
66
+ });
67
+ });
68
+
69
+ describe("Appending strings", () => {
70
+ it("should append a string as text node", () => {
71
+ const doc = parseHTML("<div></div>");
72
+ const div = doc.querySelector("div");
73
+
74
+ div!.append("Hello");
75
+
76
+ expect(div!.childNodes.length).toBe(1);
77
+ expect(div!.childNodes[0]!.nodeType).toBe(3);
78
+ expect(div!.textContent).toBe("Hello");
79
+ });
80
+
81
+ it("should append multiple strings", () => {
82
+ const doc = parseHTML("<div></div>");
83
+ const div = doc.querySelector("div");
84
+
85
+ div!.append("Hello", " ", "World");
86
+
87
+ expect(div!.childNodes.length).toBe(3);
88
+ expect(div!.textContent).toBe("Hello World");
89
+ });
90
+
91
+ it("should append empty string", () => {
92
+ const doc = parseHTML("<div></div>");
93
+ const div = doc.querySelector("div");
94
+
95
+ div!.append("");
96
+
97
+ expect(div!.childNodes.length).toBe(1);
98
+ expect(div!.textContent).toBe("");
99
+ });
100
+
101
+ it("should append mixed nodes and strings", () => {
102
+ const doc = parseHTML("<div></div>");
103
+ const div = doc.querySelector("div");
104
+ const span = doc.createElement("span");
105
+ span.textContent = "Element";
106
+
107
+ div!.append("Text ", span, " More text");
108
+
109
+ expect(div!.childNodes.length).toBe(3);
110
+ expect(div!.childNodes[0]!.nodeType).toBe(3);
111
+ expect((div!.childNodes[1] as Element).tagName).toBe("SPAN");
112
+ expect(div!.childNodes[2]!.nodeType).toBe(3);
113
+ });
114
+
115
+ it("should append string with special characters", () => {
116
+ const doc = parseHTML("<div></div>");
117
+ const div = doc.querySelector("div");
118
+
119
+ div!.append("<script>alert('xss')</script>");
120
+
121
+ expect(div!.childNodes.length).toBe(1);
122
+ expect(div!.childNodes[0]!.nodeType).toBe(3);
123
+ expect(div!.textContent).toBe("<script>alert('xss')</script>");
124
+ });
125
+
126
+ it("should append string with unicode characters", () => {
127
+ const doc = parseHTML("<div></div>");
128
+ const div = doc.querySelector("div");
129
+
130
+ div!.append("日本語 🎉 émoji");
131
+
132
+ expect(div!.textContent).toBe("日本語 🎉 émoji");
133
+ });
134
+ });
135
+
136
+ describe("Moving nodes (node already has parent)", () => {
137
+ it("should move node from one parent to another", () => {
138
+ const doc = parseHTML("<div><span>Child</span></div><p></p>");
139
+ const div = doc.querySelector("div");
140
+ const p = doc.querySelector("p");
141
+ const span = doc.querySelector("span");
142
+
143
+ p!.append(span!);
144
+
145
+ expect(div!.childNodes.length).toBe(0);
146
+ expect(p!.childNodes.length).toBe(1);
147
+ expect(span!.parentNode).toBe(p);
148
+ });
149
+
150
+ it("should update sibling references when moving node", () => {
151
+ const doc = parseHTML(
152
+ "<div><span>A</span><span>B</span><span>C</span></div><p></p>",
153
+ );
154
+ const div = doc.querySelector("div");
155
+ const p = doc.querySelector("p");
156
+ const spanB = div!.childNodes[1]!;
157
+
158
+ p!.append(spanB);
159
+
160
+ expect(div!.childNodes.length).toBe(2);
161
+ expect(div!.childNodes[0]?.nextSibling).toBe(div!.childNodes[1] ?? null);
162
+ expect(div!.childNodes[1]?.previousSibling).toBe(
163
+ div!.childNodes[0] ?? null,
164
+ );
165
+ });
166
+
167
+ it("should handle moving multiple nodes in sequence", () => {
168
+ const doc = parseHTML("<div><span>1</span><span>2</span></div><p></p>");
169
+ const div = doc.querySelector("div");
170
+ const p = doc.querySelector("p");
171
+ const span1 = div!.childNodes[0]!;
172
+ const span2 = div!.childNodes[1]!;
173
+
174
+ p!.append(span1, span2);
175
+
176
+ expect(div!.childNodes.length).toBe(0);
177
+ expect(p!.childNodes.length).toBe(2);
178
+ });
179
+ });
180
+
181
+ describe("Edge cases", () => {
182
+ it("should handle no arguments", () => {
183
+ const doc = parseHTML("<div><span>Existing</span></div>");
184
+ const div = doc.querySelector("div");
185
+
186
+ div!.append();
187
+
188
+ expect(div!.childNodes.length).toBe(1);
189
+ });
190
+
191
+ it("should handle deeply nested elements", () => {
192
+ const doc = parseHTML("<div></div>");
193
+ const div = doc.querySelector("div");
194
+ const level1 = doc.createElement("div");
195
+ const level2 = doc.createElement("div");
196
+ const level3 = doc.createElement("div");
197
+ level2.append(level3);
198
+ level1.append(level2);
199
+
200
+ div!.append(level1);
201
+
202
+ expect(div!.childNodes.length).toBe(1);
203
+ expect(div!.childNodes[0]!.childNodes[0]!.childNodes[0]).toBe(level3);
204
+ });
205
+
206
+ it("should append parsed comment nodes", () => {
207
+ const doc = parseHTML("<div><!-- existing --></div>");
208
+ const div = doc.querySelector("div");
209
+ const comment = div!.childNodes[0]!;
210
+ const target = parseHTML("<p></p>").querySelector("p");
211
+
212
+ target!.append(comment);
213
+
214
+ expect(target!.childNodes.length).toBe(1);
215
+ expect(target!.childNodes[0]!.nodeType).toBe(8);
216
+ });
217
+
218
+ it("should maintain order when appending many nodes", () => {
219
+ const doc = parseHTML("<div></div>");
220
+ const div = doc.querySelector("div");
221
+ const nodes: any[] = [];
222
+ for (let i = 0; i < 100; i++) {
223
+ const span = doc.createElement("span");
224
+ span.textContent = String(i);
225
+ nodes.push(span);
226
+ }
227
+
228
+ div!.append(...nodes);
229
+
230
+ expect(div!.childNodes.length).toBe(100);
231
+ for (let i = 0; i < 100; i++) {
232
+ expect(div!.childNodes[i]!.textContent).toBe(String(i));
233
+ }
234
+ });
235
+
236
+ it("should handle appending to void-like elements", () => {
237
+ const doc = parseHTML("<div></div>");
238
+ const input = doc.createElement("input");
239
+ const span = doc.createElement("span");
240
+
241
+ input.append(span);
242
+
243
+ expect(input.childNodes.length).toBe(1);
244
+ });
245
+ });
246
+ });
@@ -0,0 +1,313 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { parseHTML } from "../index";
3
+
4
+ describe("DOM Manipulation - appendChild", () => {
5
+ describe("Basic appendChild functionality", () => {
6
+ it("should append a node to an element", () => {
7
+ const doc = parseHTML("<div><span>First</span></div>");
8
+ const div = doc.querySelector("div");
9
+ const newSpan = doc.createElement("span");
10
+ newSpan.textContent = "Second";
11
+
12
+ div!.appendChild(newSpan);
13
+
14
+ expect(div!.childNodes.length).toBe(2);
15
+ expect((div!.childNodes[1] as Element).tagName).toBe("SPAN");
16
+ expect(div!.childNodes[1]!.textContent).toBe("Second");
17
+ expect((newSpan.parentNode as Element).tagName).toBe("DIV");
18
+ });
19
+
20
+ it("should append multiple nodes sequentially", () => {
21
+ const doc = parseHTML("<div></div>");
22
+ const div = doc.querySelector("div");
23
+ const span1 = doc.createElement("span");
24
+ span1.textContent = "1";
25
+ const span2 = doc.createElement("span");
26
+ span2.textContent = "2";
27
+
28
+ div!.appendChild(span1);
29
+ div!.appendChild(span2);
30
+
31
+ expect(div!.childNodes.length).toBe(2);
32
+ expect((div!.childNodes[0] as Element).tagName).toBe("SPAN");
33
+ expect(div!.childNodes[0]!.textContent).toBe("1");
34
+ expect((div!.childNodes[1] as Element).tagName).toBe("SPAN");
35
+ expect(div!.childNodes[1]!.textContent).toBe("2");
36
+ });
37
+
38
+ it("should handle appending text nodes", () => {
39
+ const doc = parseHTML("<div></div>");
40
+ const div = doc.querySelector("div");
41
+ const textNode = doc.createTextNode("Hello");
42
+
43
+ div!.appendChild(textNode);
44
+
45
+ expect(div!.childNodes.length).toBe(1);
46
+ expect(div!.childNodes[0]!.nodeType).toBe(3);
47
+ expect(div!.childNodes[0]!.textContent).toBe("Hello");
48
+ expect(div!.textContent).toBe("Hello");
49
+ });
50
+
51
+ it("should throw error when appending to itself", () => {
52
+ const doc = parseHTML("<div></div>");
53
+ const div = doc.querySelector("div");
54
+
55
+ expect(() => div!.appendChild(div!)).toThrow();
56
+ });
57
+
58
+ it("should move node from another parent", () => {
59
+ const doc = parseHTML("<div><span></span></div><p></p>");
60
+ const div = doc.querySelector("div");
61
+ const span = doc.querySelector("span");
62
+ const p = doc.querySelector("p");
63
+
64
+ p!.appendChild(span!);
65
+
66
+ expect(div!.childNodes.length).toBe(0);
67
+ expect(p!.childNodes.length).toBe(1);
68
+ expect((p!.childNodes[0] as Element).tagName).toBe("SPAN");
69
+ expect((span!.parentNode as Element).tagName).toBe("P");
70
+ });
71
+
72
+ it("should update sibling references", () => {
73
+ const doc = parseHTML("<div><span>A</span></div>");
74
+ const div = doc.querySelector("div");
75
+ const spanA = div!.childNodes[0]!;
76
+ const spanB = doc.createElement("span");
77
+ spanB.textContent = "B";
78
+
79
+ div!.appendChild(spanB);
80
+
81
+ expect(spanA.nextSibling).toBe(spanB);
82
+ expect(spanB.previousSibling).toBe(spanA);
83
+ });
84
+
85
+ it("should update firstChild and lastChild", () => {
86
+ const doc = parseHTML("<div></div>");
87
+ const div = doc.querySelector("div");
88
+ const span = doc.createElement("span");
89
+
90
+ div!.appendChild(span);
91
+
92
+ expect(div!.firstChild).toBe(span);
93
+ expect(div!.lastChild).toBe(span);
94
+
95
+ const span2 = doc.createElement("span");
96
+ div!.appendChild(span2);
97
+
98
+ expect(div!.firstChild).toBe(span);
99
+ expect(div!.lastChild).toBe(span2);
100
+ });
101
+
102
+ it("should return the appended node", () => {
103
+ const doc = parseHTML("<div></div>");
104
+ const div = doc.querySelector("div");
105
+ const span = doc.createElement("span");
106
+
107
+ const result = div!.appendChild(span);
108
+
109
+ expect(result).toBe(span);
110
+ });
111
+ });
112
+
113
+ describe("Appending different node types", () => {
114
+ it("should append parsed comment nodes", () => {
115
+ const doc = parseHTML("<div><!-- This is a comment --></div>");
116
+ const div = doc.querySelector("div");
117
+ const comment = div!.childNodes[0]!;
118
+ const target = parseHTML("<p></p>").querySelector("p");
119
+
120
+ target!.appendChild(comment);
121
+
122
+ expect(target!.childNodes.length).toBe(1);
123
+ expect(target!.childNodes[0]!.nodeType).toBe(8);
124
+ });
125
+
126
+ it("should append text node with empty string", () => {
127
+ const doc = parseHTML("<div></div>");
128
+ const div = doc.querySelector("div");
129
+ const textNode = doc.createTextNode("");
130
+
131
+ div!.appendChild(textNode);
132
+
133
+ expect(div!.childNodes.length).toBe(1);
134
+ expect(div!.textContent).toBe("");
135
+ });
136
+
137
+ it("should append text node with whitespace only", () => {
138
+ const doc = parseHTML("<div></div>");
139
+ const div = doc.querySelector("div");
140
+ const textNode = doc.createTextNode(" \n\t ");
141
+
142
+ div!.appendChild(textNode);
143
+
144
+ expect(div!.childNodes.length).toBe(1);
145
+ expect(div!.childNodes[0]!.textContent).toBe(" \n\t ");
146
+ });
147
+
148
+ it("should append deeply nested elements", () => {
149
+ const doc = parseHTML("<div></div>");
150
+ const div = doc.querySelector("div");
151
+ const level1 = doc.createElement("div");
152
+ const level2 = doc.createElement("div");
153
+ const level3 = doc.createElement("span");
154
+ level3.textContent = "Deep";
155
+ level2.appendChild(level3);
156
+ level1.appendChild(level2);
157
+
158
+ div!.appendChild(level1);
159
+
160
+ expect(
161
+ div!.childNodes[0]!.childNodes[0]!.childNodes[0]!.textContent,
162
+ ).toBe("Deep");
163
+ });
164
+ });
165
+
166
+ describe("Hierarchy validation", () => {
167
+ it("should throw when appending ancestor to descendant", () => {
168
+ const doc = parseHTML("<div><span><em></em></span></div>");
169
+ const div = doc.querySelector("div");
170
+ const em = doc.querySelector("em");
171
+
172
+ expect(() => em!.appendChild(div!)).toThrow();
173
+ });
174
+
175
+ it("should throw when creating circular reference", () => {
176
+ const doc = parseHTML("<div><span></span></div>");
177
+ const div = doc.querySelector("div");
178
+ const span = doc.querySelector("span");
179
+
180
+ expect(() => span!.appendChild(div!)).toThrow();
181
+ });
182
+
183
+ it("should allow appending sibling", () => {
184
+ const doc = parseHTML("<div><span>A</span><em>B</em></div>");
185
+ const span = doc.querySelector("span");
186
+ const em = doc.querySelector("em");
187
+
188
+ span!.appendChild(em!);
189
+
190
+ expect(span!.childNodes.length).toBe(2);
191
+ expect(span!.childNodes[1] ?? null).toBe(em);
192
+ });
193
+ });
194
+
195
+ describe("Moving nodes", () => {
196
+ it("should properly remove from original parent", () => {
197
+ const doc = parseHTML("<div><span>Child</span></div><p></p>");
198
+ const div = doc.querySelector("div");
199
+ const p = doc.querySelector("p");
200
+ const span = doc.querySelector("span");
201
+
202
+ p!.appendChild(span!);
203
+
204
+ expect(div!.childNodes.length).toBe(0);
205
+ expect(div!.firstChild).toBe(null);
206
+ expect(div!.lastChild).toBe(null);
207
+ });
208
+
209
+ it("should update children array when moving elements", () => {
210
+ const doc = parseHTML("<div><span>1</span><span>2</span></div><p></p>");
211
+ const div = doc.querySelector("div");
212
+ const p = doc.querySelector("p");
213
+ const span1 = div!.children[0]!;
214
+
215
+ p!.appendChild(span1);
216
+
217
+ expect(div!.children.length).toBe(1);
218
+ expect(p!.children.length).toBe(1);
219
+ expect(p!.children[0]!).toBe(span1);
220
+ });
221
+
222
+ it("should handle moving first child", () => {
223
+ const doc = parseHTML("<div><span>A</span><span>B</span></div><p></p>");
224
+ const div = doc.querySelector("div");
225
+ const p = doc.querySelector("p");
226
+ const spanA = div!.childNodes[0]!;
227
+ const spanB = div!.childNodes[1]!;
228
+
229
+ p!.appendChild(spanA);
230
+
231
+ expect(div!.firstChild).toBe(spanB);
232
+ expect(spanB.previousSibling).toBe(null);
233
+ });
234
+
235
+ it("should handle moving last child", () => {
236
+ const doc = parseHTML("<div><span>A</span><span>B</span></div><p></p>");
237
+ const div = doc.querySelector("div");
238
+ const p = doc.querySelector("p");
239
+ const spanA = div!.childNodes[0]!;
240
+ const spanB = div!.childNodes[1]!;
241
+
242
+ p!.appendChild(spanB);
243
+
244
+ expect(div!.lastChild).toBe(spanA);
245
+ expect(spanA.nextSibling).toBe(null);
246
+ });
247
+
248
+ it("should handle moving middle child", () => {
249
+ const doc = parseHTML(
250
+ "<div><span>A</span><span>B</span><span>C</span></div><p></p>",
251
+ );
252
+ const div = doc.querySelector("div");
253
+ const p = doc.querySelector("p");
254
+ const spanA = div!.childNodes[0]!;
255
+ const spanB = div!.childNodes[1]!;
256
+ const spanC = div!.childNodes[2]!;
257
+
258
+ p!.appendChild(spanB);
259
+
260
+ expect(div!.childNodes.length).toBe(2);
261
+ expect(spanA.nextSibling).toBe(spanC);
262
+ expect(spanC.previousSibling).toBe(spanA);
263
+ });
264
+ });
265
+
266
+ describe("Edge cases", () => {
267
+ it("should handle appending many nodes", () => {
268
+ const doc = parseHTML("<div></div>");
269
+ const div = doc.querySelector("div");
270
+
271
+ for (let i = 0; i < 100; i++) {
272
+ const span = doc.createElement("span");
273
+ span.textContent = String(i);
274
+ div!.appendChild(span);
275
+ }
276
+
277
+ expect(div!.childNodes.length).toBe(100);
278
+ expect(div!.childNodes[0]!.textContent).toBe("0");
279
+ expect(div!.childNodes[99]!.textContent).toBe("99");
280
+ });
281
+
282
+ it("should handle unicode content", () => {
283
+ const doc = parseHTML("<div></div>");
284
+ const div = doc.querySelector("div");
285
+ const textNode = doc.createTextNode("日本語 🎉 émoji");
286
+
287
+ div!.appendChild(textNode);
288
+
289
+ expect(div!.textContent).toBe("日本語 🎉 émoji");
290
+ });
291
+
292
+ it("should handle special HTML characters in text nodes", () => {
293
+ const doc = parseHTML("<div></div>");
294
+ const div = doc.querySelector("div");
295
+ const textNode = doc.createTextNode("<script>alert('xss')</script>");
296
+
297
+ div!.appendChild(textNode);
298
+
299
+ expect(div!.textContent).toBe("<script>alert('xss')</script>");
300
+ });
301
+
302
+ it("should handle appending to document fragment-like structure", () => {
303
+ const doc = parseHTML("<div></div>");
304
+ const container = doc.createElement("div");
305
+ const span1 = doc.createElement("span");
306
+ const span2 = doc.createElement("span");
307
+ container.appendChild(span1);
308
+ container.appendChild(span2);
309
+
310
+ expect(container.childNodes.length).toBe(2);
311
+ });
312
+ });
313
+ });