Support easier full text matching

This commit is contained in:
Thomas Walpole 2016-12-23 12:17:45 -08:00
parent f6bbc6b639
commit e85cce5778
11 changed files with 96 additions and 12 deletions

View File

@ -29,6 +29,7 @@ module Capybara
attr_accessor :raise_server_errors, :server_errors
attr_writer :default_driver, :current_driver, :javascript_driver, :session_name, :server_host
attr_reader :save_and_open_page_path
attr_accessor :exact_text
attr_accessor :app
##
@ -495,6 +496,7 @@ Capybara.configure do |config|
config.automatic_reload = true
config.match = :smart
config.exact = false
config.exact_text = false
config.raise_server_errors = true
config.server_errors = [StandardError]
config.visible_text_only = false

View File

@ -28,8 +28,14 @@ module Capybara
# @param [String] text Text to escape
# @return [String] Escaped text
#
def to_regexp(text, options=nil)
text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(normalize_whitespace(text)), options)
def to_regexp(text, regexp_options=nil, exact=false)
if text.is_a?(Regexp)
text
else
escaped = Regexp.escape(normalize_whitespace(text))
escaped = "\\A#{escaped}\\z" if exact
Regexp.new(escaped, regexp_options)
end
end
##

View File

@ -7,10 +7,11 @@ module Capybara
#
# @!macro title_query_params
# @overload $0(string, options = {})
# @param string [String] The string that title should include
# @param string [String] The string that title should include #
# @overload $0(regexp, options = {})
# @param regexp [Regexp] The regexp that title should match to
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for title to eq/match given string/regexp argument
# @option options [Boolean] :exact (false) When passed a string should the match be exact or just substring
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
# @return [true]
#

View File

@ -188,6 +188,7 @@ module Capybara
# @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to Capybara.default_selector
# @param [String] locator The selector
# @option options [String, Regexp] text Only find elements which contain this text or match this regexp
# @option options [String, Boolean] exact_text (Capybara.exact_text) When String the string the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
# @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
# * true - only finds visible elements.
# * false - finds invisible _and_ visible elements.

View File

@ -546,6 +546,7 @@ module Capybara
# @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
# @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
# @option options [Boolean] :exact (Capybara.exact_text) Whether text must be an exact match or just substring
# @overload $0(text, options = {})
# @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
# @option options [Integer] :count (nil) Number of times the text is expected to occur
@ -553,6 +554,7 @@ module Capybara
# @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
# @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
# @option options [Boolean] :exact (Capybara.exact_text) Whether text must be an exact match or just substring
# @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
# @return [true]
#

View File

@ -4,7 +4,7 @@ module Capybara
class SelectorQuery < Queries::BaseQuery
attr_accessor :selector, :locator, :options, :expression, :find, :negative
VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :match, :wait, :filter_set]
VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set]
VALID_MATCH = [:first, :smart, :prefer_exact, :one]
def initialize(*args, &filter_block)
@ -42,7 +42,8 @@ module Capybara
def description
@description = String.new("#{label} #{locator.inspect}")
@description << " with text #{options[:text].inspect}" if options[:text]
@description << " with#{" exact" if exact_text === true} text #{options[:text].inspect}" if options[:text]
@description << " with exact text #{options[:exact_text]}" if options[:exact_text].is_a?(String)
@description << " with id #{options[:id]}" if options[:id]
@description << " with classes #{Array(options[:class]).join(',')}]" if options[:class]
@description << selector.description(options)
@ -52,7 +53,22 @@ module Capybara
def matches_filters?(node)
if options[:text]
regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
regexp = if options[:text].is_a?(Regexp)
options[:text]
else
if exact_text === true
"\\A#{Regexp.escape(options[:text].to_s)}\\z"
else
Regexp.escape(options[:text].to_s)
end
end
text_visible = visible
text_visible = :all if text_visible == :hidden
return false if not node.text(text_visible).match(regexp)
end
if exact_text.is_a?(String)
regexp = "\\A#{Regexp.escape(options[:exact_text])}\\z"
text_visible = visible
text_visible = :all if text_visible == :hidden
return false if not node.text(text_visible).match(regexp)
@ -187,6 +203,10 @@ module Capybara
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression.to_s}\" has no effect."
end
end
def exact_text
exact_text = options.fetch(:exact_text, Capybara.exact_text)
end
end
end
end

View File

@ -10,8 +10,8 @@ module Capybara
unless @expected_text.is_a?(Regexp)
@expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
end
@search_regexp = Capybara::Helpers.to_regexp(@expected_text)
@options ||= {}
@search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
assert_valid_keys
end
@ -33,12 +33,16 @@ module Capybara
if @expected_text.is_a?(Regexp)
"text matching #{@expected_text.inspect}"
else
"text #{@expected_text.inspect}"
"#{"exact " if exact?}text #{@expected_text.inspect}"
end
end
private
def exact?
options.fetch(:exact, Capybara.exact_text)
end
def build_message(report_on_invisible)
message = String.new()
unless (COUNT_KEYS & @options.keys).empty?
@ -70,7 +74,7 @@ module Capybara
end
def valid_keys
COUNT_KEYS + [:wait]
COUNT_KEYS + [:wait, :exact]
end
def check_visible_text?

View File

@ -9,7 +9,7 @@ module Capybara
unless @expected_title.is_a?(Regexp)
@expected_title = Capybara::Helpers.normalize_whitespace(@expected_title)
end
@search_regexp = Capybara::Helpers.to_regexp(@expected_title)
@search_regexp = Capybara::Helpers.to_regexp(@expected_title, nil, options.fetch(:exact, false))
assert_valid_keys
end
@ -34,7 +34,7 @@ module Capybara
end
def valid_keys
[:wait]
[:wait, :exact]
end
end
end

View File

@ -77,6 +77,27 @@ Capybara::SpecHelper.spec '#has_selector?' do
expect(@session).to have_selector(:css, "p a#foo", 'extra')
end
end
context "with exact_text" do
context "string" do
it "should only match elements that match exactly" do
expect(@session).to have_selector(:id, "h2one", exact_text: "Header Class Test One")
expect(@session).to have_no_selector(:id, "h2one", exact_text: "Header Class Test")
end
end
context "boolean" do
it "should only match elements that match exactly when true" do
expect(@session).to have_selector(:id, "h2one", text: "Header Class Test One", exact_text: true)
expect(@session).to have_no_selector(:id, "h2one", text: "Header Class Test", exact_text: true)
end
it "should match substrings when false" do
expect(@session).to have_selector(:id, "h2one", text: "Header Class Test One", exact_text: false)
expect(@session).to have_selector(:id, "h2one", text: "Header Class Test", exact_text: false)
end
end
end
end
Capybara::SpecHelper.spec '#has_no_selector?' do

View File

@ -96,6 +96,18 @@ Capybara::SpecHelper.spec '#has_text?' do
expect(@session).not_to have_text(/xxxxyzzz/)
end
context "with exact: true option" do
it "should be true if text matches exactly" do
@session.visit('/with_html')
expect(@session.find(:id, "h2one")).to have_text("Header Class Test One", exact: true)
end
it "should be false if text doesn't match exactly" do
@session.visit('/with_html')
expect(@session.find(:id, "h2one")).not_to have_text("Header Class Test On", exact: true)
end
end
it "should escape any characters that would have special meaning in a regexp" do
@session.visit('/with_html')
expect(@session).not_to have_text('.orem')
@ -205,7 +217,7 @@ Capybara::SpecHelper.spec '#has_text?' do
it "should raise an error if an invalid option is passed" do
@session.visit('/with_html')
expect do
expect(@session).to have_text('Lorem', exact: true)
expect(@session).to have_text('Lorem', invalid: true)
end.to raise_error(ArgumentError)
end
end

View File

@ -21,6 +21,21 @@ Capybara::SpecHelper.spec '#has_title?' do
it "should be false if the page has not the given title" do
expect(@session).not_to have_title('monkey')
end
it "should default to exact: false matching" do
expect(@session).to have_title('with_js', exact: false)
expect(@session).to have_title('with_', exact: false)
end
it "should match exactly if exact: true option passed" do
expect(@session).to have_title('with_js', exact: true)
expect(@session).not_to have_title('with_', exact: true)
end
it "should match partial if exact: false option passed" do
expect(@session).to have_title('with_js', exact: false)
expect(@session).to have_title('with_', exact: false)
end
end
Capybara::SpecHelper.spec '#has_no_title?' do