File: fuzzy_matcher_spec.rb

package info (click to toggle)
ruby-rspec 3.12.0c0e1m1s0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 6,752 kB
  • sloc: ruby: 69,818; sh: 1,861; makefile: 99
file content (215 lines) | stat: -rw-r--r-- 6,842 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
require 'spec_helper'
require 'rspec/support/fuzzy_matcher'

module RSpec
  module Support
    RSpec.describe FuzzyMatcher, ".values_match?" do
      matcher :match_against do |actual|
        match { |expected| FuzzyMatcher.values_match?(expected, actual) }
      end

      it 'returns true when given equal values' do
        expect(1).to match_against(1.0)
      end

      it 'returns false when given unequal values that do not provide match logic' do
        expect(1).not_to match_against(1.1)
      end

      it 'can match a regex against a string' do
        expect(/foo/).to match_against("foobar")
        expect(/foo/).not_to match_against("fobar")
      end

      it 'can match a regex against itself' do
        expect(/foo/).to match_against(/foo/)
        expect(/foo/).not_to match_against(/bar/)
      end

      it 'can match a class against an instance' do
        expect(String).to match_against("foo")
        expect(String).not_to match_against(123)
      end

      it 'can match a class against itself' do
        expect(String).to match_against(String)
        expect(String).not_to match_against(Regexp)
      end

      it 'can match against a matcher' do
        expect(be_within(0.1).of(2)).to match_against(2.05)
        expect(be_within(0.1).of(2)).not_to match_against(2.15)
      end

      it 'does not ask the second argument if it fuzzy matches (===)' do
        expect("foo").not_to match_against(String)
      end

      context "when given two 0-arg lambdas" do
        it 'returns true when given the same lambda' do
          k = lambda { 3 }
          expect(k).to match_against(k)
        end

        it 'returns false when given different lambdas' do
          expect(lambda { 3 }).not_to match_against(lambda { 4 })
        end
      end

      context "when given an object whose implementation of `==` wrongly assumes it will only be called with objects of the same type" do
        Color = Struct.new(:r, :g, :b) do
          def ==(other)
            other.r == r && other.g == g && other.b == b
          end
        end

        before(:context) do
          expect { Color.new(0, 0, 0) == Object.new }.to raise_error(NoMethodError, /undefined method `r'/)
        end

        it 'can match against an expected value that matches anything' do
          anything = Object.new.tap do |o|
            def o.===(*); true; end
          end

          expect(anything).to match_against(Color.new(0, 0, 0))
        end

        it 'surfaces the `NoMethodError` when used as the expected value' do
          expect {
            FuzzyMatcher.values_match?(Color.new(0, 0, 0), Object.new)
          }.to raise_error(NoMethodError, /undefined method `r'/)
        end

        it 'can match against objects of the same type' do
          expect(Color.new(0, 0, 0)).to match_against(Color.new(0, 0, 0))
          expect(Color.new(0, 0, 0)).not_to match_against(Color.new(0, 1, 0))
        end
      end

      context "when given an object whose implementation of `==` raises an ArgumentError" do
        it 'surfaces the error' do
          klass = Class.new do
            attr_accessor :foo
            def ==(other)
              other.foo == foo
            end
          end
          instance = klass.new

          other = Object.new
          def other.foo(arg); end

          expect { instance == other }.to raise_error(ArgumentError)
          expect { FuzzyMatcher.values_match?(instance, other) }.to raise_error(ArgumentError)
        end
      end

      it "does not match a struct against an array" do
        struct = Struct.new(:foo, :bar).new("first", 2)
        expect(["first", 2]).not_to match_against(struct)
      end

      context "when given two arrays" do
        it 'returns true if they have equal values' do
          expect([1, 2.0]).to match_against([1.0, 2])
        end

        it 'returns false when given unequal values that do not provide match logic' do
          expect([1, 2.0]).not_to match_against([1.1, 2])
        end

        it 'does the fuzzy matching on the individual elements' do
          expect([String, Integer]).to match_against(["a", 2])
          expect([String, Integer]).not_to match_against([2, "a"])
        end

        it 'returns false if they have a different number of elements' do
          expect([String, Integer]).not_to match_against(['a', 2, nil])
        end

        it 'supports arbitrary nested arrays' do
          a1 = [
            [String, Integer, [be_within(0.1).of(2)]],
            3, [[[ /foo/ ]]]
          ]

          a2 = [
            ["a", 1, [2.05]],
            3, [[[ "foobar" ]]]
          ]

          expect(a1).to match_against(a2)
          a2[0][2][0] += 1
          expect(a1).not_to match_against(a2)
        end
      end

      it 'can match an array an arbitrary enumerable' do
        my_enum = Class.new do
          include Enumerable

          def each
            yield 1; yield "foo"
          end
        end.new

        expect([Integer, String]).to match_against(my_enum)
        expect([String, Integer]).not_to match_against(my_enum)
      end

      it 'does not match an empty hash against an empty array or vice-versa' do
        expect({}).not_to match_against([])
        expect([]).not_to match_against({})
      end

      context 'when given two hashes' do
        it 'returns true when their keys and values are equal' do
          expect(:a => 5, :b => 2.0).to match_against(:a => 5.0, :b => 2)
        end

        it 'returns false when given unequal values that do not provide match logic' do
          expect(:a => 5).not_to match_against(:a => 5.1)
        end

        it 'does the fuzzy matching on the individual values' do
          expect(:a => String, :b => /bar/).to match_against(:a => "foo", :b => "barn")
          expect(:a => String, :b => /bar/).not_to match_against(:a => "foo", :b => "brn")
        end

        it 'returns false if the expected hash has nil values that are not in the actual hash' do
          expect(:a => 'b', :b => nil).not_to match_against(:a => "b")
        end

        it 'returns false if actual hash has extra entries' do
          expect(:a => 'b').not_to match_against(:a => "b", :b => nil)
        end

        it 'does not fuzzy match on keys' do
          expect(/foo/ => 1).not_to match_against("foo" => 1)
        end

        it 'supports arbitrary nested hashes' do
          h1 = {
            :a => {
              :b => [String, Integer],
              :c => { :d => be_within(0.1).of(2) }
            }
          }

          h2 = {
            :a => {
              :b => ["foo", 5],
              :c => { :d => 2.05 }
            }
          }

          expect(h1).to match_against(h2)
          h2[:a][:c][:d] += 1
          expect(h1).not_to match_against(h2)
        end
      end
    end
  end
end