Skip to content

Commit e32389a

Browse files
authored
fix: align HTML parsing with the spec (#2387)
1 parent cc400f1 commit e32389a

9 files changed

Lines changed: 3246 additions & 978 deletions

src/Parser.events.spec.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ describe("Events", () => {
8080
it("end slash: non-void element ending with />, recognizeSelfClosing=true", () =>
8181
runTest("<xx / ><p>Hold the line.", { recognizeSelfClosing: true }));
8282

83+
it("end slash: special tag ending with />, recognizeSelfClosing=true", () =>
84+
runTest("<script/><b>x</b>", { recognizeSelfClosing: true }));
85+
86+
it("end slash: plaintext tag ending with />, recognizeSelfClosing=true", () =>
87+
runTest("<plaintext/><b>x</b>", { recognizeSelfClosing: true }));
88+
8389
it("end slash: as part of attrib value of void element", () =>
8490
runTest("<img src=gif.com/123/><p>Hold the line."));
8591

@@ -162,6 +168,77 @@ describe("Events", () => {
162168

163169
it("Comment false ending", () => runTest("<!-- a-b-> -->"));
164170

171+
it("Short bang comment", () => runTest("<!--!>"));
172+
173+
it("Abruptly closed comment", () => runTest("<!--->text"));
174+
175+
it("Empty EOF comment", () => runTest("<!--"));
176+
177+
it("HTML bogus comments", () => runTest("<?foo><!foo>"));
178+
179+
it("Short HTML bogus comments", () => runTest("<!><!->"));
180+
181+
it("Bogus comment after </ and whitespace", () => runTest("<div></ div>"));
182+
183+
it("Empty closing tag", () => runTest("</>"));
184+
185+
it("Empty closing tag, xmlMode=true", () =>
186+
runTest("</>", { xmlMode: true }));
187+
188+
it("Doctype without whitespace", () => runTest("<!DOCTYPEhtml>"));
189+
190+
it("Foreign CDATA in SVG", () => runTest("<svg><![CDATA[a<b]]></svg>"));
191+
192+
it("Foreign CDATA in MathML", () =>
193+
runTest("<math><![CDATA[a<b]]></math>"));
194+
195+
it("SVG title is not HTML special", () =>
196+
runTest("<svg><title>&amp;<b>x</b></title></svg>"));
197+
198+
it("SVG tag case adjustment", () =>
199+
runTest("<svg><foreignObject><b>x</b></foreignObject></svg>"));
200+
201+
it("SVG integration point closing with unclosed child", () =>
202+
runTest("<svg><foreignObject><div>x</foreignObject></svg>after"));
203+
204+
it("Content after SVG integration point", () =>
205+
runTest("<svg><foreignObject><b>x</b></foreignObject><rect/></svg>"));
206+
207+
it("Stray </svg> does not break foreign context", () =>
208+
runTest("</svg><script><b>not a tag</b></script>"));
209+
210+
it("Implicit close of nested foreign elements", () =>
211+
runTest("<svg><math><mi>text</svg><script><b>x</b></script>"));
212+
213+
it("Self-closing foreign element with recognizeSelfClosing", () =>
214+
runTest("<svg/><script><b>x</b></script>", {
215+
recognizeSelfClosing: true,
216+
}));
217+
218+
it("HTML image alias", () => runTest("<image></image>"));
219+
220+
it("SVG image is not aliased", () => runTest("<svg><image></image></svg>"));
221+
222+
it("EOF bogus comments", () => runTest("<!-"));
223+
224+
it("EOF empty bogus comment", () => runTest("<!"));
225+
226+
it("EOF partial doctype", () => runTest("<!DOC"));
227+
228+
it("EOF complete doctype", () => runTest("<!DOCTYPE"));
229+
230+
it("EOF incomplete doctype", () => runTest("<!DOCTYPE h"));
231+
232+
it("EOF processing instruction", () => runTest("<?"));
233+
234+
it("Partial CDATA match with >", () => runTest("<![CD>"));
235+
236+
it("Unclosed CDATA at EOF", () => runTest("<![CDATA[foo"));
237+
238+
it("Empty unclosed CDATA at EOF", () => runTest("<![CDATA["));
239+
240+
it("Partial CDATA at EOF", () => runTest("<![C"));
241+
165242
it("Scripts ending with <", () => runTest("<script><</script>"));
166243

167244
it("Special end tags ending with /> in script", () =>
@@ -191,9 +268,13 @@ describe("Events", () => {
191268

192269
it("entity in title (#592)", () => runTest("<title>the &quot;title&quot"));
193270

271+
it("entity in textarea", () => runTest("<textarea>&amp;</textarea>"));
272+
194273
it("entity in title - decodeEntities=false (#592)", () =>
195274
runTest("<title>the &quot;title&quot;", { decodeEntities: false }));
196275

276+
it("plaintext", () => runTest("<plaintext><b>hi</b>"));
277+
197278
it("</title> in <script> (#745)", () =>
198279
runTest("<script>'</title>'</script>"));
199280

src/Parser.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,38 @@ describe("API", () => {
110110
expect(onclosetag).toHaveBeenNthCalledWith(2, "hr", 9);
111111
});
112112

113+
it("should preserve declaration names outside of HTML mode", () => {
114+
const onprocessinginstruction = vi.fn();
115+
116+
new Parser(
117+
{
118+
onprocessinginstruction,
119+
},
120+
{ xmlMode: true },
121+
).end("<!DOCTYPEhtml>");
122+
123+
expect(onprocessinginstruction).toHaveBeenCalledWith(
124+
"!DOCTYPEhtml",
125+
"!DOCTYPEhtml",
126+
);
127+
});
128+
129+
it("should preserve declaration casing when lowerCaseTags is disabled", () => {
130+
const onprocessinginstruction = vi.fn();
131+
132+
new Parser(
133+
{
134+
onprocessinginstruction,
135+
},
136+
{ lowerCaseTags: false },
137+
).end("<!DOCTYPEhtml>");
138+
139+
expect(onprocessinginstruction).toHaveBeenCalledWith(
140+
"!DOCTYPE",
141+
"!DOCTYPEhtml",
142+
);
143+
});
144+
113145
it("should update the position when a single tag is spread across multiple chunks", () => {
114146
let called = false;
115147
const p = new Parser({

0 commit comments

Comments
 (0)