matchi 3.0.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16b38ee69034c01d5f2961a5d4e14b6c150cad89a9368f7c0cf84071761e2999
4
- data.tar.gz: f76f7a3286443d49f6e449af9ffefdb2f38b9d974aada733e1a611d75c627199
3
+ metadata.gz: 5596c7ba2d1153eadaa1ba438b009c0cc17954a360b4192bb6419c674d864822
4
+ data.tar.gz: 0c717e7642961b8707433584c58a1e2369f810c11114caa7530c890563df78a3
5
5
  SHA512:
6
- metadata.gz: ed8abd4dd002520dc4740fc80b91573098e3242319e74b807e6621482beb131d4a40e299584fc507bacbb806867a00691a3f4bdcebf611dd2c45eb1b90e9e76e
7
- data.tar.gz: 3fd0b621816f91d0569fdebeb4a8c80c55890f83250ed87b0df2cd1b7683e02f390df159f372f6e87f62aab853b7d8042d438f1efc31f83fd3b169a12eab2b8e
6
+ metadata.gz: 8e89b77b4f7fd25fc53ea570b865b3479be5a3291bc437f55b3ea126111fdd120afd907439ec87c565bbb28b4fe6e7be34a932a218e55933bb3d4bb890b0edcf
7
+ data.tar.gz: f19a1f799d1b8f9c1f40dc747c00863ae1cd9bf1ed545aabdea774e4c658a242a74d2bd6f03709fe124786f327fd7401b104aa9e567496265b966937c61841e3
data/README.md CHANGED
@@ -12,9 +12,9 @@
12
12
 
13
13
  ## Project goals
14
14
 
15
- * Provide a collection of useful generic matchers.
16
15
  * Adding matchers should be as simple as possible.
17
16
  * Being framework agnostic and easy to integrate.
17
+ * Avoid false positives/negatives due to malicious actual values.
18
18
 
19
19
  ## Installation
20
20
 
@@ -52,16 +52,20 @@ All examples here assume that this has been done.
52
52
 
53
53
  ### Anatomy of a matcher
54
54
 
55
- A __Matchi__ matcher is just an object that responds to the `matches?` method with a block as argument, and returns a boolean. That's all it is.
55
+ A __Matchi__ matcher is an object that must respond to the `matches?` method with a block as argument, and return a boolean.
56
56
 
57
- But let's see some examples.
57
+ To facilitate the integration of the matchers in other tools, __Matchi__ matchers may expose expected values via the `expected` method.
58
58
 
59
59
  ### Built-in matchers
60
60
 
61
+ Here is the collection of useful generic matchers.
62
+
61
63
  **Equivalence** matcher:
62
64
 
63
65
  ```ruby
64
66
  matcher = Matchi::Eq.new("foo")
67
+
68
+ matcher.expected # => "foo"
65
69
  matcher.matches? { "foo" } # => true
66
70
  ```
67
71
 
@@ -69,20 +73,35 @@ matcher.matches? { "foo" } # => true
69
73
 
70
74
  ```ruby
71
75
  matcher = Matchi::Be.new(:foo)
76
+
77
+ matcher.expected # => :foo
72
78
  matcher.matches? { :foo } # => true
73
79
  ```
74
80
 
81
+ **Comparisons** matcher:
82
+
83
+ ```ruby
84
+ matcher = Matchi::BeWithin.new(8).of(37)
85
+
86
+ matcher.expected # => 37
87
+ matcher.matches? { 42 } # => true
88
+ ```
89
+
75
90
  **Regular expressions** matcher:
76
91
 
77
92
  ```ruby
78
93
  matcher = Matchi::Match.new(/^foo$/)
94
+
95
+ matcher.expected # => /^foo$/
79
96
  matcher.matches? { "foo" } # => true
80
97
  ```
81
98
 
82
99
  **Expecting errors** matcher:
83
100
 
84
101
  ```ruby
85
- matcher = Matchi::RaiseException.new(NameError)
102
+ matcher = Matchi::RaiseException.new(:NameError)
103
+
104
+ matcher.expected # => "NameError"
86
105
  matcher.matches? { Boom } # => true
87
106
  ```
88
107
 
@@ -90,30 +109,56 @@ matcher.matches? { Boom } # => true
90
109
 
91
110
  ```ruby
92
111
  matcher = Matchi::BeAnInstanceOf.new(:String)
112
+
113
+ matcher.expected # => "String"
93
114
  matcher.matches? { "foo" } # => true
94
115
  ```
95
116
 
117
+ **Predicate** matcher:
118
+
119
+ ```ruby
120
+ matcher = Matchi::Predicate.new(:be_empty)
121
+
122
+ matcher.expected # => [:empty?, [], {}, nil]
123
+ matcher.matches? { [] } # => true
124
+
125
+ matcher = Matchi::Predicate.new(:have_key, :foo)
126
+
127
+ matcher.expected # => [:has_key?, [:foo], {}, nil]
128
+ matcher.matches? { { foo: 42 } } # => true
129
+ ```
130
+
96
131
  **Change** matcher:
97
132
 
98
133
  ```ruby
99
134
  object = []
100
135
  matcher = Matchi::Change.new(object, :length).by(1)
136
+
137
+ matcher.expected # => 1
101
138
  matcher.matches? { object << 1 } # => true
102
139
 
103
140
  object = []
104
141
  matcher = Matchi::Change.new(object, :length).by_at_least(1)
142
+
143
+ matcher.expected # => 1
105
144
  matcher.matches? { object << 1 } # => true
106
145
 
107
146
  object = []
108
147
  matcher = Matchi::Change.new(object, :length).by_at_most(1)
148
+
149
+ matcher.expected # => 1
109
150
  matcher.matches? { object << 1 } # => true
110
151
 
111
152
  object = "foo"
112
153
  matcher = Matchi::Change.new(object, :to_s).from("foo").to("FOO")
154
+
155
+ matcher.expected # => "FOO"
113
156
  matcher.matches? { object.upcase! } # => true
114
157
 
115
158
  object = "foo"
116
159
  matcher = Matchi::Change.new(object, :to_s).to("FOO")
160
+
161
+ matcher.expected # => "FOO"
117
162
  matcher.matches? { object.upcase! } # => true
118
163
  ```
119
164
 
@@ -121,6 +166,8 @@ matcher.matches? { object.upcase! } # => true
121
166
 
122
167
  ```ruby
123
168
  matcher = Matchi::Satisfy.new { |value| value == 42 }
169
+
170
+ matcher.expected # => #<Proc:0x00007fbaafc65540>
124
171
  matcher.matches? { 42 } # => true
125
172
  ```
126
173
 
@@ -133,13 +180,19 @@ A **Be the answer** matcher:
133
180
  ```ruby
134
181
  module Matchi
135
182
  class BeTheAnswer
183
+ def expected
184
+ 42
185
+ end
186
+
136
187
  def matches?
137
- 42.equal?(yield)
188
+ expected.equal?(yield)
138
189
  end
139
190
  end
140
191
  end
141
192
 
142
193
  matcher = Matchi::BeTheAnswer.new
194
+
195
+ matcher.expected # => 42
143
196
  matcher.matches? { 42 } # => true
144
197
  ```
145
198
 
@@ -157,6 +210,7 @@ module Matchi
157
210
  end
158
211
 
159
212
  matcher = Matchi::BePrime.new
213
+
160
214
  matcher.matches? { 42 } # => false
161
215
  ```
162
216
 
@@ -178,6 +232,8 @@ module Matchi
178
232
  end
179
233
 
180
234
  matcher = Matchi::StartWith.new("foo")
235
+
236
+ matcher.expected # => "foo"
181
237
  matcher.matches? { "foobar" } # => true
182
238
  ```
183
239
 
data/lib/matchi/be.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  module Matchi
4
4
  # *Identity* matcher.
5
5
  class Be
6
+ # @return [#equal?] The expected identical object.
7
+ attr_reader :expected
8
+
6
9
  # Initialize the matcher with an object.
7
10
  #
8
11
  # @example
@@ -21,24 +24,26 @@ module Matchi
21
24
  # require "matchi/be"
22
25
  #
23
26
  # matcher = Matchi::Be.new(:foo)
27
+ #
28
+ # matcher.expected # => :foo
24
29
  # matcher.matches? { :foo } # => true
25
30
  #
26
31
  # @yieldreturn [#object_id] The actual value to compare to the expected
27
32
  # one.
28
33
  #
29
34
  # @return [Boolean] Comparison between actual and expected values.
30
- def matches?(*, **)
31
- @expected.equal?(yield)
35
+ def matches?
36
+ expected.equal?(yield)
32
37
  end
33
38
 
34
39
  # A string containing a human-readable representation of the matcher.
35
40
  def inspect
36
- "#{self.class}(#{@expected.inspect})"
41
+ "#{self.class}(#{expected.inspect})"
37
42
  end
38
43
 
39
44
  # Returns a string representing the matcher.
40
45
  def to_s
41
- "be #{@expected.inspect}"
46
+ "be #{expected.inspect}"
42
47
  end
43
48
  end
44
49
  end
@@ -3,6 +3,9 @@
3
3
  module Matchi
4
4
  # *Type/class* matcher.
5
5
  class BeAnInstanceOf
6
+ # @return [String] The expected class name.
7
+ attr_reader :expected
8
+
6
9
  # Initialize the matcher with (the name of) a class or module.
7
10
  #
8
11
  # @example
@@ -10,9 +13,9 @@ module Matchi
10
13
  #
11
14
  # Matchi::BeAnInstanceOf.new(String)
12
15
  #
13
- # @param expected [#to_s] A (name of a) class or module.
16
+ # @param expected [Class, #to_s] The expected class name.
14
17
  def initialize(expected)
15
- @expected = String(expected).to_sym
18
+ @expected = String(expected)
16
19
  end
17
20
 
18
21
  # Boolean comparison between the class of the actual value and the
@@ -22,23 +25,25 @@ module Matchi
22
25
  # require "matchi/be_an_instance_of"
23
26
  #
24
27
  # matcher = Matchi::BeAnInstanceOf.new(String)
28
+ #
29
+ # matcher.expected # => "String"
25
30
  # matcher.matches? { "foo" } # => true
26
31
  #
27
32
  # @yieldreturn [#class] the actual value to compare to the expected one.
28
33
  #
29
34
  # @return [Boolean] Comparison between actual and expected values.
30
- def matches?(*, **)
31
- self.class.const_get(@expected).equal?(yield.class)
35
+ def matches?
36
+ self.class.const_get(expected).equal?(yield.class)
32
37
  end
33
38
 
34
39
  # A string containing a human-readable representation of the matcher.
35
40
  def inspect
36
- "#{self.class}(#{@expected})"
41
+ "#{self.class}(#{expected})"
37
42
  end
38
43
 
39
44
  # Returns a string representing the matcher.
40
45
  def to_s
41
- "be an instance of #{@expected}"
46
+ "be an instance of #{expected}"
42
47
  end
43
48
  end
44
49
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("be_within", "of")
4
+
5
+ module Matchi
6
+ # Wraps the target of a be_within matcher.
7
+ class BeWithin
8
+ # Initialize a wrapper of the be_within matcher with a numeric value.
9
+ #
10
+ # @example
11
+ # require "matchi/be_within"
12
+ #
13
+ # Matchi::BeWithin.new(1)
14
+ #
15
+ # @param delta [Numeric] A numeric value.
16
+ def initialize(delta)
17
+ @delta = delta
18
+ end
19
+
20
+ # Specifies an expected numeric value.
21
+ #
22
+ # @example
23
+ # require "matchi/be_within"
24
+ #
25
+ # be_within_wrapper = Matchi::BeWithin.new(1)
26
+ # be_within_wrapper.of(41)
27
+ #
28
+ # @param expected [Numeric] The expected value.
29
+ #
30
+ # @return [#matches?] A *be_within of* matcher.
31
+ def of(expected)
32
+ Of.new(@delta, expected)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ class BeWithin
5
+ # *BeWithin of* matcher.
6
+ class Of
7
+ # @return [Numeric] An expected value.
8
+ attr_reader :expected
9
+
10
+ # Initialize the matcher with a delta and an expected value.
11
+ #
12
+ # @example
13
+ # require "matchi/be_within/of"
14
+ #
15
+ # Matchi::BeWithin::Of.new(1, 41)
16
+ #
17
+ # @param delta [Numeric] The accepted variation of the actual value.
18
+ # @param expected [Numeric] The expected value.
19
+ def initialize(delta, expected)
20
+ @delta = delta
21
+ @expected = expected
22
+ end
23
+
24
+ # Boolean comparison on the expected be_within by comparing the actual
25
+ # value and the expected value.
26
+ #
27
+ # @example
28
+ # require "matchi/be_within/of"
29
+ #
30
+ # matcher = Matchi::BeWithin::Of.new(1, 41)
31
+ #
32
+ # matcher.expected # => 41
33
+ # matcher.matches? { 42 } # => true
34
+ #
35
+ # @yieldreturn [Numeric] The block of code to execute.
36
+ #
37
+ # @return [Boolean] Comparison between the actual and the expected values.
38
+ def matches?
39
+ (expected - yield).abs <= @delta
40
+ end
41
+
42
+ # A string containing a human-readable representation of the matcher.
43
+ def inspect
44
+ "#{self.class}(#{@delta}, #{expected})"
45
+ end
46
+
47
+ # Returns a string representing the matcher.
48
+ def to_s
49
+ "be within #{@delta} of #{expected}"
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/matchi/change.rb CHANGED
@@ -32,14 +32,14 @@ module Matchi
32
32
  #
33
33
  # object = []
34
34
  #
35
- # change = Matchi::Change.new(object, :length)
36
- # change.by_at_least(1)
35
+ # change_wrapper = Matchi::Change.new(object, :length)
36
+ # change_wrapper.by_at_least(1)
37
37
  #
38
- # @param expected [#object_id] The minimum delta of the expected change.
38
+ # @param minimum_delta [#object_id] The minimum delta of the expected change.
39
39
  #
40
40
  # @return [#matches?] A *change by at least* matcher.
41
- def by_at_least(expected)
42
- ByAtLeast.new(expected, &@state)
41
+ def by_at_least(minimum_delta)
42
+ ByAtLeast.new(minimum_delta, &@state)
43
43
  end
44
44
 
45
45
  # Specifies a maximum delta of the expected change.
@@ -49,14 +49,14 @@ module Matchi
49
49
  #
50
50
  # object = []
51
51
  #
52
- # change = Matchi::Change.new(object, :length)
53
- # change.by_at_most(1)
52
+ # change_wrapper = Matchi::Change.new(object, :length)
53
+ # change_wrapper.by_at_most(1)
54
54
  #
55
- # @param expected [#object_id] The maximum delta of the expected change.
55
+ # @param maximum_delta [#object_id] The maximum delta of the expected change.
56
56
  #
57
57
  # @return [#matches?] A *change by at most* matcher.
58
- def by_at_most(expected)
59
- ByAtMost.new(expected, &@state)
58
+ def by_at_most(maximum_delta)
59
+ ByAtMost.new(maximum_delta, &@state)
60
60
  end
61
61
 
62
62
  # Specifies the delta of the expected change.
@@ -66,14 +66,14 @@ module Matchi
66
66
  #
67
67
  # object = []
68
68
  #
69
- # change = Matchi::Change.new(object, :length)
70
- # change.by(1)
69
+ # change_wrapper = Matchi::Change.new(object, :length)
70
+ # change_wrapper.by(1)
71
71
  #
72
- # @param expected [#object_id] The delta of the expected change.
72
+ # @param delta [#object_id] The delta of the expected change.
73
73
  #
74
74
  # @return [#matches?] A *change by* matcher.
75
- def by(expected)
76
- By.new(expected, &@state)
75
+ def by(delta)
76
+ By.new(delta, &@state)
77
77
  end
78
78
 
79
79
  # Specifies the original value.
@@ -81,14 +81,14 @@ module Matchi
81
81
  # @example
82
82
  # require "matchi/change"
83
83
  #
84
- # change = Matchi::Change.new("foo", :to_s)
85
- # change.from("foo")
84
+ # change_wrapper = Matchi::Change.new("foo", :to_s)
85
+ # change_wrapper.from("foo")
86
86
  #
87
- # @param expected [#object_id] The original value.
87
+ # @param old_value [#object_id] The original value.
88
88
  #
89
89
  # @return [#matches?] A *change from* wrapper.
90
- def from(expected)
91
- From.new(expected, &@state)
90
+ def from(old_value)
91
+ From.new(old_value, &@state)
92
92
  end
93
93
 
94
94
  # Specifies the new value to expect.
@@ -96,14 +96,14 @@ module Matchi
96
96
  # @example
97
97
  # require "matchi/change"
98
98
  #
99
- # change = Matchi::Change.new("foo", :to_s)
100
- # change.to("FOO")
99
+ # change_wrapper = Matchi::Change.new("foo", :to_s)
100
+ # change_wrapper.to("FOO")
101
101
  #
102
- # @param expected [#object_id] The new value to expect.
102
+ # @param new_value [#object_id] The new value to expect.
103
103
  #
104
104
  # @return [#matches?] A *change to* matcher.
105
- def to(expected)
106
- To.new(expected, &@state)
105
+ def to(new_value)
106
+ To.new(new_value, &@state)
107
107
  end
108
108
  end
109
109
  end
@@ -4,6 +4,9 @@ module Matchi
4
4
  class Change
5
5
  # *Change by* matcher.
6
6
  class By
7
+ # @return [#object_id] An expected delta.
8
+ attr_reader :expected
9
+
7
10
  # Initialize the matcher with an object and a block.
8
11
  #
9
12
  # @example
@@ -30,28 +33,30 @@ module Matchi
30
33
  # object = []
31
34
  #
32
35
  # matcher = Matchi::Change::By.new(1) { object.length }
36
+ #
37
+ # matcher.expected # => 1
33
38
  # matcher.matches? { object << "foo" } # => true
34
39
  #
35
40
  # @yieldreturn [#object_id] The block of code to execute.
36
41
  #
37
42
  # @return [Boolean] Comparison between the value before and after the
38
43
  # code execution.
39
- def matches?(*, **)
44
+ def matches?
40
45
  value_before = @state.call
41
46
  yield
42
47
  value_after = @state.call
43
48
 
44
- @expected == (value_after - value_before)
49
+ expected == (value_after - value_before)
45
50
  end
46
51
 
47
52
  # A string containing a human-readable representation of the matcher.
48
53
  def inspect
49
- "#{self.class}(#{@expected.inspect})"
54
+ "#{self.class}(#{expected.inspect})"
50
55
  end
51
56
 
52
57
  # Returns a string representing the matcher.
53
58
  def to_s
54
- "change by #{@expected.inspect}"
59
+ "change by #{expected.inspect}"
55
60
  end
56
61
  end
57
62
  end
@@ -4,6 +4,9 @@ module Matchi
4
4
  class Change
5
5
  # *Change by at least* matcher.
6
6
  class ByAtLeast
7
+ # @return [#object_id] An expected delta.
8
+ attr_reader :expected
9
+
7
10
  # Initialize the matcher with an object and a block.
8
11
  #
9
12
  # @example
@@ -30,28 +33,30 @@ module Matchi
30
33
  # object = []
31
34
  #
32
35
  # matcher = Matchi::Change::ByAtLeast.new(1) { object.length }
36
+ #
37
+ # matcher.expected # => 1
33
38
  # matcher.matches? { object << "foo" } # => true
34
39
  #
35
40
  # @yieldreturn [#object_id] The block of code to execute.
36
41
  #
37
42
  # @return [Boolean] Comparison between the value before and after the
38
43
  # code execution.
39
- def matches?(*, **)
44
+ def matches?
40
45
  value_before = @state.call
41
46
  yield
42
47
  value_after = @state.call
43
48
 
44
- @expected <= (value_after - value_before)
49
+ expected <= (value_after - value_before)
45
50
  end
46
51
 
47
52
  # A string containing a human-readable representation of the matcher.
48
53
  def inspect
49
- "#{self.class}(#{@expected.inspect})"
54
+ "#{self.class}(#{expected.inspect})"
50
55
  end
51
56
 
52
57
  # Returns a string representing the matcher.
53
58
  def to_s
54
- "change by at least #{@expected.inspect}"
59
+ "change by at least #{expected.inspect}"
55
60
  end
56
61
  end
57
62
  end
@@ -4,6 +4,9 @@ module Matchi
4
4
  class Change
5
5
  # *Change by at most* matcher.
6
6
  class ByAtMost
7
+ # @return [#object_id] An expected delta.
8
+ attr_reader :expected
9
+
7
10
  # Initialize the matcher with an object and a block.
8
11
  #
9
12
  # @example
@@ -30,28 +33,30 @@ module Matchi
30
33
  # object = []
31
34
  #
32
35
  # matcher = Matchi::Change::ByAtMost.new(1) { object.length }
36
+ #
37
+ # matcher.expected # => 1
33
38
  # matcher.matches? { object << "foo" } # => true
34
39
  #
35
40
  # @yieldreturn [#object_id] The block of code to execute.
36
41
  #
37
42
  # @return [Boolean] Comparison between the value before and after the
38
43
  # code execution.
39
- def matches?(*, **)
44
+ def matches?
40
45
  value_before = @state.call
41
46
  yield
42
47
  value_after = @state.call
43
48
 
44
- @expected >= (value_after - value_before)
49
+ expected >= (value_after - value_before)
45
50
  end
46
51
 
47
52
  # A string containing a human-readable representation of the matcher.
48
53
  def inspect
49
- "#{self.class}(#{@expected.inspect})"
54
+ "#{self.class}(#{expected.inspect})"
50
55
  end
51
56
 
52
57
  # Returns a string representing the matcher.
53
58
  def to_s
54
- "change by at most #{@expected.inspect}"
59
+ "change by at most #{expected.inspect}"
55
60
  end
56
61
  end
57
62
  end
@@ -30,8 +30,8 @@ module Matchi
30
30
  #
31
31
  # object = "foo"
32
32
  #
33
- # change = Matchi::Change::From.new("foo") { object.to_s }
34
- # change.to("FOO")
33
+ # change_from_wrapper = Matchi::Change::From.new("foo") { object.to_s }
34
+ # change_from_wrapper.to("FOO")
35
35
  #
36
36
  # @param expected_new_value [#object_id] The new value to expect.
37
37
  #
@@ -5,6 +5,9 @@ module Matchi
5
5
  class From
6
6
  # *Change from to* matcher.
7
7
  class To
8
+ # @return [#object_id] An expected new value.
9
+ attr_reader :expected
10
+
8
11
  # Initialize the matcher with two objects and a block.
9
12
  #
10
13
  # @example
@@ -33,30 +36,32 @@ module Matchi
33
36
  # object = "foo"
34
37
  #
35
38
  # matcher = Matchi::Change::From::To.new("foo", "FOO") { object.to_s }
39
+ #
40
+ # matcher.expected # => "FOO"
36
41
  # matcher.matches? { object.upcase! } # => true
37
42
  #
38
43
  # @yieldreturn [#object_id] The block of code to execute.
39
44
  #
40
45
  # @return [Boolean] Comparison between the value before and after the
41
46
  # code execution.
42
- def matches?(*, **)
47
+ def matches?
43
48
  value_before = @state.call
44
49
  return false unless @expected_init == value_before
45
50
 
46
51
  yield
47
52
  value_after = @state.call
48
53
 
49
- @expected == value_after
54
+ expected == value_after
50
55
  end
51
56
 
52
57
  # A string containing a human-readable representation of the matcher.
53
58
  def inspect
54
- "#{self.class}(#{@expected_init.inspect}, #{@expected.inspect})"
59
+ "#{self.class}(#{@expected_init.inspect}, #{expected.inspect})"
55
60
  end
56
61
 
57
62
  # Returns a string representing the matcher.
58
63
  def to_s
59
- "change from #{@expected_init.inspect} to #{@expected.inspect}"
64
+ "change from #{@expected_init.inspect} to #{expected.inspect}"
60
65
  end
61
66
  end
62
67
  end
@@ -4,6 +4,9 @@ module Matchi
4
4
  class Change
5
5
  # *Change to* matcher.
6
6
  class To
7
+ # @return [#object_id] An expected new value.
8
+ attr_reader :expected
9
+
7
10
  # Initialize the matcher with an object and a block.
8
11
  #
9
12
  # @example
@@ -13,7 +16,7 @@ module Matchi
13
16
  #
14
17
  # Matchi::Change::To.new("FOO") { object.to_s }
15
18
  #
16
- # @param expected [#object_id] An expected result value.
19
+ # @param expected [#object_id] An expected new value.
17
20
  # @param state [Proc] A block of code to execute to get the
18
21
  # state of the object.
19
22
  def initialize(expected, &state)
@@ -30,27 +33,29 @@ module Matchi
30
33
  # object = "foo"
31
34
  #
32
35
  # matcher = Matchi::Change::To.new("FOO") { object.to_s }
36
+ #
37
+ # matcher.expected # => "FOO"
33
38
  # matcher.matches? { object.upcase! } # => true
34
39
  #
35
40
  # @yieldreturn [#object_id] The block of code to execute.
36
41
  #
37
42
  # @return [Boolean] Comparison between the value before and after the
38
43
  # code execution.
39
- def matches?(*, **)
44
+ def matches?
40
45
  yield
41
46
  value_after = @state.call
42
47
 
43
- @expected == value_after
48
+ expected == value_after
44
49
  end
45
50
 
46
51
  # A string containing a human-readable representation of the matcher.
47
52
  def inspect
48
- "#{self.class}(#{@expected.inspect})"
53
+ "#{self.class}(#{expected.inspect})"
49
54
  end
50
55
 
51
56
  # Returns a string representing the matcher.
52
57
  def to_s
53
- "change to #{@expected.inspect}"
58
+ "change to #{expected.inspect}"
54
59
  end
55
60
  end
56
61
  end
data/lib/matchi/eq.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  module Matchi
4
4
  # *Equivalence* matcher.
5
5
  class Eq
6
+ # @return [#eql?] An expected equivalent object.
7
+ attr_reader :expected
8
+
6
9
  # Initialize the matcher with an object.
7
10
  #
8
11
  # @example
@@ -21,24 +24,26 @@ module Matchi
21
24
  # require "matchi/eq"
22
25
  #
23
26
  # matcher = Matchi::Eq.new("foo")
27
+ #
28
+ # matcher.expected # => "foo"
24
29
  # matcher.matches? { "foo" } # => true
25
30
  #
26
31
  # @yieldreturn [#object_id] The actual value to compare to the expected
27
32
  # one.
28
33
  #
29
34
  # @return [Boolean] Comparison between actual and expected values.
30
- def matches?(*, **)
31
- @expected.eql?(yield)
35
+ def matches?
36
+ expected.eql?(yield)
32
37
  end
33
38
 
34
39
  # A string containing a human-readable representation of the matcher.
35
40
  def inspect
36
- "#{self.class}(#{@expected.inspect})"
41
+ "#{self.class}(#{expected.inspect})"
37
42
  end
38
43
 
39
44
  # Returns a string representing the matcher.
40
45
  def to_s
41
- "eq #{@expected.inspect}"
46
+ "eq #{expected.inspect}"
42
47
  end
43
48
  end
44
49
  end
data/lib/matchi/match.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  module Matchi
4
4
  # *Regular expressions* matcher.
5
5
  class Match
6
+ # @return [#match] A regular expression.
7
+ attr_reader :expected
8
+
6
9
  # Initialize the matcher with an instance of Regexp.
7
10
  #
8
11
  # @example
@@ -21,24 +24,26 @@ module Matchi
21
24
  # require "matchi/match"
22
25
  #
23
26
  # matcher = Matchi::Match.new(/^foo$/)
27
+ #
28
+ # matcher.expected # => /^foo$/
24
29
  # matcher.matches? { "foo" } # => true
25
30
  #
26
31
  # @yieldreturn [#object_id] The actual value to compare to the expected
27
32
  # one.
28
33
  #
29
34
  # @return [Boolean] Comparison between actual and expected values.
30
- def matches?(*, **)
31
- @expected.match?(yield)
35
+ def matches?
36
+ expected.match?(yield)
32
37
  end
33
38
 
34
39
  # A string containing a human-readable representation of the matcher.
35
40
  def inspect
36
- "#{self.class}(#{@expected.inspect})"
41
+ "#{self.class}(#{expected.inspect})"
37
42
  end
38
43
 
39
44
  # Returns a string representing the matcher.
40
45
  def to_s
41
- "match #{@expected.inspect}"
46
+ "match #{expected.inspect}"
42
47
  end
43
48
  end
44
49
  end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Predicate* matcher.
5
+ class Predicate
6
+ # Initialize the matcher with a name and arguments.
7
+ #
8
+ # @example
9
+ # require "matchi/predicate"
10
+ #
11
+ # Matchi::Predicate.new(:be_empty)
12
+ #
13
+ # @param name [#to_s] A matcher name.
14
+ # @param args [Array] A list of parameters.
15
+ # @param kwargs [Hash] A list of keyword parameters.
16
+ # @param block [Proc] A block of code.
17
+ def initialize(name, *args, **kwargs, &block)
18
+ @name = String(name)
19
+
20
+ raise ::ArgumentError unless valid_name?
21
+
22
+ @args = args
23
+ @kwargs = kwargs
24
+ @block = block
25
+ end
26
+
27
+ # @return [Array] The method name with any arguments to send to the subject.
28
+ def expected
29
+ [method_name, @args, @kwargs, @block]
30
+ end
31
+
32
+ # Boolean comparison between the actual value and the expected value.
33
+ #
34
+ # @example
35
+ # require "matchi/predicate"
36
+ #
37
+ # matcher = Matchi::Predicate.new(:be_empty)
38
+ #
39
+ # matcher.expected # => [:empty?, [], {}, nil]
40
+ # matcher.matches? { [] } # => true
41
+ #
42
+ # @example
43
+ # require "matchi/predicate"
44
+ #
45
+ # matcher = Matchi::Predicate.new(:have_key, :foo)
46
+ #
47
+ # matcher.expected # => [:has_key?, [:foo], {}, nil]
48
+ # matcher.matches? { { foo: 42 } } # => true
49
+ #
50
+ # @yieldreturn [#object_id] The actual value to receive the method request.
51
+ #
52
+ # @return [Boolean] A boolean returned by the actual value being tested.
53
+ def matches?
54
+ value = yield.send(method_name, *@args, **@kwargs, &@block)
55
+ return value if [false, true].include?(value)
56
+
57
+ raise ::TypeError, "Boolean expected, but #{value.class} instance returned."
58
+ end
59
+
60
+ # A string containing a human-readable representation of the matcher.
61
+ def inspect
62
+ "#{self.class}(#{@name}, *#{@args.inspect}, **#{@kwargs.inspect}, &#{@block.inspect})"
63
+ end
64
+
65
+ # Returns a string representing the matcher.
66
+ def to_s
67
+ (
68
+ "#{@name.tr('_', ' ')} " + [
69
+ @args.map(&:inspect).join(", "),
70
+ @kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", "),
71
+ (@block.nil? ? "" : "&block")
72
+ ].reject { |i| i.eql?("") }.join(", ")
73
+ ).strip
74
+ end
75
+
76
+ private
77
+
78
+ # The name of the method to send to the object.
79
+ def method_name
80
+ if @name.start_with?("be_")
81
+ :"#{@name.gsub("be_", "")}?"
82
+ else
83
+ :"#{@name.gsub("have_", "has_")}?"
84
+ end
85
+ end
86
+
87
+ # Verify the matcher name structure.
88
+ def valid_name?
89
+ return false if @name.end_with?("?", "!")
90
+
91
+ @name.start_with?("be_", "have_")
92
+ end
93
+ end
94
+ end
@@ -3,6 +3,9 @@
3
3
  module Matchi
4
4
  # *Expecting errors* matcher.
5
5
  class RaiseException
6
+ # @return [String] The expected exception name.
7
+ attr_reader :expected
8
+
6
9
  # Initialize the matcher with a descendant of class Exception.
7
10
  #
8
11
  # @example
@@ -10,9 +13,9 @@ module Matchi
10
13
  #
11
14
  # Matchi::RaiseException.new(NameError)
12
15
  #
13
- # @param expected [Exception] The class of the expected exception.
16
+ # @param expected [Exception, #to_s] The expected exception name.
14
17
  def initialize(expected)
15
- @expected = expected
18
+ @expected = String(expected)
16
19
  end
17
20
 
18
21
  # Boolean comparison between the actual value and the expected value.
@@ -21,15 +24,17 @@ module Matchi
21
24
  # require "matchi/raise_exception"
22
25
  #
23
26
  # matcher = Matchi::RaiseException.new(NameError)
27
+ #
28
+ # matcher.expected # => "NameError"
24
29
  # matcher.matches? { Boom } # => true
25
30
  #
26
31
  # @yieldreturn [#object_id] The actual value to compare to the expected
27
32
  # one.
28
33
  #
29
34
  # @return [Boolean] Comparison between actual and expected values.
30
- def matches?(*, **)
35
+ def matches?
31
36
  yield
32
- rescue @expected => _e
37
+ rescue self.class.const_get(expected) => _e
33
38
  true
34
39
  else
35
40
  false
@@ -37,12 +42,12 @@ module Matchi
37
42
 
38
43
  # A string containing a human-readable representation of the matcher.
39
44
  def inspect
40
- "#{self.class}(#{@expected.inspect})"
45
+ "#{self.class}(#{expected})"
41
46
  end
42
47
 
43
48
  # Returns a string representing the matcher.
44
49
  def to_s
45
- "raise exception #{@expected.inspect}"
50
+ "raise exception #{expected}"
46
51
  end
47
52
  end
48
53
  end
@@ -3,6 +3,9 @@
3
3
  module Matchi
4
4
  # *Satisfy* matcher.
5
5
  class Satisfy
6
+ # @return [Proc] A block of code.
7
+ attr_reader :expected
8
+
6
9
  # Initialize the matcher with a block.
7
10
  #
8
11
  # @example
@@ -21,14 +24,16 @@ module Matchi
21
24
  # require "matchi/satisfy"
22
25
  #
23
26
  # matcher = Matchi::Satisfy.new { |value| value == 42 }
27
+ #
28
+ # matcher.expected # => #<Proc:0x00007fbaafc65540>
24
29
  # matcher.matches? { 42 } # => true
25
30
  #
26
31
  # @yieldreturn [#object_id] The actual value to compare to the expected
27
32
  # one.
28
33
  #
29
34
  # @return [Boolean] Comparison between actual and expected values.
30
- def matches?(*, **)
31
- @expected.call(yield)
35
+ def matches?
36
+ expected.call(yield)
32
37
  end
33
38
 
34
39
  # A string containing a human-readable representation of the matcher.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matchi
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Kato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-22 00:00:00.000000000 Z
11
+ date: 2021-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -133,6 +133,8 @@ files:
133
133
  - lib/matchi.rb
134
134
  - lib/matchi/be.rb
135
135
  - lib/matchi/be_an_instance_of.rb
136
+ - lib/matchi/be_within.rb
137
+ - lib/matchi/be_within/of.rb
136
138
  - lib/matchi/change.rb
137
139
  - lib/matchi/change/by.rb
138
140
  - lib/matchi/change/by_at_least.rb
@@ -142,6 +144,7 @@ files:
142
144
  - lib/matchi/change/to.rb
143
145
  - lib/matchi/eq.rb
144
146
  - lib/matchi/match.rb
147
+ - lib/matchi/predicate.rb
145
148
  - lib/matchi/raise_exception.rb
146
149
  - lib/matchi/satisfy.rb
147
150
  homepage: https://github.com/fixrb/matchi