Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/super_diff/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,22 @@ def self.a_hash_including_something?(value)
value.expecteds.first.is_a?(::Hash)
end

# HINT: `a_hash_including` is an alias of `include` in the rspec-expectations gem.
# `hash_including` is an argument matcher in the rspec-mocks gem.
def self.hash_including_something?(value)
value.is_a?(::RSpec::Mocks::ArgumentMatchers::HashIncludingMatcher)
end

def self.a_collection_including_something?(value)
fuzzy_object?(value) &&
value.respond_to?(:expecteds) &&
!(value.expecteds.one? && value.expecteds.first.is_a?(::Hash))
end

def self.array_including_something?(value)
value.is_a?(::RSpec::Mocks::ArgumentMatchers::ArrayIncludingMatcher)
end

def self.an_object_having_some_attributes?(value)
fuzzy_object?(value) &&
value.base_matcher.is_a?(::RSpec::Matchers::BuiltIn::HaveAttributes)
Expand All @@ -47,11 +57,23 @@ def self.a_kind_of_something?(value)
value.base_matcher.is_a?(::RSpec::Matchers::BuiltIn::BeAKindOf)
end

# HINT: `a_kind_of` is a matcher in the rspec-expectations gem.
# `kind_of` is an argument matcher in the rspec-mocks gem.
def self.kind_of_something?(value)
value.is_a?(::RSpec::Mocks::ArgumentMatchers::KindOf)
end

def self.an_instance_of_something?(value)
fuzzy_object?(value) &&
value.base_matcher.is_a?(::RSpec::Matchers::BuiltIn::BeAnInstanceOf)
end

# HINT: `an_instance_of` is a matcher in the rspec-expectations gem.
# `instance_of` is an argument matcher in the rspec-mocks gem.
def self.instance_of_something?(value)
value.is_a?(::RSpec::Mocks::ArgumentMatchers::InstanceOf)
end

def self.a_value_within_something?(value)
fuzzy_object?(value) &&
value.base_matcher.is_a?(::RSpec::Matchers::BuiltIn::BeWithin)
Expand Down
6 changes: 4 additions & 2 deletions lib/super_diff/rspec/differs/collection_including.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module RSpec
module Differs
class CollectionIncluding < SuperDiff::Differs::Array
def self.applies_to?(expected, actual)
SuperDiff::RSpec.a_collection_including_something?(expected) &&
actual.is_a?(::Array)
(
SuperDiff::RSpec.a_collection_including_something?(expected) ||
SuperDiff::RSpec.array_including_something?(expected)
) && actual.is_a?(::Array)
end

private
Expand Down
6 changes: 4 additions & 2 deletions lib/super_diff/rspec/differs/hash_including.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module RSpec
module Differs
class HashIncluding < SuperDiff::Differs::Hash
def self.applies_to?(expected, actual)
SuperDiff::RSpec.a_hash_including_something?(expected) &&
actual.is_a?(::Hash)
(
SuperDiff::RSpec.a_hash_including_something?(expected) ||
SuperDiff::RSpec.hash_including_something?(expected)
) && actual.is_a?(::Hash)
end

private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module ObjectInspection
module Inspectors
class CollectionIncluding < SuperDiff::ObjectInspection::Inspectors::Base
def self.applies_to?(value)
SuperDiff::RSpec.a_collection_including_something?(value)
SuperDiff::RSpec.a_collection_including_something?(value) || SuperDiff::RSpec.array_including_something?(value)
end

protected
Expand All @@ -13,8 +13,13 @@ def inspection_tree
SuperDiff::ObjectInspection::InspectionTree.new do
add_text "#<a collection including ("

nested do |aliased_matcher|
insert_array_inspection_of(aliased_matcher.expecteds)
nested do |value|
array = if SuperDiff::RSpec.a_collection_including_something?(value)
value.expecteds
else
value.instance_variable_get(:@expected)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this is a bad practice, but unfortunately, the rspec-mocks matcher didn't have a public interface to access the values.
https://github.com/rspec/rspec-mocks/blob/v3.10.2/lib/rspec/mocks/argument_matchers.rb#L231-L259

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! (I also do this in other places)

end
insert_array_inspection_of(array)
end

add_break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module ObjectInspection
module Inspectors
class HashIncluding < SuperDiff::ObjectInspection::Inspectors::Base
def self.applies_to?(value)
SuperDiff::RSpec.a_hash_including_something?(value)
SuperDiff::RSpec.a_hash_including_something?(value) || SuperDiff::RSpec.hash_including_something?(value)
end

protected
Expand All @@ -13,9 +13,14 @@ def inspection_tree
SuperDiff::ObjectInspection::InspectionTree.new do
add_text "#<a hash including ("

nested do |aliased_matcher|
nested do |value|
hash = if SuperDiff::RSpec.a_hash_including_something?(value)
value.expecteds.first
else
value.instance_variable_get(:@expected)
end
insert_hash_inspection_of(
aliased_matcher.expecteds.first,
hash,
initial_break: nil,
)
end
Expand Down
11 changes: 8 additions & 3 deletions lib/super_diff/rspec/object_inspection/inspectors/instance_of.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ module ObjectInspection
module Inspectors
class InstanceOf < SuperDiff::ObjectInspection::Inspectors::Base
def self.applies_to?(value)
SuperDiff::RSpec.an_instance_of_something?(value)
SuperDiff::RSpec.an_instance_of_something?(value) || SuperDiff::RSpec.instance_of_something?(value)
end

protected

def inspection_tree
SuperDiff::ObjectInspection::InspectionTree.new do
add_text do |aliased_matcher|
"#<an instance of #{aliased_matcher.expected}>"
add_text do |value|
klass = if SuperDiff::RSpec.an_instance_of_something?(value)
value.expected
else
value.instance_variable_get(:@klass)
end
"#<an instance of #{klass}>"
end
end
end
Expand Down
11 changes: 8 additions & 3 deletions lib/super_diff/rspec/object_inspection/inspectors/kind_of.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ module ObjectInspection
module Inspectors
class KindOf < SuperDiff::ObjectInspection::Inspectors::Base
def self.applies_to?(value)
SuperDiff::RSpec.a_kind_of_something?(value)
SuperDiff::RSpec.a_kind_of_something?(value) || SuperDiff::RSpec.kind_of_something?(value)
end

protected

def inspection_tree
SuperDiff::ObjectInspection::InspectionTree.new do
add_text do |aliased_matcher|
"#<a kind of #{aliased_matcher.expected}>"
add_text do |value|
klass = if SuperDiff::RSpec.a_kind_of_something?(value)
value.expected
else
value.instance_variable_get(:@klass)
end
"#<a kind of #{klass}>"
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ module RSpec
module OperationTreeBuilders
class CollectionIncluding < SuperDiff::OperationTreeBuilders::Array
def self.applies_to?(expected, actual)
SuperDiff::RSpec.a_collection_including_something?(expected) &&
actual.is_a?(::Array)
(
SuperDiff::RSpec.a_collection_including_something?(expected) ||
SuperDiff::RSpec.array_including_something?(expected)
) && actual.is_a?(::Array)
end

def initialize(expected:, actual:, **rest)
Expand All @@ -16,7 +18,12 @@ def initialize(expected:, actual:, **rest)
private

def actual_with_extra_items_in_expected_at_end
actual + (expected.expecteds - actual)
value = if SuperDiff::RSpec.a_collection_including_something?(expected)
expected.expecteds
else
expected.instance_variable_get(:@expected)
end
actual + (value - actual)
end
end
end
Expand Down
13 changes: 10 additions & 3 deletions lib/super_diff/rspec/operation_tree_builders/hash_including.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ module RSpec
module OperationTreeBuilders
class HashIncluding < SuperDiff::OperationTreeBuilders::Hash
def self.applies_to?(expected, actual)
SuperDiff::RSpec.a_hash_including_something?(expected) &&
actual.is_a?(::Hash)
(
SuperDiff::RSpec.a_hash_including_something?(expected) ||
SuperDiff::RSpec.hash_including_something?(expected)
) && actual.is_a?(::Hash)
end

def initialize(expected:, **rest)
super(expected: expected.expecteds.first, **rest)
hash = if SuperDiff::RSpec.a_hash_including_something?(expected)
expected.expecteds.first
else
expected.instance_variable_get(:@expected)
end
super(expected: hash, **rest)
end

private
Expand Down
83 changes: 83 additions & 0 deletions spec/integration/rspec/match_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,48 @@
end
end

# HINT: `a_hash_including` is an alias of `include` in the rspec-expectations gem.
# `hash_including` is an argument matcher in the rspec-mocks gem.
context "when the expected value is `hash-including-<something>`, not `a-hash-including-<something>`" do
it "produces the correct failure message" do
as_both_colored_and_uncolored do |color_enabled|
snippet = <<~TEST.strip
expected = hash_including(city: "Hill Valley")
actual = { city: "Burbank" }
expect(actual).to match(expected)
TEST
program = make_plain_test_program(
snippet,
color_enabled: color_enabled,
)

expected_output = build_expected_output(
color_enabled: color_enabled,
snippet: %|expect(actual).to match(expected)|,
expectation: proc {
line do
plain %|Expected |
actual %|{ city: "Burbank" }|
plain %| to match |
expected %|#<a hash including (city: "Hill Valley")>|
plain %|.|
end
},
diff: proc {
plain_line %| {|
expected_line %|- city: "Hill Valley"|
actual_line %|+ city: "Burbank"|
plain_line %| }|
},
)

expect(program).
to produce_output_when_run(expected_output).
in_color(color_enabled)
end
end
end

context "when the expected value is a collection-including-<something>" do
context "that is small" do
it "produces the correct failure message when used in the positive" do
Expand Down Expand Up @@ -647,6 +689,47 @@
end
end

context "when the expected value is an array-including-<something>" do
it "produces the correct failure message" do
as_both_colored_and_uncolored do |color_enabled|
snippet = <<~TEST.strip
expected = array_including("a")
actual = ["b"]
expect(actual).to match(expected)
TEST
program = make_plain_test_program(
snippet,
color_enabled: color_enabled,
)

expected_output = build_expected_output(
color_enabled: color_enabled,
snippet: %|expect(actual).to match(expected)|,
expectation: proc {
line do
plain %|Expected |
actual %|["b"]|
plain %| to match |
expected %|#<a collection including ("a")>|
plain %|.|
end
},
diff: proc {
plain_line %| [|
plain_line %| "b"|
# expected_line %|- "a",| # FIXME
expected_line %|- "a"|
plain_line %| ]|
},
)

expect(program).
to produce_output_when_run(expected_output).
in_color(color_enabled)
end
end
end

context "when the expected value is an object-having-attributes" do
context "that is small" do
it "produces the correct failure message when used in the positive" do
Expand Down