Repository URL to install this package:
Version:
1.6.8.1 ▾
|
require "helper"
module Nokogiri
module CSS
class TestParser < Nokogiri::TestCase
def setup
super
@parser = Nokogiri::CSS::Parser.new
@parser_with_ns = Nokogiri::CSS::Parser.new({
"xmlns" => "http://default.example.com/",
"hoge" => "http://hoge.example.com/",
})
end
def test_extra_single_quote
assert_raises(CSS::SyntaxError) { @parser.parse("'") }
end
def test_syntax_error_raised
assert_raises(CSS::SyntaxError) { @parser.parse("a[x=]") }
end
def test_function_and_pseudo
assert_xpath '//child::text()[position() = 99]', @parser.parse('text():nth-of-type(99)')
end
def test_find_by_type
ast = @parser.parse("a:nth-child(2)").first
matches = ast.find_by_type(
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME],
[:PSEUDO_CLASS,
[:FUNCTION]
]
]
)
assert_equal(1, matches.length)
assert_equal(ast, matches.first)
end
def test_to_type
ast = @parser.parse("a:nth-child(2)").first
assert_equal(
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME],
[:PSEUDO_CLASS,
[:FUNCTION]
]
], ast.to_type
)
end
def test_to_a
asts = @parser.parse("a:nth-child(2)")
assert_equal(
[:CONDITIONAL_SELECTOR,
[:ELEMENT_NAME, ["a"]],
[:PSEUDO_CLASS,
[:FUNCTION, ["nth-child("], ["2"]]
]
], asts.first.to_a
)
end
def test_has
assert_xpath "//a[b]", @parser.parse("a:has(b)")
assert_xpath "//a[b/c]", @parser.parse("a:has(b > c)")
end
def test_dashmatch
assert_xpath "//a[@class = 'bar' or starts-with(@class, concat('bar', '-'))]",
@parser.parse("a[@class|='bar']")
assert_xpath "//a[@class = 'bar' or starts-with(@class, concat('bar', '-'))]",
@parser.parse("a[@class |= 'bar']")
end
def test_includes
assert_xpath "//a[contains(concat(\" \", @class, \" \"),concat(\" \", 'bar', \" \"))]",
@parser.parse("a[@class~='bar']")
assert_xpath "//a[contains(concat(\" \", @class, \" \"),concat(\" \", 'bar', \" \"))]",
@parser.parse("a[@class ~= 'bar']")
end
def test_function_with_arguments
assert_xpath "//a[count(preceding-sibling::*) = 1]",
@parser.parse("a[2]")
assert_xpath "//a[count(preceding-sibling::*) = 1]",
@parser.parse("a:nth-child(2)")
end
def test_carrot
assert_xpath "//a[starts-with(@id, 'Boing')]",
@parser.parse("a[id^='Boing']")
assert_xpath "//a[starts-with(@id, 'Boing')]",
@parser.parse("a[id ^= 'Boing']")
end
def test_suffix_match
assert_xpath "//a[substring(@id, string-length(@id) - string-length('Boing') + 1, string-length('Boing')) = 'Boing']",
@parser.parse("a[id$='Boing']")
assert_xpath "//a[substring(@id, string-length(@id) - string-length('Boing') + 1, string-length('Boing')) = 'Boing']",
@parser.parse("a[id $= 'Boing']")
end
def test_attributes_with_at
## This is non standard CSS
assert_xpath "//a[@id = 'Boing']",
@parser.parse("a[@id='Boing']")
assert_xpath "//a[@id = 'Boing']",
@parser.parse("a[@id = 'Boing']")
end
def test_attributes_with_at_and_stuff
## This is non standard CSS
assert_xpath "//a[@id = 'Boing']//div",
@parser.parse("a[@id='Boing'] div")
end
def test_not_equal
## This is non standard CSS
assert_xpath "//a[child::text() != 'Boing']",
@parser.parse("a[text()!='Boing']")
assert_xpath "//a[child::text() != 'Boing']",
@parser.parse("a[text() != 'Boing']")
end
def test_function
## This is non standard CSS
assert_xpath "//a[child::text()]",
@parser.parse("a[text()]")
## This is non standard CSS
assert_xpath "//child::text()",
@parser.parse("text()")
## This is non standard CSS
assert_xpath "//a[contains(child::text(), 'Boing')]",
@parser.parse("a[text()*='Boing']")
assert_xpath "//a[contains(child::text(), 'Boing')]",
@parser.parse("a[text() *= 'Boing']")
## This is non standard CSS
assert_xpath "//script//comment()",
@parser.parse("script comment()")
end
def test_nonstandard_nth_selectors
## These are non standard CSS
assert_xpath '//a[position() = 1]', @parser.parse('a:first()')
assert_xpath '//a[position() = 1]', @parser.parse('a:first') # no parens
assert_xpath '//a[position() = 99]', @parser.parse('a:eq(99)')
assert_xpath '//a[position() = 99]', @parser.parse('a:nth(99)')
assert_xpath '//a[position() = last()]', @parser.parse('a:last()')
assert_xpath '//a[position() = last()]', @parser.parse('a:last') # no parens
assert_xpath '//a[node()]', @parser.parse('a:parent')
end
def test_standard_nth_selectors
assert_xpath '//a[position() = 1]', @parser.parse('a:first-of-type()')
assert_xpath '//a[position() = 1]', @parser.parse('a:first-of-type') # no parens
assert_xpath "//a[contains(concat(' ', normalize-space(@class), ' '), ' b ')][position() = 1]",
@parser.parse('a.b:first-of-type') # no parens
assert_xpath '//a[position() = 99]', @parser.parse('a:nth-of-type(99)')
assert_xpath "//a[contains(concat(' ', normalize-space(@class), ' '), ' b ')][position() = 99]",
@parser.parse('a.b:nth-of-type(99)')
assert_xpath '//a[position() = last()]', @parser.parse('a:last-of-type()')
assert_xpath '//a[position() = last()]', @parser.parse('a:last-of-type') # no parens
assert_xpath "//a[contains(concat(' ', normalize-space(@class), ' '), ' b ')][position() = last()]",
@parser.parse('a.b:last-of-type') # no parens
assert_xpath '//a[position() = last()]', @parser.parse('a:nth-last-of-type(1)')
assert_xpath '//a[position() = last() - 98]', @parser.parse('a:nth-last-of-type(99)')
assert_xpath "//a[contains(concat(' ', normalize-space(@class), ' '), ' b ')][position() = last() - 98]",
@parser.parse('a.b:nth-last-of-type(99)')
end
def test_nth_child_selectors
assert_xpath '//a[count(preceding-sibling::*) = 0]', @parser.parse('a:first-child')
assert_xpath '//a[count(preceding-sibling::*) = 98]', @parser.parse('a:nth-child(99)')
assert_xpath '//a[count(following-sibling::*) = 0]', @parser.parse('a:last-child')
assert_xpath '//a[count(following-sibling::*) = 0]', @parser.parse('a:nth-last-child(1)')
assert_xpath '//a[count(following-sibling::*) = 98]', @parser.parse('a:nth-last-child(99)')
end
def test_miscellaneous_selectors
assert_xpath '//a[count(preceding-sibling::*) = 0 and count(following-sibling::*) = 0]', @parser.parse('a:only-child')
assert_xpath '//a[last() = 1]', @parser.parse('a:only-of-type')
assert_xpath '//a[not(node())]', @parser.parse('a:empty')
end
def test_nth_a_n_plus_b
assert_xpath '//a[(position() mod 2) = 0]', @parser.parse('a:nth-of-type(2n)')
assert_xpath '//a[(position() >= 1) and (((position()-1) mod 2) = 0)]', @parser.parse('a:nth-of-type(2n+1)')
assert_xpath '//a[(position() mod 2) = 0]', @parser.parse('a:nth-of-type(even)')
assert_xpath '//a[(position() >= 1) and (((position()-1) mod 2) = 0)]', @parser.parse('a:nth-of-type(odd)')
assert_xpath '//a[(position() >= 3) and (((position()-3) mod 4) = 0)]', @parser.parse('a:nth-of-type(4n+3)')
assert_xpath '//a[position() <= 3]', @parser.parse('a:nth-of-type(-1n+3)')
assert_xpath '//a[position() <= 3]', @parser.parse('a:nth-of-type(-n+3)')
assert_xpath '//a[position() >= 3]', @parser.parse('a:nth-of-type(1n+3)')
assert_xpath '//a[position() >= 3]', @parser.parse('a:nth-of-type(n+3)')
assert_xpath '//a[((last()-position()+1) mod 2) = 0]', @parser.parse('a:nth-last-of-type(2n)')
assert_xpath '//a[((last()-position()+1) >= 1) and ((((last()-position()+1)-1) mod 2) = 0)]', @parser.parse('a:nth-last-of-type(2n+1)')
assert_xpath '//a[((last()-position()+1) mod 2) = 0]', @parser.parse('a:nth-last-of-type(even)')
assert_xpath '//a[((last()-position()+1) >= 1) and ((((last()-position()+1)-1) mod 2) = 0)]', @parser.parse('a:nth-last-of-type(odd)')
assert_xpath '//a[((last()-position()+1) >= 3) and ((((last()-position()+1)-3) mod 4) = 0)]', @parser.parse('a:nth-last-of-type(4n+3)')
assert_xpath '//a[(last()-position()+1) <= 3]', @parser.parse('a:nth-last-of-type(-1n+3)')
assert_xpath '//a[(last()-position()+1) <= 3]', @parser.parse('a:nth-last-of-type(-n+3)')
assert_xpath '//a[(last()-position()+1) >= 3]', @parser.parse('a:nth-last-of-type(1n+3)')
assert_xpath '//a[(last()-position()+1) >= 3]', @parser.parse('a:nth-last-of-type(n+3)')
end
def test_preceding_selector
assert_xpath "//E/following-sibling::F",
@parser.parse("E ~ F")
assert_xpath "//E/following-sibling::F//G",
@parser.parse("E ~ F G")
end
def test_direct_preceding_selector
assert_xpath "//E/following-sibling::*[1]/self::F",
@parser.parse("E + F")
assert_xpath "//E/following-sibling::*[1]/self::F//G",
@parser.parse("E + F G")
end
def test_child_selector
assert_xpath("//a//b/i", @parser.parse('a b>i'))
assert_xpath("//a//b/i", @parser.parse('a b > i'))
assert_xpath("//a/b/i", @parser.parse('a > b > i'))
end
def test_prefixless_child_selector
assert_xpath("./a", @parser.parse('>a'))
assert_xpath("./a", @parser.parse('> a'))
assert_xpath("./a//b/i", @parser.parse('>a b>i'))
assert_xpath("./a/b/i", @parser.parse('> a > b > i'))
end
def test_prefixless_preceding_sibling_selector
assert_xpath("./following-sibling::a", @parser.parse('~a'))
assert_xpath("./following-sibling::a", @parser.parse('~ a'))
assert_xpath("./following-sibling::a//b/following-sibling::i", @parser.parse('~a b~i'))
assert_xpath("./following-sibling::a//b/following-sibling::i", @parser.parse('~ a b ~ i'))
end
def test_prefixless_direct_adjacent_selector
assert_xpath("./following-sibling::*[1]/self::a", @parser.parse('+a'))
assert_xpath("./following-sibling::*[1]/self::a", @parser.parse('+ a'))
assert_xpath("./following-sibling::*[1]/self::a/following-sibling::*[1]/self::b", @parser.parse('+a+b'))
assert_xpath("./following-sibling::*[1]/self::a/following-sibling::*[1]/self::b", @parser.parse('+ a + b'))
end
def test_attribute
assert_xpath "//h1[@a = 'Tender Lovemaking']",
@parser.parse("h1[a='Tender Lovemaking']")
end
def test_id
assert_xpath "//*[@id = 'foo']", @parser.parse('#foo')
assert_xpath "//*[@id = 'escape:needed,']", @parser.parse('#escape\:needed\,')
assert_xpath "//*[@id = 'escape:needed,']", @parser.parse('#escape\3Aneeded\,')
assert_xpath "//*[@id = 'escape:needed,']", @parser.parse('#escape\3A needed\2C')
assert_xpath "//*[@id = 'escape:needed']", @parser.parse('#escape\00003Aneeded')
end
def test_pseudo_class_no_ident
assert_xpath "//*[link(.)]", @parser.parse(':link')
end
def test_pseudo_class
assert_xpath "//a[link(.)]", @parser.parse('a:link')
assert_xpath "//a[visited(.)]", @parser.parse('a:visited')
assert_xpath "//a[hover(.)]", @parser.parse('a:hover')
assert_xpath "//a[active(.)]", @parser.parse('a:active')
assert_xpath "//a[active(.) and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]",
@parser.parse('a:active.foo')
end
def test_significant_space
assert_xpath "//x//*[count(preceding-sibling::*) = 0]//*[@a]//*[@b]", @parser.parse("x :first-child [a] [b]")
assert_xpath "//*[@a]//*[@b]", @parser.parse(" [a] [b]")
end
def test_star
assert_xpath "//*", @parser.parse('*')
assert_xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' pastoral ')]",
@parser.parse('*.pastoral')
end
def test_class
assert_xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' a ') and contains(concat(' ', normalize-space(@class), ' '), ' b ')]",
@parser.parse('.a.b')
assert_xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' awesome ')]",
@parser.parse('.awesome')
assert_xpath "//foo[contains(concat(' ', normalize-space(@class), ' '), ' awesome ')]",
@parser.parse('foo.awesome')
assert_xpath "//foo//*[contains(concat(' ', normalize-space(@class), ' '), ' awesome ')]",
@parser.parse('foo .awesome')
assert_xpath "//foo//*[contains(concat(' ', normalize-space(@class), ' '), ' awe.some ')]",
@parser.parse('foo .awe\.some')
end
def test_bare_not
assert_xpath "//*[not(contains(concat(' ', normalize-space(@class), ' '), ' a '))]",
@parser.parse(':not(.a)')
end
def test_not_so_simple_not
assert_xpath "//*[@id = 'p' and not(contains(concat(' ', normalize-space(@class), ' '), ' a '))]",
@parser.parse('#p:not(.a)')
assert_xpath "//p[contains(concat(' ', normalize-space(@class), ' '), ' a ') and not(contains(concat(' ', normalize-space(@class), ' '), ' b '))]",
@parser.parse('p.a:not(.b)')
assert_xpath "//p[@a = 'foo' and not(contains(concat(' ', normalize-space(@class), ' '), ' b '))]",
@parser.parse("p[a='foo']:not(.b)")
end
def test_multiple_not
assert_xpath "//p[not(contains(concat(' ', normalize-space(@class), ' '), ' a ')) and not(contains(concat(' ', normalize-space(@class), ' '), ' b ')) and not(contains(concat(' ', normalize-space(@class), ' '), ' c '))]",
@parser.parse("p:not(.a):not(.b):not(.c)")
end
def test_ident
assert_xpath '//x', @parser.parse('x')
end
def test_parse_space
assert_xpath '//x//y', @parser.parse('x y')
end
def test_parse_descendant
assert_xpath '//x/y', @parser.parse('x > y')
end
def test_parse_slash
## This is non standard CSS
assert_xpath '//x/y', @parser.parse('x/y')
end
def test_parse_doubleslash
## This is non standard CSS
assert_xpath '//x//y', @parser.parse('x//y')
end
def test_multi_path
assert_xpath ['//x/y', '//y/z'], @parser.parse('x > y, y > z')
assert_xpath ['//x/y', '//y/z'], @parser.parse('x > y,y > z')
###
# TODO: should we make this work?
# assert_xpath ['//x/y', '//y/z'], @parser.parse('x > y | y > z')
end
def test_attributes_with_namespace
## Default namespace is not applied to attributes.
## So this must be @class, not @xmlns:class.
assert_xpath "//xmlns:a[@class = 'bar']", @parser_with_ns.parse("a[class='bar']")
assert_xpath "//xmlns:a[@hoge:class = 'bar']", @parser_with_ns.parse("a[hoge|class='bar']")
end
def assert_xpath expecteds, asts
expecteds = [expecteds].flatten
expecteds.zip(asts).each do |expected, actual|
assert_equal expected, actual.to_xpath
end
end
end
end
end