als-document 0.11.0 → 1.0.0-alpha

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,351 @@
1
+ describe('HTML Parser', () => {
2
+ let parsedHTML;
3
+
4
+ beforeEach(() => {
5
+ parsedHTML = parseHTML('<div><p>Text</p></div>').children[0];
6
+ });
7
+
8
+ describe('parseHTML', () => {
9
+ it('returns an instance of Node', () => {
10
+ expect(parsedHTML).instanceof(Node);
11
+ });
12
+
13
+ it('correctly sets node type', () => {
14
+ expect(parsedHTML.tagName).equalTo('div');
15
+ });
16
+
17
+ it('correctly sets child nodes', () => {
18
+ expect(parsedHTML.children.length).equalTo(1);
19
+ expect(parsedHTML.children[0].tagName).equalTo('p');
20
+ });
21
+
22
+ it('correctly parses nested HTML', () => {
23
+ const nestedHTML = parseHTML('<div><span><a href="#">Link</a></span></div>');
24
+ expect(nestedHTML.children[0].children[0].children[0].tagName).equalTo('a');
25
+ });
26
+
27
+ it('correctly parses text nodes', () => {
28
+ expect(parsedHTML.children[0].innerHTML).equalTo('Text');
29
+ });
30
+ });
31
+
32
+ });
33
+
34
+ describe('Query API', () => {
35
+ beforeEach(() => {
36
+ parsedHTML = parseHTML('<div><p>Text</p></div>').children[0];
37
+ });
38
+
39
+ it('can select a node by type', () => {
40
+ const collection = parsedHTML.querySelectorAll('p')
41
+ expect(collection[0].innerHTML).equalTo('Text');
42
+ });
43
+
44
+ it('can select nested nodes', () => {
45
+ const nestedHTML = parseHTML('<div><span><a href="#">Link</a></span></div>');
46
+ const collection = nestedHTML.querySelectorAll('a');
47
+ expect(collection[0].tagName).equalTo('a');
48
+ });
49
+ });
50
+
51
+ describe('Advanced tests', () => {
52
+ it('handles empty HTML correctly', () => {
53
+ const result = parseHTML('');
54
+ expect(result.children.length).equalTo(0);
55
+ });
56
+
57
+ it('handles deeply nested HTML', () => {
58
+ let deepHTML = '<div>';
59
+ for (let i = 0; i < 1000; i++) {
60
+ deepHTML += '<div>';
61
+ deepHTML += generator.generate();
62
+ }
63
+ for (let i = 0; i < 1000; i++) {
64
+ deepHTML += '</div>';
65
+ }
66
+ const now = Date.now()
67
+ // const memoryBefore = performance.memory.totalJSHeapSize
68
+ const result = parseHTML(deepHTML);
69
+ // const memoryAfter = performance.memory.totalJSHeapSize
70
+ let time = Date.now() - now
71
+ // console.log(memoryAfter - memoryBefore)
72
+ assert(time < 20, `Big html (${(deepHTML.length / 1024).toFixed(2)}KB) in less then 20ms (${time}ms)`)
73
+ expect(result).instanceof(Node); // or any other validation you see fit
74
+ });
75
+
76
+ it('handles incorrectly closed tags', () => {
77
+ const result = parseHTML('<div><p>Text</div>');
78
+ // Depending on the behavior you expect: either an error, or a specific structure.
79
+ // For this example, I'll assume you expect the <p> tag to be auto-closed
80
+ expect(result.children[0].children[0].tagName).equalTo('p');
81
+ });
82
+
83
+ it('handles attributes without values', () => {
84
+ const result = parseHTML('<input disabled>');
85
+ expect(result.children[0].attributes.disabled).defined();
86
+ });
87
+
88
+ it('ignores comments', () => {
89
+ const result = parseHTML('<!-- this is a comment --><div></div>');
90
+ expect(result.children.length).equalTo(1);
91
+ expect(result.children[0].tagName).equalTo('div');
92
+ });
93
+
94
+ it('correctly parses script content', () => {
95
+ const result = parseHTML('<script>let x = 5;<\/script>');
96
+ expect(result.children[0].innerHTML).equalTo('let x = 5;');
97
+ });
98
+
99
+ it('parses special entities correctly', () => {
100
+ const result = parseHTML('<p>&lt; &amp; &gt;</p>');
101
+ expect(result.children[0].innerHTML).equalTo('&lt; &amp; &gt;');
102
+ });
103
+
104
+ it('should correctly retrieve data-* attributes', () => {
105
+ const element = document.createElement('div');
106
+ element.setAttribute('data-test', 'testValue');
107
+
108
+ const value = element.dataset.test;
109
+ assert(value === 'testValue');
110
+ });
111
+
112
+ it('dataset2', () => {
113
+ const node = new Node('div', { 'data-example': 'test' });
114
+ assert(node.dataset.example === 'test')
115
+ node.dataset.example = 'new value';
116
+ assert(node.dataset.example === 'new value');
117
+ delete node.dataset.example;
118
+ assert(node.dataset.example === undefined);
119
+ })
120
+
121
+ })
122
+
123
+ describe('More adnvanced tests', () => {
124
+
125
+ it('Attributes parsing', () => {
126
+ const customHTML = '<div class="some class" data-value="A &quot;value&quot;" custom-attr=\'test\'></div>';
127
+ const parsedDiv = parseHTML(customHTML).querySelector('div');
128
+ assert(parsedDiv.getAttribute('class') === 'some class', 'Class attribute parsed correctly');
129
+ assert(parsedDiv.getAttribute('custom-attr') === 'test', 'Attribute with single quotes parsed');
130
+ });
131
+
132
+ it('Nested hierarchy check', () => {
133
+ const customHTML = '<div><span><a href="#">Link</a></span></div>';
134
+ const parsedDiv = parseHTML(customHTML).querySelector('div');
135
+ const parsedSpan = parsedDiv.querySelector('span');
136
+ const parsedA = parsedSpan.querySelector('a');
137
+
138
+ assert(parsedA.getAttribute('href') === '#', 'Anchor tag nested correctly');
139
+ });
140
+
141
+ it('Self-closing tags check', () => {
142
+ const customHTML = '<input type="text"/><br/><hr/>';
143
+ const parsedHTML = parseHTML(customHTML);
144
+ assert(parsedHTML.querySelectorAll('input').length === 1, 'Input tag parsed correctly');
145
+ assert(parsedHTML.querySelectorAll('br').length === 1, 'BR tag parsed correctly');
146
+ assert(parsedHTML.querySelectorAll('hr').length === 1, 'HR tag parsed correctly');
147
+ });
148
+
149
+ it('Edge cases', () => {
150
+ // Несколько открывающих тегов подряд
151
+ const erroneousHTML1 = '<div><div><div></div></div>';
152
+ const parsedHTML1 = parseHTML(erroneousHTML1);
153
+ assert(parsedHTML1.querySelectorAll('div').length === 3, 'Multiple opening tags handled');
154
+
155
+ // Несколько закрывающих тегов подряд
156
+ const erroneousHTML2 = '<div></div></div></div>';
157
+ const parsedHTML2 = parseHTML(erroneousHTML2);
158
+ assert(parsedHTML2.querySelectorAll('div').length === 1, 'Multiple closing tags handled');
159
+ });
160
+
161
+ })
162
+
163
+ describe('CDATA', () => {
164
+ it('basic test', () => {
165
+ const test1 = '<![CDATA[This is CDATA content]]>';
166
+ const root1 = parseHTML(test1);
167
+ assert(root1.childNodes[0].tagName === "#cdata-section", "Test 1: CDATA node not created");
168
+ assert(root1.childNodes[0].nodeValue === "This is CDATA content", "Test 1: CDATA content not correct");
169
+ })
170
+
171
+ it('nested CDATA', () => {
172
+ const test2 = '<div><![CDATA[Inside a div]]><p>Paragraph</p></div>';
173
+ const root2 = parseHTML(test2);
174
+ assert(root2.childNodes[0].tagName === "div", "Test 2: Parent div not created");
175
+ assert(root2.childNodes[0].childNodes[0].tagName === "#cdata-section", "Test 2: CDATA node not created inside div");
176
+ assert(root2.childNodes[0].childNodes[0].nodeValue === "Inside a div", "Test 2: CDATA content not correct inside div");
177
+ assert(root2.childNodes[0].childNodes[1].tagName === "p", "Test 2: Paragraph not created after CDATA");
178
+ })
179
+
180
+ it('multiple lines', () => {
181
+ const test3 = `
182
+ <![CDATA[
183
+ Multiple lines
184
+ inside this CDATA
185
+ block.
186
+ ]]>`;
187
+ const root3 = parseHTML(test3);
188
+ assert(root3.childNodes[0].tagName === "#cdata-section", "Test 3: CDATA node not created");
189
+ assert(root3.childNodes[0].nodeValue.trim() === "Multiple lines \ninside this CDATA\nblock.", "Test 3: Multi-line CDATA content not correct");
190
+
191
+ })
192
+
193
+ it('inside html', () => {
194
+ const test4 = '<![CDATA[<span>This should be text</span>]]>';
195
+ const root4 = parseHTML(test4);
196
+ assert(root4.childNodes[0].tagName === "#cdata-section", "Test 4: CDATA node not created");
197
+ assert(root4.childNodes[0].nodeValue === "<span>This should be text</span>", "Test 4: HTML inside CDATA not treated as text");
198
+
199
+ })
200
+ })
201
+
202
+ describe('signle tags, script and style', () => {
203
+ it('script and style', () => {
204
+ const testStyleScript = `
205
+ <style>
206
+ body { color: red; }
207
+ p > a { text-decoration: none; }
208
+ </style>
209
+ <script>
210
+ if (x < 5 && y > 3) {
211
+ console.log("This shouldn't be parsed as tags");
212
+ }
213
+ </script>
214
+ `;
215
+ const rootStyleScript = parseHTML(testStyleScript);
216
+ assert(rootStyleScript.childNodes[0].tagName === "style", "Test Style/Script 1: Style tag not created");
217
+ assert(rootStyleScript.childNodes[0].textContent.trim() === "body { color: red; }\n p > a { text-decoration: none; }", "Test Style/Script 1: Style content not correct");
218
+ assert(rootStyleScript.childNodes[1].tagName === "script", "Test Style/Script 2: Script tag not created");
219
+ assert(rootStyleScript.childNodes[1].textContent.trim() === 'if (x < 5 && y > 3) {\n console.log("This shouldn\'t be parsed as tags");\n }', "Test Style/Script 2: Script content not correct");
220
+ })
221
+
222
+ it('meta and link tags', () => {
223
+ const testMetaLink = `
224
+ <meta charset="UTF-8">
225
+ <link rel="stylesheet" href="styles.css">
226
+ `;
227
+ const rootMetaLink = parseHTML(testMetaLink);
228
+ assert(rootMetaLink.childNodes[0].tagName === "meta", "Test Meta/Link 1: Meta tag not created");
229
+ assert(rootMetaLink.childNodes[0].getAttribute("charset") === "UTF-8", "Test Meta/Link 1: Meta content not correct");
230
+ assert(rootMetaLink.childNodes[1].tagName === "link", "Test Meta/Link 2: Link tag not created");
231
+ assert(rootMetaLink.childNodes[1].getAttribute("rel") === "stylesheet", "Test Meta/Link 2: Link rel attribute not correct");
232
+ assert(rootMetaLink.childNodes[1].getAttribute("href") === "styles.css", "Test Meta/Link 2: Link href attribute not correct");
233
+ })
234
+
235
+ it('broken html structure', () => {
236
+ const testUnmatchedClose = `<div>Some content</p></div>`;
237
+ const rootUnmatchedClose = parseHTML(testUnmatchedClose);
238
+ // Зависит от вашего решения обработки. Если вы решите исправлять такой HTML, тест может выглядеть так:
239
+ assert(rootUnmatchedClose.childNodes[0].tagName === "div", "Test Unmatched Close: Div tag not created");
240
+ assert(rootUnmatchedClose.childNodes[0].textContent.trim() === "Some content", "Test Unmatched Close: Div content not correct");
241
+ })
242
+
243
+ it('wrong open tags', () => {
244
+ const testUnmatchedOpen = `<div>Some content`;
245
+ const rootUnmatchedOpen = parseHTML(testUnmatchedOpen);
246
+ // Снова зависит от вашего решения. Если вы решите автоматически закрывать тег:
247
+ assert(rootUnmatchedOpen.childNodes[0].tagName === "div", "Test Unmatched Open: Div tag not created");
248
+ assert(rootUnmatchedOpen.childNodes[0].textContent.trim() === "Some content", "Test Unmatched Open: Div content not correct");
249
+ })
250
+ })
251
+
252
+ describe('js as content', () => {
253
+ const jsCode = 'console.log(`<div>hello</div>`)'
254
+ it('html inside onclick', () => {
255
+ const html = parseHTML(`<button onclick="${jsCode}">test</button>'`)
256
+ assert(html.childNodes[0].getAttribute('onclick') === jsCode)
257
+ })
258
+ it('html in script', () => {
259
+ const html = parseHTML(`<script>${jsCode}</script>`)
260
+ assert(html.childNodes[0].innerHTML === jsCode)
261
+ })
262
+ })
263
+
264
+
265
+ describe('Specific elements handling', () => {
266
+
267
+ it('should correctly handle SVG elements', async () => {
268
+ document.body.insertAdjacentHTML('beforeend',/*html*/`<div id="svg">${svg}</div>`)
269
+ const svgElement = document.querySelector('#svg>svg')
270
+ const html = parseHTML(svg)
271
+ const svgParsed = html.querySelector('svg')
272
+
273
+ assert(svgParsed.tagName === svgElement.tagName)
274
+
275
+ assert(svgElement.getAttribute('version') === svgParsed.getAttribute('version'))
276
+ assert(svgElement.getAttribute('id') === svgParsed.getAttribute('id'))
277
+ assert(svgElement.getAttribute('width') === svgParsed.getAttribute('width'))
278
+ assert(svgElement.getAttribute('height') === svgParsed.getAttribute('height'))
279
+
280
+ assert(svgElement.querySelector('defs') !== null); // Проверка наличия элемента <defs>
281
+ assert(svgParsed.querySelector('defs') !== null)
282
+
283
+ const layer1Group = svgElement.querySelector('g[id="layer1"]');
284
+ const layer1GroupParsed = svgParsed.querySelector('g[id="layer1"]');
285
+
286
+ assert(layer1Group !== null); // Проверка наличия группы с id="layer1"
287
+ assert(layer1GroupParsed !== null); // Проверка наличия группы с id="layer1"
288
+
289
+ const paths = layer1Group.querySelectorAll('path');
290
+ const pathsParsed = layer1GroupParsed.querySelectorAll('path');
291
+ expect(paths.length).atLeast(1); // Проверка, что есть хотя бы один элемент <path>
292
+ expect(pathsParsed.length).atLeast(1); // Проверка, что есть хотя бы один элемент <path>
293
+ });
294
+
295
+ it('should correctly handle canvas', () => {
296
+ const canvas = /*html*/`<canvas id="myCanvas" width="300" height="150" style="border:1px solid grey"></canvas>`
297
+ const html = parseHTML(canvas)
298
+ const canvasElement = html.children[0]
299
+
300
+ expect(canvasElement.tagName).equalTo('canvas');
301
+ expect(canvasElement.getAttribute('id')).equalTo('myCanvas');
302
+ expect(canvasElement.getAttribute('width')).equalTo('300');
303
+ expect(canvasElement.getAttribute('height')).equalTo('150');
304
+ expect(canvasElement.style.border).equalTo('1px solid grey');
305
+ });
306
+
307
+
308
+ it('should handle namespaces correctly', () => {
309
+ const svgNamespace = 'http://www.w3.org/2000/svg';
310
+ const xmlNamespace = 'http://www.w3.org/XML/1998/namespace';
311
+
312
+ const svgHTML = /*html*/`<svg xmlns="${svgNamespace}" xmlns:xml="${xmlNamespace}"><circle cx="50" cy="50" r="10" stroke="black" stroke-width="2" fill="red" /></svg>`;
313
+ const html = parseHTML(svgHTML);
314
+ const svgElement = html.children[0]
315
+
316
+ expect(svgElement.tagName).equalTo('svg');
317
+ expect(svgElement.getAttribute('xmlns')).equalTo(svgNamespace);
318
+ expect(svgElement.getAttribute('xmlns:xml')).equalTo(xmlNamespace);
319
+ });
320
+
321
+
322
+ it('should handle large amounts of data efficiently', () => {
323
+ const largeAmountOfDivs = /*html*/`<div>${new Array(10000).fill('<div class="test-div"></div>').join('')}</div>`;
324
+ let start = performance.now();
325
+ const rootElement = parseHTML(largeAmountOfDivs);
326
+ let end = performance.now();
327
+ time = end-start
328
+ expect(time).is(`${time} ms`).below(200);
329
+
330
+ // 1. Проверка, что все элементы были добавлены
331
+ expect(rootElement.querySelectorAll('.test-div').length).equalTo(10000);
332
+
333
+ start = performance.now();
334
+ const randomDiv = rootElement.querySelector('.test-div');
335
+ randomDiv.setAttribute('id', 'randomDiv');
336
+ expect(randomDiv.getAttribute('id')).equalTo('randomDiv');
337
+
338
+ end = performance.now();
339
+
340
+ time = end-start
341
+ expect(time).is(`${time} ms`).below(200);
342
+ });
343
+
344
+ it('image data:', () => {
345
+ const src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
346
+ const img = /*html*/`<img src="${src}" alt="Red dot" />`
347
+ const rootElement = parseHTML(img);
348
+ expect(rootElement.children[0].getAttribute('src')).equalTo(src)
349
+ })
350
+
351
+ });
package/tests/query.js ADDED
@@ -0,0 +1,66 @@
1
+ const q1 = 'html>body>div.tabs~.some[type $= "some"][test]>p+div>.some-id .tab-content~input[disabled] div.some'
2
+ const s1 = Query.get(q1)[0]
3
+
4
+ describe('query tests',() => {
5
+ it('Check target',() => {
6
+ const expected = 'div.some'
7
+ let {tag,classList} = s1
8
+ expect(`${tag}.${classList[0]}`).equalTo(expected)
9
+ })
10
+ it('Check ancestors length',() => {
11
+ expect(s1.ancestors.length).equalTo(2)
12
+ })
13
+ it('Check prev group',() => {
14
+ const expected = s1.ancestors[0].group.split('+')[0]
15
+ expect(s1.ancestors[0].prev.group).equalTo(expected)
16
+ })
17
+ it('Check parents length',() => {
18
+ expect(s1.ancestors[0].parents.length).equalTo(1)
19
+ })
20
+ it('Check prev any, prev and classList',() => {
21
+ expect(s1.ancestors[0].prev.prevAny.classList[0]).equalTo('tabs')
22
+ })
23
+ it('Check attributes length',() => {
24
+ expect(s1.ancestors[0].prev.parents[0].attribs.length).equalTo(2)
25
+ })
26
+ it('Check attributes fn $',() => {
27
+ expect(s1.ancestors[0].prev.parents[0].attribs[0].check('test and some')).equalTo(true)
28
+ })
29
+ it('Check attributes fn *=',() => {
30
+ expect(s1.ancestors[0].prev.parents[0].attribs[0].check('some'))
31
+ .equalTo(true)
32
+ })
33
+ it('Check attributes fn *=',() => {
34
+ let s = Query.get('[test*="some value"]')[0]
35
+ expect(s.attribs[0].check('some value test')).equalTo(true)
36
+ })
37
+ it('Check attributes fn ~= prase instead single word',() => {
38
+ let s = Query.get('[test~="some value"]')[0]
39
+ expect(s.attribs[0].check('some value test')).equalTo(false)
40
+ })
41
+ it('Check attributes fn ~=',() => {
42
+ let s = Query.get('[test~="value"]')[0]
43
+ expect(s.attribs[0].check('some value test'))
44
+ .equalTo(true)
45
+ })
46
+ it('Check attributes fn ^=',() => {
47
+ let s = Query.get('[test^="some"]')[0]
48
+ expect(s.attribs[0].check('some value test'))
49
+ .equalTo(true)
50
+ })
51
+ it('Check attributes fn |= with few words',() => {
52
+ let s = Query.get('[test|="some value test"]')[0]
53
+ expect(s.attribs[0].check('some'))
54
+ .equalTo(false)
55
+ })
56
+ it('Check attributes fn |=word',() => {
57
+ let s = Query.get('[test|="some"]')[0]
58
+ expect(s.attribs[0].check('some'))
59
+ .equalTo(true)
60
+ })
61
+ it('Check attributes fn |=word-word',() => {
62
+ let s = Query.get('[test|="some"]')[0]
63
+ expect(s.attribs[0].check('some-value'))
64
+ .equalTo(true)
65
+ })
66
+ })
package/tests/utils.js ADDED
@@ -0,0 +1,37 @@
1
+ function createIframe(htmlContent) {
2
+ const iframe = document.createElement('iframe');
3
+ return new Promise((resolve,reject) => {
4
+ iframe.onload = function() {
5
+ const iframeDocument = iframe.contentDocument;
6
+ iframeDocument.childNodes[0].innerHTML = htmlContent
7
+ // iframeDocument.body.insertAdjacentHTML('afterbegin',htmlContent)
8
+ resolve(iframeDocument)
9
+ }
10
+ document.body.appendChild(iframe);
11
+ })
12
+ }
13
+
14
+ class RandomSentenceGenerator {
15
+ constructor() {
16
+ this.subjects = ["The cat", "A monkey", "A child", "The robot", "The alien", "My friend"];
17
+ this.verbs = ["jumps", "runs", "flies", "dances", "sings", "reads"];
18
+ this.objects = ["on the bed", "in the garden", "above the clouds", "in the spaceship", "with a book", "under the tree"];
19
+ this.adverbs = ["quickly", "happily", "sadly", "gracefully", "lazily", "silently"];
20
+ }
21
+
22
+ getRandomItem(arr) {
23
+ const randomIndex = Math.floor(Math.random() * arr.length);
24
+ return arr[randomIndex];
25
+ }
26
+
27
+ generate() {
28
+ const subject = this.getRandomItem(this.subjects);
29
+ const verb = this.getRandomItem(this.verbs);
30
+ const object = this.getRandomItem(this.objects);
31
+ const adverb = this.getRandomItem(this.adverbs);
32
+
33
+ return `${subject} ${verb} ${object} ${adverb}.`;
34
+ }
35
+ }
36
+
37
+ const generator = new RandomSentenceGenerator()