diff --git a/lib/capybara/minitest.rb b/lib/capybara/minitest.rb index 90dbd50b..a74a64ca 100644 --- a/lib/capybara/minitest.rb +++ b/lib/capybara/minitest.rb @@ -81,7 +81,9 @@ module Capybara # @!method assert_xpath # see {Capybara::Node::Matchers#assert_not_matches_selector} - %w(assert_selector assert_no_selector assert_matches_selector assert_not_matches_selector).each do |assertion_name| + %w(assert_selector assert_no_selector + assert_all_of_selectors assert_none_of_selectors + assert_matches_selector assert_not_matches_selector).each do |assertion_name| self.class_eval <<-EOM, __FILE__, __LINE__ + 1 def #{assertion_name} *args, &optional_filter_block self.assertions +=1 diff --git a/lib/capybara/minitest/spec.rb b/lib/capybara/minitest/spec.rb index fe5212ee..af0e15c5 100644 --- a/lib/capybara/minitest/spec.rb +++ b/lib/capybara/minitest/spec.rb @@ -11,7 +11,9 @@ module Capybara (%w(selector xpath css link button field select table checked_field unchecked_field).flat_map do |assertion| [["assert_#{assertion}", "must_have_#{assertion}"], ["refute_#{assertion}", "wont_have_#{assertion}"]] - end + %w(selector xpath css).flat_map do |assertion| + end + [["assert_all_of_selectors", "must_have_all_of_selectors"], + ["assert_none_of_selectors", "must_have_none_of_selectors"]] + + %w(selector xpath css).flat_map do |assertion| [["assert_matches_#{assertion}", "must_match_#{assertion}"], ["refute_matches_#{assertion}", "wont_match_#{assertion}"]] end).each do |(meth, new_name)| diff --git a/lib/capybara/rspec/matchers.rb b/lib/capybara/rspec/matchers.rb index f2f0029d..29092627 100644 --- a/lib/capybara/rspec/matchers.rb +++ b/lib/capybara/rspec/matchers.rb @@ -59,7 +59,6 @@ module Capybara end class HaveSelector < Matcher - def initialize(*args, &filter_block) @args = args @filter_block = filter_block @@ -82,6 +81,44 @@ module Capybara end end + class HaveAllSelectors < Matcher + def initialize(*args, &filter_block) + @args = args + @filter_block = filter_block + end + + def matches?(actual) + wrap_matches?(actual){ |el| el.assert_all_of_selectors(*@args, &@filter_block) } + end + + def does_not_match?(actual) + raise ArgumentError, "The have_all_selectors matcher does not support use with not_to/should_not" + end + + def description + "have all selectors" + end + end + + class HaveNoSelectors < Matcher + def initialize(*args, &filter_block) + @args = args + @filter_block = filter_block + end + + def matches?(actual) + wrap_matches?(actual){ |el| el.assert_none_of_selectors(*@args, &@filter_block) } + end + + def does_not_match?(actual) + raise ArgumentError, "The have_none_of_selectors matcher does not support use with not_to/should_not" + end + + def description + "have no selectors" + end + end + class MatchSelector < HaveSelector def matches?(actual) wrap_matches?(actual) { |el| el.assert_matches_selector(*@args, &@filter_block) } @@ -211,6 +248,18 @@ module Capybara HaveSelector.new(*args, &optional_filter_block) end + # RSpec matcher for whether the element(s) matching a group of selectors exist + # See {Capybara::Node::Matcher#assert_all_of_selectors} + def have_all_of_selectors(*args, &optional_filter_block) + HaveAllSelectors.new(*args, &optional_filter_block) + end + + # RSpec matcher for whether no element(s) matching a group of selectors exist + # See {Capybara::Node::Matcher#assert_none_of_selectors} + def have_none_of_selectors(*args, &optional_filter_block) + HaveNoSelectors.new(*args, &optional_filter_block) + end + # RSpec matcher for whether the current element matches a given selector # See {Capybara::Node::Matchers#assert_matches_selector} def match_selector(*args, &optional_filter_block) diff --git a/lib/capybara/spec/session/has_all_selectors_spec.rb b/lib/capybara/spec/session/has_all_selectors_spec.rb new file mode 100644 index 00000000..5cacedb6 --- /dev/null +++ b/lib/capybara/spec/session/has_all_selectors_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true +Capybara::SpecHelper.spec '#have_all_selectors' do + before do + @session.visit('/with_html') + end + + it "should be true if the given selectors are on the page" do + expect(@session).to have_all_of_selectors(:css, "p a#foo", "h2#h2one", "h2#h2two" ) + end + + it "should be false if any of the given selectors are not on the page" do + expect { + expect(@session).to have_all_of_selectors(:css, "p a#foo", "h2#h2three", "h2#h2one") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + + it "should use default selector" do + Capybara.default_selector = :css + expect(@session).to have_all_of_selectors("p a#foo", "h2#h2two", "h2#h2one" ) + expect { + expect(@session).to have_all_of_selectors("p a#foo", "h2#h2three", "h2#h2one") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + + context "should respect scopes" do + it "when used with `within`" do + @session.within "//p[@id='first']" do + expect(@session).to have_all_of_selectors(".//a[@id='foo']") + expect { + expect(@session).to have_all_of_selectors(".//a[@id='red']") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + end + + it "when called on elements" do + el = @session.find "//p[@id='first']" + expect(el).to have_all_of_selectors(".//a[@id='foo']") + expect { + expect(el).to have_all_of_selectors(".//a[@id='red']") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + end + + context "with options" do + it "should apply options to all locators" do + expect(@session).to have_all_of_selectors(:field, 'normal', 'additional_newline', type: :textarea) + expect { + expect(@session).to have_all_of_selectors(:field, 'normal', 'test_field', 'additional_newline', type: :textarea) + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + end + + context "with wait", requires: [:js] do + it "should not raise error if all the elements appear before given wait duration" do + Capybara.using_wait_time(0.1) do + @session.visit('/with_js') + @session.click_link('Click me') + expect(@session).to have_all_of_selectors(:css, "a#clickable", "a#has-been-clicked", '#drag', wait: 0.9) + end + end + end + + it "cannot be negated" do + expect { + expect(@session).not_to have_all_of_selectors(:css, "p a#foo", "h2#h2one", "h2#h2two") + }.to raise_error ArgumentError + end +end + diff --git a/lib/capybara/spec/session/has_none_selectors_spec.rb b/lib/capybara/spec/session/has_none_selectors_spec.rb new file mode 100644 index 00000000..914c18bd --- /dev/null +++ b/lib/capybara/spec/session/has_none_selectors_spec.rb @@ -0,0 +1,76 @@ +Capybara::SpecHelper.spec '#have_none_of_selectors' do + before do + @session.visit('/with_html') + end + + it "should be false if any of the given locators are on the page" do + expect { + expect(@session).to have_none_of_selectors(:xpath, "//p", "//a") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + expect { + expect(@session).to have_none_of_selectors(:css, "p a#foo") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + + it "should be true if none of the given locators are on the page" do + expect(@session).to have_none_of_selectors(:xpath, "//abbr", "//td" ) + expect(@session).to have_none_of_selectors(:css, "p a#doesnotexist", "abbr") + end + + it "should use default selector" do + Capybara.default_selector = :css + expect(@session).to have_none_of_selectors("p a#doesnotexist", "abbr") + expect { + expect(@session).to have_none_of_selectors("abbr", "p a#foo") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + end + + context "should respect scopes" do + it "when used with `within`" do + @session.within "//p[@id='first']" do + expect { + expect(@session).to have_none_of_selectors(".//a[@id='foo']") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + expect(@session).to have_none_of_selectors(".//a[@id='red']") + end + end + + it "when called on an element" do + el = @session.find "//p[@id='first']" + expect { + expect(el).to have_none_of_selectors(".//a[@id='foo']") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + expect(el).to have_none_of_selectors(".//a[@id='red']") + end + end + + context "with options" do + it "should apply the options to all locators" do + expect { + expect(@session).to have_none_of_selectors("//p//a", text: "Redirect") + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + expect(@session).to have_none_of_selectors("//p", text: "Doesnotexist") + end + + it "should discard all matches where the given regexp is matched" do + expect { + expect(@session).to have_none_of_selectors("//p//a", text: /re[dab]i/i, count: 1) + }.to raise_error ::RSpec::Expectations::ExpectationNotMetError + expect(@session).to have_none_of_selectors("//p//a", text: /Red$/) + end + end + + context "with wait", requires: [:js] do + it "should not find elements if they appear after given wait duration" do + @session.visit('/with_js') + @session.click_link('Click me') + expect(@session).to have_none_of_selectors(:css, "#new_field", "a#has-been-clicked", wait: 0.1) + end + end + + it "cannot be negated" do + expect { + expect(@session).not_to have_none_of_selectors(:css, "p a#foo", "h2#h2one", "h2#h2two") + }.to raise_error ArgumentError + end +end diff --git a/spec/minitest_spec.rb b/spec/minitest_spec.rb index 3d77534b..e9495724 100644 --- a/spec/minitest_spec.rb +++ b/spec/minitest_spec.rb @@ -88,6 +88,14 @@ class MinitestTest < Minitest::Test refute_table('not_on_form') end + def test_assert_all_of_selectors + assert_all_of_selectors(:css, 'select#form_other_title', 'input#form_last_name') + end + + def test_assert_none_of_selectors + assert_none_of_selectors(:css, 'input#not_on_page', 'input#also_not_on_page') + end + def test_assert_matches_selector assert_matches_selector(find(:field, 'customer_email'), :field, 'customer_email') assert_not_matches_selector(find(:select, 'form_title'), :field, 'customer_email') @@ -117,6 +125,6 @@ RSpec.describe 'capybara/minitest' do reporter.start MinitestTest.run reporter, {} reporter.report - expect(output.string).to include("15 runs, 42 assertions, 0 failures, 0 errors, 0 skips") + expect(output.string).to include("17 runs, 44 assertions, 0 failures, 0 errors, 0 skips") end end diff --git a/spec/minitest_spec_spec.rb b/spec/minitest_spec_spec.rb index ab425899..4cffc7fd 100644 --- a/spec/minitest_spec_spec.rb +++ b/spec/minitest_spec_spec.rb @@ -88,6 +88,14 @@ class MinitestSpecTest < Minitest::Spec page.wont_have_table('not_on_form') end + it "supports all_of_selectors expectations" do + page.must_have_all_of_selectors(:css, 'select#form_other_title', 'input#form_last_name') + end + + it "supports none_of_selectors expectations" do + page.must_have_none_of_selectors(:css, 'input#not_on_page', 'input#also_not_on_page') + end + it "supports match_selector expectations" do find(:field, 'customer_email').must_match_selector(:field, 'customer_email') find(:select, 'form_title').wont_match_selector(:field, 'customer_email') @@ -120,7 +128,7 @@ RSpec.describe 'capybara/minitest/spec' do reporter.start MinitestSpecTest.run reporter, {} reporter.report - expect(output.string).to include("16 runs, 39 assertions, 1 failures, 0 errors, 0 skips") + expect(output.string).to include("18 runs, 41 assertions, 1 failures, 0 errors, 0 skips") #Make sure error messages are displayed expect(output.string).to include('expected to find visible select box "non_existing_form_title" that is not disabled but there were no matches') end