context("parser")

test_that("parser parses canonical test expressions", {
    parse_many <- function(css) {
        selectors <- lapply(css, function(x) parse(x))
        n <- length(selectors)
        results <- list()
        for (i in seq_len(n)) {
            selector <- selectors[[i]]
            if (is.list(selector)) {
                results[[i]] <- unlist(lapply(selector, function(x) x$repr()))
            } else {
                results[[i]] <- selector$repr()
            }
        }
        if (n)
            unlist(results)
        else
            character(0)
    }

    expect_that(parse_many("*"), equals("Element[*]"))
    expect_that(parse_many("*|*"), equals("Element[*]"))
    expect_that(parse_many("*|foo"), equals("Element[foo]"))
    expect_that(parse_many("|foo"), equals("Element[foo]"))
    expect_that(parse_many("foo|*"), equals("Element[foo|*]"))
    expect_that(parse_many("foo|bar"), equals("Element[foo|bar]"))
    expect_that(parse_many('foo[lang|="zh"]'), equals("Attrib[Element[foo][lang |= 'zh']]"))
    # This will never match, but it is valid:
    expect_that(parse_many("#foo#bar"),
                equals("Hash[Hash[Element[*]#foo]#bar]"))
    expect_that(parse_many(c("div>.foo",
                             "div> .foo",
                             "div >.foo",
                             "div > .foo",
                             "div > .foo",
                             "div \n>  \t \t .foo",
                             "div\r>\n\n\n.foo",
                             "div\f>\f.foo")),
                equals(rep("CombinedSelector[Element[div] > Class[Element[*].foo]]", 8)))
    expect_that(parse_many(c("td.foo,.bar",
                             "td.foo, .bar",
                             "td.foo\t\r\n\f ,\t\r\n\f .bar")),
                equals(rep(c("Class[Element[td].foo]",
                             "Class[Element[*].bar]"), 3)))
    expect_that(parse_many(c("div, td.foo, div.bar span")),
                equals(c("Element[div]",
                         "Class[Element[td].foo]",
                         "CombinedSelector[Class[Element[div].bar] <followed> Element[span]]")))
    expect_that(parse_many("div > p"),
                equals("CombinedSelector[Element[div] > Element[p]]"))
    expect_that(parse_many("td:first"),
                equals("Pseudo[Element[td]:first]"))
    expect_that(parse_many("td :first"),
                equals("CombinedSelector[Element[td] <followed> Pseudo[Element[*]:first]]"))
    expect_that(parse_many(c("a[name]", "a[ name\t]")),
                equals(rep("Attrib[Element[a][name]]", 2)))
    expect_that(parse_many("a [name]"),
                equals("CombinedSelector[Element[a] <followed> Attrib[Element[*][name]]]"))
    expect_that(parse_many(c('a[rel="include"]', 'a[rel = include]')),
                equals(rep("Attrib[Element[a][rel = 'include']]", 2)))
    expect_that(parse_many(c("a[hreflang |= 'en']", "a[hreflang|=en]")),
                equals(rep("Attrib[Element[a][hreflang |= 'en']]", 2)))
    expect_that(parse_many("div:nth-child(10)"),
                equals("Function[Element[div]:nth-child(['10'])]"))
    expect_that(parse_many(":nth-child(2n+2)"),
                equals("Function[Element[*]:nth-child(['2', 'n', '+2'])]"))
    expect_that(parse_many("div:nth-of-type(10)"),
                equals("Function[Element[div]:nth-of-type(['10'])]"))
    expect_that(parse_many("div div:nth-of-type(10) .aclass"),
                equals("CombinedSelector[CombinedSelector[Element[div] <followed> Function[Element[div]:nth-of-type(['10'])]] <followed> Class[Element[*].aclass]]"))
    expect_that(parse_many("label:only"),
                equals("Pseudo[Element[label]:only]"))
    expect_that(parse_many("a:lang(fr)"),
                equals("Function[Element[a]:lang(['fr'])]"))
    expect_that(parse_many('div:contains("foo")'),
                equals("Function[Element[div]:contains(['foo'])]"))
    expect_that(parse_many("div#foobar"),
                equals("Hash[Element[div]#foobar]"))
    expect_that(parse_many("div:not(div.foo)"),
                equals("Negation[Element[div]:not(Class[Element[div].foo])]"))

    # :not() with multiple arguments
    expect_that(parse_many("div:not(.foo, .bar)"),
                equals("Negation[Element[div]:not(Class[Element[*].foo], Class[Element[*].bar])]"))
    expect_that(parse_many("p:not(.foo, #bar)"),
                equals("Negation[Element[p]:not(Class[Element[*].foo], Hash[Element[*]#bar])]"))
    expect_that(parse_many(":not(p, span, div)"),
                equals("Negation[Element[*]:not(Element[p], Element[span], Element[div])]"))
    expect_that(parse_many("div:not([disabled], .hidden)"),
                equals("Negation[Element[div]:not(Attrib[Element[*][disabled]], Class[Element[*].hidden])]"))
    expect_that(parse_many(":not(:hover, :visited, :active)"),
                equals("Negation[Element[*]:not(Pseudo[Element[*]:hover], Pseudo[Element[*]:visited], Pseudo[Element[*]:active])]"))
    expect_that(parse_many("a:not(.link, [href], #special)"),
                equals("Negation[Element[a]:not(Class[Element[*].link], Attrib[Element[*][href]], Hash[Element[*]#special])]"))

    expect_that(parse_many("div:is(.foo, #bar)"),
                equals("Matching[Element[div]:is(Class[Element[*].foo], Hash[Element[*]#bar])]"))
    expect_that(parse_many(":is(:hover, :visited)"),
                equals("Matching[Element[*]:is(Pseudo[Element[*]:hover], Pseudo[Element[*]:visited])]"))
    expect_that(parse_many("div:matches(.foo, #bar)"),
                equals("Matching[Element[div]:is(Class[Element[*].foo], Hash[Element[*]#bar])]"))
    expect_that(parse_many(":matches(:hover, :visited)"),
                equals("Matching[Element[*]:is(Pseudo[Element[*]:hover], Pseudo[Element[*]:visited])]"))

    expect_that(parse_many("div:where(.foo, #bar)"),
                equals("Where[Element[div]:where(Class[Element[*].foo], Hash[Element[*]#bar])]"))
    expect_that(parse_many(":where(:hover, :visited)"),
                equals("Where[Element[*]:where(Pseudo[Element[*]:hover], Pseudo[Element[*]:visited])]"))

    expect_that(parse_many("div:has(.foo)"),
                equals("Has[Element[div]:has(Class[Element[*].foo])]"))
    expect_that(parse_many("ul:has(li)"),
                equals("Has[Element[ul]:has(Element[li])]"))
    expect_that(parse_many(":has(p, div)"),
                equals("Has[Element[*]:has(Element[p], Element[div])]"))
 
    expect_that(parse_many("td ~ th"),
                equals("CombinedSelector[Element[td] ~ Element[th]]"))

    # handle comments
    expect_that(parse_many("a /* test */"),
                equals("Element[a]"))
    expect_that(parse_many("a/* test */"),
                equals("Element[a]"))
    expect_that(parse_many("/* test */ a"),
                equals("Element[a]"))
    expect_that(parse_many("/* test */a"),
                equals("Element[a]"))
    expect_that(parse_many("a /* test */ b"),
                equals("CombinedSelector[Element[a] <followed> Element[b]]"))
    expect_that(parse_many("a /* test "),
                equals("Element[a]"))
})

test_that("parsed elements print correctly", {
    shw <- function(x) trimws(capture.output(parse(x)[[1]]$show()))

    expect_that(shw("a"), equals("Element[a]"))
    expect_that(shw(".test"), equals("Class[Element[*].test]"))
    expect_that(shw(":active"), equals("Pseudo[Element[*]:active]"))
    expect_that(shw("a:not(.toggle)"), equals("Negation[Element[a]:not(Class[Element[*].toggle])]"))

    # :not() with multiple arguments print tests
    expect_that(shw("div:not(.foo, .bar)"),
                equals("Negation[Element[div]:not(Class[Element[*].foo], Class[Element[*].bar])]"))
    expect_that(shw("p:not(span, div, a)"),
                equals("Negation[Element[p]:not(Element[span], Element[div], Element[a])]"))

    expect_that(shw("[href]"), equals("Attrib[Element[*][href]]"))
    expect_that(shw("#id"), equals("Hash[Element[*]#id]"))
})

test_that("compiled regex parsing functions behave as expected", {
    m_whitespace <- compile_('[ \t\r\n\f]+')
    m_number <- compile_('[+-]?(?:[0-9]*\\.[0-9]+|[0-9]+)')
    m_hash <- compile_(paste0("^#([_a-zA-Z0-9-]|", nonascii, "|\\\\(?:", delim_escapes, "))+"))
    m_ident <- compile_(paste0("^([_a-zA-Z0-9-]|", nonascii, "|\\\\(?:", delim_escapes, "))+"))

    expect_that(m_whitespace("a b"), equals(match_whitespace("a b")))
    expect_that(m_number("a 1"), equals(match_number("a 1")))
    expect_that(m_hash("a #test"), equals(match_hash("a #test")))
    expect_that(m_ident(" test"), equals(match_ident(" test")))
})
