import Testing @testable import LegadoCore struct HTMLRuleEvaluatorTests { private let evaluator = HTMLRuleEvaluator() private let html = """ """ @Test func legacyListChainsPreserveTableRowsAndCurrentElementOutputs() throws { let page = "
书名
测试书作者
" let rows = try evaluator.selectCSSFragments("class.grid@tag.tr!0", html: page) #expect(rows.count == 1) let row = try #require(rows.first) #expect(try evaluator.extractCSS("tag.td.0@text", html: row, isFragment: true) == ["测试书"]) #expect(try evaluator.extractCSS("data-id", html: row, isFragment: true) == ["42"]) let links = try evaluator.selectCSSFragments("tag.a", html: row, isFragment: true) let link = try #require(links.first) #expect(try evaluator.extractCSS("text", html: link, isFragment: true) == ["测试书"]) #expect(try evaluator.extractCSS("href", html: link, isFragment: true) == ["/42"]) #expect(try evaluator.extractXPath("./@href", html: link, isFragment: true) == ["/42"]) #expect(try evaluator.extractXPath("./text()", html: link, isFragment: true) == ["测试书"]) } @Test func selectsCSSFragmentsAndExtractsOutputs() throws { let fragments = try evaluator.selectCSSFragments("li.book", html: html) #expect(fragments.count == 2) #expect(try evaluator.extractCSS(".title@text", html: fragments[0]) == ["A & One"]) #expect(try evaluator.extractCSS("a@href", html: fragments[0]) == ["/a"]) #expect(try evaluator.extractCSS("p@html", html: fragments[0]) == ["

Intro A

"]) #expect(try evaluator.extractCSS("a@all", html: fragments[0]).first?.contains("href=\"/a\"") == true) } @Test func selectsXPathFragmentsAndScalarValues() throws { let fragments = try evaluator.selectXPathFragments("//li[@class='book']", html: html) #expect(fragments.count == 2) #expect(try evaluator.extractXPath("//span[@class='title']/text()", html: fragments[1]) == ["B"]) #expect(try evaluator.extractXPath("count(//li[@class='book'])", html: html) == ["2"]) } @Test func ruleEngineAppliesHTMLCombination() async { let engine = RuleEngine() let result = await engine.evaluate("@CSS:.missing@text||@CSS:.title@text", inText: html) #expect(result.strings == ["A & One", "B"]) #expect(result.diagnostics.isEmpty) } @Test func supportsLegadoChainAndIndexedSelectors() throws { #expect(try evaluator.extractCSS("id.books@tag.li[1]@class.title@text", html: html) == ["B"]) #expect(try evaluator.extractCSS("li.book[-1:0]@class.title@text", html: html) == ["B", "A & One"]) #expect(try evaluator.extractCSS("li.book[!0]@class.title@text", html: html) == ["B"]) } @Test func supportsLegacyOutputOnlyRulesOnCurrentDocument() throws { #expect(try evaluator.extractCSS("@text", html: "
正文
") == ["正文"]) #expect(try evaluator.extractCSS("@html", html: "
正文
").first?.contains("
") == true) #expect(try evaluator.extractCSS("meta@content", html: "正文", isFragment: true) == ["书名"]) } @Test func reportsSwiftSoupSelectorMessageInsteadOfOpaqueNSError() throws { do { _ = try evaluator.extractCSS("div[", html: html) Issue.record("应拒绝未闭合的 CSS selector") } catch let error as RuleEngineError { #expect(!error.localizedDescription.contains("SwiftSoup.Exception error 0")) #expect(error.localizedDescription.contains("CSS 规则执行失败")) } } @Test func supportsLegacyDiscreteNegativeIndexes() throws { #expect(try evaluator.extractCSS("tag.li.-1:0@class.title@text", html: html) == ["B", "A & One"]) #expect(try evaluator.selectCSSFragments("li.book[-1]", html: html).count == 1) } @Test func htmlAndAllCombineOuterHTMLAndDeduplicateAttributes() throws { let value = """
A
B
onetwo """ let sanitized = try evaluator.extractCSS(".item@html", html: value) let all = try evaluator.extractCSS(".item@all", html: value) let links = try evaluator.extractCSS("a.same@href", html: value) #expect(sanitized.count == 1) #expect(sanitized[0].contains("
") == true) #expect(sanitized[0].contains("script") == false) #expect(sanitized[0].contains("style") == false) #expect(all.count == 1) #expect(links == ["/same"]) } @Test func negativeRangeStepUsesListLengthAdjustment() throws { let value = "

0

1

2

3

4

" #expect(try evaluator.extractCSS("p[0:4:-2]@text", html: value) == ["0", "3"]) } @Test func rangeWhollyOutsideOneSideIsIgnoredInsteadOfClamped() throws { let value = "

0

1

2

" #expect(try evaluator.extractCSS("p[8:9]@text", html: value).isEmpty) #expect(try evaluator.extractCSS("p[-9:-8]@text", html: value).isEmpty) #expect(try evaluator.extractCSS("p[-9:1]@text", html: value) == ["0", "1"]) } }