@tkeron/html-parser 0.1.4 → 0.1.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.
@@ -1,325 +0,0 @@
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
- });
@@ -1,235 +0,0 @@
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();