als-document 1.2.1 → 1.3.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.
- package/browser-tests/node.js +9 -9
- package/browser-tests/parser.js +21 -21
- package/docs/#.md +7 -10
- package/docs/2.node.md +22 -1
- package/docs/5. Document.md +8 -25
- package/document.js +6 -6
- package/index.js +6 -6
- package/index.mjs +6 -6
- package/lib/node/document.js +39 -7
- package/lib/node/find.js +1 -0
- package/lib/node/node.js +19 -5
- package/lib/node/single-node.js +2 -2
- package/lib/parse/cache.js +1 -1
- package/lib/query/check-element.js +2 -2
- package/package.json +1 -1
- package/readme.md +31 -29
- package/tests/class-list.test.js +67 -0
- package/tests/document.test.js +90 -0
- package/tests/node.test.js +9 -9
- package/tests/parser.test.js +19 -19
package/lib/node/document.js
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
class Document extends Node {
|
|
2
|
-
constructor() {
|
|
3
|
-
super('
|
|
2
|
+
constructor(html,url) {
|
|
3
|
+
super('ROOT',{},null);
|
|
4
4
|
this.isSingle = false
|
|
5
|
-
this.
|
|
5
|
+
this.URL = url
|
|
6
|
+
if(html instanceof Document) {
|
|
7
|
+
this.childNodes = [...buildFromCache(cacheDoc(html)).childNodes]
|
|
8
|
+
// this.innerHTML = html.innerHTML
|
|
9
|
+
}
|
|
10
|
+
else if(typeof html === 'string') this.innerHTML = html
|
|
11
|
+
else this.body // create innerHTML
|
|
6
12
|
}
|
|
7
13
|
|
|
8
|
-
get
|
|
9
|
-
get
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
get documentElement() {return this.html}
|
|
15
|
+
get html() {
|
|
16
|
+
const html = this.$('html')
|
|
17
|
+
if(html === null) this.innerHTML = /*html*/`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
|
|
18
|
+
return this.$('html')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get head() {
|
|
22
|
+
const head = this.$('head')
|
|
23
|
+
if(head === null) this.html.insert(1,'<head></head>')
|
|
24
|
+
return this.$('head')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get body() {
|
|
28
|
+
const body = this.$('body')
|
|
29
|
+
if(body === null) this.head.insert(3,'<body></body>')
|
|
30
|
+
return this.$('body')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get title() {
|
|
34
|
+
const title = this.$('title')
|
|
35
|
+
if(title === null) this.head.insert(1,'<title></title>')
|
|
36
|
+
return this.$('title').innerText
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get charset() {return this.$('meta[charset]')}
|
|
40
|
+
|
|
41
|
+
set title(title) {return this.$('title').innerText = title}
|
|
42
|
+
|
|
43
|
+
get clone() {return new Document(this)}
|
|
12
44
|
}
|
package/lib/node/find.js
CHANGED
package/lib/node/node.js
CHANGED
|
@@ -7,7 +7,8 @@ function insertBefore(arr, index, newItem) {
|
|
|
7
7
|
class Node {
|
|
8
8
|
constructor(tagName, attributes = {}, parent = null) {
|
|
9
9
|
this.isSingle = false;
|
|
10
|
-
this.
|
|
10
|
+
this._tagName = tagName.toLowerCase();
|
|
11
|
+
this.tagName = tagName.toUpperCase();
|
|
11
12
|
this.attributes = attributes;
|
|
12
13
|
this.childNodes = [];
|
|
13
14
|
if (parent !== null) parent.childNodes.push(this)
|
|
@@ -43,13 +44,13 @@ class Node {
|
|
|
43
44
|
}
|
|
44
45
|
get previousElementSibling() { return this.prev }
|
|
45
46
|
get prev() {
|
|
46
|
-
if (
|
|
47
|
+
if (this.childIndex === null) return null // if no index or index == 0
|
|
47
48
|
return this.parent.children[this.childIndex - 1]
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
get nextElementSibling() { return this.next }
|
|
51
52
|
get next() {
|
|
52
|
-
if (
|
|
53
|
+
if (this.childIndex === null) return null
|
|
53
54
|
return this.parent.children[this.childIndex + 1] || null
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -70,7 +71,7 @@ class Node {
|
|
|
70
71
|
|
|
71
72
|
get outerHTML() {
|
|
72
73
|
const attrs = Object.entries(this.attributes).map(([key, val]) => val.length ? `${key}="${val}"` : key).join(" ");
|
|
73
|
-
return `<${this.
|
|
74
|
+
return `<${this._tagName}${attrs ? ' ' + attrs : ''}>${this.innerHTML}</${this._tagName}>`;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
getAttribute(attrName) { return this.attributes[attrName] !== undefined ? this.attributes[attrName] : null }
|
|
@@ -91,6 +92,19 @@ class Node {
|
|
|
91
92
|
}).join("");
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
get innerText() {
|
|
96
|
+
return this.childNodes.map(child => {
|
|
97
|
+
if (child instanceof Node || child instanceof SingleNode) return child.innerText;
|
|
98
|
+
else if (child instanceof TextNode) return child.textContent; // Assuming child is a text node or something that can be stringified.
|
|
99
|
+
else return child
|
|
100
|
+
}).join("");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
set innerText(value) {
|
|
104
|
+
this.childNodes = [new TextNode(value)]
|
|
105
|
+
return value
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
$$(query) { return this.querySelectorAll(query) }
|
|
95
109
|
querySelectorAll(query) {
|
|
96
110
|
const selectors = Query.get(query)
|
|
@@ -109,7 +123,7 @@ class Node {
|
|
|
109
123
|
get children() {
|
|
110
124
|
return this.childNodes.filter(child => {
|
|
111
125
|
if (!(child instanceof Node)) return false
|
|
112
|
-
if (child.
|
|
126
|
+
if (child._tagName === '#comment') return false
|
|
113
127
|
return true
|
|
114
128
|
});
|
|
115
129
|
}
|
package/lib/node/single-node.js
CHANGED
|
@@ -6,9 +6,9 @@ class SingleNode extends Node {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
get outerHTML() { // outerHTML for single node
|
|
9
|
-
if (this.
|
|
9
|
+
if (this._tagName === "#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
|
|
10
10
|
const attrs = Object.entries(this.attributes).map(([key, val]) => val.length ? `${key}="${val}"` : key).join(" ");
|
|
11
|
-
return `<${this.
|
|
11
|
+
return `<${this._tagName} ${attrs}${this._tagName === '?xml' ? '?' : ''}>`;
|
|
12
12
|
}
|
|
13
13
|
// Remove getters,setters and methods which no make sence in single node
|
|
14
14
|
get innerHTML() { return ""; }
|
package/lib/parse/cache.js
CHANGED
|
@@ -3,7 +3,7 @@ function buildFromCache(cached) {
|
|
|
3
3
|
if(typeof cache === 'string') return parent.childNodes.push(cache)
|
|
4
4
|
const {isSingle,tagName,attributes,childNodes,textContent} = cache
|
|
5
5
|
if(textContent) return parent.childNodes.push(new TextNode(textContent))
|
|
6
|
-
if(isSingle) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
6
|
+
if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
7
7
|
const newDoc = tagName === 'ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
8
8
|
childNodes.forEach(childNode => {
|
|
9
9
|
buildNode(childNode,newDoc)
|
|
@@ -5,8 +5,8 @@ function checkElement(el,selector) {
|
|
|
5
5
|
if(typeof el === 'string') return false
|
|
6
6
|
if(id !== undefined && el.id === null) return false
|
|
7
7
|
if(id && id !== el.id) return false
|
|
8
|
-
if(tag && el.
|
|
9
|
-
else if(tag && tag !== el.
|
|
8
|
+
if(tag && el._tagName === undefined) return false
|
|
9
|
+
else if(tag && tag !== el._tagName) return false
|
|
10
10
|
const clas = el.attributes.class
|
|
11
11
|
if(classList !== undefined && (clas === undefined || clas === '')) return false
|
|
12
12
|
else if(classList !== undefined) {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -40,16 +40,14 @@ import { parseHTML, Node, Query, TextNode, SingleNode, Root, Document } from 'al
|
|
|
40
40
|
</script>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## Change log
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* Document
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* get body
|
|
52
|
-
* get head
|
|
43
|
+
## Change log for 1.3
|
|
44
|
+
* added getter and setter for node.innerText
|
|
45
|
+
* prev and next now works with childIndex=0
|
|
46
|
+
* querySelctor not includes the parent any more
|
|
47
|
+
* Document new getters and setters include clone
|
|
48
|
+
* tagName - uppers, _tagName - lowers
|
|
49
|
+
|
|
50
|
+
|
|
53
51
|
## parseHTML
|
|
54
52
|
|
|
55
53
|
`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.
|
|
@@ -95,7 +93,9 @@ Remember, the actual tree structure will be more complex and detailed, but the p
|
|
|
95
93
|
`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.
|
|
96
94
|
|
|
97
95
|
### Properties:
|
|
98
|
-
- **tagName**: Represents the tag name of the element.
|
|
96
|
+
- **tagName**: Represents the tag name of the element (upper cased).
|
|
97
|
+
- **_tagName**: Represents the tag name of the element (lower cased).
|
|
98
|
+
- **innerText**
|
|
99
99
|
- **attributes**: A dictionary of attributes and their values.
|
|
100
100
|
- **childNodes**: An array of child nodes for the element.
|
|
101
101
|
- **isSingle**: Boolean value to check if the node is a self-closing tag.
|
|
@@ -115,24 +115,6 @@ Remember, the actual tree structure will be more complex and detailed, but the p
|
|
|
115
115
|
- **appendChild**: Add a child node to the element.
|
|
116
116
|
- **insert(place,element)**: place (0-3) or beforebegin,afterbegin,... eleemnt - raw html or element
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
### SingleNode
|
|
120
|
-
|
|
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.
|
|
122
|
-
|
|
123
|
-
### TextNode
|
|
124
|
-
|
|
125
|
-
`TextNode` is a class that represents text content within the DOM. A TextNode holds raw text data and does not have child nodes.
|
|
126
|
-
|
|
127
|
-
### Document node (extends Node)
|
|
128
|
-
|
|
129
|
-
Has additional getters and setters:
|
|
130
|
-
* getter root.title
|
|
131
|
-
* setter root.title
|
|
132
|
-
* getter root.body
|
|
133
|
-
* getter root.head
|
|
134
|
-
|
|
135
|
-
|
|
136
118
|
|
|
137
119
|
### Examples:
|
|
138
120
|
|
|
@@ -153,6 +135,26 @@ console.log(foundP.textContent); // Outputs: Hello, world!
|
|
|
153
135
|
```
|
|
154
136
|
|
|
155
137
|
|
|
138
|
+
### SingleNode
|
|
139
|
+
|
|
140
|
+
`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.
|
|
141
|
+
|
|
142
|
+
### TextNode
|
|
143
|
+
|
|
144
|
+
`TextNode` is a class that represents text content within the DOM. A TextNode holds raw text data and does not have child nodes.
|
|
145
|
+
|
|
146
|
+
### Document node (extends Node)
|
|
147
|
+
|
|
148
|
+
Has additional getters and setters:
|
|
149
|
+
* get documentElement
|
|
150
|
+
* get html
|
|
151
|
+
* get head
|
|
152
|
+
* get body
|
|
153
|
+
* get title
|
|
154
|
+
* get charset
|
|
155
|
+
* set title
|
|
156
|
+
* get clone - return cloned new instance of Document
|
|
157
|
+
|
|
156
158
|
## Query
|
|
157
159
|
|
|
158
160
|
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.
|
|
@@ -0,0 +1,67 @@
|
|
|
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('NodeClassList', () => {
|
|
6
|
+
let node;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
node = new Node('div');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('className', () => {
|
|
13
|
+
node.setAttribute('class','some test')
|
|
14
|
+
assert(node.className === 'some test')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('initially has no classes', () => {
|
|
18
|
+
assert.deepStrictEqual(node.classList.classes, []);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('adds a class', () => {
|
|
22
|
+
node.classList.add('test-class');
|
|
23
|
+
assert.deepStrictEqual(node.classList.classes, ['test-class']);
|
|
24
|
+
assert.strictEqual(node.attributes.class, 'test-class');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('removes a class', () => {
|
|
28
|
+
node.attributes.class = 'test-class another-class';
|
|
29
|
+
node.classList.remove('test-class');
|
|
30
|
+
assert.deepStrictEqual(node.classList.classes, ['another-class']);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('does not remove a class that does not exist', () => {
|
|
34
|
+
node.attributes.class = 'test-class';
|
|
35
|
+
node.classList.remove('nonexistent-class');
|
|
36
|
+
assert.deepStrictEqual(node.classList.classes, ['test-class']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('toggles a class off if it is present', () => {
|
|
40
|
+
node.attributes.class = 'test-class';
|
|
41
|
+
node.classList.toggle('test-class');
|
|
42
|
+
assert.deepStrictEqual(node.classList.classes, []);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('toggles a class on if it is not present', () => {
|
|
46
|
+
node.classList.toggle('test-class');
|
|
47
|
+
assert.deepStrictEqual(node.classList.classes, ['test-class']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('replaces an old class with a new one', () => {
|
|
51
|
+
node.attributes.class = 'old-class';
|
|
52
|
+
node.classList.replace('old-class', 'new-class');
|
|
53
|
+
assert.deepStrictEqual(node.classList.classes, ['new-class']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('contains checks for the presence of a class', () => {
|
|
57
|
+
node.attributes.class = 'test-class';
|
|
58
|
+
assert(node.classList.contains('test-class'));
|
|
59
|
+
assert(!node.classList.contains('nonexistent-class'));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('sets multiple classes', () => {
|
|
63
|
+
node.classList.classes = ['class1', 'class2'];
|
|
64
|
+
assert.strictEqual(node.attributes.class, 'class1 class2');
|
|
65
|
+
assert.deepStrictEqual(node.classList.classes, ['class1', 'class2']);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { Node, Document } = require('../index');
|
|
4
|
+
|
|
5
|
+
describe('Constructor and Basic Properties', () => {
|
|
6
|
+
let doc;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
doc = new Document('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Test Title</title></head><body></body></html>', 'http://example.com');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('get documentElement', () => {
|
|
13
|
+
assert.strictEqual(doc.documentElement.tagName, 'HTML');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('get html', () => {
|
|
17
|
+
assert.strictEqual(doc.html.tagName, 'HTML');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('get head', () => {
|
|
21
|
+
assert.strictEqual(doc.head.tagName, 'HEAD');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('get body', () => {
|
|
25
|
+
assert.strictEqual(doc.body.tagName, 'BODY');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('get title', () => {
|
|
29
|
+
assert.strictEqual(doc.title, 'Test Title');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('get charset', () => {
|
|
33
|
+
assert.strictEqual(doc.charset.getAttribute('charset'), 'UTF-8');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('set title', () => {
|
|
37
|
+
doc.title = 'New Title';
|
|
38
|
+
assert.strictEqual(doc.title, 'New Title');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('get clone', () => {
|
|
42
|
+
const clone = doc.clone;
|
|
43
|
+
assert(clone instanceof Document);
|
|
44
|
+
assert.strictEqual(clone.title, doc.title);
|
|
45
|
+
assert.notStrictEqual(clone, doc); // Ensure that cloned object is a separate instance
|
|
46
|
+
assert(doc.innerHTML === clone.innerHTML)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('Document Class Tests with Various Scenarios', () => {
|
|
52
|
+
describe('Missing Elements Handling', () => {
|
|
53
|
+
it('should create and return a new head if head is initially missing', () => {
|
|
54
|
+
const doc = new Document('<!DOCTYPE html><html lang="en"><body></body></html>', 'http://example.com');
|
|
55
|
+
assert.strictEqual(doc.head.tagName, 'HEAD');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should create and return a new body if body is initially missing', () => {
|
|
59
|
+
const doc = new Document('<!DOCTYPE html><html lang="en"><head></head></html>', 'http://example.com');
|
|
60
|
+
assert.strictEqual(doc.body.tagName, 'BODY');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle missing html element gracefully', () => {
|
|
64
|
+
const doc = new Document('', 'http://example.com');
|
|
65
|
+
assert.strictEqual(doc.html.tagName, 'HTML');
|
|
66
|
+
assert.strictEqual(doc.head.tagName, 'HEAD');
|
|
67
|
+
assert.strictEqual(doc.body.tagName, 'BODY');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle completely empty document and reconstruct the HTML structure', () => {
|
|
71
|
+
const doc = new Document('', 'http://example.com');
|
|
72
|
+
assert.strictEqual(doc.html.tagName, 'HTML');
|
|
73
|
+
assert(doc.head, 'Head element should be created if missing');
|
|
74
|
+
assert(doc.body, 'Body element should be created if missing');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle missing title tag and be able to set a new title', () => {
|
|
78
|
+
const doc = new Document('<!DOCTYPE html><html lang="en"><head></head><body></body></html>', 'http://example.com');
|
|
79
|
+
assert.strictEqual(doc.title, ''); // Assuming title is empty if missing
|
|
80
|
+
doc.title = 'New Title';
|
|
81
|
+
assert.strictEqual(doc.title, 'New Title');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle documents with no charset meta tag', () => {
|
|
85
|
+
const doc = new Document('<!DOCTYPE html><html lang="en"><head><title></title></head><body></body></html>', 'http://example.com');
|
|
86
|
+
assert.strictEqual(doc.charset, null); // Assuming charset is null if no meta charset tag
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
});
|
|
90
|
+
});
|
package/tests/node.test.js
CHANGED
|
@@ -13,7 +13,7 @@ describe('Constructor and Basic Properties', () => {
|
|
|
13
13
|
const rootNode = new Node('ROOT');
|
|
14
14
|
const divNode = new Node('div', { class: 'container' }, rootNode);
|
|
15
15
|
|
|
16
|
-
assert(divNode.tagName === '
|
|
16
|
+
assert(divNode.tagName === 'DIV', "Tag name is correct");
|
|
17
17
|
assert(divNode.attributes.class === 'container', "Attributes are correct");
|
|
18
18
|
assert(divNode.parent === rootNode, "Parent node is correct");
|
|
19
19
|
});
|
|
@@ -128,13 +128,13 @@ describe('Content Manipulation', () => {
|
|
|
128
128
|
const childNode = new Node('p', {}, rootNode);
|
|
129
129
|
|
|
130
130
|
childNode.insertAdjacentElement('beforebegin', new Node('span'));
|
|
131
|
-
assert(rootNode.childNodes[0].tagName === '
|
|
131
|
+
assert(rootNode.childNodes[0].tagName === 'SPAN');
|
|
132
132
|
|
|
133
133
|
childNode.insertAdjacentElement('afterend', new Node('a'));
|
|
134
|
-
assert(rootNode.childNodes[2].tagName === '
|
|
134
|
+
assert(rootNode.childNodes[2].tagName === 'A');
|
|
135
135
|
|
|
136
136
|
childNode.insertAdjacentHTML('beforebegin', '<strong></strong>');
|
|
137
|
-
assert(rootNode.childNodes[1].tagName === '
|
|
137
|
+
assert(rootNode.childNodes[1].tagName === 'STRONG');
|
|
138
138
|
|
|
139
139
|
childNode.insertAdjacentText('afterend', 'Some text');
|
|
140
140
|
assert(typeof rootNode.childNodes[3].nodeValue === 'string');
|
|
@@ -185,9 +185,9 @@ describe('Other Methods and Properties', () => {
|
|
|
185
185
|
|
|
186
186
|
// Проверяем свойство children
|
|
187
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');
|
|
188
|
+
assert(rootNode.children[0].tagName.toLowerCase() === 'p');
|
|
189
|
+
assert(rootNode.children[1].tagName.toLowerCase() === 'span');
|
|
190
|
+
assert(rootNode.children[2].tagName.toLowerCase() === 'a');
|
|
191
191
|
});
|
|
192
192
|
|
|
193
193
|
it('Gets the parent element', () => {
|
|
@@ -403,9 +403,9 @@ describe('Document element', () => {
|
|
|
403
403
|
it('get and set title', () => {
|
|
404
404
|
const root = new Document();
|
|
405
405
|
root.title = 'Original Title';
|
|
406
|
-
assert.strictEqual(root.title
|
|
406
|
+
assert.strictEqual(root.title, 'Original Title', 'Should get the title');
|
|
407
407
|
root.title = 'New Title';
|
|
408
|
-
assert.strictEqual(root.title
|
|
408
|
+
assert.strictEqual(root.title, 'New Title', 'Should set the new title');
|
|
409
409
|
});
|
|
410
410
|
|
|
411
411
|
})
|
package/tests/parser.test.js
CHANGED
|
@@ -16,17 +16,17 @@ describe('HTML Parser', () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it('correctly sets node type', () => {
|
|
19
|
-
assert(parsedHTML.tagName === 'div');
|
|
19
|
+
assert(parsedHTML.tagName.toLowerCase() === 'div');
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it.skip('correctly sets child nodes', () => {
|
|
23
23
|
assert(parsedHTML.children.leng === 1);
|
|
24
|
-
assert(parsedHTML.children[0].tagName === 'p');
|
|
24
|
+
assert(parsedHTML.children[0].tagName.toLowerCase() === 'p');
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('correctly parses nested HTML', () => {
|
|
28
28
|
const nestedHTML = parseHTML('<div><span><a href="#">Link</a></span></div>');
|
|
29
|
-
assert(nestedHTML.children[0].children[0].children[0].tagName === 'a');
|
|
29
|
+
assert(nestedHTML.children[0].children[0].children[0].tagName.toLowerCase() === 'a');
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
it('correctly parses text nodes', () => {
|
|
@@ -65,7 +65,7 @@ describe('Advanced tests', () => {
|
|
|
65
65
|
const result = parseHTML('<div><p>Text</div>');
|
|
66
66
|
// Depending on the behavior you expect: either an error, or a specific structure.
|
|
67
67
|
// For this example, I'll assume you expect the <p> tag to be auto-closed
|
|
68
|
-
assert(result.children[0].children[0].tagName === 'p');
|
|
68
|
+
assert(result.children[0].children[0].tagName.toLowerCase() === 'p');
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
it('handles attributes without values', () => {
|
|
@@ -76,7 +76,7 @@ describe('Advanced tests', () => {
|
|
|
76
76
|
it('ignores comments', () => {
|
|
77
77
|
const result = parseHTML('<!-- this is a comment --><div></div>');
|
|
78
78
|
assert(result.children.length === 1);
|
|
79
|
-
assert(result.children[0].tagName === 'div');
|
|
79
|
+
assert(result.children[0].tagName.toLowerCase() === 'div');
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
it('correctly parses script content', () => {
|
|
@@ -152,17 +152,17 @@ describe('CDATA', () => {
|
|
|
152
152
|
it('basic test', () => {
|
|
153
153
|
const test1 = '<![CDATA[This is CDATA content]]>';
|
|
154
154
|
const root1 = parseHTML(test1);
|
|
155
|
-
assert(root1.childNodes[0].tagName === "#cdata-section", "Test 1: CDATA node not created");
|
|
155
|
+
assert(root1.childNodes[0].tagName.toLowerCase() === "#cdata-section", "Test 1: CDATA node not created");
|
|
156
156
|
assert(root1.childNodes[0].nodeValue === "This is CDATA content", "Test 1: CDATA content not correct");
|
|
157
157
|
})
|
|
158
158
|
|
|
159
159
|
it('nested CDATA', () => {
|
|
160
160
|
const test2 = '<div><![CDATA[Inside a div]]><p>Paragraph</p></div>';
|
|
161
161
|
const root2 = parseHTML(test2);
|
|
162
|
-
assert(root2.childNodes[0].tagName === "div", "Test 2: Parent div not created");
|
|
163
|
-
assert(root2.childNodes[0].childNodes[0].tagName === "#cdata-section", "Test 2: CDATA node not created inside div");
|
|
162
|
+
assert(root2.childNodes[0].tagName.toLowerCase() === "div", "Test 2: Parent div not created");
|
|
163
|
+
assert(root2.childNodes[0].childNodes[0].tagName.toLowerCase() === "#cdata-section", "Test 2: CDATA node not created inside div");
|
|
164
164
|
assert(root2.childNodes[0].childNodes[0].nodeValue === "Inside a div", "Test 2: CDATA content not correct inside div");
|
|
165
|
-
assert(root2.childNodes[0].childNodes[1].tagName === "p", "Test 2: Paragraph not created after CDATA");
|
|
165
|
+
assert(root2.childNodes[0].childNodes[1].tagName.toLowerCase() === "p", "Test 2: Paragraph not created after CDATA");
|
|
166
166
|
})
|
|
167
167
|
|
|
168
168
|
it('multiple lines', () => {
|
|
@@ -173,7 +173,7 @@ inside this CDATA
|
|
|
173
173
|
block.
|
|
174
174
|
]]>`;
|
|
175
175
|
const root3 = parseHTML(test3);
|
|
176
|
-
assert(root3.childNodes[0].tagName === "#cdata-section", "Test 3: CDATA node not created");
|
|
176
|
+
assert(root3.childNodes[0].tagName.toLowerCase() === "#cdata-section", "Test 3: CDATA node not created");
|
|
177
177
|
assert(root3.childNodes[0].nodeValue.trim() === "Multiple lines \ninside this CDATA\nblock.", "Test 3: Multi-line CDATA content not correct");
|
|
178
178
|
|
|
179
179
|
})
|
|
@@ -181,7 +181,7 @@ block.
|
|
|
181
181
|
it('inside html', () => {
|
|
182
182
|
const test4 = '<![CDATA[<span>This should be text</span>]]>';
|
|
183
183
|
const root4 = parseHTML(test4);
|
|
184
|
-
assert(root4.childNodes[0].tagName === "#cdata-section", "Test 4: CDATA node not created");
|
|
184
|
+
assert(root4.childNodes[0].tagName.toLowerCase() === "#cdata-section", "Test 4: CDATA node not created");
|
|
185
185
|
assert(root4.childNodes[0].nodeValue === "<span>This should be text</span>", "Test 4: HTML inside CDATA not treated as text");
|
|
186
186
|
|
|
187
187
|
})
|
|
@@ -201,9 +201,9 @@ describe('signle tags, script and style', () => {
|
|
|
201
201
|
</script>
|
|
202
202
|
`;
|
|
203
203
|
const rootStyleScript = parseHTML(testStyleScript);
|
|
204
|
-
assert(rootStyleScript.childNodes[0].tagName === "style", "Test Style/Script 1: Style tag not created");
|
|
204
|
+
assert(rootStyleScript.childNodes[0].tagName.toLowerCase() === "style", "Test Style/Script 1: Style tag not created");
|
|
205
205
|
assert(rootStyleScript.childNodes[0].textContent.trim() === "body { color: red; }\n p > a { text-decoration: none; }", "Test Style/Script 1: Style content not correct");
|
|
206
|
-
assert(rootStyleScript.childNodes[1].tagName === "script", "Test Style/Script 2: Script tag not created");
|
|
206
|
+
assert(rootStyleScript.childNodes[1].tagName.toLowerCase() === "script", "Test Style/Script 2: Script tag not created");
|
|
207
207
|
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");
|
|
208
208
|
})
|
|
209
209
|
|
|
@@ -213,9 +213,9 @@ describe('signle tags, script and style', () => {
|
|
|
213
213
|
<link rel="stylesheet" href="styles.css">
|
|
214
214
|
`;
|
|
215
215
|
const rootMetaLink = parseHTML(testMetaLink);
|
|
216
|
-
assert(rootMetaLink.children[0].tagName === "meta", "Test Meta/Link 1: Meta tag not created");
|
|
216
|
+
assert(rootMetaLink.children[0].tagName.toLowerCase() === "meta", "Test Meta/Link 1: Meta tag not created");
|
|
217
217
|
assert(rootMetaLink.children[0].getAttribute("charset") === "UTF-8", "Test Meta/Link 1: Meta content not correct");
|
|
218
|
-
assert(rootMetaLink.children[1].tagName === "link", "Test Meta/Link 2: Link tag not created");
|
|
218
|
+
assert(rootMetaLink.children[1].tagName.toLowerCase() === "link", "Test Meta/Link 2: Link tag not created");
|
|
219
219
|
assert(rootMetaLink.children[1].getAttribute("rel") === "stylesheet", "Test Meta/Link 2: Link rel attribute not correct");
|
|
220
220
|
assert(rootMetaLink.children[1].getAttribute("href") === "styles.css", "Test Meta/Link 2: Link href attribute not correct");
|
|
221
221
|
})
|
|
@@ -224,7 +224,7 @@ describe('signle tags, script and style', () => {
|
|
|
224
224
|
const testUnmatchedClose = `<div>Some content</p></div>`;
|
|
225
225
|
const rootUnmatchedClose = parseHTML(testUnmatchedClose);
|
|
226
226
|
// Зависит от вашего решения обработки. Если вы решите исправлять такой HTML, тест может выглядеть так:
|
|
227
|
-
assert(rootUnmatchedClose.childNodes[0].tagName === "div", "Test Unmatched Close: Div tag not created");
|
|
227
|
+
assert(rootUnmatchedClose.childNodes[0].tagName.toLowerCase() === "div", "Test Unmatched Close: Div tag not created");
|
|
228
228
|
assert(rootUnmatchedClose.childNodes[0].textContent.trim() === "Some content", "Test Unmatched Close: Div content not correct");
|
|
229
229
|
})
|
|
230
230
|
|
|
@@ -232,7 +232,7 @@ describe('signle tags, script and style', () => {
|
|
|
232
232
|
const testUnmatchedOpen = `<div>Some content`;
|
|
233
233
|
const rootUnmatchedOpen = parseHTML(testUnmatchedOpen);
|
|
234
234
|
// Снова зависит от вашего решения. Если вы решите автоматически закрывать тег:
|
|
235
|
-
assert(rootUnmatchedOpen.childNodes[0].tagName === "div", "Test Unmatched Open: Div tag not created");
|
|
235
|
+
assert(rootUnmatchedOpen.childNodes[0].tagName.toLowerCase() === "div", "Test Unmatched Open: Div tag not created");
|
|
236
236
|
assert(rootUnmatchedOpen.childNodes[0].textContent.trim() === "Some content", "Test Unmatched Open: Div content not correct");
|
|
237
237
|
})
|
|
238
238
|
})
|
|
@@ -284,7 +284,7 @@ describe('Specific elements handling', () => {
|
|
|
284
284
|
const html = parseHTML(canvas)
|
|
285
285
|
const canvasElement = html.children[0]
|
|
286
286
|
|
|
287
|
-
assert(canvasElement.tagName === 'canvas');
|
|
287
|
+
assert(canvasElement.tagName.toLowerCase() === 'canvas');
|
|
288
288
|
assert(canvasElement.getAttribute('id') === 'myCanvas');
|
|
289
289
|
assert(canvasElement.getAttribute('width') === '300');
|
|
290
290
|
assert(canvasElement.getAttribute('height') === '150');
|
|
@@ -299,7 +299,7 @@ describe('Specific elements handling', () => {
|
|
|
299
299
|
const html = parseHTML(svgHTML);
|
|
300
300
|
const svgElement = html.children[0]
|
|
301
301
|
|
|
302
|
-
assert(svgElement.tagName === 'svg');
|
|
302
|
+
assert(svgElement.tagName.toLowerCase() === 'svg');
|
|
303
303
|
assert(svgElement.getAttribute('xmlns') === svgNamespace);
|
|
304
304
|
assert(svgElement.getAttribute('xmlns:xml') === xmlNamespace);
|
|
305
305
|
});
|