als-document 1.1.2 → 1.2.0

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.
Files changed (47) hide show
  1. package/browser-tests/data/html1.js +2579 -0
  2. package/browser-tests/data/html2.js +1124 -0
  3. package/browser-tests/data/svg.js +66 -0
  4. package/{tests → browser-tests}/index.html +1 -1
  5. package/browser-tests/utils.js +37 -0
  6. package/build-readme.js +8 -0
  7. package/{src/build.js → build.js} +4 -4
  8. package/docs/#.md +52 -0
  9. package/docs/1.parseHTML.md +39 -0
  10. package/docs/2.node.md +25 -0
  11. package/docs/3.singleNode.md +3 -0
  12. package/docs/4.tTextNode.md +3 -0
  13. package/docs/5. Document.md +28 -0
  14. package/docs/6.Query.md +110 -0
  15. package/docs/7.cache.md +14 -0
  16. package/document.js +6 -4
  17. package/index.js +6 -4
  18. package/index.mjs +6 -4
  19. package/{src/node/root.js → lib/node/document.js} +3 -2
  20. package/{src → lib}/node/node.js +5 -2
  21. package/lib/node/root.js +6 -0
  22. package/{src → lib}/node/single-node.js +22 -4
  23. package/package.json +6 -4
  24. package/readme.md +21 -14
  25. package/tests/cache.test.js +23 -0
  26. package/tests/node.test.js +411 -0
  27. package/tests/parser.test.js +353 -0
  28. package/tests/query.test.js +65 -0
  29. package/tests/single-node.test.js +88 -0
  30. package/tests/utils.js +2 -0
  31. /package/{tests → browser-tests}/cache.js +0 -0
  32. /package/{tests → browser-tests}/node.js +0 -0
  33. /package/{tests → browser-tests}/parse-real.js +0 -0
  34. /package/{tests → browser-tests}/parser.js +0 -0
  35. /package/{tests → browser-tests}/query.js +0 -0
  36. /package/{tests/test.js → browser-tests/simple-test.js} +0 -0
  37. /package/{src → lib}/node/class-list.js +0 -0
  38. /package/{src → lib}/node/dataset.js +0 -0
  39. /package/{src → lib}/node/find.js +0 -0
  40. /package/{src → lib}/node/style.js +0 -0
  41. /package/{src → lib}/node/text-node.js +0 -0
  42. /package/{src → lib}/parse/cache.js +0 -0
  43. /package/{src → lib}/parse/parse-atts.js +0 -0
  44. /package/{src → lib}/parse/parser.js +0 -0
  45. /package/{src → lib}/parse/void-tags.js +0 -0
  46. /package/{src → lib}/query/check-element.js +0 -0
  47. /package/{src → lib}/query/query.js +0 -0
package/readme.md CHANGED
@@ -3,11 +3,8 @@
3
3
 
4
4
  ## Overview
5
5
 
6
- `als-document` is a powerful library for parsing HTML and manipulating the DOM structure on backend and frontend. It provides a robust and intuitive API for querying and interacting with DOM elements using selectors, making it a valuable tool for web developers.
6
+ `als-document` is a powerful library for parsing HTML and XML, building and manipulating virtual DOM structure on backend and frontend. It provides a robust and intuitive API for querying and interacting with DOM elements using selectors, making it a valuable tool for web developers.
7
7
 
8
- ## Release notes
9
- * als-document is still on alpha testing. All tested features works fine, but through the use, discovering some bugs or things that should work different. For example in this release, changed the way for storing attributes with empty value.
10
- * Also, this release, has additional very powefull feature which is building cache for storing DOM tree as json and building back DOM from cache.
11
8
 
12
9
  ## Installation
13
10
 
@@ -25,13 +22,13 @@ The library provides three different files to cater to different module systems:
25
22
  1. **index.js**: This file uses the CommonJS module system. It's suitable for projects using Node.js or bundlers like Browserify or Webpack. The entry point in `package.json` for this file is "main".
26
23
 
27
24
  ```javascript
28
- const { parseHTML, Node, Query, TextNode, SingleNode,Root } = require('als-document');
25
+ const { parseHTML, Node, Query, TextNode, SingleNode, Root, Document } = require('als-document');
29
26
  ```
30
27
 
31
28
  2. **index.mjs**: This file uses the ES Modules (ESM) system. It's suitable for modern JavaScript environments that support ESM. The entry point in `package.json` for this file is "module".
32
29
 
33
30
  ```js
34
- import { parseHTML, Node, Query, TextNode, SingleNode, Root } from 'als-document';
31
+ import { parseHTML, Node, Query, TextNode, SingleNode, Root, Document } from 'als-document';
35
32
  ```
36
33
 
37
34
  3. **document.js**: By including this file, a constant variable named `alsDocument` is created, which wraps all the exports.
@@ -39,10 +36,20 @@ import { parseHTML, Node, Query, TextNode, SingleNode, Root } from 'als-document
39
36
  ```html
40
37
  <script src="/node_modules/als-document/document.js"></script>
41
38
  <script>
42
- const { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root } = alsDocument
39
+ const { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root, Document } = alsDocument
43
40
  </script>
44
41
  ```
45
42
 
43
+ ## Change log
44
+ New in 1.2.0:
45
+ * singleNode can insert(0) and insert(3)
46
+ * insertAdjacentElement replace element instead cloning it
47
+ * Document class
48
+ * has structure <!DOCTYPE html><html lang="en"><head><title></title></head><body></body></html>
49
+ * get title
50
+ * set title
51
+ * get body
52
+ * get head
46
53
  ## parseHTML
47
54
 
48
55
  `parseHTML` is a function that takes an HTML string and constructs a DOM tree representation from it. It recognizes various HTML elements, such as comments, scripts, styles, and CDATA, and organizes them into nodes that can be manipulated and queried.
@@ -82,6 +89,7 @@ const parsedScript = parseHTML('<script>console.log("Hello, world!");</script>')
82
89
 
83
90
  Remember, the actual tree structure will be more complex and detailed, but the provided examples give you a basic understanding of how to navigate through the parsed result.
84
91
 
92
+
85
93
  ## Node
86
94
 
87
95
  `Node` is a fundamental class that represents an element node in the DOM tree. It provides functionality similar to the native DOM API in browsers, but with its own implementation.
@@ -107,17 +115,16 @@ Remember, the actual tree structure will be more complex and detailed, but the p
107
115
  - **appendChild**: Add a child node to the element.
108
116
  - **insert(place,element)**: place (0-3) or beforebegin,afterbegin,... eleemnt - raw html or element
109
117
 
110
-
118
+
111
119
  ### SingleNode
112
120
 
113
121
  `SingleNode` extends from the `Node` class and represents elements that don't have closing tags (self-closing tags) in HTML. Examples include `<img>`, `<br>`, and `<!DOCTYPE>`. This class has restricted methods and properties since these elements can't have child nodes.
114
-
122
+
115
123
  ### TextNode
116
124
 
117
125
  `TextNode` is a class that represents text content within the DOM. A TextNode holds raw text data and does not have child nodes.
118
-
119
-
120
- ### Root node (extends Node)
126
+
127
+ ### Document node (extends Node)
121
128
 
122
129
  Has additional getters and setters:
123
130
  * getter root.title
@@ -145,7 +152,7 @@ const foundP = div.querySelector('p');
145
152
  console.log(foundP.textContent); // Outputs: Hello, world!
146
153
  ```
147
154
 
148
-
155
+
149
156
  ## Query
150
157
 
151
158
  The `Query` class is designed to parse CSS selector strings and transform them into a structured object format, providing detailed insights into each selector and its components.
@@ -256,7 +263,7 @@ if attribute has value, attrib object will contain check function with one param
256
263
  let s = Query.get('[test^="some"]')[0]
257
264
  console.log(s.attribs[0].check('some value test')) // true
258
265
  ```
259
-
266
+
260
267
  ## buildFromCache and cacheDoc
261
268
 
262
269
  Building DOM from raw html, usually takes tens of milliseconds. But now, you can build DOM once and save it's cache as regular stringified JSON.
@@ -0,0 +1,23 @@
1
+ const { describe, it } = require('node:test')
2
+ const assert = require('node:assert')
3
+ const { parseHTML, cacheDoc, buildFromCache } = require('../index')
4
+ const html1 = require('./data/html1')
5
+ function mesureTime(fn) {
6
+ let time = performance.now()
7
+ const result = fn()
8
+ time = performance.now() - time
9
+ return { result, time }
10
+ }
11
+
12
+ describe('Cache and build from cache', () => {
13
+ const { result: root, time: rootTime } = mesureTime(() => parseHTML(html1))
14
+ const { result: cache, time: cacheTime } = mesureTime(() => cacheDoc(root))
15
+ const { result: root1, time: root1Time } = mesureTime(() => buildFromCache(cache))
16
+
17
+ it('HTML from cache and HTML from not cached same', () => {
18
+ // console.log({ rootTime, cacheTime, root1Time })
19
+ assert(cacheTime < 5, 'Build cache takes less then 5ms')
20
+ assert(root1Time < 5, 'Build DOM from cache takes less then 5ms')
21
+ assert(root.innerHTML === root1.innerHTML)
22
+ })
23
+ })
@@ -0,0 +1,411 @@
1
+ const { describe, it, beforeEach } = require('node:test')
2
+ const assert = require('node:assert')
3
+ const { Node, Root, TextNode, Document } = require('../index')
4
+
5
+ describe('Constructor and Basic Properties', () => {
6
+
7
+ it('Creates an element', () => {
8
+ const node = new Node('div');
9
+ assert(node instanceof Node, "Element was not created correctly");
10
+ });
11
+
12
+ it('Checks the setting of element properties: tagName, attributes, parent, etc.', () => {
13
+ const rootNode = new Node('ROOT');
14
+ const divNode = new Node('div', { class: 'container' }, rootNode);
15
+
16
+ assert(divNode.tagName === 'div', "Tag name is correct");
17
+ assert(divNode.attributes.class === 'container', "Attributes are correct");
18
+ assert(divNode.parent === rootNode, "Parent node is correct");
19
+ });
20
+
21
+ it('Adds a new element to the parent\'s childNodes', () => {
22
+ const rootNode = new Node('ROOT');
23
+ const childNode = new Node('child', {}, rootNode);
24
+
25
+ assert(rootNode.childNodes.includes(childNode), "New element was added to parent's childNodes");
26
+ });
27
+
28
+ });
29
+
30
+ describe('DOM Search and Navigation', () => {
31
+
32
+ it('Gets the previous and next element', () => {
33
+ const rootNode = new Node('ROOT');
34
+ const div1 = new Node('div', {}, rootNode);
35
+ const div2 = new Node('div', {}, rootNode);
36
+
37
+ assert(div2.previousElementSibling === div1, "Previous sibling is correct");
38
+ assert(div2.nextElementSibling === null, "Next sibling is correct");
39
+ });
40
+
41
+ it('Gets the list of ancestors', () => {
42
+ const rootNode = new Node('ROOT');
43
+ const firstLevel = new Node('first', {}, rootNode);
44
+ const secondLevel = new Node('second', {}, firstLevel);
45
+ const thirdLevel = new Node('third', {}, secondLevel);
46
+
47
+ const ancestors = thirdLevel.ancestors;
48
+
49
+ assert(ancestors.length === 2, "The number of ancestors is correct");
50
+ assert(ancestors[0] === firstLevel, "First ancestor is correct");
51
+ assert(ancestors[1] === secondLevel, "Second ancestor is correct");
52
+ });
53
+
54
+ it('Searches for elements using different methods', () => {
55
+ const rootNode = new Node('ROOT');
56
+ const div1 = new Node('div', { class: 'test', id: 'first' }, rootNode);
57
+ const div2 = new Node('div', { class: 'test' }, rootNode);
58
+ const p = new Node('p', {}, rootNode);
59
+
60
+ const divs = rootNode.getElementsByTagName('div');
61
+ assert(divs.length === 2, "getElementsByTagName did find correct number of divs");
62
+
63
+ const testClassNodes = rootNode.getElementsByClassName('test');
64
+ assert(testClassNodes.length === 2, "getElementsByClassName did find correct number of .test elements");
65
+
66
+ const firstDiv = rootNode.getElementById('first');
67
+ assert(firstDiv === div1, "getElementById did find the correct element");
68
+
69
+ const firstQuery = rootNode.querySelector('.test');
70
+ assert(firstQuery === div1, "querySelector did return the first matching element");
71
+
72
+ const allQuery = rootNode.querySelectorAll('.test');
73
+ assert(allQuery.length === 2 && allQuery[0] === div1 && allQuery[1] === div2, "querySelectorAll did not find all matching elements");
74
+ });
75
+ });
76
+
77
+ describe('Attribute Operations', () => {
78
+
79
+ it('Gets, sets, and removes attributes', () => {
80
+ const attrNode = new Node('div');
81
+ attrNode.setAttribute('data-test', 'testValue');
82
+
83
+ assert(attrNode.getAttribute('data-test') === 'testValue', "getAttribute/setAttribute is correct");
84
+
85
+ attrNode.removeAttribute('data-test');
86
+ assert(!attrNode.getAttribute('data-test'), "removeAttribute is correct");
87
+ });
88
+
89
+ it('Handles classList', () => {
90
+ const nodeWithClasses = new Node('div', { class: 'class1 class2' });
91
+ // Assertion to check if classList contains the right classes
92
+ assert(nodeWithClasses.classList.contains('class1'), "The classList contain 'class1'");
93
+ assert(nodeWithClasses.classList.contains('class2'), "The classList contain 'class2'");
94
+
95
+ // Modifying classList and checking
96
+ nodeWithClasses.classList.add('class3');
97
+ assert(nodeWithClasses.classList.contains('class3'), "The classList contain 'class3' after addition");
98
+
99
+ nodeWithClasses.classList.remove('class1');
100
+ assert(!nodeWithClasses.classList.contains('class1'), "The classList doesn't contains 'class1' after removal");
101
+ });
102
+
103
+ it('Handles style', () => {
104
+ const nodeWithStyle = new Node('div', { style: 'color: red; font-size: 16px;' });
105
+ // Assertions to check if style is correctly set
106
+ assert(nodeWithStyle.style.color === 'red', "The style property color is correctly set or retrieved");
107
+ assert(nodeWithStyle.style.fontSize === '16px', "The style property fontSize is correctly set or retrieved");
108
+ });
109
+
110
+ });
111
+
112
+ describe('Content Manipulation', () => {
113
+
114
+ it('Adds and removes child elements', () => {
115
+ const rootNode = new Node('ROOT');
116
+ const childNode = new Node('child');
117
+ rootNode.appendChild(childNode);
118
+
119
+ assert(rootNode.childNodes.includes(childNode), "Child node was added");
120
+
121
+ childNode.remove();
122
+ assert(!rootNode.childNodes.includes(childNode), "Child node was removed");
123
+ });
124
+
125
+
126
+ it('Inserts content adjacent to an element', () => {
127
+ const rootNode = new Node('div');
128
+ const childNode = new Node('p', {}, rootNode);
129
+
130
+ childNode.insertAdjacentElement('beforebegin', new Node('span'));
131
+ assert(rootNode.childNodes[0].tagName === 'span');
132
+
133
+ childNode.insertAdjacentElement('afterend', new Node('a'));
134
+ assert(rootNode.childNodes[2].tagName === 'a');
135
+
136
+ childNode.insertAdjacentHTML('beforebegin', '<strong></strong>');
137
+ assert(rootNode.childNodes[1].tagName === 'strong');
138
+
139
+ childNode.insertAdjacentText('afterend', 'Some text');
140
+ assert(typeof rootNode.childNodes[3].nodeValue === 'string');
141
+ assert(rootNode.childNodes[3].nodeValue === 'Some text');
142
+ });
143
+
144
+ // it('Gets and sets innerHTML and outerHTML', () => {
145
+ // const rootNode = new Node('div');
146
+ // new Node('p', {id: 'test'}, rootNode);
147
+
148
+ // assert(rootNode.innerHTML === '<p id="test"></p>');
149
+
150
+ // const newNode = new Node('span');
151
+ // newNode.innerHTML = '<a href="#">Link</a>';
152
+ // assert(newNode.childNodes[0].tagName === 'a');
153
+
154
+ // newNode.childNodes[0].outerHTML = '<div><strong>New content</strong></div>';
155
+ // assert(newNode.childNodes[0].childNodes[0].tagName === 'strong');
156
+ // });
157
+
158
+ // it('Works with text content', () => {
159
+ // const rootNode = new Node('div');
160
+ // new Node('p', {}, rootNode).textContent = 'Hello World';
161
+
162
+ // assert(rootNode.textContent === 'Hello World');
163
+ // rootNode.textContent = 'Changed Text';
164
+ // assert(rootNode.textContent === 'Changed Text');
165
+ // assert(rootNode.childNodes.leng === 1);
166
+ // assert(rootNode.childNodes[0] === 'Changed Text');
167
+ // });
168
+
169
+ });
170
+
171
+ describe('Other Methods and Properties', () => {
172
+
173
+ it('Gets the list of child elements', () => {
174
+ const rootNode = new Node('div');
175
+
176
+ // Добавление дочерних элементов
177
+ new Node('p', {}, rootNode);
178
+ new Node('span', {}, rootNode);
179
+ new Node('a', {}, rootNode);
180
+
181
+ rootNode.appendChild('Some text'); // Добавляем текстовый узел
182
+
183
+ // Проверяем, что у корневого элемента теперь 4 дочерних узла (3 элемента + 1 текстовый узел)
184
+ assert(rootNode.childNodes.length === 4);
185
+
186
+ // Проверяем свойство children
187
+ assert(rootNode.children.length === 3);
188
+ assert(rootNode.children[0].tagName === 'p');
189
+ assert(rootNode.children[1].tagName === 'span');
190
+ assert(rootNode.children[2].tagName === 'a');
191
+ });
192
+
193
+ it('Gets the parent element', () => {
194
+ const rootNode = new Node('ROOT');
195
+ const childNode = new Node('child', {}, rootNode);
196
+
197
+ assert(childNode.parentNode === rootNode, "Parent node is correct");
198
+ });
199
+
200
+ });
201
+
202
+ describe('Method and properties 2', () => {
203
+ let node
204
+ beforeEach(() => {node = new Node('div')})
205
+
206
+ it('set id', () => {
207
+ assert(node.getAttribute('id') === null)
208
+ assert(node.id === null)
209
+ node.id = 'test'
210
+ assert(node.getAttribute('id') === 'test')
211
+ assert(node.id === 'test')
212
+ })
213
+
214
+ it('get className', () => {
215
+ assert(node.classList.constructor.name === 'NodeClassList')
216
+ })
217
+
218
+ it('get outerHTML', () => {
219
+ assert(node.outerHTML === '<div></div>')
220
+ })
221
+
222
+ it('set innerHTML', () => {
223
+ assert(node.innerHTML === '')
224
+ node.innerHTML = 'some test'
225
+ assert(node.innerHTML === 'some test')
226
+ })
227
+
228
+ it('set outerHTML error no parent', () => {
229
+ assert.throws(() => {node.outerHTML = '<div>some test</div>'}) // element has no parent node
230
+ })
231
+
232
+ it('set outerHTML', () => {
233
+ const parent = new Node('div')
234
+ parent.insert(1,node)
235
+ assert(parent.children[0].outerHTML === '<div></div>')
236
+ const html = '<div>some test</div>'
237
+ node.outerHTML = html
238
+ assert(parent.children[0].outerHTML === html)
239
+
240
+ })
241
+
242
+ describe('set textContent', () => {
243
+ it('sets and gets textContent correctly', () => {
244
+ const node = new Node('div');
245
+ node.textContent = 'Hello World';
246
+ assert.strictEqual(node.textContent, 'Hello World', 'textContent should be "Hello World"');
247
+ });
248
+ });
249
+
250
+ describe('get innerHTML', () => {
251
+ it('gets correct innerHTML', () => {
252
+ const parent = new Node('div');
253
+ const child = new Node('span');
254
+ parent.appendChild(child);
255
+ assert.strictEqual(parent.innerHTML, '<span></span>', 'innerHTML should include child node markup');
256
+ });
257
+ });
258
+
259
+ describe('insertAdjacentHTML', () => {
260
+ it('inserts HTML correctly before begin', () => {
261
+ const node = new Node('div');
262
+ node.insertAdjacentHTML('afterbegin', '<span>test</span>');
263
+ assert(node.innerHTML === '<span>test</span>', 'Should insert <span>test</span> before the node');
264
+ });
265
+ });
266
+
267
+ describe('insertAdjacentText', () => {
268
+ it('inserts text correctly after end', () => {
269
+ const node = new Node('div');
270
+ const parent = new Node('div');
271
+ parent.appendChild(node);
272
+ node.insertAdjacentText('afterend', 'Hello');
273
+ assert.strictEqual(parent.childNodes[1].nodeValue, 'Hello', 'Should insert "Hello" after the node');
274
+ });
275
+ });
276
+
277
+ describe('appendChild', () => {
278
+ it('appends a child node correctly', () => {
279
+ const parentNode = new Node('div');
280
+ const childNode = new Node('span');
281
+ parentNode.appendChild(childNode);
282
+ assert(parentNode.childNodes.includes(childNode), 'Child node should be in parent\'s childNodes array');
283
+ });
284
+ });
285
+
286
+ describe('get textContent', () => {
287
+ it('returns correct text content', () => {
288
+ const node = new Node('div');
289
+ const child1 = new TextNode('Hello');
290
+ const child2 = new TextNode('World');
291
+ node.appendChild(child1);
292
+ node.appendChild(child2);
293
+ assert.strictEqual(node.textContent, 'HelloWorld', 'textContent should be "HelloWorld"');
294
+ });
295
+ });
296
+
297
+ describe('insertAdjacentElement', () => {
298
+ it('inserts an element adjacent to another', () => {
299
+ const root = new Root();
300
+ const refNode = new Node('div');
301
+ const newNode = new Node('span');
302
+ root.appendChild(refNode);
303
+ refNode.insertAdjacentElement('afterend', newNode);
304
+ assert.strictEqual(root.childNodes[1], newNode, 'New node should be inserted after the reference node');
305
+ });
306
+ });
307
+
308
+ })
309
+
310
+ describe('query', () => {
311
+
312
+ describe('$$', () => {
313
+ it('returns all elements matching the selector', () => {
314
+ const root = new Root();
315
+ const child1 = new Node('div', { class: 'test' });
316
+ const child2 = new Node('div', { class: 'test' });
317
+ root.appendChild(child1);
318
+ root.appendChild(child2);
319
+ assert.strictEqual(root.$$('.test').length, 2, 'Should find two elements with class "test"');
320
+ });
321
+ });
322
+
323
+ describe('$', () => {
324
+ it('returns the first element matching the selector', () => {
325
+ const root = new Root();
326
+ const child1 = new Node('div', { class: 'test' });
327
+ const child2 = new Node('div', { class: 'test' });
328
+ root.appendChild(child1);
329
+ root.appendChild(child2);
330
+ assert.strictEqual(root.$('.test'), child1, 'Should return the first element with class "test"');
331
+ });
332
+ });
333
+
334
+ describe('querySelectorAll', () => {
335
+ it('finds all elements matching a complex query', () => {
336
+ const root = new Root();
337
+ const child1 = new Node('div', { id: 'unique' });
338
+ const child2 = new Node('div', { class: 'test' });
339
+ root.appendChild(child1);
340
+ root.appendChild(child2);
341
+ assert.strictEqual(root.querySelectorAll('div').length, 2, 'Should find two div elements');
342
+ });
343
+ });
344
+
345
+ describe('querySelector', () => {
346
+ it('finds the first element matching a complex query', () => {
347
+ const root = new Root();
348
+ const child1 = new Node('div', { id: 'unique' });
349
+ const child2 = new Node('div', { class: 'test' });
350
+ root.appendChild(child1);
351
+ root.appendChild(child2);
352
+ assert.strictEqual(root.querySelector('#unique'), child1, 'Should find the first div with id "unique"');
353
+ });
354
+ });
355
+
356
+ describe('getElementsByClassName', () => {
357
+ it('finds elements by class name', () => {
358
+ const root = new Root();
359
+ const child1 = new Node('div', { class: 'test' });
360
+ const child2 = new Node('div', { class: 'test' });
361
+ root.appendChild(child1);
362
+ root.appendChild(child2);
363
+ assert.strictEqual(root.getElementsByClassName('test').length, 2, 'Should find two elements with class "test"');
364
+ });
365
+ });
366
+
367
+ describe('getElementsByTagName', () => {
368
+ it('finds elements by tag name', () => {
369
+ const root = new Root();
370
+ const child1 = new Node('div');
371
+ const child2 = new Node('span');
372
+ root.appendChild(child1);
373
+ root.appendChild(child2);
374
+ assert.strictEqual(root.getElementsByTagName('div').length, 1, 'Should find one div element');
375
+ });
376
+ });
377
+
378
+ describe('getElementById', () => {
379
+ it('finds an element by ID', () => {
380
+ const root = new Root();
381
+ const child = new Node('div', { id: 'unique' });
382
+ root.appendChild(child);
383
+ assert.strictEqual(root.getElementById('unique'), child, 'Should find the element with id "unique"');
384
+ });
385
+ });
386
+
387
+ })
388
+
389
+ describe('Document element', () => {
390
+
391
+ it('get body', () => {
392
+ const document = new Document();
393
+ assert(document.body !== undefined);
394
+ assert(document.body instanceof Node);
395
+ });
396
+
397
+ it('get head', () => {
398
+ const document = new Document();
399
+ assert(document.head !== undefined);
400
+ assert(document.head instanceof Node);
401
+ });
402
+
403
+ it('get and set title', () => {
404
+ const root = new Document();
405
+ root.title = 'Original Title';
406
+ assert.strictEqual(root.title.textContent, 'Original Title', 'Should get the title');
407
+ root.title = 'New Title';
408
+ assert.strictEqual(root.title.textContent, 'New Title', 'Should set the new title');
409
+ });
410
+
411
+ })