context(":has() pseudo-class") test_that(":has() generates correct XPath", { xpath <- function(css) { css_to_xpath(css, prefix = "") } # Simple :has() with element expect_that(xpath("div:has(p)"), equals("div[(.//*[(name() = 'p')])]")) # :has() with class selector expect_that(xpath("div:has(.foo)"), equals("div[(.//*[(@class and contains(concat(' ', normalize-space(@class), ' '), ' foo '))])]")) # :has() with ID selector expect_that(xpath("section:has(#main)"), equals("section[(.//*[(@id = 'main')])]")) # :has() with attribute selector expect_that(xpath("form:has([required])"), equals("form[(.//*[(@required)])]")) # :has() with multiple selectors (OR logic) expect_that(xpath("div:has(p, span)"), equals("div[(.//*[(name() = 'p')] | .//*[(name() = 'span')])]")) # Multiple :has() selectors expect_that(xpath("div:has(p):has(span)"), equals("div[(.//*[(name() = 'p')]) and (.//*[(name() = 'span')])]")) # :has() on universal selector expect_that(xpath("*:has(img)"), equals("*[(.//*[(name() = 'img')])]")) # Complex: :has() with class on descendant expect_that(xpath("section:has(div.content)"), equals("section[(.//*[(@class and contains(concat(' ', normalize-space(@class), ' '), ' content ')) and (name() = 'div')])]")) }) test_that(":has() works correctly with XML documents", { library(XML) # Create test document html <- paste0( '', '
', '
', '

Paragraph in section 1

', '
', '
', '
', ' ', '
', '
', '
', '

Title

', '
', '
', '
', '

Article paragraph

', '
', '
' ) doc <- xmlRoot(xmlParse(html)) # Helper to get IDs get_ids <- function(css) { results <- querySelectorAll(doc, css) sapply(results, function(x) xmlGetAttr(x, "id")) } # Section containing a p element expect_that(get_ids("section:has(p)"), equals("s1")) # Section containing a div expect_that(get_ids("section:has(div)"), equals(c("s1", "s2"))) # Section containing an h1 expect_that(get_ids("section:has(h1)"), equals("s3")) # Section with div.content expect_that(get_ids("section:has(div.content)"), equals("s1")) # Section with div.sidebar expect_that(get_ids("section:has(div.sidebar)"), equals("s2")) # Any element containing a p # Note: XML returns root element too since it's also ancestor ids <- get_ids(":has(p)") expect_that("s1" %in% ids && "a1" %in% ids, equals(TRUE)) # Multiple selectors: section with p OR span expect_that(get_ids("section:has(p, span)"), equals(c("s1", "s2"))) # Chained :has() - section with both div and p expect_that(get_ids("section:has(div):has(p)"), equals("s1")) # :has() should not match the element itself expect_that(length(querySelectorAll(doc, "p:has(p)")), equals(0)) }) test_that(":has() works correctly with xml2 documents", { library(xml2) # Create test document html <- paste0( '', '
', '
', '

Paragraph in section 1

', '
', '
', '
', ' ', '
', '
', '
', '

Title

', '
', '
', '
', '

Article paragraph

', '
', '
' ) doc <- read_xml(html) # Helper to get IDs get_ids <- function(css) { results <- querySelectorAll(doc, css) xml_attr(results, "id") } # Section containing a p element expect_that(get_ids("section:has(p)"), equals("s1")) # Section containing a div expect_that(get_ids("section:has(div)"), equals(c("s1", "s2"))) # Section containing an h1 expect_that(get_ids("section:has(h1)"), equals("s3")) # Section with div.content expect_that(get_ids("section:has(div.content)"), equals("s1")) # Section with div.sidebar expect_that(get_ids("section:has(div.sidebar)"), equals("s2")) # Any element containing a p # Note: returns all ancestors including root ids <- get_ids(":has(p)") expect_that("s1" %in% ids && "a1" %in% ids, equals(TRUE)) # Multiple selectors: section with p OR span expect_that(get_ids("section:has(p, span)"), equals(c("s1", "s2"))) # Chained :has() - section with both div and p expect_that(get_ids("section:has(div):has(p)"), equals("s1")) # :has() should not match the element itself expect_that(length(querySelectorAll(doc, "p:has(p)")), equals(0)) }) test_that(":has() handles edge cases correctly", { library(XML) # Empty elements html1 <- '

' doc1 <- xmlRoot(xmlParse(html1)) # Only d2 has a p descendant result1 <- querySelectorAll(doc1, "div:has(p)") expect_that(length(result1), equals(1)) expect_that(xmlGetAttr(result1[[1]], "id"), equals("d2")) # Nested :has() html2 <- paste0( '', '
', '
', '
', '

Text

', '
', '
', '
', '
', '
', '

Text

', '
', '
', '
' ) doc2 <- xmlRoot(xmlParse(html2)) # Section containing article with div result2 <- querySelectorAll(doc2, "section:has(article:has(div))") expect_that(length(result2), equals(1)) expect_that(xmlGetAttr(result2[[1]], "id"), equals("s1")) # Section containing p.highlight result3 <- querySelectorAll(doc2, "section:has(p.highlight)") expect_that(length(result3), equals(1)) expect_that(xmlGetAttr(result3[[1]], "id"), equals("s1")) # :has() with universal selector html3 <- '
' doc3 <- xmlRoot(xmlParse(html3)) # Div that has any descendant result4 <- querySelectorAll(doc3, "div:has(*)") expect_that(length(result4), equals(1)) expect_that(xmlGetAttr(result4[[1]], "id"), equals("d1")) }) test_that(":has() works with querySelector (returns first match)", { library(xml2) html <- paste0( '', '

First

', '

Second

', '
Third
', '
' ) doc <- read_xml(html) # Should return first section with p result <- querySelector(doc, "section:has(p)") expect_that(xml_attr(result, "id"), equals("s1")) # Should return NULL when no match result_none <- querySelector(doc, "section:has(article)") expect_that(result_none, equals(NULL)) })