als-document 0.12.0 → 1.0.0-beta
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/document.js +41 -589
- package/index.js +40 -0
- package/index.mjs +40 -0
- package/package.json +9 -9
- package/readme.md +219 -150
- package/src/build.js +66 -0
- package/src/node/class-list.js +25 -0
- package/src/node/dataset.js +15 -0
- package/src/node/find.js +12 -0
- package/src/node/node.js +193 -0
- package/src/node/root.js +11 -0
- package/src/node/single-node.js +31 -0
- package/src/node/style.js +26 -0
- package/src/node/text-node.js +9 -0
- package/src/parse/cache.js +33 -0
- package/src/parse/parse-atts.js +36 -0
- package/src/parse/parser.js +98 -0
- package/src/parse/void-tags.js +5 -0
- package/src/query/check-element.js +83 -0
- package/src/query/query.js +142 -0
- package/tests/cache.js +19 -0
- package/tests/data/html1.js +2579 -0
- package/tests/data/html2.js +1124 -0
- package/tests/data/svg.js +66 -0
- package/tests/index.html +31 -0
- package/tests/node.js +196 -0
- package/tests/parse-real.js +53 -0
- package/tests/parser.js +351 -0
- package/tests/query.js +66 -0
- package/tests/test.js +169 -0
- package/tests/utils.js +37 -0
package/tests/parser.js
ADDED
|
@@ -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(Root); // 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>< & ></p>');
|
|
101
|
+
expect(result.children[0].innerHTML).equalTo('< & >');
|
|
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 "value"" 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.children[0].tagName === "meta", "Test Meta/Link 1: Meta tag not created");
|
|
229
|
+
assert(rootMetaLink.children[0].getAttribute("charset") === "UTF-8", "Test Meta/Link 1: Meta content not correct");
|
|
230
|
+
assert(rootMetaLink.children[1].tagName === "link", "Test Meta/Link 2: Link tag not created");
|
|
231
|
+
assert(rootMetaLink.children[1].getAttribute("rel") === "stylesheet", "Test Meta/Link 2: Link rel attribute not correct");
|
|
232
|
+
assert(rootMetaLink.children[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/test.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
class SimpleTest {
|
|
2
|
+
static tests = [];
|
|
3
|
+
static beforeEachCallback;
|
|
4
|
+
static afterEachCallback;
|
|
5
|
+
static nestingLevel = 0;
|
|
6
|
+
static currentParent = [];
|
|
7
|
+
static space = ''
|
|
8
|
+
static results = {}
|
|
9
|
+
static testTitle = ''
|
|
10
|
+
static showFullError = false
|
|
11
|
+
static colors = {
|
|
12
|
+
bold:'\x1b[1m',
|
|
13
|
+
cblue:'\x1b[34m',
|
|
14
|
+
cred:'\x1b[31m',
|
|
15
|
+
cgreen:'\x1b[32m',
|
|
16
|
+
cgray:'\x1b[37m',
|
|
17
|
+
reset:'\x1b[0m'
|
|
18
|
+
}
|
|
19
|
+
static delay = (ms,toResolve) => new Promise((resolve) => {
|
|
20
|
+
setTimeout(() => {resolve(toResolve)}, ms);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
static describe(title, callback,pause=false) {
|
|
24
|
+
if(pause) return
|
|
25
|
+
let { nestingLevel, currentParent } = SimpleTest
|
|
26
|
+
nestingLevel++;
|
|
27
|
+
currentParent.push(title);
|
|
28
|
+
callback();
|
|
29
|
+
|
|
30
|
+
currentParent.pop();
|
|
31
|
+
nestingLevel--;
|
|
32
|
+
if (nestingLevel === 0) {
|
|
33
|
+
SimpleTest.beforeEachCallback = null; // Clear beforeEachCallback after finishing the outermost describe block
|
|
34
|
+
SimpleTest.afterEachCallback = null; // Clear beforeEachCallback after finishing the outermost describe block
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static beforeEach(callback) {SimpleTest.beforeEachCallback = callback;}
|
|
39
|
+
static afterEach(callback) {SimpleTest.afterEachCallback = callback;}
|
|
40
|
+
static beforeAll(callback) {SimpleTest.beforAllCallback = callback;}
|
|
41
|
+
static afterAll(callback) {SimpleTest.afterAllCallback = callback;}
|
|
42
|
+
|
|
43
|
+
static it(title, callback) {
|
|
44
|
+
SimpleTest.tests.push({
|
|
45
|
+
titles: [...SimpleTest.currentParent, title],
|
|
46
|
+
before: SimpleTest.beforeEachCallback,
|
|
47
|
+
after:SimpleTest.afterEachCallback,
|
|
48
|
+
test: callback,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static async runTests(consoleLog=true) {
|
|
53
|
+
const {bold,cblue,cred,reset} = SimpleTest.colors
|
|
54
|
+
SimpleTest.consoleLog = consoleLog
|
|
55
|
+
let { tests,results,showFullError,beforAllCallback,afterAllCallback } = SimpleTest;
|
|
56
|
+
let previousTitles = [];
|
|
57
|
+
if(typeof beforAllCallback == 'function') await beforAllCallback()
|
|
58
|
+
for(const test of tests) {
|
|
59
|
+
let curentResult = results
|
|
60
|
+
SimpleTest.space = test.titles.map(t => ' ').join('');
|
|
61
|
+
test.titles.forEach((title, index) => {
|
|
62
|
+
if(curentResult[title] == undefined) {
|
|
63
|
+
if(index == test.titles.length-1) curentResult[title] = []
|
|
64
|
+
else curentResult[title] = {}
|
|
65
|
+
}
|
|
66
|
+
curentResult = curentResult[title]
|
|
67
|
+
if(previousTitles[index] !== title) {
|
|
68
|
+
const indentation = ' '.repeat(index);
|
|
69
|
+
console.log(bold+cblue+'%s'+reset,`${indentation} ${title}`);
|
|
70
|
+
previousTitles[index] = title;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
SimpleTest.curentResult = curentResult
|
|
74
|
+
try {
|
|
75
|
+
if (test.before) await test.before();
|
|
76
|
+
await test.test();
|
|
77
|
+
if (test.after) await test.after();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if(showFullError) console.log(error)
|
|
80
|
+
else console.log(`${SimpleTest.space}${cred}Error:${reset} ${error.message}`);
|
|
81
|
+
curentResult.push({error})
|
|
82
|
+
}
|
|
83
|
+
console.log('\n');
|
|
84
|
+
}
|
|
85
|
+
if(typeof afterAllCallback == 'function') await afterAllCallback()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static assert(value,testTitle) {
|
|
89
|
+
if(testTitle) SimpleTest.testTitle = testTitle
|
|
90
|
+
SimpleTest.logResult(value)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static expect(value1) {
|
|
94
|
+
let { logResult, isEqual } = SimpleTest
|
|
95
|
+
const methods = {
|
|
96
|
+
is: (text='') => {
|
|
97
|
+
SimpleTest.testTitle = text;
|
|
98
|
+
SimpleTest.not = false
|
|
99
|
+
return methods;
|
|
100
|
+
},
|
|
101
|
+
isNot: (text='') => {
|
|
102
|
+
SimpleTest.testTitle = text;
|
|
103
|
+
SimpleTest.not = true
|
|
104
|
+
return methods;
|
|
105
|
+
},
|
|
106
|
+
equalTo: (value2) => {logResult(value1 === value2);},
|
|
107
|
+
between(value2,value3) {logResult(value1 >= value2 && value1 <= value3);},
|
|
108
|
+
below(value2) {logResult(value1 < value2);},
|
|
109
|
+
above(value2) {logResult(value1 > value2);},
|
|
110
|
+
atLeast(value2) {logResult(value1 >= value2);},
|
|
111
|
+
atMost(value2) {logResult(value1 <= value2);},
|
|
112
|
+
sameAs: (value2) => {logResult(isEqual(value1, value2));},
|
|
113
|
+
defined: () => {logResult(value1 !== undefined);},
|
|
114
|
+
matchTo(pattern) {logResult(pattern.test(new RegExp(value1)));},
|
|
115
|
+
includes(value2) {logResult(value1.includes(value2));},
|
|
116
|
+
error: () => {
|
|
117
|
+
if(typeof value1 !== 'function') return console.log('Expected value has to be function')
|
|
118
|
+
try {
|
|
119
|
+
let result = value1()
|
|
120
|
+
if(result instanceof Error) logResult(true)
|
|
121
|
+
else logResult(false);
|
|
122
|
+
} catch (error) {logResult(true)}
|
|
123
|
+
},
|
|
124
|
+
hasProperty: (property) => {logResult(Object.prototype.hasOwnProperty.call(value1, property));},
|
|
125
|
+
instanceof: (classType) => {logResult(value1.constructor.name == classType.name)},
|
|
126
|
+
closeTo(value2, decimalPlaces = 2) {
|
|
127
|
+
const multiplier = 10 ** decimalPlaces;
|
|
128
|
+
const roundedActual = Math.round(value1 * multiplier);
|
|
129
|
+
const roundedExpected = Math.round(value2 * multiplier);
|
|
130
|
+
const diff = Math.abs(roundedActual - roundedExpected) / multiplier;
|
|
131
|
+
logResult(diff < 1 / multiplier);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return methods;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static logResult(result) {
|
|
138
|
+
let {curentResult,testTitle,not,space,colors} = SimpleTest
|
|
139
|
+
let {cgreen,cred,cgray,reset} = colors
|
|
140
|
+
if(not) result = !result
|
|
141
|
+
curentResult.push({result:result ? true : false,testTitle,error:null})
|
|
142
|
+
const status = result ? 'Success' : 'Failed';
|
|
143
|
+
const color = result ? cgreen : cred;
|
|
144
|
+
if (testTitle == '') console.log(`${color}${space}${status}${reset}`);
|
|
145
|
+
else console.log(`${space}${color}${status}: ${testTitle ? `${cgray}${testTitle}` : ''}`+reset,);
|
|
146
|
+
SimpleTest.not = false
|
|
147
|
+
SimpleTest.testTitle = ''
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static isEqual(obj1, obj2, visited = new WeakMap()) {
|
|
151
|
+
if (obj1 === obj2) return true;
|
|
152
|
+
if (visited.has(obj1) && visited.get(obj1) === obj2) return true;
|
|
153
|
+
const type1 = typeof obj1;
|
|
154
|
+
const type2 = typeof obj2;
|
|
155
|
+
|
|
156
|
+
if (type1 !== type2 || type1 !== 'object' || obj1 === null || obj2 === null) return false;
|
|
157
|
+
visited.set(obj1, obj2);
|
|
158
|
+
|
|
159
|
+
const keys1 = Object.keys(obj1);
|
|
160
|
+
const keys2 = Object.keys(obj2);
|
|
161
|
+
if(keys1.length !== keys2.length) return false;
|
|
162
|
+
|
|
163
|
+
for (let key of keys1) {
|
|
164
|
+
if (!keys2.includes(key) || !SimpleTest.isEqual(obj1[key], obj2[key], visited)) return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
try {module.exports = SimpleTest} catch (error) {}
|
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()
|