# assert_select plugins for Rails # # Copyright (c) 2006 Assaf Arkin, under Creative Commons Attribution and/or MIT License # Developed for http://co.mments.com # Code and documention: http://labnotes.org unless defined?(RAILS_ROOT) RAILS_ROOT = ENV["RAILS_ROOT"] end require File.join(RAILS_ROOT, "test", "test_helper") require File.join(File.dirname(__FILE__), "..", "init") class SelectorTest < Test::Unit::TestCase def setup end def teardown end # # Basic selector: element, id, class, attributes. # def test_element html = parse(%Q{

}) # Match element by name. match = HTML.selector("div").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] # Not case sensitive. match = HTML.selector("DIV").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] # Universal match (all elements). match = HTML.selector("*").select(html) assert_equal 3, match.size assert_equal "1", match[0].attributes["id"] assert_equal nil, match[1].attributes["id"] assert_equal "2", match[2].attributes["id"] end def test_identifier html = parse(%Q{

}) # Match element by ID. match = HTML.selector("div#1").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] # Match element by ID, substitute value. match = HTML.selector("div#?", 2).select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Element name does not match ID. match = HTML.selector("p#?", 2).select(html) assert_equal 0, match.size # Use regular expression. match = HTML.selector("#?", /\d/).select(html) assert_equal 2, match.size end def test_class_name html = parse(%Q{

}) # Match element with specified class. match = HTML.selector("div.foo").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] # Match any element with specified class. match = HTML.selector("*.foo").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] # Match elements with other class. match = HTML.selector("*.bar").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] # Match only element with both class names. match = HTML.selector("*.bar.foo").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] end def test_attribute html = parse(%Q{

}) # Match element with attribute. match = HTML.selector("div[title]").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] # Match any element with attribute. match = HTML.selector("*[title]").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] # Match alement with attribute value. match = HTML.selector("*[title=foo]").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] # Match alement with attribute and attribute value. match = HTML.selector("[bar=foo][title]").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Not case sensitive. match = HTML.selector("[BAR=foo][TiTle]").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] end def test_attribute_quoted html = parse(%Q{
}) # Match without quotes. match = HTML.selector("[title = bar]").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Match with single quotes. match = HTML.selector("[title = 'bar' ]").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Match with double quotes. match = HTML.selector("[title = \"bar\" ]").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Match with spaces. match = HTML.selector("[title = \" bar \" ]").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] end def test_attribute_equality html = parse(%Q{
}) # Match (fail) complete value. match = HTML.selector("[title=bar]").select(html) assert_equal 0, match.size # Match space-separate word. match = HTML.selector("[title~=foo]").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] match = HTML.selector("[title~=bar]").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] # Match beginning of value. match = HTML.selector("[title^=ba]").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Match end of value. match = HTML.selector("[title$=ar]").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] # Match text in value. match = HTML.selector("[title*=bar]").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] # Match first space separated word. match = HTML.selector("[title|=foo]").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] match = HTML.selector("[title|=bar]").select(html) assert_equal 0, match.size end # # Selector composition: groups, sibling, children # def test_selector_group html = parse(%Q{

}) # Simple group selector. match = HTML.selector("h1,h3").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] match = HTML.selector("h1 , h3").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] # Complex group selector. html = parse(%Q{

}) match = HTML.selector("h1 a, h3 a").select(html) assert_equal 2, match.size assert_equal "foo", match[0].attributes["href"] assert_equal "baz", match[1].attributes["href"] # And now for the three selector challange. html = parse(%Q{

}) match = HTML.selector("h1 a, h2 a, h3 a").select(html) assert_equal 3, match.size assert_equal "foo", match[0].attributes["href"] assert_equal "bar", match[1].attributes["href"] assert_equal "baz", match[2].attributes["href"] end def test_sibling_selector html = parse(%Q{

}) # Test next sibling. match = HTML.selector("h1+*").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] match = HTML.selector("h1+h2").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] match = HTML.selector("h1+h3").select(html) assert_equal 0, match.size match = HTML.selector("*+h3").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] # Test any sibling. match = HTML.selector("h1~*").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] match = HTML.selector("h2~*").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] end def test_children_selector html = parse(%Q{

}) # Test child selector. match = HTML.selector("div>p").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] match = HTML.selector("div>span").select(html) assert_equal 0, match.size match = HTML.selector("div>p#3").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] match = HTML.selector("div>p>span").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] # Test descendant selector. match = HTML.selector("div p").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] match = HTML.selector("div span").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] match = HTML.selector("div *#3").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] match = HTML.selector("div *#4").select(html) assert_equal 1, match.size assert_equal "4", match[0].attributes["id"] # This is here because it failed before when whitespaces # were not properly stripped. match = HTML.selector("div .foo").select(html) assert_equal 1, match.size assert_equal "4", match[0].attributes["id"] end # # Pseudo selectors: root, nth-child, empty, content, etc # def test_root_selector html = parse(%Q{
}) # Can only find element if it's root. match = HTML.selector(":root").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] match = HTML.selector("#1:root").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] match = HTML.selector("#2:root").select(html) assert_equal 0, match.size # Opposite for nth-child. match = HTML.selector("#1:nth-child(1)").select(html) assert_equal 0, match.size end def test_nth_child_odd_even html = parse(%Q{
}) # Test odd nth children. match = HTML.selector("tr:nth-child(odd)").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] # Test even nth children. match = HTML.selector("tr:nth-child(even)").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] end def test_nth_child_a_is_zero html = parse(%Q{
}) # Test the third child. match = HTML.selector("tr:nth-child(0n+3)").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] # Same but an can be omitted when zero. match = HTML.selector("tr:nth-child(3)").select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] # Second element (but not every second element). match = HTML.selector("tr:nth-child(0n+2)").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Before first and past last returns nothing.: assert_raises(ArgumentError) { match = HTML.selector("tr:nth-child(-1)").select(html) } match = HTML.selector("tr:nth-child(0)").select(html) assert_equal 0, match.size match = HTML.selector("tr:nth-child(5)").select(html) assert_equal 0, match.size end def test_nth_child_a_is_one html = parse(%Q{
}) # a is group of one, pick every element in group. match = HTML.selector("tr:nth-child(1n+0)").select(html) assert_equal 4, match.size # Same but a can be omitted when one. match = HTML.selector("tr:nth-child(n+0)").select(html) assert_equal 4, match.size # Same but b can be omitted when zero. match = HTML.selector("tr:nth-child(n)").select(html) assert_equal 4, match.size end def test_nth_child_b_is_zero html = parse(%Q{
}) # If b is zero, pick the n-th element (here each one). match = HTML.selector("tr:nth-child(n+0)").select(html) assert_equal 4, match.size # If b is zero, pick the n-th element (here every second). match = HTML.selector("tr:nth-child(2n+0)").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] # If a and b are both zero, no element selected. match = HTML.selector("tr:nth-child(0n+0)").select(html) assert_equal 0, match.size match = HTML.selector("tr:nth-child(0)").select(html) assert_equal 0, match.size end def test_nth_child_a_is_negative html = parse(%Q{
}) # Since a is -1, picks the first three elements. match = HTML.selector("tr:nth-child(-n+3)").select(html) assert_equal 3, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] assert_equal "3", match[2].attributes["id"] # Since a is -2, picks the first in every second of first four elements. match = HTML.selector("tr:nth-child(-2n+3)").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] # Since a is -2, picks the first in every second of first three elements. match = HTML.selector("tr:nth-child(-2n+2)").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] end def test_nth_child_b_is_negative html = parse(%Q{
}) # Select last of four. match = HTML.selector("tr:nth-child(4n-1)").select(html) assert_equal 1, match.size assert_equal "4", match[0].attributes["id"] # Select first of four. match = HTML.selector("tr:nth-child(4n-4)").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] # Select last of every second. match = HTML.selector("tr:nth-child(2n-1)").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] # Select nothing since an+b always < 0 match = HTML.selector("tr:nth-child(-1n-1)").select(html) assert_equal 0, match.size end def test_nth_child_substitution_values html = parse(%Q{
}) # Test with ?n?. match = HTML.selector("tr:nth-child(?n?)", 2, 1).select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "3", match[1].attributes["id"] match = HTML.selector("tr:nth-child(?n?)", 2, 2).select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] match = HTML.selector("tr:nth-child(?n?)", 4, 2).select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] # Test with ? (b only). match = HTML.selector("tr:nth-child(?)", 3).select(html) assert_equal 1, match.size assert_equal "3", match[0].attributes["id"] match = HTML.selector("tr:nth-child(?)", 5).select(html) assert_equal 0, match.size end def test_nth_last_child html = parse(%Q{
}) # Last two elements. match = HTML.selector("tr:nth-last-child(-n+2)").select(html) assert_equal 2, match.size assert_equal "3", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] # All old elements counting from last one. match = HTML.selector("tr:nth-last-child(odd)").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] end def test_nth_of_type html = parse(%Q{
}) # First two elements. match = HTML.selector("tr:nth-of-type(-n+2)").select(html) assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] # All old elements counting from last one. match = HTML.selector("tr:nth-last-of-type(odd)").select(html) assert_equal 2, match.size assert_equal "2", match[0].attributes["id"] assert_equal "4", match[1].attributes["id"] end def test_first_and_last html = parse(%Q{
}) # First child. match = HTML.selector("tr:first-child").select(html) assert_equal 0, match.size match = HTML.selector(":first-child").select(html) assert_equal 1, match.size assert_equal "thead", match[0].name # First of type. match = HTML.selector("tr:first-of-type").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] match = HTML.selector("div:first-of-type").select(html) assert_equal 0, match.size # Last child. match = HTML.selector("tr:last-child").select(html) assert_equal 1, match.size assert_equal "4", match[0].attributes["id"] # Last of type. match = HTML.selector("tr:last-of-type").select(html) assert_equal 1, match.size assert_equal "4", match[0].attributes["id"] match = HTML.selector("thead:last-of-type").select(html) assert_equal 1, match.size assert_equal "thead", match[0].name match = HTML.selector("div:last-of-type").select(html) assert_equal 0, match.size end def test_first_and_last # Only child. html = parse(%Q{
}) match = HTML.selector("table:only-child").select(html) assert_equal 0, match.size match = HTML.selector("tr:only-child").select(html) assert_equal 1, match.size assert_equal "tr", match[0].name html = parse(%Q{
}) match = HTML.selector("tr:only-child").select(html) assert_equal 0, match.size # Only of type. html = parse(%Q{
}) match = HTML.selector("thead:only-of-type").select(html) assert_equal 1, match.size assert_equal "thead", match[0].name match = HTML.selector("td:only-of-type").select(html) assert_equal 0, match.size end def test_empty html = parse(%Q{
}) match = HTML.selector("table:empty").select(html) assert_equal 0, match.size match = HTML.selector("tr:empty").select(html) assert_equal 1, match.size html = parse(%Q{
}) match = HTML.selector("div:empty").select(html) assert_equal 1, match.size end def test_content html = parse(%Q{
}) match = HTML.selector("div:content()").select(html) assert_equal 1, match.size html = parse(%Q{
something
}) match = HTML.selector("div:content()").select(html) assert_equal 0, match.size match = HTML.selector("div:content(something)").select(html) assert_equal 1, match.size match = HTML.selector("div:content( 'something' )").select(html) assert_equal 1, match.size match = HTML.selector("div:content( \"something\" )").select(html) assert_equal 1, match.size match = HTML.selector("div:content(?)", "something").select(html) assert_equal 1, match.size match = HTML.selector("div:content(?)", /something/).select(html) assert_equal 1, match.size end # # Test negation. # def test_element_negation html = parse(%Q{

}) match = HTML.selector("*").select(html) assert_equal 2, match.size match = HTML.selector("*:not(p)").select(html) assert_equal 1, match.size assert_equal "div", match[0].name match = HTML.selector("*:not(div)").select(html) assert_equal 1, match.size assert_equal "p", match[0].name match = HTML.selector("*:not(span)").select(html) assert_equal 2, match.size end def test_id_negation html = parse(%Q{

}) match = HTML.selector("p").select(html) assert_equal 2, match.size match = HTML.selector(":not(#1)").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] match = HTML.selector(":not(#2)").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] end def test_class_name_negation html = parse(%Q{

}) match = HTML.selector("p").select(html) assert_equal 2, match.size match = HTML.selector(":not(.foo)").select(html) assert_equal 1, match.size assert_equal "bar", match[0].attributes["class"] match = HTML.selector(":not(.bar)").select(html) assert_equal 1, match.size assert_equal "foo", match[0].attributes["class"] end def test_attribute_negation html = parse(%Q{

}) match = HTML.selector("p").select(html) assert_equal 2, match.size match = HTML.selector(":not([title=foo])").select(html) assert_equal 1, match.size assert_equal "bar", match[0].attributes["title"] match = HTML.selector(":not([title=bar])").select(html) assert_equal 1, match.size assert_equal "foo", match[0].attributes["title"] end def test_pseudo_class_negation html = parse(%Q{

}) match = HTML.selector("p").select(html) assert_equal 2, match.size match = HTML.selector("p:not(:first-child)").select(html) assert_equal 1, match.size assert_equal "2", match[0].attributes["id"] match = HTML.selector("p:not(:nth-child(2))").select(html) assert_equal 1, match.size assert_equal "1", match[0].attributes["id"] end def test_negation_details html = parse(%Q{

}) assert_raises(ArgumentError) { match = HTML.selector(":not(").select(html) } assert_raises(ArgumentError) { match = HTML.selector(":not(:not())").select(html) } end def test_select_from_element html = parse(%Q{

}) match = HTML.selector("div").select(html)[0] match = match.select("p") assert_equal 2, match.size assert_equal "1", match[0].attributes["id"] assert_equal "2", match[1].attributes["id"] end protected def parse(html) return HTML::Document.new(html).root end end