@tkeron/html-parser 0.1.3 → 0.1.4

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/bun.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "lockfileVersion": 1,
3
- "configVersion": 0,
3
+ "configVersion": 1,
4
4
  "workspaces": {
5
5
  "": {
6
- "name": "html-parser",
6
+ "name": "@tkeron/html-parser",
7
7
  "devDependencies": {
8
8
  "@types/bun": "^1.3.4",
9
9
  },
@@ -15,12 +15,12 @@
15
15
  "packages": {
16
16
  "@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
17
17
 
18
- "@types/node": ["@types/node@24.0.4", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA=="],
18
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
19
19
 
20
20
  "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
21
21
 
22
22
  "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
23
23
 
24
- "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
24
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
25
25
  }
26
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tkeron/html-parser",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A fast and lightweight HTML parser for Bun",
5
5
  "main": "index.js",
6
6
  "module": "index.ts",
@@ -0,0 +1,325 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseHTML } from "../index.js";
3
+
4
+ /**
5
+ * Tests específicos para reproducir el problema reportado:
6
+ * "Cuando se clona un nodo con cloneNode(true), el nodo clonado
7
+ * pierde su contenido interno (_internalInnerHTML y estructura de hijos)."
8
+ *
9
+ * Estos tests intentan reproducir el problema exacto descrito.
10
+ */
11
+ describe("cloneNode - Bug Reproduction Tests", () => {
12
+
13
+ it("REPRODUCCIÓN: debe copiar _internalInnerHTML correctamente", () => {
14
+ const html = `
15
+ <div id="container">
16
+ <h1>Título Principal</h1>
17
+ <p>Este es un párrafo con <strong>texto en negrita</strong>.</p>
18
+ <ul>
19
+ <li>Item 1</li>
20
+ <li>Item 2</li>
21
+ <li>Item 3</li>
22
+ </ul>
23
+ </div>
24
+ `;
25
+
26
+ const doc = parseHTML(html);
27
+ const container = doc.querySelector("#container")!;
28
+
29
+ // Verificar estado ANTES del clonado
30
+ console.log("=== ANTES DEL CLONADO ===");
31
+ console.log("Original innerHTML:", container.innerHTML);
32
+ console.log("Original _internalInnerHTML:", (container as any)._internalInnerHTML);
33
+ console.log("Original childNodes.length:", container.childNodes.length);
34
+ console.log("Original children.length:", container.children.length);
35
+
36
+ // Realizar el clonado
37
+ const cloned = container.cloneNode(true);
38
+
39
+ // Verificar estado DESPUÉS del clonado
40
+ console.log("\n=== DESPUÉS DEL CLONADO ===");
41
+ console.log("Clonado innerHTML:", cloned.innerHTML);
42
+ console.log("Clonado _internalInnerHTML:", (cloned as any)._internalInnerHTML);
43
+ console.log("Clonado childNodes.length:", cloned.childNodes.length);
44
+ console.log("Clonado children.length:", cloned.children.length);
45
+
46
+ // VERIFICACIONES CRÍTICAS
47
+
48
+ // 1. innerHTML no debe estar vacío
49
+ expect(cloned.innerHTML).toBeTruthy();
50
+ expect(cloned.innerHTML.length).toBeGreaterThan(0);
51
+
52
+ // 2. Debe contener los elementos esperados
53
+ expect(cloned.innerHTML).toContain("Título Principal");
54
+ expect(cloned.innerHTML).toContain("<strong>texto en negrita</strong>");
55
+ expect(cloned.innerHTML).toContain("<li>Item 1</li>");
56
+
57
+ // 3. childNodes debe tener contenido
58
+ expect(cloned.childNodes.length).toBeGreaterThan(0);
59
+ expect(cloned.childNodes.length).toBe(container.childNodes.length);
60
+
61
+ // 4. children debe tener los elementos
62
+ expect(cloned.children.length).toBe(3); // h1, p, ul
63
+ expect(cloned.children.length).toBe(container.children.length);
64
+
65
+ // 5. Los elementos deben ser accesibles mediante querySelector
66
+ expect(cloned.querySelector("h1")).toBeTruthy();
67
+ expect(cloned.querySelector("p")).toBeTruthy();
68
+ expect(cloned.querySelector("ul")).toBeTruthy();
69
+ expect(cloned.querySelectorAll("li").length).toBe(3);
70
+ });
71
+
72
+ it("REPRODUCCIÓN: debe mantener la estructura completa de hijos", () => {
73
+ const html = `
74
+ <div id="parent">
75
+ <div class="level-1">
76
+ <div class="level-2">
77
+ <div class="level-3">
78
+ Contenido profundo
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ `;
84
+
85
+ const doc = parseHTML(html);
86
+ const parent = doc.querySelector("#parent")!;
87
+
88
+ console.log("\n=== ESTRUCTURA DE HIJOS ANTES ===");
89
+ console.log("Parent childNodes:", parent.childNodes.length);
90
+ console.log("Level-1 existe:", !!parent.querySelector(".level-1"));
91
+ console.log("Level-2 existe:", !!parent.querySelector(".level-2"));
92
+ console.log("Level-3 existe:", !!parent.querySelector(".level-3"));
93
+
94
+ const cloned = parent.cloneNode(true);
95
+
96
+ console.log("\n=== ESTRUCTURA DE HIJOS DESPUÉS ===");
97
+ console.log("Clonado childNodes:", cloned.childNodes.length);
98
+ console.log("Level-1 existe:", !!cloned.querySelector(".level-1"));
99
+ console.log("Level-2 existe:", !!cloned.querySelector(".level-2"));
100
+ console.log("Level-3 existe:", !!cloned.querySelector(".level-3"));
101
+
102
+ // VERIFICAR que la estructura se mantiene
103
+ expect(cloned.childNodes.length).toBe(parent.childNodes.length);
104
+ expect(cloned.querySelector(".level-1")).toBeTruthy();
105
+ expect(cloned.querySelector(".level-2")).toBeTruthy();
106
+ expect(cloned.querySelector(".level-3")).toBeTruthy();
107
+ expect(cloned.querySelector(".level-3")?.textContent).toContain("Contenido profundo");
108
+ });
109
+
110
+ it("REPRODUCCIÓN: debe copiar atributos Y contenido simultáneamente", () => {
111
+ const html = `
112
+ <div
113
+ id="complex"
114
+ class="container main"
115
+ data-id="123"
116
+ data-type="test"
117
+ aria-label="Complex element"
118
+ >
119
+ <header>
120
+ <h1>Header Title</h1>
121
+ </header>
122
+ <main>
123
+ <p>Main content</p>
124
+ </main>
125
+ <footer>
126
+ <p>Footer content</p>
127
+ </footer>
128
+ </div>
129
+ `;
130
+
131
+ const doc = parseHTML(html);
132
+ const complex = doc.querySelector("#complex")!;
133
+
134
+ console.log("\n=== ANTES: ATRIBUTOS Y CONTENIDO ===");
135
+ console.log("Atributos:", JSON.stringify(complex.attributes, null, 2));
136
+ console.log("innerHTML length:", complex.innerHTML.length);
137
+ console.log("Hijos:", complex.children.length);
138
+
139
+ const cloned = complex.cloneNode(true);
140
+
141
+ console.log("\n=== DESPUÉS: ATRIBUTOS Y CONTENIDO ===");
142
+ console.log("Atributos clonados:", JSON.stringify(cloned.attributes, null, 2));
143
+ console.log("innerHTML length clonado:", cloned.innerHTML.length);
144
+ console.log("Hijos clonados:", cloned.children.length);
145
+
146
+ // VERIFICAR atributos
147
+ expect(cloned.getAttribute("id")).toBe("complex");
148
+ expect(cloned.getAttribute("class")).toBe("container main");
149
+ expect(cloned.getAttribute("data-id")).toBe("123");
150
+ expect(cloned.getAttribute("data-type")).toBe("test");
151
+ expect(cloned.getAttribute("aria-label")).toBe("Complex element");
152
+
153
+ // VERIFICAR contenido
154
+ expect(cloned.innerHTML.length).toBeGreaterThan(0);
155
+ expect(cloned.children.length).toBe(3); // header, main, footer
156
+ expect(cloned.querySelector("header h1")?.textContent).toBe("Header Title");
157
+ expect(cloned.querySelector("main p")?.textContent).toBe("Main content");
158
+ expect(cloned.querySelector("footer p")?.textContent).toBe("Footer content");
159
+ });
160
+
161
+ it("REPRODUCCIÓN: comparar original vs clonado lado a lado", () => {
162
+ const html = `
163
+ <article id="article">
164
+ <h1>Artículo de Prueba</h1>
165
+ <p class="intro">Introducción del artículo.</p>
166
+ <section>
167
+ <h2>Sección 1</h2>
168
+ <p>Contenido de la sección 1.</p>
169
+ </section>
170
+ <section>
171
+ <h2>Sección 2</h2>
172
+ <p>Contenido de la sección 2.</p>
173
+ </section>
174
+ </article>
175
+ `;
176
+
177
+ const doc = parseHTML(html);
178
+ const original = doc.querySelector("#article")!;
179
+ const cloned = original.cloneNode(true);
180
+
181
+ console.log("\n=== COMPARACIÓN ORIGINAL VS CLONADO ===");
182
+
183
+ const comparison = {
184
+ nodeName: {
185
+ original: original.nodeName,
186
+ cloned: cloned.nodeName,
187
+ match: original.nodeName === cloned.nodeName
188
+ },
189
+ id: {
190
+ original: original.getAttribute("id"),
191
+ cloned: cloned.getAttribute("id"),
192
+ match: original.getAttribute("id") === cloned.getAttribute("id")
193
+ },
194
+ childNodesLength: {
195
+ original: original.childNodes.length,
196
+ cloned: cloned.childNodes.length,
197
+ match: original.childNodes.length === cloned.childNodes.length
198
+ },
199
+ childrenLength: {
200
+ original: original.children.length,
201
+ cloned: cloned.children.length,
202
+ match: original.children.length === cloned.children.length
203
+ },
204
+ innerHTMLLength: {
205
+ original: original.innerHTML.length,
206
+ cloned: cloned.innerHTML.length,
207
+ match: original.innerHTML.length === cloned.innerHTML.length
208
+ },
209
+ textContentLength: {
210
+ original: original.textContent.length,
211
+ cloned: cloned.textContent.length,
212
+ match: original.textContent.length === cloned.textContent.length
213
+ }
214
+ };
215
+
216
+ console.table(comparison);
217
+
218
+ // VERIFICAR que todos coinciden
219
+ expect(comparison.nodeName.match).toBe(true);
220
+ expect(comparison.id.match).toBe(true);
221
+ expect(comparison.childNodesLength.match).toBe(true);
222
+ expect(comparison.childrenLength.match).toBe(true);
223
+ expect(comparison.innerHTMLLength.match).toBe(true);
224
+ expect(comparison.textContentLength.match).toBe(true);
225
+
226
+ // Verificar contenido específico
227
+ expect(cloned.querySelector("h1")?.textContent).toBe("Artículo de Prueba");
228
+ expect(cloned.querySelector(".intro")?.textContent).toBe("Introducción del artículo.");
229
+ expect(cloned.querySelectorAll("section").length).toBe(2);
230
+ expect(cloned.querySelectorAll("h2").length).toBe(2);
231
+ });
232
+
233
+ it("REPRODUCCIÓN: verificar que innerHTML NO está vacío después de clonar", () => {
234
+ const testCases = [
235
+ {
236
+ name: "Elemento simple con texto",
237
+ html: `<div>Texto simple</div>`
238
+ },
239
+ {
240
+ name: "Elemento con un hijo",
241
+ html: `<div><span>Un hijo</span></div>`
242
+ },
243
+ {
244
+ name: "Elemento con múltiples hijos",
245
+ html: `<div><p>P1</p><p>P2</p><p>P3</p></div>`
246
+ },
247
+ {
248
+ name: "Elemento con anidación profunda",
249
+ html: `<div><div><div><div>Profundo</div></div></div></div>`
250
+ },
251
+ {
252
+ name: "Lista compleja",
253
+ html: `<ul><li>A</li><li>B<ul><li>B1</li><li>B2</li></ul></li><li>C</li></ul>`
254
+ }
255
+ ];
256
+
257
+ console.log("\n=== VERIFICANDO innerHTML EN MÚLTIPLES CASOS ===");
258
+
259
+ testCases.forEach(({ name, html }) => {
260
+ const doc = parseHTML(html);
261
+ const root = doc.body || doc;
262
+ const element = root.querySelector("div") || root.querySelector("ul")!;
263
+ const cloned = element.cloneNode(true);
264
+
265
+ const result = {
266
+ caso: name,
267
+ originalInnerHTML_vacio: !element.innerHTML || element.innerHTML.length === 0,
268
+ clonedInnerHTML_vacio: !cloned.innerHTML || cloned.innerHTML.length === 0,
269
+ originalLength: element.innerHTML.length,
270
+ clonedLength: cloned.innerHTML.length,
271
+ match: element.innerHTML.length === cloned.innerHTML.length
272
+ };
273
+
274
+ console.log(JSON.stringify(result, null, 2));
275
+
276
+ // VERIFICAR que ningún innerHTML está vacío (excepto elementos sin hijos)
277
+ if (element.children.length > 0) {
278
+ expect(cloned.innerHTML).toBeTruthy();
279
+ expect(cloned.innerHTML.length).toBeGreaterThan(0);
280
+ expect(cloned.innerHTML.length).toBe(element.innerHTML.length);
281
+ }
282
+ });
283
+ });
284
+
285
+ it("REPRODUCCIÓN: acceder a childNodes inmediatamente después de clonar", () => {
286
+ const html = `
287
+ <div id="test">
288
+ Texto inicial
289
+ <span>Span 1</span>
290
+ Texto intermedio
291
+ <span>Span 2</span>
292
+ Texto final
293
+ </div>
294
+ `;
295
+
296
+ const doc = parseHTML(html);
297
+ const test = doc.querySelector("#test")!;
298
+
299
+ console.log("\n=== ACCESO A childNodes ===");
300
+ console.log("Original childNodes.length:", test.childNodes.length);
301
+
302
+ const cloned = test.cloneNode(true);
303
+
304
+ console.log("Clonado childNodes.length INMEDIATAMENTE:", cloned.childNodes.length);
305
+
306
+ // Verificar inmediatamente después del clonado
307
+ expect(cloned.childNodes).toBeTruthy();
308
+ expect(cloned.childNodes.length).toBeGreaterThan(0);
309
+ expect(cloned.childNodes.length).toBe(test.childNodes.length);
310
+
311
+ // Verificar que podemos iterar los childNodes
312
+ console.log("\nIterando childNodes del clon:");
313
+ for (let i = 0; i < cloned.childNodes.length; i++) {
314
+ const node = cloned.childNodes[i];
315
+ console.log(` ${i}: nodeType=${node.nodeType}, nodeName=${node.nodeName}, content="${node.textContent?.substring(0, 20)}"`);
316
+ expect(node).toBeTruthy();
317
+ }
318
+
319
+ // Verificar que los spans son accesibles
320
+ const spans = cloned.querySelectorAll("span");
321
+ expect(spans.length).toBe(2);
322
+ expect(spans[0]?.textContent).toBe("Span 1");
323
+ expect(spans[1]?.textContent).toBe("Span 2");
324
+ });
325
+ });
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Script interactivo para probar cloneNode manualmente
5
+ *
6
+ * Uso:
7
+ * bun run tests/cloneNode-interactive.ts
8
+ *
9
+ * Este script permite probar cloneNode con diferentes HTML inputs
10
+ * y ver en detalle qué sucede durante el clonado.
11
+ */
12
+
13
+ import { parseHTML } from "../index.js";
14
+
15
+ // Colores para la consola
16
+ const colors = {
17
+ reset: "\x1b[0m",
18
+ bright: "\x1b[1m",
19
+ green: "\x1b[32m",
20
+ blue: "\x1b[34m",
21
+ yellow: "\x1b[33m",
22
+ red: "\x1b[31m",
23
+ cyan: "\x1b[36m",
24
+ };
25
+
26
+ function log(color: string, ...args: any[]) {
27
+ console.log(color + args.join(" ") + colors.reset);
28
+ }
29
+
30
+ function separator() {
31
+ console.log("\n" + "=".repeat(70) + "\n");
32
+ }
33
+
34
+ function testCloneNode(html: string, selector: string, testName: string) {
35
+ separator();
36
+ log(colors.bright + colors.blue, `🧪 TEST: ${testName}`);
37
+ console.log();
38
+
39
+ log(colors.cyan, "📝 HTML Input:");
40
+ console.log(html);
41
+ console.log();
42
+
43
+ // Parse HTML
44
+ const doc = parseHTML(html);
45
+ const element = doc.querySelector(selector);
46
+
47
+ if (!element) {
48
+ log(colors.red, "❌ ERROR: No se encontró elemento con selector:", selector);
49
+ return;
50
+ }
51
+
52
+ log(colors.yellow, "🔍 Elemento original encontrado:");
53
+ console.log(" - nodeName:", element.nodeName);
54
+ console.log(" - id:", element.getAttribute("id"));
55
+ console.log(" - childNodes.length:", element.childNodes.length);
56
+ console.log(" - children.length:", element.children.length);
57
+ console.log(" - innerHTML.length:", element.innerHTML.length);
58
+ console.log(" - textContent.length:", element.textContent.length);
59
+ console.log();
60
+
61
+ log(colors.bright, "⚙️ Clonando con cloneNode(true)...");
62
+ const cloned = element.cloneNode(true);
63
+ console.log();
64
+
65
+ log(colors.yellow, "🔍 Elemento clonado:");
66
+ console.log(" - nodeName:", cloned.nodeName);
67
+ console.log(" - id:", cloned.getAttribute("id"));
68
+ console.log(" - childNodes.length:", cloned.childNodes.length);
69
+ console.log(" - children.length:", cloned.children.length);
70
+ console.log(" - innerHTML.length:", cloned.innerHTML.length);
71
+ console.log(" - textContent.length:", cloned.textContent.length);
72
+ console.log();
73
+
74
+ // Comparación
75
+ log(colors.bright + colors.cyan, "📊 Comparación:");
76
+ const checks = {
77
+ "nodeName coincide":
78
+ element.nodeName === cloned.nodeName ? "✅" : "❌",
79
+ "childNodes.length coincide":
80
+ element.childNodes.length === cloned.childNodes.length ? "✅" : "❌",
81
+ "children.length coincide":
82
+ element.children.length === cloned.children.length ? "✅" : "❌",
83
+ "innerHTML.length coincide":
84
+ element.innerHTML.length === cloned.innerHTML.length ? "✅" : "❌",
85
+ "innerHTML no vacío":
86
+ cloned.innerHTML.length > 0 ? "✅" : "❌",
87
+ "childNodes no vacío":
88
+ cloned.childNodes.length > 0 ? "✅" : "❌",
89
+ };
90
+
91
+ for (const [check, result] of Object.entries(checks)) {
92
+ console.log(` ${result} ${check}`);
93
+ }
94
+ console.log();
95
+
96
+ // Mostrar innerHTML
97
+ log(colors.cyan, "📄 Original innerHTML:");
98
+ console.log(element.innerHTML);
99
+ console.log();
100
+
101
+ log(colors.cyan, "📄 Clonado innerHTML:");
102
+ console.log(cloned.innerHTML);
103
+ console.log();
104
+
105
+ // Verificar querySelector en el clon
106
+ log(colors.bright + colors.cyan, "🔍 Pruebas de querySelector en el clon:");
107
+ const queries = [
108
+ "div",
109
+ "p",
110
+ "span",
111
+ "h1, h2, h3",
112
+ "ul",
113
+ "li",
114
+ "[id]",
115
+ "[class]",
116
+ ];
117
+
118
+ for (const query of queries) {
119
+ const result = cloned.querySelector(query);
120
+ console.log(
121
+ ` ${result ? "✅" : "⚪"} querySelector("${query}"): ${
122
+ result ? result.nodeName : "no encontrado"
123
+ }`
124
+ );
125
+ }
126
+
127
+ const allDone = Object.values(checks).every((v) => v === "✅");
128
+ console.log();
129
+ log(
130
+ allDone ? colors.green : colors.red,
131
+ allDone ? "✅ TEST PASÓ" : "❌ TEST FALLÓ"
132
+ );
133
+ }
134
+
135
+ // ============================================================================
136
+ // TESTS
137
+ // ============================================================================
138
+
139
+ console.clear();
140
+ log(
141
+ colors.bright + colors.green,
142
+ "\n🧬 cloneNode Interactive Test Suite\n"
143
+ );
144
+
145
+ // Test 1: Simple element
146
+ testCloneNode(
147
+ `<div id="simple">Hello World</div>`,
148
+ "#simple",
149
+ "Elemento simple con texto"
150
+ );
151
+
152
+ // Test 2: Nested elements
153
+ testCloneNode(
154
+ `
155
+ <div id="nested">
156
+ <h1>Title</h1>
157
+ <p>Paragraph with <strong>bold</strong> text</p>
158
+ <ul>
159
+ <li>Item 1</li>
160
+ <li>Item 2</li>
161
+ </ul>
162
+ </div>
163
+ `,
164
+ "#nested",
165
+ "Elementos anidados múltiples niveles"
166
+ );
167
+
168
+ // Test 3: Complex structure
169
+ testCloneNode(
170
+ `
171
+ <article id="article">
172
+ <header>
173
+ <h1>Article Title</h1>
174
+ <p class="meta">By Author Name</p>
175
+ </header>
176
+ <section class="content">
177
+ <p>First paragraph</p>
178
+ <p>Second paragraph</p>
179
+ <div class="callout">
180
+ <strong>Important:</strong> This is a callout
181
+ </div>
182
+ </section>
183
+ <footer>
184
+ <a href="#">Read more</a>
185
+ </footer>
186
+ </article>
187
+ `,
188
+ "#article",
189
+ "Estructura compleja tipo artículo"
190
+ );
191
+
192
+ // Test 4: Form elements
193
+ testCloneNode(
194
+ `
195
+ <form id="form">
196
+ <input type="text" name="username" value="john" />
197
+ <input type="email" name="email" value="test@example.com" />
198
+ <textarea name="bio">User bio</textarea>
199
+ <button type="submit">Submit</button>
200
+ </form>
201
+ `,
202
+ "#form",
203
+ "Formulario con inputs"
204
+ );
205
+
206
+ // Test 5: Table
207
+ testCloneNode(
208
+ `
209
+ <table id="table">
210
+ <thead>
211
+ <tr>
212
+ <th>Name</th>
213
+ <th>Age</th>
214
+ </tr>
215
+ </thead>
216
+ <tbody>
217
+ <tr>
218
+ <td>John</td>
219
+ <td>30</td>
220
+ </tr>
221
+ <tr>
222
+ <td>Jane</td>
223
+ <td>25</td>
224
+ </tr>
225
+ </tbody>
226
+ </table>
227
+ `,
228
+ "#table",
229
+ "Tabla HTML"
230
+ );
231
+
232
+ separator();
233
+ log(colors.bright + colors.green, "✅ Todos los tests interactivos completados");
234
+ log(colors.cyan, "\n💡 TIP: Modifica este archivo para agregar tus propios tests");
235
+ console.log();
@@ -0,0 +1,587 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseHTML } from "../index.js";
3
+ import { NodeType } from "../src/dom-simulator.js";
4
+
5
+ describe("cloneNode functionality", () => {
6
+ describe("cloneNode(true) - deep cloning", () => {
7
+ it("should clone a simple element with text content", () => {
8
+ const html = `<div id="original">Hello World</div>`;
9
+ const doc = parseHTML(html);
10
+ const original = doc.querySelector("#original")!;
11
+
12
+ const cloned = original.cloneNode(true);
13
+
14
+ // Verificar que el nodo clonado existe
15
+ expect(cloned).toBeTruthy();
16
+ expect(cloned.nodeName).toBe("DIV");
17
+
18
+ // Verificar que los atributos se copian
19
+ expect(cloned.getAttribute("id")).toBe("original");
20
+
21
+ // Verificar que el contenido de texto se copia
22
+ expect(cloned.textContent).toBe("Hello World");
23
+
24
+ // Verificar que tiene los hijos correctos
25
+ expect(cloned.childNodes.length).toBeGreaterThan(0);
26
+ });
27
+
28
+ it("should clone nested elements with multiple levels", () => {
29
+ const html = `
30
+ <div id="parent">
31
+ <div class="child">
32
+ <span>Nested Text</span>
33
+ </div>
34
+ </div>
35
+ `;
36
+ const doc = parseHTML(html);
37
+ const parent = doc.querySelector("#parent")!;
38
+
39
+ const cloned = parent.cloneNode(true);
40
+
41
+ // Verificar estructura básica
42
+ expect(cloned.nodeName).toBe("DIV");
43
+ expect(cloned.getAttribute("id")).toBe("parent");
44
+
45
+ // Verificar que los hijos se clonaron
46
+ expect(cloned.childNodes.length).toBeGreaterThan(0);
47
+
48
+ // Verificar que el hijo div está presente
49
+ const childDiv = cloned.querySelector(".child");
50
+ expect(childDiv).toBeTruthy();
51
+ expect(childDiv?.nodeName).toBe("DIV");
52
+ expect(childDiv?.getAttribute("class")).toBe("child");
53
+
54
+ // Verificar el nieto span
55
+ const span = cloned.querySelector("span");
56
+ expect(span).toBeTruthy();
57
+ expect(span?.textContent).toBe("Nested Text");
58
+ });
59
+
60
+ it("should clone element with multiple children", () => {
61
+ const html = `
62
+ <ul id="list">
63
+ <li>Item 1</li>
64
+ <li>Item 2</li>
65
+ <li>Item 3</li>
66
+ </ul>
67
+ `;
68
+ const doc = parseHTML(html);
69
+ const list = doc.querySelector("#list")!;
70
+
71
+ const cloned = list.cloneNode(true);
72
+
73
+ // Verificar que todos los items se clonaron
74
+ const items = cloned.querySelectorAll("li");
75
+ expect(items.length).toBe(3);
76
+ expect(items[0]?.textContent).toBe("Item 1");
77
+ expect(items[1]?.textContent).toBe("Item 2");
78
+ expect(items[2]?.textContent).toBe("Item 3");
79
+ });
80
+
81
+ it("should preserve innerHTML after cloning", () => {
82
+ const html = `
83
+ <div id="container">
84
+ <h1>Title</h1>
85
+ <p>Paragraph 1</p>
86
+ <p>Paragraph 2</p>
87
+ </div>
88
+ `;
89
+ const doc = parseHTML(html);
90
+ const container = doc.querySelector("#container")!;
91
+
92
+ // Guardar innerHTML original
93
+ const originalInnerHTML = container.innerHTML;
94
+ expect(originalInnerHTML).toBeTruthy();
95
+ expect(originalInnerHTML.length).toBeGreaterThan(0);
96
+
97
+ const cloned = container.cloneNode(true);
98
+
99
+ // Verificar que innerHTML del clon no esté vacío
100
+ expect(cloned.innerHTML).toBeTruthy();
101
+ expect(cloned.innerHTML.length).toBeGreaterThan(0);
102
+
103
+ // Verificar que el contenido es similar
104
+ expect(cloned.innerHTML).toContain("<h1>Title</h1>");
105
+ expect(cloned.innerHTML).toContain("<p>Paragraph 1</p>");
106
+ expect(cloned.innerHTML).toContain("<p>Paragraph 2</p>");
107
+ });
108
+
109
+ it("should clone element with mixed content (elements and text nodes)", () => {
110
+ const html = `<div id="mixed">Text before<strong>bold text</strong>Text after</div>`;
111
+ const doc = parseHTML(html);
112
+ const mixed = doc.querySelector("#mixed")!;
113
+
114
+ // Verificar estructura original
115
+ const originalChildCount = mixed.childNodes.length;
116
+ expect(originalChildCount).toBeGreaterThan(0);
117
+
118
+ const cloned = mixed.cloneNode(true);
119
+
120
+ // Verificar que tiene la misma cantidad de hijos
121
+ expect(cloned.childNodes.length).toBe(originalChildCount);
122
+
123
+ // Verificar contenido completo
124
+ expect(cloned.textContent).toBe("Text beforebold textText after");
125
+
126
+ // Verificar que el elemento strong existe
127
+ const strong = cloned.querySelector("strong");
128
+ expect(strong).toBeTruthy();
129
+ expect(strong?.textContent).toBe("bold text");
130
+ });
131
+
132
+ it("should clone all attributes including custom ones", () => {
133
+ const html = `<div id="attrs" class="test" data-value="123" data-custom="abc">Content</div>`;
134
+ const doc = parseHTML(html);
135
+ const element = doc.querySelector("#attrs")!;
136
+
137
+ const cloned = element.cloneNode(true);
138
+
139
+ // Verificar todos los atributos
140
+ expect(cloned.getAttribute("id")).toBe("attrs");
141
+ expect(cloned.getAttribute("class")).toBe("test");
142
+ expect(cloned.getAttribute("data-value")).toBe("123");
143
+ expect(cloned.getAttribute("data-custom")).toBe("abc");
144
+ expect(cloned.textContent).toBe("Content");
145
+ });
146
+
147
+ it("should clone complex structure with different node types", () => {
148
+ const html = `
149
+ <article id="article">
150
+ <h2>Article Title</h2>
151
+ <!-- This is a comment -->
152
+ <p>First paragraph</p>
153
+ <div class="highlight">
154
+ <span>Highlighted</span> text
155
+ </div>
156
+ <p>Last paragraph</p>
157
+ </article>
158
+ `;
159
+ const doc = parseHTML(html);
160
+ const article = doc.querySelector("#article")!;
161
+
162
+ const cloned = article.cloneNode(true);
163
+
164
+ // Verificar estructura
165
+ expect(cloned.nodeName).toBe("ARTICLE");
166
+ expect(cloned.getAttribute("id")).toBe("article");
167
+
168
+ // Verificar elementos
169
+ expect(cloned.querySelector("h2")?.textContent).toBe("Article Title");
170
+
171
+ const paragraphs = cloned.querySelectorAll("p");
172
+ expect(paragraphs.length).toBe(2);
173
+ expect(paragraphs[0]?.textContent).toBe("First paragraph");
174
+ expect(paragraphs[1]?.textContent).toBe("Last paragraph");
175
+
176
+ // Verificar div anidado
177
+ const highlight = cloned.querySelector(".highlight");
178
+ expect(highlight).toBeTruthy();
179
+ expect(highlight?.querySelector("span")?.textContent).toBe("Highlighted");
180
+
181
+ // Verificar que se copió el comentario
182
+ const hasComment = Array.from(cloned.childNodes).some(
183
+ (node: any) => node.nodeType === NodeType.COMMENT_NODE
184
+ );
185
+ expect(hasComment).toBe(true);
186
+ });
187
+
188
+ it("should clone element with empty children", () => {
189
+ const html = `<div id="container"><p></p><span></span></div>`;
190
+ const doc = parseHTML(html);
191
+ const container = doc.querySelector("#container")!;
192
+
193
+ const cloned = container.cloneNode(true);
194
+
195
+ expect(cloned.querySelector("p")).toBeTruthy();
196
+ expect(cloned.querySelector("span")).toBeTruthy();
197
+ });
198
+
199
+ it("should maintain outerHTML structure in cloned node", () => {
200
+ const html = `<section class="main"><h1>Title</h1><p>Text</p></section>`;
201
+ const doc = parseHTML(html);
202
+ const section = doc.querySelector("section")!;
203
+
204
+ const cloned = section.cloneNode(true);
205
+
206
+ // Verificar outerHTML
207
+ expect(cloned.outerHTML).toBeTruthy();
208
+ expect(cloned.outerHTML).toContain("section");
209
+ expect(cloned.outerHTML).toContain("class=\"main\"");
210
+ expect(cloned.outerHTML).toContain("<h1>Title</h1>");
211
+ expect(cloned.outerHTML).toContain("<p>Text</p>");
212
+ });
213
+ });
214
+
215
+ describe("cloneNode(false) - shallow cloning", () => {
216
+ it("should clone element without children when deep is false", () => {
217
+ const html = `<div id="parent"><p>Child</p></div>`;
218
+ const doc = parseHTML(html);
219
+ const parent = doc.querySelector("#parent")!;
220
+
221
+ const cloned = parent.cloneNode(false);
222
+
223
+ // Debe copiar el elemento pero no los hijos
224
+ expect(cloned.nodeName).toBe("DIV");
225
+ expect(cloned.getAttribute("id")).toBe("parent");
226
+ expect(cloned.childNodes.length).toBe(0);
227
+ expect(cloned.querySelector("p")).toBeNull();
228
+ });
229
+
230
+ it("should preserve attributes in shallow clone", () => {
231
+ const html = `<div id="test" class="container" data-value="123"><span>Content</span></div>`;
232
+ const doc = parseHTML(html);
233
+ const element = doc.querySelector("#test")!;
234
+
235
+ const cloned = element.cloneNode(false);
236
+
237
+ // Atributos deben copiarse
238
+ expect(cloned.getAttribute("id")).toBe("test");
239
+ expect(cloned.getAttribute("class")).toBe("container");
240
+ expect(cloned.getAttribute("data-value")).toBe("123");
241
+
242
+ // Hijos NO deben copiarse
243
+ expect(cloned.childNodes.length).toBe(0);
244
+ expect(cloned.innerHTML).toBe("");
245
+ });
246
+ });
247
+
248
+ describe("cloneNode independence", () => {
249
+ it("cloned node should be independent from original", () => {
250
+ const html = `<div id="original">Original</div>`;
251
+ const doc = parseHTML(html);
252
+ const original = doc.querySelector("#original")!;
253
+
254
+ const cloned = original.cloneNode(true);
255
+
256
+ // Modificar el clon no debe afectar el original
257
+ cloned.setAttribute("id", "cloned");
258
+ cloned.setAttribute("data-modified", "true");
259
+
260
+ expect(original.getAttribute("id")).toBe("original");
261
+ expect(original.hasAttribute("data-modified")).toBe(false);
262
+ expect(cloned.getAttribute("id")).toBe("cloned");
263
+ expect(cloned.getAttribute("data-modified")).toBe("true");
264
+ });
265
+
266
+ it("modifying cloned children should not affect original", () => {
267
+ const html = `<div id="parent"><p id="child">Text</p></div>`;
268
+ const doc = parseHTML(html);
269
+ const parent = doc.querySelector("#parent")!;
270
+
271
+ const cloned = parent.cloneNode(true);
272
+ const clonedChild = cloned.querySelector("#child");
273
+
274
+ expect(clonedChild).toBeTruthy();
275
+
276
+ // Modificar hijo del clon
277
+ clonedChild?.setAttribute("data-cloned", "yes");
278
+
279
+ // El hijo original no debe verse afectado
280
+ const originalChild = parent.querySelector("#child");
281
+ expect(originalChild?.hasAttribute("data-cloned")).toBe(false);
282
+ });
283
+ });
284
+
285
+ describe("cloneNode edge cases", () => {
286
+ it("should handle cloning of self-closing tags", () => {
287
+ const html = `<div><img src="test.jpg" alt="Test" /><br /></div>`;
288
+ const doc = parseHTML(html);
289
+ const div = doc.querySelector("div")!;
290
+
291
+ const cloned = div.cloneNode(true);
292
+
293
+ const img = cloned.querySelector("img");
294
+ expect(img).toBeTruthy();
295
+ expect(img?.getAttribute("src")).toBe("test.jpg");
296
+ expect(img?.getAttribute("alt")).toBe("Test");
297
+ });
298
+
299
+ it("should clone elements with special characters in content", () => {
300
+ const html = `<div id="special">Text with &amp; &lt; &gt; entities</div>`;
301
+ const doc = parseHTML(html);
302
+ const element = doc.querySelector("#special")!;
303
+
304
+ const cloned = element.cloneNode(true);
305
+
306
+ expect(cloned.textContent).toBeTruthy();
307
+ expect(cloned.textContent.length).toBeGreaterThan(0);
308
+ });
309
+
310
+ it("should handle deeply nested structures", () => {
311
+ const html = `
312
+ <div id="level1">
313
+ <div id="level2">
314
+ <div id="level3">
315
+ <div id="level4">
316
+ <div id="level5">Deep Content</div>
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ `;
322
+ const doc = parseHTML(html);
323
+ const level1 = doc.querySelector("#level1")!;
324
+
325
+ const cloned = level1.cloneNode(true);
326
+
327
+ // Verificar todos los niveles
328
+ expect(cloned.querySelector("#level2")).toBeTruthy();
329
+ expect(cloned.querySelector("#level3")).toBeTruthy();
330
+ expect(cloned.querySelector("#level4")).toBeTruthy();
331
+ const level5 = cloned.querySelector("#level5");
332
+ expect(level5).toBeTruthy();
333
+ expect(level5?.textContent).toBe("Deep Content");
334
+ });
335
+ });
336
+
337
+ describe("cloneNode internal properties", () => {
338
+ it("should verify _internalInnerHTML is properly set in clone", () => {
339
+ const html = `<div id="container"><p>Paragraph 1</p><p>Paragraph 2</p></div>`;
340
+ const doc = parseHTML(html);
341
+ const container = doc.querySelector("#container")!;
342
+
343
+ // Acceder a innerHTML para asegurar que _internalInnerHTML esté establecido
344
+ const originalInnerHTML = container.innerHTML;
345
+ expect(originalInnerHTML).toBeTruthy();
346
+
347
+ const cloned = container.cloneNode(true);
348
+
349
+ // Verificar que innerHTML funciona en el clon
350
+ const clonedInnerHTML = cloned.innerHTML;
351
+ expect(clonedInnerHTML).toBeTruthy();
352
+ expect(clonedInnerHTML.length).toBeGreaterThan(0);
353
+
354
+ // Verificar que contiene el mismo contenido
355
+ expect(clonedInnerHTML).toContain("<p>Paragraph 1</p>");
356
+ expect(clonedInnerHTML).toContain("<p>Paragraph 2</p>");
357
+
358
+ // Verificar que el accessor de innerHTML funciona correctamente
359
+ expect(typeof cloned.innerHTML).toBe("string");
360
+ });
361
+
362
+ it("should maintain proper childNodes structure after clone", () => {
363
+ const html = `<div id="parent">Text<span>Span</span>More text</div>`;
364
+ const doc = parseHTML(html);
365
+ const parent = doc.querySelector("#parent")!;
366
+
367
+ const originalChildCount = parent.childNodes.length;
368
+ expect(originalChildCount).toBeGreaterThan(0);
369
+
370
+ const cloned = parent.cloneNode(true);
371
+
372
+ // Verificar que childNodes tiene la misma estructura
373
+ expect(cloned.childNodes.length).toBe(originalChildCount);
374
+
375
+ // Verificar que podemos acceder a cada hijo
376
+ for (let i = 0; i < cloned.childNodes.length; i++) {
377
+ expect(cloned.childNodes[i]).toBeTruthy();
378
+ expect(cloned.childNodes[i].nodeType).toBeDefined();
379
+ }
380
+ });
381
+
382
+ it("should properly initialize children array in cloned element", () => {
383
+ const html = `<div id="container"><span>1</span><span>2</span><span>3</span></div>`;
384
+ const doc = parseHTML(html);
385
+ const container = doc.querySelector("#container")!;
386
+
387
+ const cloned = container.cloneNode(true);
388
+
389
+ // Verificar que el array children está correctamente poblado
390
+ expect(cloned.children).toBeTruthy();
391
+ expect(Array.isArray(cloned.children)).toBe(true);
392
+ expect(cloned.children.length).toBe(3);
393
+
394
+ // Verificar que todos son elementos
395
+ for (const child of cloned.children) {
396
+ expect(child.nodeType).toBe(NodeType.ELEMENT_NODE);
397
+ }
398
+ });
399
+
400
+ it("should clone and maintain firstChild and lastChild references", () => {
401
+ const html = `<ul id="list"><li>First</li><li>Middle</li><li>Last</li></ul>`;
402
+ const doc = parseHTML(html);
403
+ const list = doc.querySelector("#list")!;
404
+
405
+ const cloned = list.cloneNode(true);
406
+
407
+ // Verificar referencias firstChild y lastChild
408
+ expect(cloned.firstChild).toBeTruthy();
409
+ expect(cloned.lastChild).toBeTruthy();
410
+
411
+ // En DOM real, firstChild puede ser un nodo de texto (whitespace)
412
+ // pero debemos asegurar que existen
413
+ expect(cloned.firstElementChild).toBeTruthy();
414
+ expect(cloned.lastElementChild).toBeTruthy();
415
+
416
+ const firstLi = cloned.firstElementChild;
417
+ const lastLi = cloned.lastElementChild;
418
+
419
+ expect(firstLi?.textContent).toContain("First");
420
+ expect(lastLi?.textContent).toContain("Last");
421
+ });
422
+ });
423
+
424
+ describe("cloneNode with innerHTML manipulation", () => {
425
+ it("should clone element after innerHTML was modified", () => {
426
+ const html = `<div id="dynamic"></div>`;
427
+ const doc = parseHTML(html);
428
+ const dynamic = doc.querySelector("#dynamic")!;
429
+
430
+ // Modificar innerHTML antes de clonar
431
+ dynamic.innerHTML = "<p>Dynamic content</p><span>More content</span>";
432
+
433
+ const cloned = dynamic.cloneNode(true);
434
+
435
+ // Verificar que el contenido modificado se clonó
436
+ expect(cloned.querySelector("p")).toBeTruthy();
437
+ expect(cloned.querySelector("p")?.textContent).toBe("Dynamic content");
438
+ expect(cloned.querySelector("span")).toBeTruthy();
439
+ expect(cloned.querySelector("span")?.textContent).toBe("More content");
440
+ });
441
+
442
+ it("should clone element and allow innerHTML manipulation on clone", () => {
443
+ const html = `<div id="original"><p>Original</p></div>`;
444
+ const doc = parseHTML(html);
445
+ const original = doc.querySelector("#original")!;
446
+
447
+ const cloned = original.cloneNode(true);
448
+
449
+ // Verificar contenido clonado
450
+ expect(cloned.querySelector("p")?.textContent).toBe("Original");
451
+
452
+ // Modificar innerHTML del clon
453
+ cloned.innerHTML = "<span>Modified</span>";
454
+
455
+ // Original no debe cambiar
456
+ expect(original.querySelector("p")?.textContent).toBe("Original");
457
+ expect(original.querySelector("span")).toBeNull();
458
+
459
+ // Clon debe tener el nuevo contenido
460
+ expect(cloned.querySelector("span")?.textContent).toBe("Modified");
461
+ expect(cloned.querySelector("p")).toBeNull();
462
+ });
463
+ });
464
+
465
+ describe("cloneNode real-world scenarios", () => {
466
+ it("should clone a complete card component", () => {
467
+ const html = `
468
+ <div class="card" data-id="123">
469
+ <div class="card-header">
470
+ <h3 class="card-title">Card Title</h3>
471
+ <button class="close">×</button>
472
+ </div>
473
+ <div class="card-body">
474
+ <p>This is the card content with <strong>bold</strong> text.</p>
475
+ <ul>
476
+ <li>Item 1</li>
477
+ <li>Item 2</li>
478
+ </ul>
479
+ </div>
480
+ <div class="card-footer">
481
+ <button class="btn-primary">Save</button>
482
+ <button class="btn-secondary">Cancel</button>
483
+ </div>
484
+ </div>
485
+ `;
486
+ const doc = parseHTML(html);
487
+ const card = doc.querySelector(".card")!;
488
+
489
+ const cloned = card.cloneNode(true);
490
+
491
+ // Verificar estructura completa
492
+ expect(cloned.getAttribute("data-id")).toBe("123");
493
+ expect(cloned.querySelector(".card-header")).toBeTruthy();
494
+ expect(cloned.querySelector(".card-body")).toBeTruthy();
495
+ expect(cloned.querySelector(".card-footer")).toBeTruthy();
496
+
497
+ // Verificar contenido específico
498
+ expect(cloned.querySelector(".card-title")?.textContent).toBe("Card Title");
499
+ expect(cloned.querySelector("strong")?.textContent).toBe("bold");
500
+
501
+ const items = cloned.querySelectorAll("li");
502
+ expect(items.length).toBe(2);
503
+
504
+ const buttons = cloned.querySelectorAll("button");
505
+ expect(buttons.length).toBe(3); // close, save, cancel
506
+ });
507
+
508
+ it("should clone a form with various input types", () => {
509
+ const html = `
510
+ <form id="user-form">
511
+ <input type="text" name="username" value="john" />
512
+ <input type="email" name="email" value="john@example.com" />
513
+ <textarea name="bio">User bio</textarea>
514
+ <select name="country">
515
+ <option value="us">USA</option>
516
+ <option value="uk" selected>UK</option>
517
+ </select>
518
+ </form>
519
+ `;
520
+ const doc = parseHTML(html);
521
+ const form = doc.querySelector("#user-form")!;
522
+
523
+ const cloned = form.cloneNode(true);
524
+
525
+ // Verificar que todos los inputs se clonaron
526
+ const textInput = cloned.querySelector('[name="username"]');
527
+ expect(textInput).toBeTruthy();
528
+ expect(textInput?.getAttribute("value")).toBe("john");
529
+
530
+ const emailInput = cloned.querySelector('[name="email"]');
531
+ expect(emailInput).toBeTruthy();
532
+ expect(emailInput?.getAttribute("value")).toBe("john@example.com");
533
+
534
+ const textarea = cloned.querySelector("textarea");
535
+ expect(textarea).toBeTruthy();
536
+ expect(textarea?.textContent).toBe("User bio");
537
+
538
+ const select = cloned.querySelector("select");
539
+ expect(select).toBeTruthy();
540
+ const options = select?.querySelectorAll("option");
541
+ expect(options?.length).toBe(2);
542
+ });
543
+
544
+ it("should clone a table structure", () => {
545
+ const html = `
546
+ <table id="data-table">
547
+ <thead>
548
+ <tr>
549
+ <th>Name</th>
550
+ <th>Age</th>
551
+ </tr>
552
+ </thead>
553
+ <tbody>
554
+ <tr>
555
+ <td>John</td>
556
+ <td>30</td>
557
+ </tr>
558
+ <tr>
559
+ <td>Jane</td>
560
+ <td>25</td>
561
+ </tr>
562
+ </tbody>
563
+ </table>
564
+ `;
565
+ const doc = parseHTML(html);
566
+ const table = doc.querySelector("#data-table")!;
567
+
568
+ const cloned = table.cloneNode(true);
569
+
570
+ expect(cloned.querySelector("thead")).toBeTruthy();
571
+ expect(cloned.querySelector("tbody")).toBeTruthy();
572
+
573
+ const headers = cloned.querySelectorAll("th");
574
+ expect(headers.length).toBe(2);
575
+ expect(headers[0]?.textContent).toBe("Name");
576
+ expect(headers[1]?.textContent).toBe("Age");
577
+
578
+ const rows = cloned.querySelectorAll("tbody tr");
579
+ expect(rows.length).toBe(2);
580
+
581
+ const firstRowCells = rows[0]?.querySelectorAll("td");
582
+ expect(firstRowCells?.length).toBe(2);
583
+ expect(firstRowCells?.[0]?.textContent).toBe("John");
584
+ expect(firstRowCells?.[1]?.textContent).toBe("30");
585
+ });
586
+ });
587
+ });