class RSpec::SleepingKingStudios::Matchers::Core::DeepMatcher
Matcher for performing a deep comparison between two objects.
@since 2.5.0
Public Class Methods
@param [Object] expected The expected object.
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 14 def initialize(expected) @expected = expected end
Public Instance Methods
(see BaseMatcher#description
)
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 19 def description "match #{format_expected(@expected)}" end
Inverse of matches?
method.
@param [Object] actual The object to check.
@return [Boolean] true if the actual object does not match the
expectation, otherwise true.
@see matches?
RSpec::SleepingKingStudios::Matchers::BaseMatcher#does_not_match?
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 31 def does_not_match? actual super if matcher?(@expected) delegate_to_negated_matcher(@expected) elsif @expected.is_a?(Array) && actual.is_a?(Array) diff_arrays_negated elsif @expected.is_a?(Hash) && actual.is_a?(Hash) diff_hashes_negated else delegate_to_negated_matcher(equality_matcher) end !@matches end
(see BaseMatcher#failure_message
)
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 48 def failure_message @failure_message end
(see BaseMatcher#failure_message_when_negated
)
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 53 def failure_message_when_negated @failure_message_when_negated end
Performs a deep comparison between the actual object and the expected object. The type of comparison depends on the type of the expected object:
-
If the expected object is an
RSpec
matcher, thematches?
method on the matcher is called with the expected object. -
If the expected object is an Array, then each item is compared based on the type of the expected item.
-
If the expected object is a Hash, then the keys must match and each value is compared based on the type of the expected value.
-
Otherwise, the two objects are compared using an equality comparison.
@param [Object] actual The object to check.
@return [Boolean] true if the actual object matches the expectation,
otherwise false.
RSpec::SleepingKingStudios::Matchers::BaseMatcher#matches?
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 72 def matches?(actual) super if matcher?(@expected) delegate_to_matcher(@expected) elsif @expected.is_a?(Array) && actual.is_a?(Array) diff_arrays elsif @expected.is_a?(Hash) && actual.is_a?(Hash) diff_hashes else delegate_to_matcher(equality_matcher) end @matches end
Private Instance Methods
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 90 def compare_arrays(expected, actual) compare_hashes({ _ary: expected }, { _ary: actual }) .map { |(char, path, *values)| [char, path[1..-1], *values] } end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 95 def compare_hashes(expected, actual) HashDiff.diff(expected, actual, array_path: true, use_lcs: false) \ do |path, exp, act| # Handle missing keys with matcher values. next nil unless nested_key?(actual, path) next exp.matches?(act) if matcher?(exp) end end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 105 def delegate_to_matcher(matcher) @matches = matcher.matches?(actual) return if @matches @failure_message = matcher.failure_message end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 113 def delegate_to_negated_matcher(matcher) @matches = if matcher.respond_to?(:does_not_match?) !matcher.does_not_match?(actual) else matcher.matches?(actual) end return unless @matches @failure_message_when_negated = matcher.failure_message_when_negated end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 126 def diff_arrays diff = compare_arrays(@expected, actual) @matches = diff.empty? @failure_message = format_message(diff) end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 133 def diff_arrays_negated diff = compare_arrays(@expected, actual) @matches = diff.empty? @failure_message_when_negated = "`expect(#{format_expected(@expected)}).not_to be == #{actual.inspect}`" end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 141 def diff_hashes diff = compare_hashes(@expected, actual) @matches = diff.empty? @failure_message = format_message(diff) end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 148 def diff_hashes_negated diff = compare_hashes(@expected, actual) @matches = diff.empty? @failure_message_when_negated = "`expect(#{format_expected(@expected)}).not_to be == #{actual.inspect}`" end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 156 def equality_matcher matchers_delegate.be == @expected end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 160 def format_diff(diff) diff .sort_by { |(char, path, *_values)| [path.map(&:to_s)] } .map { |item| format_diff_item(*item) } .join "\n" end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 167 def format_diff_item(char, path, *values) "#{char} #{format_diff_path(path)} => #{format_diff_values(char, values)}" end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 171 def format_diff_path(path) path.map(&:inspect).join('.') end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 175 def format_diff_values(char, values) case char when '-' "expected #{format_expected(values.first)}" when '~' "expected #{format_expected(values.first)}, got #{values.last.inspect}" when '+' "got #{values.last.inspect}" end end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 186 def format_expected(object) RSpec::Support::ObjectFormatter.format(object) end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 190 def format_message(diff) "expected: == #{format_expected(@expected)}\n" \ " got: #{@actual.inspect}\n" \ "\n" \ "(compared using HashDiff)\n" \ "\n" \ "Diff:\n" \ "#{format_diff(diff)}" end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 200 def matcher?(object) %i[description failure_message failure_message_when_negated matches?] .all? { |method_name| object.respond_to?(method_name) } end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 205 def matchers_delegate Object.new.extend RSpec::Matchers end
# File lib/rspec/sleeping_king_studios/matchers/core/deep_matcher.rb, line 209 def nested_key?(object, path) key = path.last object = object.dig(*path[0...-1]) if path.size > 1 return object.key?(key) if object.is_a?(Hash) return object.size > key if object.is_a?(Array) false end