@tkeron/html-parser 1.1.2 → 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/.github/workflows/npm_deploy.yml +14 -4
- package/README.md +6 -6
- package/bun.lock +6 -8
- package/check-versions.ts +147 -0
- package/index.ts +4 -8
- package/package.json +5 -6
- package/src/dom-simulator/append-child.ts +130 -0
- package/src/dom-simulator/append.ts +18 -0
- package/src/dom-simulator/attributes.ts +23 -0
- package/src/dom-simulator/clone-node.ts +51 -0
- package/src/dom-simulator/convert-ast-node-to-dom.ts +37 -0
- package/src/dom-simulator/create-cdata.ts +18 -0
- package/src/dom-simulator/create-comment.ts +23 -0
- package/src/dom-simulator/create-doctype.ts +24 -0
- package/src/dom-simulator/create-document.ts +81 -0
- package/src/dom-simulator/create-element.ts +195 -0
- package/src/dom-simulator/create-processing-instruction.ts +19 -0
- package/src/dom-simulator/create-temp-parent.ts +9 -0
- package/src/dom-simulator/create-text-node.ts +23 -0
- package/src/dom-simulator/escape-text-content.ts +6 -0
- package/src/dom-simulator/find-special-elements.ts +14 -0
- package/src/dom-simulator/get-text-content.ts +18 -0
- package/src/dom-simulator/index.ts +36 -0
- package/src/dom-simulator/inner-outer-html.ts +182 -0
- package/src/dom-simulator/insert-after.ts +20 -0
- package/src/dom-simulator/insert-before.ts +108 -0
- package/src/dom-simulator/matches.ts +26 -0
- package/src/dom-simulator/node-types.ts +26 -0
- package/src/dom-simulator/prepend.ts +24 -0
- package/src/dom-simulator/remove-child.ts +68 -0
- package/src/dom-simulator/remove.ts +7 -0
- package/src/dom-simulator/replace-child.ts +152 -0
- package/src/dom-simulator/set-text-content.ts +33 -0
- package/src/dom-simulator/update-element-content.ts +56 -0
- package/src/dom-simulator.ts +12 -1126
- package/src/encoding/constants.ts +8 -0
- package/src/encoding/detect-encoding.ts +21 -0
- package/src/encoding/index.ts +1 -0
- package/src/encoding/normalize-encoding.ts +6 -0
- package/src/html-entities.ts +2127 -0
- package/src/index.ts +5 -5
- package/src/parser/adoption-agency-helpers.ts +145 -0
- package/src/parser/constants.ts +137 -0
- package/src/parser/dom-to-ast.ts +79 -0
- package/src/parser/index.ts +9 -0
- package/src/parser/parse.ts +772 -0
- package/src/parser/types.ts +56 -0
- package/src/selectors/find-elements-descendant.ts +47 -0
- package/src/selectors/index.ts +2 -0
- package/src/selectors/matches-selector.ts +12 -0
- package/src/selectors/matches-token.ts +27 -0
- package/src/selectors/parse-selector.ts +48 -0
- package/src/selectors/query-selector-all.ts +43 -0
- package/src/selectors/query-selector.ts +6 -0
- package/src/selectors/types.ts +10 -0
- package/src/serializer/attributes.ts +74 -0
- package/src/serializer/escape.ts +13 -0
- package/src/serializer/index.ts +1 -0
- package/src/serializer/serialize-tokens.ts +511 -0
- package/src/tokenizer/calculate-position.ts +10 -0
- package/src/tokenizer/constants.ts +11 -0
- package/src/tokenizer/decode-entities.ts +64 -0
- package/src/tokenizer/index.ts +2 -0
- package/src/tokenizer/parse-attributes.ts +74 -0
- package/src/tokenizer/tokenize.ts +165 -0
- package/src/tokenizer/types.ts +25 -0
- package/tests/adoption-agency-helpers.test.ts +304 -0
- package/tests/advanced.test.ts +242 -221
- package/tests/cloneNode.test.ts +19 -66
- package/tests/custom-elements-head.test.ts +54 -55
- package/tests/dom-extended.test.ts +77 -64
- package/tests/dom-manipulation.test.ts +51 -24
- package/tests/dom.test.ts +15 -13
- package/tests/encoding/detect-encoding.test.ts +33 -0
- package/tests/google-dom.test.ts +2 -2
- package/tests/helpers/tokenizer-adapter.test.ts +29 -43
- package/tests/helpers/tokenizer-adapter.ts +36 -33
- package/tests/helpers/tree-adapter.test.ts +20 -20
- package/tests/helpers/tree-adapter.ts +34 -24
- package/tests/html-entities-text.test.ts +6 -2
- package/tests/innerhtml-void-elements.test.ts +52 -36
- package/tests/outerHTML-replacement.test.ts +37 -65
- package/tests/parser/dom-to-ast.test.ts +109 -0
- package/tests/parser/parse.test.ts +139 -0
- package/tests/parser.test.ts +281 -217
- package/tests/selectors/query-selector-all.test.ts +39 -0
- package/tests/selectors/query-selector.test.ts +42 -0
- package/tests/serializer/attributes.test.ts +132 -0
- package/tests/serializer/escape.test.ts +51 -0
- package/tests/serializer/serialize-tokens.test.ts +80 -0
- package/tests/serializer-core.test.ts +6 -6
- package/tests/serializer-injectmeta.test.ts +6 -6
- package/tests/serializer-optionaltags.test.ts +9 -6
- package/tests/serializer-options.test.ts +6 -6
- package/tests/serializer-whitespace.test.ts +6 -6
- package/tests/tokenizer/calculate-position.test.ts +34 -0
- package/tests/tokenizer/decode-entities.test.ts +31 -0
- package/tests/tokenizer/parse-attributes.test.ts +44 -0
- package/tests/tokenizer/tokenize.test.ts +757 -0
- package/tests/tokenizer-namedEntities.test.ts +10 -7
- package/tests/tokenizer-pendingSpecChanges.test.ts +10 -7
- package/tests/tokenizer.test.ts +268 -256
- package/tests/tree-construction-adoption01.test.ts +25 -16
- package/tests/tree-construction-adoption02.test.ts +30 -19
- package/tests/tree-construction-domjs-unsafe.test.ts +6 -4
- package/tests/tree-construction-entities02.test.ts +18 -16
- package/tests/tree-construction-html5test-com.test.ts +16 -10
- package/tests/tree-construction-math.test.ts +11 -9
- package/tests/tree-construction-namespace-sensitivity.test.ts +11 -9
- package/tests/tree-construction-noscript01.test.ts +11 -9
- package/tests/tree-construction-ruby.test.ts +6 -4
- package/tests/tree-construction-scriptdata01.test.ts +6 -4
- package/tests/tree-construction-svg.test.ts +6 -4
- package/tests/tree-construction-template.test.ts +6 -4
- package/tests/tree-construction-tests10.test.ts +6 -4
- package/tests/tree-construction-tests11.test.ts +6 -4
- package/tests/tree-construction-tests20.test.ts +7 -4
- package/tests/tree-construction-tests21.test.ts +7 -4
- package/tests/tree-construction-tests23.test.ts +7 -4
- package/tests/tree-construction-tests24.test.ts +7 -4
- package/tests/tree-construction-tests5.test.ts +6 -5
- package/tests/tree-construction-tests6.test.ts +6 -5
- package/tests/tree-construction-tests_innerHTML_1.test.ts +6 -5
- package/tests/void-elements.test.ts +85 -40
- package/tsconfig.json +1 -1
- package/src/css-selector.ts +0 -185
- package/src/encoding.ts +0 -39
- package/src/parser.ts +0 -682
- package/src/serializer.ts +0 -450
- package/src/tokenizer.ts +0 -325
- package/tests/selectors.test.ts +0 -128
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { querySelectorAll } from "../../src/selectors";
|
|
3
|
+
import { parseHTML } from "../../index";
|
|
4
|
+
|
|
5
|
+
describe("querySelectorAll", () => {
|
|
6
|
+
const htmlContent = `
|
|
7
|
+
<html>
|
|
8
|
+
<body>
|
|
9
|
+
<p id="intro" class="first">
|
|
10
|
+
<span class="highlight">Hello</span>
|
|
11
|
+
</p>
|
|
12
|
+
<p class="second">World</p>
|
|
13
|
+
<div>
|
|
14
|
+
<p class="note">Note</p>
|
|
15
|
+
</div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const doc = parseHTML(htmlContent);
|
|
21
|
+
|
|
22
|
+
it("should be a function", () => {
|
|
23
|
+
expect(typeof querySelectorAll).toBe("function");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should find all elements by tag name", () => {
|
|
27
|
+
const paragraphs = querySelectorAll(doc, "p");
|
|
28
|
+
expect(paragraphs.length).toBe(3);
|
|
29
|
+
expect(paragraphs[0]!.attributes.class).toBe("first");
|
|
30
|
+
expect(paragraphs[1]!.attributes.class).toBe("second");
|
|
31
|
+
expect(paragraphs[2]!.attributes.class).toBe("note");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should find all elements by class name", () => {
|
|
35
|
+
const second = querySelectorAll(doc, ".second");
|
|
36
|
+
expect(second.length).toBe(1);
|
|
37
|
+
expect(second[0]!.tagName).toBe("P");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { querySelector } from "../../src/selectors";
|
|
3
|
+
import { parseHTML } from "../../index";
|
|
4
|
+
|
|
5
|
+
describe("querySelector", () => {
|
|
6
|
+
const htmlContent = `
|
|
7
|
+
<html>
|
|
8
|
+
<body>
|
|
9
|
+
<p id="intro" class="first">
|
|
10
|
+
<span class="highlight">Hello</span>
|
|
11
|
+
</p>
|
|
12
|
+
<p class="second">World</p>
|
|
13
|
+
<div>
|
|
14
|
+
<p class="note">Note</p>
|
|
15
|
+
</div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
const doc = parseHTML(htmlContent);
|
|
21
|
+
|
|
22
|
+
it("should be a function", () => {
|
|
23
|
+
expect(typeof querySelector).toBe("function");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should find the first element by tag name", () => {
|
|
27
|
+
const firstParagraph = querySelector(doc, "p");
|
|
28
|
+
expect(firstParagraph).not.toBeNull();
|
|
29
|
+
expect(firstParagraph?.attributes.id).toBe("intro");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should find an element by ID", () => {
|
|
33
|
+
const intro = querySelector(doc, "#intro");
|
|
34
|
+
expect(intro).not.toBeNull();
|
|
35
|
+
expect(intro?.tagName).toBe("P");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return null if no element is found", () => {
|
|
39
|
+
const nonExistent = querySelector(doc, "#nonexistent");
|
|
40
|
+
expect(nonExistent).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
needsQuotes,
|
|
3
|
+
serializeAttribute,
|
|
4
|
+
serializeAttributes,
|
|
5
|
+
} from "../../src/serializer/attributes.js";
|
|
6
|
+
|
|
7
|
+
describe("needsQuotes", () => {
|
|
8
|
+
it("should return true for empty string", () => {
|
|
9
|
+
expect(needsQuotes("")).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should return true for strings with spaces", () => {
|
|
13
|
+
expect(needsQuotes("a b")).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should return true for strings with tabs", () => {
|
|
17
|
+
expect(needsQuotes("a\tb")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return true for strings with newlines", () => {
|
|
21
|
+
expect(needsQuotes("a\nb")).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return true for strings with carriage returns", () => {
|
|
25
|
+
expect(needsQuotes("a\rb")).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should return true for strings with form feeds", () => {
|
|
29
|
+
expect(needsQuotes("a\fb")).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should return true for strings with single quotes", () => {
|
|
33
|
+
expect(needsQuotes("a'b")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should return true for strings with double quotes", () => {
|
|
37
|
+
expect(needsQuotes('a"b')).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should return true for strings with equals", () => {
|
|
41
|
+
expect(needsQuotes("a=b")).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should return true for strings with greater than", () => {
|
|
45
|
+
expect(needsQuotes("a>b")).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should return true for strings with backticks", () => {
|
|
49
|
+
expect(needsQuotes("a`b")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should return false for simple strings", () => {
|
|
53
|
+
expect(needsQuotes("abc")).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("serializeAttribute", () => {
|
|
58
|
+
it("should minimize boolean attributes", () => {
|
|
59
|
+
expect(serializeAttribute("disabled", "disabled")).toBe("disabled");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should not minimize when option is false", () => {
|
|
63
|
+
expect(
|
|
64
|
+
serializeAttribute("disabled", "disabled", {
|
|
65
|
+
minimize_boolean_attributes: false,
|
|
66
|
+
}),
|
|
67
|
+
).toBe("disabled=disabled");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should serialize without quotes when not needed", () => {
|
|
71
|
+
expect(serializeAttribute("id", "test")).toBe("id=test");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should serialize with double quotes when needed", () => {
|
|
75
|
+
expect(serializeAttribute("class", "a b")).toBe('class="a b"');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should serialize with single quotes when value contains double quotes", () => {
|
|
79
|
+
expect(serializeAttribute("data", 'a"b')).toBe("data='a\"b'");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should serialize with double quotes when value contains single quotes", () => {
|
|
83
|
+
expect(serializeAttribute("data", "a'b")).toBe('data="a\'b"');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should serialize with double quotes when value contains both quotes", () => {
|
|
87
|
+
expect(serializeAttribute("data", "a\"b'c")).toBe('data="a"b\'c"');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should force quote character when specified", () => {
|
|
91
|
+
expect(serializeAttribute("data", "test", { quote_char: "'" })).toBe(
|
|
92
|
+
"data='test'",
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should escape less-than in attributes when option is set", () => {
|
|
97
|
+
expect(
|
|
98
|
+
serializeAttribute("data", "a<b", { escape_lt_in_attrs: true }),
|
|
99
|
+
).toBe("data=a<b");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should always quote when quote_attr_values is true", () => {
|
|
103
|
+
expect(serializeAttribute("id", "test", { quote_attr_values: true })).toBe(
|
|
104
|
+
'id="test"',
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("serializeAttributes", () => {
|
|
110
|
+
it("should serialize array of attributes", () => {
|
|
111
|
+
const attrs = [
|
|
112
|
+
{ name: "id", value: "test" },
|
|
113
|
+
{ name: "class", value: "a b" },
|
|
114
|
+
];
|
|
115
|
+
expect(serializeAttributes(attrs)).toBe(' class="a b" id=test');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should serialize object attributes", () => {
|
|
119
|
+
const attrs = { id: "test", class: "a b" };
|
|
120
|
+
expect(serializeAttributes(attrs)).toBe(' class="a b" id=test');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should sort attributes alphabetically", () => {
|
|
124
|
+
const attrs = { z: "1", a: "2" };
|
|
125
|
+
expect(serializeAttributes(attrs)).toBe(" a=2 z=1");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should handle empty attributes", () => {
|
|
129
|
+
expect(serializeAttributes([])).toBe("");
|
|
130
|
+
expect(serializeAttributes({})).toBe("");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
escapeText,
|
|
4
|
+
escapeAttributeValue,
|
|
5
|
+
} from "../../src/serializer/escape.js";
|
|
6
|
+
|
|
7
|
+
describe("escapeText", () => {
|
|
8
|
+
it("should escape ampersands", () => {
|
|
9
|
+
expect(escapeText("a & b")).toBe("a & b");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should escape less-than signs", () => {
|
|
13
|
+
expect(escapeText("a < b")).toBe("a < b");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should escape greater-than signs", () => {
|
|
17
|
+
expect(escapeText("a > b")).toBe("a > b");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should escape multiple characters", () => {
|
|
21
|
+
expect(escapeText("a & b < c > d")).toBe("a & b < c > d");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should handle empty string", () => {
|
|
25
|
+
expect(escapeText("")).toBe("");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("escapeAttributeValue", () => {
|
|
30
|
+
it("should escape ampersands", () => {
|
|
31
|
+
expect(escapeAttributeValue("a & b")).toBe("a & b");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should escape double quotes", () => {
|
|
35
|
+
expect(escapeAttributeValue('a " b')).toBe("a " b");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should escape single quotes", () => {
|
|
39
|
+
expect(escapeAttributeValue("a ' b")).toBe("a ' b");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should escape multiple characters", () => {
|
|
43
|
+
expect(escapeAttributeValue("a & b \" c ' d")).toBe(
|
|
44
|
+
"a & b " c ' d",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle empty string", () => {
|
|
49
|
+
expect(escapeAttributeValue("")).toBe("");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { serializeTokens } from "../../src/serializer/serialize-tokens.js";
|
|
3
|
+
|
|
4
|
+
describe("serializeTokens", () => {
|
|
5
|
+
it("should serialize empty tokens", () => {
|
|
6
|
+
expect(serializeTokens([])).toBe("");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should serialize text tokens", () => {
|
|
10
|
+
const tokens = [["Characters", "Hello world"]];
|
|
11
|
+
expect(serializeTokens(tokens)).toBe("Hello world");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should serialize start and end tags", () => {
|
|
15
|
+
const tokens = [
|
|
16
|
+
["StartTag", null, "div", []],
|
|
17
|
+
["Characters", "content"],
|
|
18
|
+
["EndTag", null, "div"],
|
|
19
|
+
];
|
|
20
|
+
expect(serializeTokens(tokens)).toBe("<div>content</div>");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should serialize self-closing tags", () => {
|
|
24
|
+
const tokens = [["EmptyTag", "br", []]];
|
|
25
|
+
expect(serializeTokens(tokens)).toBe("<br>");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should serialize attributes", () => {
|
|
29
|
+
const tokens = [
|
|
30
|
+
[
|
|
31
|
+
"StartTag",
|
|
32
|
+
null,
|
|
33
|
+
"div",
|
|
34
|
+
[
|
|
35
|
+
{ name: "id", value: "test" },
|
|
36
|
+
{ name: "class", value: "a b" },
|
|
37
|
+
],
|
|
38
|
+
],
|
|
39
|
+
];
|
|
40
|
+
expect(serializeTokens(tokens)).toBe('<div class="a b" id=test>');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should serialize comments", () => {
|
|
44
|
+
const tokens = [["Comment", " comment "]];
|
|
45
|
+
expect(serializeTokens(tokens)).toBe("<!-- comment -->");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should serialize doctype", () => {
|
|
49
|
+
const tokens = [["Doctype", "html", "", ""]];
|
|
50
|
+
expect(serializeTokens(tokens)).toBe("<!DOCTYPE html>");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should escape text content", () => {
|
|
54
|
+
const tokens = [["Characters", "a & b < c > d"]];
|
|
55
|
+
expect(serializeTokens(tokens)).toBe("a & b < c > d");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should handle script content", () => {
|
|
59
|
+
const tokens = [
|
|
60
|
+
["StartTag", null, "script", []],
|
|
61
|
+
["Characters", "<script>alert('test')</script>"],
|
|
62
|
+
["EndTag", null, "script"],
|
|
63
|
+
];
|
|
64
|
+
expect(serializeTokens(tokens)).toBe(
|
|
65
|
+
"<script><script>alert('test')</script></script>",
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("should inject meta charset when option is set", () => {
|
|
70
|
+
const tokens = [
|
|
71
|
+
["StartTag", null, "html", []],
|
|
72
|
+
["StartTag", null, "head", []],
|
|
73
|
+
["EndTag", null, "head"],
|
|
74
|
+
["StartTag", null, "body", []],
|
|
75
|
+
["EndTag", null, "body"],
|
|
76
|
+
["EndTag", null, "html"],
|
|
77
|
+
];
|
|
78
|
+
expect(serializeTokens(tokens, { inject_meta_charset: true })).toBe("");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { expect, it, describe } from
|
|
2
|
-
import { serializeTokens } from
|
|
3
|
-
import { readFileSync } from
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { serializeTokens } from "../src/serializer";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
const content = readFileSync(
|
|
5
|
+
describe("Serializer Core Tests", () => {
|
|
6
|
+
const content = readFileSync("tests/serializer-data/core.test", "utf8");
|
|
7
7
|
const data = JSON.parse(content);
|
|
8
8
|
const tests = data.tests;
|
|
9
9
|
|
|
@@ -13,4 +13,4 @@ describe('Serializer Core Tests', () => {
|
|
|
13
13
|
expect(result).toBe(test.expected[0]);
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
-
});
|
|
16
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { expect, it, describe } from
|
|
2
|
-
import { serializeTokens } from
|
|
3
|
-
import { readFileSync } from
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { serializeTokens } from "../src/serializer";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
const content = readFileSync(
|
|
5
|
+
describe("Serializer Inject Meta Tests", () => {
|
|
6
|
+
const content = readFileSync("tests/serializer-data/injectmeta.test", "utf8");
|
|
7
7
|
const data = JSON.parse(content);
|
|
8
8
|
const tests = data.tests;
|
|
9
9
|
|
|
@@ -13,4 +13,4 @@ describe('Serializer Inject Meta Tests', () => {
|
|
|
13
13
|
expect(result).toBe(test.expected[0]);
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
-
});
|
|
16
|
+
});
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { expect, it, describe } from
|
|
2
|
-
import { serializeTokens } from
|
|
3
|
-
import { readFileSync } from
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { serializeTokens } from "../src/serializer";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
const content = readFileSync(
|
|
5
|
+
describe("Serializer Optional Tags Tests", () => {
|
|
6
|
+
const content = readFileSync(
|
|
7
|
+
"tests/serializer-data/optionaltags.test",
|
|
8
|
+
"utf8",
|
|
9
|
+
);
|
|
7
10
|
const data = JSON.parse(content);
|
|
8
11
|
const tests = data.tests;
|
|
9
12
|
|
|
@@ -13,4 +16,4 @@ describe('Serializer Optional Tags Tests', () => {
|
|
|
13
16
|
expect(result).toBe(test.expected[0]);
|
|
14
17
|
});
|
|
15
18
|
});
|
|
16
|
-
});
|
|
19
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { expect, it, describe } from
|
|
2
|
-
import { serializeTokens } from
|
|
3
|
-
import { readFileSync } from
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { serializeTokens } from "../src/serializer";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
const content = readFileSync(
|
|
5
|
+
describe("Serializer Options Tests", () => {
|
|
6
|
+
const content = readFileSync("tests/serializer-data/options.test", "utf8");
|
|
7
7
|
const data = JSON.parse(content);
|
|
8
8
|
const tests = data.tests;
|
|
9
9
|
|
|
@@ -13,4 +13,4 @@ describe('Serializer Options Tests', () => {
|
|
|
13
13
|
expect(result).toBe(test.expected[0]);
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
-
});
|
|
16
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { expect, it, describe } from
|
|
2
|
-
import { serializeTokens } from
|
|
3
|
-
import { readFileSync } from
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { serializeTokens } from "../src/serializer";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
4
|
|
|
5
|
-
describe(
|
|
6
|
-
const content = readFileSync(
|
|
5
|
+
describe("Serializer Whitespace Tests", () => {
|
|
6
|
+
const content = readFileSync("tests/serializer-data/whitespace.test", "utf8");
|
|
7
7
|
const data = JSON.parse(content);
|
|
8
8
|
const tests = data.tests;
|
|
9
9
|
|
|
@@ -13,4 +13,4 @@ describe('Serializer Whitespace Tests', () => {
|
|
|
13
13
|
expect(result).toBe(test.expected[0]);
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
-
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { calculatePosition } from "../../src/tokenizer/calculate-position.js";
|
|
3
|
+
|
|
4
|
+
describe("calculatePosition", () => {
|
|
5
|
+
it("should calculate position at start", () => {
|
|
6
|
+
const result = calculatePosition("hello", 0);
|
|
7
|
+
expect(result).toEqual({ line: 1, column: 0, offset: 0 });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should calculate position in middle of line", () => {
|
|
11
|
+
const result = calculatePosition("hello", 2);
|
|
12
|
+
expect(result).toEqual({ line: 1, column: 2, offset: 2 });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should calculate position after newline", () => {
|
|
16
|
+
const result = calculatePosition("hello\nworld", 6);
|
|
17
|
+
expect(result).toEqual({ line: 2, column: 0, offset: 6 });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should calculate position in second line", () => {
|
|
21
|
+
const result = calculatePosition("hello\nworld", 8);
|
|
22
|
+
expect(result).toEqual({ line: 2, column: 2, offset: 8 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should handle multiple newlines", () => {
|
|
26
|
+
const result = calculatePosition("a\nb\nc", 4);
|
|
27
|
+
expect(result).toEqual({ line: 3, column: 0, offset: 4 });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should handle empty string", () => {
|
|
31
|
+
const result = calculatePosition("", 0);
|
|
32
|
+
expect(result).toEqual({ line: 1, column: 0, offset: 0 });
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { decodeEntities } from "../../src/tokenizer/decode-entities.js";
|
|
3
|
+
|
|
4
|
+
describe("decodeEntities", () => {
|
|
5
|
+
it("should decode named entities", () => {
|
|
6
|
+
expect(decodeEntities("&")).toBe("&");
|
|
7
|
+
expect(decodeEntities("<")).toBe("<");
|
|
8
|
+
expect(decodeEntities(">")).toBe(">");
|
|
9
|
+
expect(decodeEntities(""")).toBe('"');
|
|
10
|
+
expect(decodeEntities("'")).toBe("'");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should decode numeric entities", () => {
|
|
14
|
+
expect(decodeEntities("A")).toBe("A");
|
|
15
|
+
expect(decodeEntities("A")).toBe("A");
|
|
16
|
+
expect(decodeEntities("'")).toBe("'");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should handle invalid entities", () => {
|
|
20
|
+
expect(decodeEntities("&invalid;")).toBe("&invalid;");
|
|
21
|
+
expect(decodeEntities("&")).toBe("&");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should replace null characters", () => {
|
|
25
|
+
expect(decodeEntities("test\u0000test")).toBe("test\uFFFDtest");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should handle mixed content", () => {
|
|
29
|
+
expect(decodeEntities("a<b>c&d")).toBe("a<b>c&d");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expect, it, describe } from "bun:test";
|
|
2
|
+
import { parseAttributes } from "../../src/tokenizer/parse-attributes.js";
|
|
3
|
+
|
|
4
|
+
describe("parseAttributes", () => {
|
|
5
|
+
it("should parse simple attributes", () => {
|
|
6
|
+
const result = parseAttributes('id="test" class="foo"');
|
|
7
|
+
expect(result).toEqual({ id: "test", class: "foo" });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should parse attributes with single quotes", () => {
|
|
11
|
+
const result = parseAttributes("id='test' class='foo'");
|
|
12
|
+
expect(result).toEqual({ id: "test", class: "foo" });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should parse attributes without quotes", () => {
|
|
16
|
+
const result = parseAttributes("id=test class=foo");
|
|
17
|
+
expect(result).toEqual({ id: "test", class: "foo" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should decode entities in attribute values", () => {
|
|
21
|
+
const result = parseAttributes('content="a<b"');
|
|
22
|
+
expect(result).toEqual({ content: "a<b" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should handle boolean attributes", () => {
|
|
26
|
+
const result = parseAttributes("disabled checked");
|
|
27
|
+
expect(result).toEqual({ disabled: "", checked: "" });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should handle empty attribute string", () => {
|
|
31
|
+
const result = parseAttributes("");
|
|
32
|
+
expect(result).toEqual({});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should handle malformed attributes", () => {
|
|
36
|
+
const result = parseAttributes('id="test" = class="foo"');
|
|
37
|
+
expect(result).toEqual({ id: "test", class: "foo" });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should lowercase attribute names", () => {
|
|
41
|
+
const result = parseAttributes('ID="test" CLASS="foo"');
|
|
42
|
+
expect(result).toEqual({ id: "test", class: "foo" });
|
|
43
|
+
});
|
|
44
|
+
});
|