diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2006-09-03 19:54:21 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2006-09-03 19:54:21 +0000 |
commit | 3142502964f94d6144312ae2c368b4c4589fa25a (patch) | |
tree | b7a9ec374c717bb6e635f545171c4ecec359136f /actionpack/test/controller | |
parent | 6fcc81b7f9a8b27b898794dfe4f0a8202393e8a3 (diff) | |
download | rails-3142502964f94d6144312ae2c368b4c4589fa25a.tar.gz rails-3142502964f94d6144312ae2c368b4c4589fa25a.tar.bz2 rails-3142502964f94d6144312ae2c368b4c4589fa25a.zip |
Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 [assaf.arkin@gmail.com]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4929 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack/test/controller')
-rw-r--r-- | actionpack/test/controller/assert_select_test.rb | 490 | ||||
-rw-r--r-- | actionpack/test/controller/selector_test.rb | 628 |
2 files changed, 1118 insertions, 0 deletions
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb new file mode 100644 index 0000000000..2117c852c0 --- /dev/null +++ b/actionpack/test/controller/assert_select_test.rb @@ -0,0 +1,490 @@ +#-- +# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) +# Under MIT and/or CC By license. +#++ + +require File.dirname(__FILE__) + '/../abstract_unit' +require File.dirname(__FILE__) + '/fake_controllers' +require "action_mailer" + +class AssertSelectTest < Test::Unit::TestCase + class AssertSelectController < ActionController::Base + def response_with=(content) + @content = content + end + + def response_with(&block) + @update = block + end + + def html() + render :text=>@content, :layout=>false, :content_type=>Mime::HTML + @content = nil + end + + def rjs() + render :update do |page| + @update.call page + end + @update = nil + end + + def xml() + render :text=>@content, :layout=>false, :content_type=>Mime::XML + @content = nil + end + + def rescue_action(e) + raise e + end + end + + class AssertSelectMailer < ActionMailer::Base + def test(html) + recipients "test <test@test.host>" + from "test@test.host" + subject "Test e-mail" + part :content_type=>"text/html", :body=>html + end + end + + AssertionFailedError = Test::Unit::AssertionFailedError + + def setup + @controller = AssertSelectController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + end + + + def teardown + ActionMailer::Base.deliveries.clear + end + + + # + # Test assert select. + # + + def test_assert_select + render_html %Q{<div id="1"></div><div id="2"></div>} + assert_select "div", 2 + assert_raises(AssertionFailedError) { assert_select "div", 3 } + assert_raises(AssertionFailedError){ assert_select "p" } + end + + + def test_equality_true_false + render_html %Q{<div id="1"></div><div id="2"></div>} + assert_nothing_raised { assert_select "div" } + assert_raises(AssertionFailedError) { assert_select "p" } + assert_nothing_raised { assert_select "div", true } + assert_raises(AssertionFailedError) { assert_select "p", true } + assert_raises(AssertionFailedError) { assert_select "div", false } + assert_nothing_raised { assert_select "p", false } + end + + + def test_equality_string_and_regexp + render_html %Q{<div id="1">foo</div><div id="2">foo</div>} + assert_nothing_raised { assert_select "div", "foo" } + assert_raises(AssertionFailedError) { assert_select "div", "bar" } + assert_nothing_raised { assert_select "div", :text=>"foo" } + assert_raises(AssertionFailedError) { assert_select "div", :text=>"bar" } + assert_nothing_raised { assert_select "div", /(foo|bar)/ } + assert_raises(AssertionFailedError) { assert_select "div", /foobar/ } + assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ } + assert_raises(AssertionFailedError) { assert_select "div", :text=>/foobar/ } + assert_raises(AssertionFailedError) { assert_select "p", :text=>/foobar/ } + end + + + def test_equality_of_html + render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>} + text = "\"This is not a big problem,\" he said." + html = "<em>\"This is <strong>not</strong> a big problem,\"</em> he said." + assert_nothing_raised { assert_select "p", text } + assert_raises(AssertionFailedError) { assert_select "p", html } + assert_nothing_raised { assert_select "p", :html=>html } + assert_raises(AssertionFailedError) { assert_select "p", :html=>text } + # No stripping for pre. + render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>} + text = "\n\"This is not a big problem,\" he said.\n" + html = "\n<em>\"This is <strong>not</strong> a big problem,\"</em> he said.\n" + assert_nothing_raised { assert_select "pre", text } + assert_raises(AssertionFailedError) { assert_select "pre", html } + assert_nothing_raised { assert_select "pre", :html=>html } + assert_raises(AssertionFailedError) { assert_select "pre", :html=>text } + end + + + def test_equality_of_instances + render_html %Q{<div id="1">foo</div><div id="2">foo</div>} + assert_nothing_raised { assert_select "div", 2 } + assert_raises(AssertionFailedError) { assert_select "div", 3 } + assert_nothing_raised { assert_select "div", 1..2 } + assert_raises(AssertionFailedError) { assert_select "div", 3..4 } + assert_nothing_raised { assert_select "div", :count=>2 } + assert_raises(AssertionFailedError) { assert_select "div", :count=>3 } + assert_nothing_raised { assert_select "div", :minimum=>1 } + assert_nothing_raised { assert_select "div", :minimum=>2 } + assert_raises(AssertionFailedError) { assert_select "div", :minimum=>3 } + assert_nothing_raised { assert_select "div", :maximum=>2 } + assert_nothing_raised { assert_select "div", :maximum=>3 } + assert_raises(AssertionFailedError) { assert_select "div", :maximum=>1 } + assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 } + assert_raises(AssertionFailedError) { assert_select "div", :minimum=>3, :maximum=>4 } + end + + + def test_substitution_values + render_html %Q{<div id="1">foo</div><div id="2">foo</div>} + assert_select "div#?", /\d+/ do |elements| + assert_equal 2, elements.size + end + assert_select "div" do + assert_select "div#?", /\d+/ do |elements| + assert_equal 2, elements.size + assert_select "#1" + assert_select "#2" + end + end + end + + + def test_nested_assert_select + render_html %Q{<div id="1">foo</div><div id="2">foo</div>} + assert_select "div" do |elements| + assert_equal 2, elements.size + assert_select elements[0], "#1" + assert_select elements[1], "#2" + end + assert_select "div" do + assert_select "div" do |elements| + assert_equal 2, elements.size + # Testing in a group is one thing + assert_select "#1,#2" + # Testing individually is another. + assert_select "#1" + assert_select "#2" + assert_select "#3", false + end + end + end + + + def test_assert_select_from_rjs + render_rjs do |page| + page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>" + end + assert_select "div" do |elements| + assert elements.size == 2 + assert_select "#1" + assert_select "#2" + end + assert_select "div#?", /\d+/ do |elements| + assert_select "#1" + assert_select "#2" + end + # With multiple results. + render_rjs do |page| + page.replace_html "test", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + end + assert_select "div" do |elements| + assert elements.size == 2 + assert_select "#1" + assert_select "#2" + end + end + + + # + # Test css_select. + # + + + def test_css_select + render_html %Q{<div id="1"></div><div id="2"></div>} + assert 2, css_select("div").size + assert 0, css_select("p").size + end + + + def test_nested_css_select + render_html %Q{<div id="1">foo</div><div id="2">foo</div>} + assert_select "div#?", /\d+/ do |elements| + assert_equal 1, css_select(elements[0], "div").size + assert_equal 1, css_select(elements[1], "div").size + end + assert_select "div" do + assert_equal 2, css_select("div").size + css_select("div").each do |element| + # Testing as a group is one thing + assert !css_select("#1,#2").empty? + # Testing individually is another + assert !css_select("#1").empty? + assert !css_select("#2").empty? + end + end + end + + + def test_css_select_from_rjs + # With one result. + render_rjs do |page| + page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>" + end + assert_equal 2, css_select("div").size + assert_equal 1, css_select("#1").size + assert_equal 1, css_select("#2").size + # With multiple results. + render_rjs do |page| + page.replace_html "test", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + end + assert_equal 2, css_select("div").size + assert_equal 1, css_select("#1").size + assert_equal 1, css_select("#2").size + end + + + # + # Test assert_select_rjs. + # + + + def test_assert_select_rjs + # Test that we can pick up all statements in the result. + render_rjs do |page| + page.replace "test", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + page.insert_html :top, "test3", "<div id=\"3\">foo</div>" + end + found = false + assert_select_rjs do + assert_select "#1" + assert_select "#2" + assert_select "#3" + found = true + end + assert found + # Test that we fail if there is nothing to pick. + render_rjs do |page| + end + assert_raises(AssertionFailedError) { assert_select_rjs } + end + + + def test_assert_select_rjs_with_id + # Test that we can pick up all statements in the result. + render_rjs do |page| + page.replace "test1", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + page.insert_html :top, "test3", "<div id=\"3\">foo</div>" + end + assert_select_rjs "test1" do + assert_select "div", 1 + assert_select "#1" + end + assert_select_rjs "test2" do + assert_select "div", 1 + assert_select "#2" + end + assert_select_rjs "test3" do + assert_select "div", 1 + assert_select "#3" + end + assert_raises(AssertionFailedError) { assert_select_rjs "test4" } + end + + + def test_assert_select_rjs_for_replace + render_rjs do |page| + page.replace "test1", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + page.insert_html :top, "test3", "<div id=\"3\">foo</div>" + end + # Replace. + assert_select_rjs :replace do + assert_select "div", 1 + assert_select "#1" + end + assert_select_rjs :replace, "test1" do + assert_select "div", 1 + assert_select "#1" + end + assert_raises(AssertionFailedError) { assert_select_rjs :replace, "test2" } + # Replace HTML. + assert_select_rjs :replace_html do + assert_select "div", 1 + assert_select "#2" + end + assert_select_rjs :replace_html, "test2" do + assert_select "div", 1 + assert_select "#2" + end + assert_raises(AssertionFailedError) { assert_select_rjs :replace_html, "test1" } + end + + + def test_assert_select_rjs_for_insert + render_rjs do |page| + page.replace "test1", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + page.insert_html :top, "test3", "<div id=\"3\">foo</div>" + end + # Non-positioned. + assert_select_rjs :insert_html do + assert_select "div", 1 + assert_select "#3" + end + assert_select_rjs :insert_html, "test3" do + assert_select "div", 1 + assert_select "#3" + end + assert_raises(AssertionFailedError) { assert_select_rjs :insert_html, "test1" } + # Positioned. + render_rjs do |page| + page.insert_html :top, "test1", "<div id=\"1\">foo</div>" + page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>" + page.insert_html :before, "test3", "<div id=\"3\">foo</div>" + page.insert_html :after, "test4", "<div id=\"4\">foo</div>" + end + assert_select_rjs :insert, :top do + assert_select "div", 1 + assert_select "#1" + end + assert_select_rjs :insert, :bottom do + assert_select "div", 1 + assert_select "#2" + end + assert_select_rjs :insert, :before do + assert_select "div", 1 + assert_select "#3" + end + assert_select_rjs :insert, :after do + assert_select "div", 1 + assert_select "#4" + end + assert_select_rjs :insert_html do + assert_select "div", 4 + end + end + + + def test_nested_assert_select_rjs + # Simple selection from a single result. + render_rjs do |page| + page.replace_html "test", "<div id=\"1\">foo</div>\n<div id=\"2\">foo</div>" + end + assert_select_rjs "test" do |elements| + assert_equal 2, elements.size + assert_select "#1" + assert_select "#2" + end + # Deal with two results. + render_rjs do |page| + page.replace_html "test", "<div id=\"1\">foo</div>" + page.replace_html "test2", "<div id=\"2\">foo</div>" + end + assert_select_rjs "test" do |elements| + assert_equal 1, elements.size + assert_select "#1" + end + assert_select_rjs "test2" do |elements| + assert_equal 1, elements.size + assert_select "#2" + end + end + + + def test_feed_item_encoded + render_xml <<-EOF +<rss version="2.0"> + <channel> + <item> + <description> + <![CDATA[ + <p>Test 1</p> + ]]> + </description> + </item> + <item> + <description> + <![CDATA[ + <p>Test 2</p> + ]]> + </description> + </item> + </channel> +</rss> +EOF + assert_select "channel item description" do + # Test element regardless of wrapper. + assert_select_encoded do + assert_select "p", :count=>2, :text=>/Test/ + end + # Test through encoded wrapper. + assert_select_encoded do + assert_select "encoded p", :count=>2, :text=>/Test/ + end + # Use :root instead (recommended) + assert_select_encoded do + assert_select ":root p", :count=>2, :text=>/Test/ + end + # Test individually. + assert_select "description" do |elements| + assert_select_encoded elements[0] do + assert_select "p", "Test 1" + end + assert_select_encoded elements[1] do + assert_select "p", "Test 2" + end + end + end + + # Test that we only un-encode element itself. + assert_select "channel item" do + assert_select_encoded do + assert_select "p", 0 + end + end + end + + + # + # Test assert_select_email + # + + def test_assert_select_email + assert_raises(AssertionFailedError) { assert_select_email {} } + AssertSelectMailer.deliver_test "<div><p>foo</p><p>bar</p></div>" + assert_select_email do + assert_select "div:root" do + assert_select "p:first-child", "foo" + assert_select "p:last-child", "bar" + end + end + end + + + protected + def render_html(html) + @controller.response_with = html + get :html + end + + def render_rjs(&block) + @controller.response_with &block + get :rjs + end + + def render_xml(xml) + @controller.response_with = xml + get :xml + end +end
\ No newline at end of file diff --git a/actionpack/test/controller/selector_test.rb b/actionpack/test/controller/selector_test.rb new file mode 100644 index 0000000000..d004290097 --- /dev/null +++ b/actionpack/test/controller/selector_test.rb @@ -0,0 +1,628 @@ +#-- +# Copyright (c) 2006 Assaf Arkin (http://labnotes.org) +# Under MIT and/or CC By license. +#++ + +require File.dirname(__FILE__) + '/../abstract_unit' +require File.dirname(__FILE__) + '/fake_controllers' + +class SelectorTest < Test::Unit::TestCase + # + # Basic selector: element, id, class, attributes. + # + + def test_element + parse(%Q{<div id="1"></div><p></p><div id="2"></div>}) + # Match element by name. + select("div") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + # Not case sensitive. + select("DIV") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + # Universal match (all elements). + select("*") + assert_equal 3, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal nil, @matches[1].attributes["id"] + assert_equal "2", @matches[2].attributes["id"] + end + + + def test_identifier + parse(%Q{<div id="1"></div><p></p><div id="2"></div>}) + # Match element by ID. + select("div#1") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + # Match element by ID, substitute value. + select("div#?", 2) + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Element name does not match ID. + select("p#?", 2) + assert_equal 0, @matches.size + # Use regular expression. + select("#?", /\d/) + assert_equal 2, @matches.size + end + + + def test_class_name + parse(%Q{<div id="1" class=" foo "></div><p id="2" class=" foo bar "></p><div id="3" class="bar"></div>}) + # Match element with specified class. + select("div.foo") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + # Match any element with specified class. + select("*.foo") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + # Match elements with other class. + select("*.bar") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + # Match only element with both class names. + select("*.bar.foo") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + end + + + def test_attribute + parse(%Q{<div id="1"></div><p id="2" title="" bar="foo"></p><div id="3" title="foo"></div>}) + # Match element with attribute. + select("div[title]") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + # Match any element with attribute. + select("*[title]") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + # Match alement with attribute value. + select("*[title=foo]") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + # Match alement with attribute and attribute value. + select("[bar=foo][title]") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Not case sensitive. + select("[BAR=foo][TiTle]") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + end + + + def test_attribute_quoted + parse(%Q{<div id="1" title="foo"></div><div id="2" title="bar"></div><div id="3" title=" bar "></div>}) + # Match without quotes. + select("[title = bar]") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Match with single quotes. + select("[title = 'bar' ]") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Match with double quotes. + select("[title = \"bar\" ]") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Match with spaces. + select("[title = \" bar \" ]") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + end + + + def test_attribute_equality + parse(%Q{<div id="1" title="foo bar"></div><div id="2" title="barbaz"></div>}) + # Match (fail) complete value. + select("[title=bar]") + assert_equal 0, @matches.size + # Match space-separate word. + select("[title~=foo]") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + select("[title~=bar]") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + # Match beginning of value. + select("[title^=ba]") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Match end of value. + select("[title$=ar]") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + # Match text in value. + select("[title*=bar]") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + # Match first space separated word. + select("[title|=foo]") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + select("[title|=bar]") + assert_equal 0, @matches.size + end + + + # + # Selector composition: groups, sibling, children + # + + + def test_selector_group + parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>}) + # Simple group selector. + select("h1,h3") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + select("h1 , h3") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + # Complex group selector. + parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>}) + select("h1 a, h3 a") + assert_equal 2, @matches.size + assert_equal "foo", @matches[0].attributes["href"] + assert_equal "baz", @matches[1].attributes["href"] + # And now for the three selector challange. + parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>}) + select("h1 a, h2 a, h3 a") + assert_equal 3, @matches.size + assert_equal "foo", @matches[0].attributes["href"] + assert_equal "bar", @matches[1].attributes["href"] + assert_equal "baz", @matches[2].attributes["href"] + end + + + def test_sibling_selector + parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>}) + # Test next sibling. + select("h1+*") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + select("h1+h2") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + select("h1+h3") + assert_equal 0, @matches.size + select("*+h3") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + # Test any sibling. + select("h1~*") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + select("h2~*") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + end + + + def test_children_selector + parse(%Q{<div><p id="1"><span id="2"></span></p></div><div><p id="3"><span id="4" class="foo"></span></p></div>}) + # Test child selector. + select("div>p") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + select("div>span") + assert_equal 0, @matches.size + select("div>p#3") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + select("div>p>span") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + # Test descendant selector. + select("div p") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + select("div span") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + select("div *#3") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + select("div *#4") + assert_equal 1, @matches.size + assert_equal "4", @matches[0].attributes["id"] + # This is here because it failed before when whitespaces + # were not properly stripped. + select("div .foo") + assert_equal 1, @matches.size + assert_equal "4", @matches[0].attributes["id"] + end + + + # + # Pseudo selectors: root, nth-child, empty, content, etc + # + + + def test_root_selector + parse(%Q{<div id="1"><div id="2"></div></div>}) + # Can only find element if it's root. + select(":root") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + select("#1:root") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + select("#2:root") + assert_equal 0, @matches.size + # Opposite for nth-child. + select("#1:nth-child(1)") + assert_equal 0, @matches.size + end + + + def test_nth_child_odd_even + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # Test odd nth children. + select("tr:nth-child(odd)") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + # Test even nth children. + select("tr:nth-child(even)") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + end + + + def test_nth_child_a_is_zero + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # Test the third child. + select("tr:nth-child(0n+3)") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + # Same but an can be omitted when zero. + select("tr:nth-child(3)") + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + # Second element (but not every second element). + select("tr:nth-child(0n+2)") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Before first and past last returns nothing.: + assert_raises(ArgumentError) { select("tr:nth-child(-1)") } + select("tr:nth-child(0)") + assert_equal 0, @matches.size + select("tr:nth-child(5)") + assert_equal 0, @matches.size + end + + + def test_nth_child_a_is_one + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # a is group of one, pick every element in group. + select("tr:nth-child(1n+0)") + assert_equal 4, @matches.size + # Same but a can be omitted when one. + select("tr:nth-child(n+0)") + assert_equal 4, @matches.size + # Same but b can be omitted when zero. + select("tr:nth-child(n)") + assert_equal 4, @matches.size + end + + + def test_nth_child_b_is_zero + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # If b is zero, pick the n-th element (here each one). + select("tr:nth-child(n+0)") + assert_equal 4, @matches.size + # If b is zero, pick the n-th element (here every second). + select("tr:nth-child(2n+0)") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + # If a and b are both zero, no element selected. + select("tr:nth-child(0n+0)") + assert_equal 0, @matches.size + select("tr:nth-child(0)") + assert_equal 0, @matches.size + end + + + def test_nth_child_a_is_negative + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # Since a is -1, picks the first three elements. + select("tr:nth-child(-n+3)") + assert_equal 3, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + assert_equal "3", @matches[2].attributes["id"] + # Since a is -2, picks the first in every second of first four elements. + select("tr:nth-child(-2n+3)") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + # Since a is -2, picks the first in every second of first three elements. + select("tr:nth-child(-2n+2)") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + end + + + def test_nth_child_b_is_negative + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # Select last of four. + select("tr:nth-child(4n-1)") + assert_equal 1, @matches.size + assert_equal "4", @matches[0].attributes["id"] + # Select first of four. + select("tr:nth-child(4n-4)") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + # Select last of every second. + select("tr:nth-child(2n-1)") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + # Select nothing since an+b always < 0 + select("tr:nth-child(-1n-1)") + assert_equal 0, @matches.size + end + + + def test_nth_child_substitution_values + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # Test with ?n?. + select("tr:nth-child(?n?)", 2, 1) + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "3", @matches[1].attributes["id"] + select("tr:nth-child(?n?)", 2, 2) + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + select("tr:nth-child(?n?)", 4, 2) + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + # Test with ? (b only). + select("tr:nth-child(?)", 3) + assert_equal 1, @matches.size + assert_equal "3", @matches[0].attributes["id"] + select("tr:nth-child(?)", 5) + assert_equal 0, @matches.size + end + + + def test_nth_last_child + parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # Last two elements. + select("tr:nth-last-child(-n+2)") + assert_equal 2, @matches.size + assert_equal "3", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + # All old elements counting from last one. + select("tr:nth-last-child(odd)") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + end + + + def test_nth_of_type + parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # First two elements. + select("tr:nth-of-type(-n+2)") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + # All old elements counting from last one. + select("tr:nth-last-of-type(odd)") + assert_equal 2, @matches.size + assert_equal "2", @matches[0].attributes["id"] + assert_equal "4", @matches[1].attributes["id"] + end + + + def test_first_and_last + parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>}) + # First child. + select("tr:first-child") + assert_equal 0, @matches.size + select(":first-child") + assert_equal 1, @matches.size + assert_equal "thead", @matches[0].name + # First of type. + select("tr:first-of-type") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + select("thead:first-of-type") + assert_equal 1, @matches.size + assert_equal "thead", @matches[0].name + select("div:first-of-type") + assert_equal 0, @matches.size + # Last child. + select("tr:last-child") + assert_equal 1, @matches.size + assert_equal "4", @matches[0].attributes["id"] + # Last of type. + select("tr:last-of-type") + assert_equal 1, @matches.size + assert_equal "4", @matches[0].attributes["id"] + select("thead:last-of-type") + assert_equal 1, @matches.size + assert_equal "thead", @matches[0].name + select("div:last-of-type") + assert_equal 0, @matches.size + end + + + def test_first_and_last + # Only child. + parse(%Q{<table><tr></tr></table>}) + select("table:only-child") + assert_equal 0, @matches.size + select("tr:only-child") + assert_equal 1, @matches.size + assert_equal "tr", @matches[0].name + parse(%Q{<table><tr></tr><tr></tr></table>}) + select("tr:only-child") + assert_equal 0, @matches.size + # Only of type. + parse(%Q{<table><thead></thead><tr></tr><tr></tr></table>}) + select("thead:only-of-type") + assert_equal 1, @matches.size + assert_equal "thead", @matches[0].name + select("td:only-of-type") + assert_equal 0, @matches.size + end + + + def test_empty + parse(%Q{<table><tr></tr></table>}) + select("table:empty") + assert_equal 0, @matches.size + select("tr:empty") + assert_equal 1, @matches.size + parse(%Q{<div> </div>}) + select("div:empty") + assert_equal 1, @matches.size + end + + + def test_content + parse(%Q{<div> </div>}) + select("div:content()") + assert_equal 1, @matches.size + parse(%Q{<div>something </div>}) + select("div:content()") + assert_equal 0, @matches.size + select("div:content(something)") + assert_equal 1, @matches.size + select("div:content( 'something' )") + assert_equal 1, @matches.size + select("div:content( \"something\" )") + assert_equal 1, @matches.size + select("div:content(?)", "something") + assert_equal 1, @matches.size + select("div:content(?)", /something/) + assert_equal 1, @matches.size + end + + + # + # Test negation. + # + + + def test_element_negation + parse(%Q{<p></p><div></div>}) + select("*") + assert_equal 2, @matches.size + select("*:not(p)") + assert_equal 1, @matches.size + assert_equal "div", @matches[0].name + select("*:not(div)") + assert_equal 1, @matches.size + assert_equal "p", @matches[0].name + select("*:not(span)") + assert_equal 2, @matches.size + end + + + def test_id_negation + parse(%Q{<p id="1"></p><p id="2"></p>}) + select("p") + assert_equal 2, @matches.size + select(":not(#1)") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + select(":not(#2)") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + end + + + def test_class_name_negation + parse(%Q{<p class="foo"></p><p class="bar"></p>}) + select("p") + assert_equal 2, @matches.size + select(":not(.foo)") + assert_equal 1, @matches.size + assert_equal "bar", @matches[0].attributes["class"] + select(":not(.bar)") + assert_equal 1, @matches.size + assert_equal "foo", @matches[0].attributes["class"] + end + + + def test_attribute_negation + parse(%Q{<p title="foo"></p><p title="bar"></p>}) + select("p") + assert_equal 2, @matches.size + select(":not([title=foo])") + assert_equal 1, @matches.size + assert_equal "bar", @matches[0].attributes["title"] + select(":not([title=bar])") + assert_equal 1, @matches.size + assert_equal "foo", @matches[0].attributes["title"] + end + + + def test_pseudo_class_negation + parse(%Q{<div><p id="1"></p><p id="2"></p></div>}) + select("p") + assert_equal 2, @matches.size + select("p:not(:first-child)") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + select("p:not(:nth-child(2))") + assert_equal 1, @matches.size + assert_equal "1", @matches[0].attributes["id"] + end + + + def test_negation_details + parse(%Q{<p id="1"></p><p id="2"></p><p id="3"></p>}) + assert_raises(ArgumentError) { select(":not(") } + assert_raises(ArgumentError) { select(":not(:not())") } + select("p:not(#1):not(#3)") + assert_equal 1, @matches.size + assert_equal "2", @matches[0].attributes["id"] + end + + + def test_select_from_element + parse(%Q{<div><p id="1"></p><p id="2"></p></div>}) + select("div") + @matches = @matches[0].select("p") + assert_equal 2, @matches.size + assert_equal "1", @matches[0].attributes["id"] + assert_equal "2", @matches[1].attributes["id"] + end + + +protected + + def parse(html) + @html = HTML::Document.new(html).root + end + + def select(*selector) + @matches = HTML.selector(*selector).select(@html) + end + +end |