@testable import PureXML import Testing @Suite("HTML tokenizer: references, NUL, RCDATA") struct HTMLTokenizerTests { /// The concatenated text content of the first parsed element. private func text(_ html: String) -> String { guard case let .document(children) = PureXML.HTML.parse(html), case let .element(element) = children.first else { return "" } return element.children.compactMap { if case let .text(value) = $0 { value } else { nil } }.joined() } @Test("A numeric reference to zero, a surrogate, or an out-of-range value yields U+FFFD") func test_numericInvalid() { #expect(text("

�

") == "\u{FFFD}") #expect(text("

�

") == "\u{FFFD}") #expect(text("

�

") == "\u{FFFD}") } @Test("A numeric reference in the C1 range is mapped to its Windows-1252 character") func test_numericC1() { #expect(text("

€

") == "\u{20AC}") // euro #expect(text("

™

") == "\u{2122}") // trade mark } @Test("A valid numeric reference resolves to its code point") func test_numericValid() { #expect(text("

AB

") == "AB") } @Test("A literal NUL byte becomes U+FFFD") func test_nullReplacement() { #expect(text("

a\u{0}b

") == "a\u{FFFD}b") } @Test("A named reference without a trailing semicolon is still decoded") func test_semicolonless() { #expect(text("

&

") == "&") #expect(text("

©

") == "\u{A9}") } @Test("RCDATA content (title, textarea) decodes references; raw text (style) does not") func test_rcdataDecoding() { #expect(text("a & b") == "a & b") #expect(text("") == "") #expect(text("") == "a & b") } @Test("A single leading newline after ") == "hello") #expect(text("") == "\nhello") } @Test("The HTML4 named entity set decodes across its categories") func test_namedEntitySet() { #expect(text("

½Á×

") == "\u{BD}\u{C1}\u{D7}") // Latin-1 #expect(text("

Œ†—

") == "\u{152}\u{2020}\u{2014}") // special #expect(text("

αΩπ

") == "\u{3B1}\u{3A9}\u{3C0}") // Greek #expect(text("

∀∑∞≠

") == "\u{2200}\u{2211}\u{221E}\u{2260}") // math #expect(text("

←→⇔

") == "\u{2190}\u{2192}\u{21D4}") // arrows #expect(text("

♥♦♠

") == "\u{2665}\u{2666}\u{2660}") // suits #expect(text("

© ®

") == "\u{A9} \u{AE}") // semicolon-less } @Test("The full WHATWG reference set decodes astral and multi-codepoint names") func test_fullEntitySet() { #expect(PureXML.HTML.Tokenizer.namedEntities.count == 2125) #expect(text("

𝕗

") == "\u{1D557}") // astral double-struck f #expect(text("

≂̸

") == "\u{2242}\u{0338}") // two codepoints #expect(text("

∳

") == "\u{2233}") // 31-char name } }