Make compound rspec matchers not run sequentially

This commit is contained in:
Thomas Walpole 2017-06-29 12:13:47 -07:00
parent 9c3b053676
commit 0a63937e38
3 changed files with 196 additions and 1 deletions

View File

@ -0,0 +1,95 @@
module Capybara
module RSpecMatchers
module Compound
include ::RSpec::Matchers::Composable
def and(matcher)
Capybara::RSpecMatchers::Compound::And.new(self,matcher)
end
def and_then(matcher)
::RSpec::Matchers::BuiltIn::Compound::And.new(self, matcher)
end
def or(matcher)
Capybara::RSpecMatchers::Compound::Or.new(self, matcher)
end
class CapybaraEvaluator
def initialize(actual, matcher_1, matcher_2)
@actual = actual
@matcher_1 = matcher_1
@matcher_2 = matcher_2
@match_results = Hash.new { |h, matcher| h[matcher] = matcher.matches?(@actual) }
end
def matcher_matches?(matcher)
@match_results[matcher]
end
def reset
@match_results.clear
end
end
class And < ::RSpec::Matchers::BuiltIn::Compound::And
private
def match(_expected, actual)
@evaluator = CapybaraEvaluator.new(actual, matcher_1, matcher_2)
syncer = sync_element(actual)
begin
syncer.synchronize do
@evaluator.reset
raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].all?
true
end
rescue
false
end
end
def sync_element(el)
if el.respond_to? :synchronize
el
elsif el.respond_to? :current_scope
el.current_scope
else
Capybara.string(el)
end
end
end
class Or < ::RSpec::Matchers::BuiltIn::Compound::Or
private
def match(_expected, actual)
@evaluator = CapybaraEvaluator.new(actual, matcher_1, matcher_2)
syncer = sync_element(actual)
begin
syncer.synchronize do
@evaluator.reset
raise ::Capybara::ElementNotFound unless [matcher_1_matches?, matcher_2_matches?].any?
true
end
rescue
false
end
end
def sync_element(el)
if el.respond_to? :synchronize
el
elsif el.respond_to? :current_scope
el.current_scope
else
Capybara.string(el)
end
end
end
end
end
end

View File

@ -2,7 +2,10 @@
module Capybara
module RSpecMatchers
class Matcher
include ::RSpec::Matchers::Composable if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.0'))
if defined?(::RSpec::Expectations::Version) && (Gem::Version.new(RSpec::Expectations::Version::STRING) >= Gem::Version.new('3.0'))
require 'capybara/rspec/compound'
include ::Capybara::RSpecMatchers::Compound
end
attr_reader :failure_message, :failure_message_when_negated

View File

@ -2,6 +2,7 @@
require 'spec_helper'
require 'capybara/dsl'
require 'capybara/rspec/matchers'
require 'benchmark'
RSpec.shared_examples Capybara::RSpecMatchers do |session, mode|
@ -825,4 +826,100 @@ RSpec.shared_examples Capybara::RSpecMatchers do |session, mode|
expect(html).to have_table('nope').or have_table('Lovely table')
end if RSpec::Version::STRING.to_f >= 3.0
end
if RSpec::Version::STRING.to_f >= 3.0
context "compounding", requires: [:js] do
before(:each) do
@session = session
@session.visit('/with_js')
@el = @session.find(:css, '#reload-me')
end
context "#and" do
it "should run 'concurrently'" do
Capybara.using_wait_time(2) do
matcher = have_text('this is not there').and have_text('neither is this')
expect(Benchmark.realtime do
expect {
expect(@el).to matcher
}.to raise_error RSpec::Expectations::ExpectationNotMetError
end).to be_between(2,3)
end
end
it "should run 'concurrently' and retry" do
@session.click_link('reload-link')
@session.using_wait_time(2) do
expect(Benchmark.realtime do
expect {
expect(@el).to have_text('waiting to be reloaded').and(have_text('has been reloaded'))
}.to raise_error RSpec::Expectations::ExpectationNotMetError, /expected to find text "waiting to be reloaded" in "has been reloaded"/
end).to be_between(2,3)
end
end
it "should ignore :wait options" do
@session.using_wait_time(2) do
matcher = have_text('this is not there', wait: 5).and have_text('neither is this', wait: 6)
expect(Benchmark.realtime do
expect {
expect(@el).to matcher
}.to raise_error RSpec::Expectations::ExpectationNotMetError
end).to be_between(2,3)
end
end
it "should work on the session" do
@session.using_wait_time(2) do
@session.click_link('reload-link')
expect(@session).to have_selector(:css, 'h1', text: 'FooBar').and have_text('has been reloaded')
end
end
end
context "#and_then" do
it "should run sequentially" do
@session.click_link('reload-link')
expect(@el).to have_text('waiting to be reloaded').and_then have_text('has been reloaded')
end
end
context "#or" do
it "should run 'concurrently'" do
@session.using_wait_time(3) do
expect(Benchmark.realtime do
expect(@el).to have_text('has been reloaded').or have_text('waiting to be reloaded')
end).to be < 1
end
end
it "should retry" do
@session.using_wait_time(3) do
expect(Benchmark.realtime do
expect {
expect(@el).to have_text('has been reloaded').or have_text('random stuff')
}.to raise_error RSpec::Expectations::ExpectationNotMetError
end).to be > 3
end
end
it "should ignore :wait options" do
@session.using_wait_time(2) do
expect(Benchmark.realtime do
expect {
expect(@el).to have_text('this is not there', wait: 10).or have_text('neither is this', wait: 15)
}.to raise_error RSpec::Expectations::ExpectationNotMetError
end).to be_between(2,3)
end
end
it "should work on the session" do
@session.using_wait_time(2) do
@session.click_link('reload-link')
expect(@session).to have_selector(:css, 'h1', text: 'Not on the page').or have_text('has been reloaded')
end
end
end
end
end
end