lib/rspec/rails/matchers/base_matcher.rb
module RSpec module Rails module Matchers # @api private # # Base class to build matchers. Should not be instantiated directly. class BaseMatcher include RSpec::Matchers::Composable # @api private # Used to detect when no arg is passed to `initialize`. # `nil` cannot be used because it's a valid value to pass. UNDEFINED = Object.new.freeze # @private attr_reader :actual, :expected, :rescued_exception # @private attr_writer :matcher_name def initialize(expected = UNDEFINED) @expected = expected unless UNDEFINED.equal?(expected) end # @api private # Indicates if the match is successful. Delegates to `match`, which # should be defined on a subclass. Takes care of consistently # initializing the `actual` attribute. def matches?(actual) @actual = actual match(expected, actual) end # @api private # Used to wrap a block of code that will indicate failure by # raising one of the named exceptions. # # This is used by rspec-rails for some of its matchers that # wrap rails' assertions. def match_unless_raises(*exceptions) exceptions.unshift Exception if exceptions.empty? begin yield true rescue *exceptions => @rescued_exception false end end # @api private # Generates a description using {RSpec::Matchers::EnglishPhrasing}. # @return [String] def description desc = RSpec::Matchers::EnglishPhrasing.split_words(self.class.matcher_name) desc << RSpec::Matchers::EnglishPhrasing.list(@expected) if defined?(@expected) desc end # @api private # Matchers are not diffable by default. Override this to make your # subclass diffable. def diffable? false end # @api private # Most matchers are value matchers (i.e. meant to work with `expect(value)`) # rather than block matchers (i.e. meant to work with `expect { }`), so # this defaults to false. Block matchers must override this to return true. def supports_block_expectations? false end # @api private def expects_call_stack_jump? false end # @private def expected_formatted RSpec::Support::ObjectFormatter.format(@expected) end # @private def actual_formatted RSpec::Support::ObjectFormatter.format(@actual) end # @private def self.matcher_name @matcher_name ||= underscore(name.split('::').last) end # @private def matcher_name if defined?(@matcher_name) @matcher_name else self.class.matcher_name end end # @private # Borrowed from ActiveSupport. def self.underscore(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') word.tr!('-', '_') word.downcase! word end private_class_method :underscore private def assert_ivars(*expected_ivars) return unless (expected_ivars - present_ivars).any? ivar_list = RSpec::Matchers::EnglishPhrasing.list(expected_ivars) raise "#{self.class.name} needs to supply#{ivar_list}" end alias present_ivars instance_variables # @private module HashFormatting # `{ :a => 5, :b => 2 }.inspect` produces: # # {:a=>5, :b=>2} # # ...but it looks much better as: # # {:a => 5, :b => 2} # # This is idempotent and safe to run on a string multiple times. def improve_hash_formatting(inspect_string) inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2') end module_function :improve_hash_formatting end include HashFormatting # @api private # Provides default implementations of failure messages, based on the `description`. module DefaultFailureMessages # @api private # Provides a good generic failure message. Based on `description`. # When subclassing, if you are not satisfied with this failure message # you often only need to override `description`. # @return [String] def failure_message "expected #{description_of @actual} to #{description}".dup end # @api private # Provides a good generic negative failure message. Based on `description`. # When subclassing, if you are not satisfied with this failure message # you often only need to override `description`. # @return [String] def failure_message_when_negated "expected #{description_of @actual} not to #{description}".dup end # @private def self.has_default_failure_messages?(matcher) matcher.method(:failure_message).owner == self && matcher.method(:failure_message_when_negated).owner == self rescue NameError false end end include DefaultFailureMessages end end end end