matchi 2.4.0 → 3.2.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: 99d5b67cbb65b32a588adc888741aa1b43d86dd26835404388de5444bee6c54c
4
- data.tar.gz: 6188812229591bbb66a7fe458eaaaf4cd5707d00cefcc8502dfc3e4ed41195a2
3
+ metadata.gz: aabf9e773ac6210d1170b3a0f3aa19f38845ea0215bb07280d97c35ef66add16
4
+ data.tar.gz: 2900c9b936d50bfbe50ba31c6418dcb1402c1fbef5cfbb9f7a1ce289ff6450c0
5
5
  SHA512:
6
- metadata.gz: 3f00907f22ba9615eeab7541ddba62e6507f6c3609268699b4c20fda2435c338e9307fec63a942a9c49ad105131c6d29f47354fdf8d7e5d8b28fc418f060e9f0
7
- data.tar.gz: 4aeea7b5f52278b733c58180753e95d6ab88a47fbc162242ecf63bcfc3bc44ce018976fa544ef48887724d247e6fcb37ccc6372c89fedb0661d008dcc46061b5
6
+ metadata.gz: e2cfb5b1c32a01b84c1e2eec84cfc3ad34df2a7a17c91ecb1e6012ef84d5b4ef5b11f24817de7dcea77e08006e72c578d0d88947470436f231efef8fed18f061
7
+ data.tar.gz: 235a73246185fec310960caf6b8c868cbc2daf7598f29c863def9b560ec66ba7a0f43ab303cd164b04c81b5ebbcbddae3224c5550002f9ab924f8202f58a289f
data/README.md CHANGED
@@ -6,9 +6,15 @@
6
6
  [![RuboCop](https://github.com/fixrb/matchi/workflows/RuboCop/badge.svg?branch=main)](https://github.com/fixrb/matchi/actions?query=workflow%3Arubocop+branch%3Amain)
7
7
  [![License](https://img.shields.io/github/license/fixrb/matchi?label=License&logo=github)](https://github.com/fixrb/matchi/raw/main/LICENSE.md)
8
8
 
9
- > Collection of expectation matchers for Ruby 🤹
9
+ > Collection of expectation matchers for Rubyists 🤹
10
10
 
11
- ![A rubyist juggling between colored balls representing expectation matchers](https://github.com/fixrb/matchi/raw/main/img/matchi.jpg)
11
+ ![A Rubyist juggling between Matchi letters](https://github.com/fixrb/matchi/raw/main/img/matchi.jpg)
12
+
13
+ ## Project goals
14
+
15
+ * Adding matchers should be as simple as possible.
16
+ * Being framework agnostic and easy to integrate.
17
+ * Avoid false positives/negatives due to malicious actual values.
12
18
 
13
19
  ## Installation
14
20
 
@@ -44,115 +50,136 @@ require "matchi"
44
50
 
45
51
  All examples here assume that this has been done.
46
52
 
53
+ ### Anatomy of a matcher
54
+
55
+ A __Matchi__ matcher is an object that must respond to the `matches?` method with a block as argument, and return a boolean.
56
+
57
+ To facilitate the integration of the matchers in other tools, __Matchi__ matchers may expose expected values via the `expected` method.
58
+
47
59
  ### Built-in matchers
48
60
 
61
+ Here is the collection of useful generic matchers.
62
+
49
63
  **Equivalence** matcher:
50
64
 
51
65
  ```ruby
52
- eql = Matchi::Matcher::Eql.new("foo")
53
- eql.matches? { "foo" } # => true
66
+ matcher = Matchi::Eq.new("foo")
67
+
68
+ matcher.expected # => "foo"
69
+ matcher.matches? { "foo" } # => true
54
70
  ```
55
71
 
56
72
  **Identity** matcher:
57
73
 
58
74
  ```ruby
59
- equal = Matchi::Matcher::Equal.new(:foo)
60
- equal.matches? { :foo } # => true
61
- ```
75
+ matcher = Matchi::Be.new(:foo)
62
76
 
63
- **Regular expressions** matcher:
64
-
65
- ```ruby
66
- match = Matchi::Matcher::Match.new(/^foo$/)
67
- match.matches? { "foo" } # => true
77
+ matcher.expected # => :foo
78
+ matcher.matches? { :foo } # => true
68
79
  ```
69
80
 
70
- **Expecting errors** matcher:
81
+ **Comparisons** matcher:
71
82
 
72
83
  ```ruby
73
- raise_exception = Matchi::Matcher::RaiseException.new(NameError)
74
- raise_exception.matches? { Boom } # => true
75
- ```
76
-
77
- **Truth** matcher:
84
+ matcher = Matchi::BeWithin.new(8).of(37)
78
85
 
79
- ```ruby
80
- be_true = Matchi::Matcher::BeTrue.new
81
- be_true.matches? { true } # => true
86
+ matcher.expected # => 37
87
+ matcher.matches? { 42 } # => true
82
88
  ```
83
89
 
84
- **Untruth** matcher:
90
+ **Regular expressions** matcher:
85
91
 
86
92
  ```ruby
87
- be_false = Matchi::Matcher::BeFalse.new
88
- be_false.matches? { false } # => true
93
+ matcher = Matchi::Match.new(/^foo$/)
94
+
95
+ matcher.expected # => /^foo$/
96
+ matcher.matches? { "foo" } # => true
89
97
  ```
90
98
 
91
- **Nil** matcher:
99
+ **Expecting errors** matcher:
92
100
 
93
101
  ```ruby
94
- be_nil = Matchi::Matcher::BeNil.new
95
- be_nil.matches? { nil } # => true
102
+ matcher = Matchi::RaiseException.new(:NameError)
103
+
104
+ matcher.expected # => "NameError"
105
+ matcher.matches? { Boom } # => true
96
106
  ```
97
107
 
98
108
  **Type/class** matcher:
99
109
 
100
110
  ```ruby
101
- be_an_instance_of = Matchi::Matcher::BeAnInstanceOf.new(:String)
102
- be_an_instance_of.matches? { "foo" } # => true
111
+ matcher = Matchi::BeAnInstanceOf.new(:String)
112
+
113
+ matcher.expected # => "String"
114
+ matcher.matches? { "foo" } # => true
103
115
  ```
104
116
 
105
117
  **Change** matcher:
106
118
 
107
119
  ```ruby
108
120
  object = []
109
- change = Matchi::Matcher::Change.new(object, :length).by(1)
110
- change.matches? { object << 1 } # => true
121
+ matcher = Matchi::Change.new(object, :length).by(1)
122
+
123
+ matcher.expected # => 1
124
+ matcher.matches? { object << 1 } # => true
111
125
 
112
126
  object = []
113
- change = Matchi::Matcher::Change.new(object, :length).by_at_least(1)
114
- change.matches? { object << 1 } # => true
127
+ matcher = Matchi::Change.new(object, :length).by_at_least(1)
128
+
129
+ matcher.expected # => 1
130
+ matcher.matches? { object << 1 } # => true
115
131
 
116
132
  object = []
117
- change = Matchi::Matcher::Change.new(object, :length).by_at_most(1)
118
- change.matches? { object << 1 } # => true
133
+ matcher = Matchi::Change.new(object, :length).by_at_most(1)
134
+
135
+ matcher.expected # => 1
136
+ matcher.matches? { object << 1 } # => true
119
137
 
120
138
  object = "foo"
121
- change = Matchi::Matcher::Change.new(object, :to_s).from("foo").to("FOO")
122
- change.matches? { object.upcase! } # => true
139
+ matcher = Matchi::Change.new(object, :to_s).from("foo").to("FOO")
140
+
141
+ matcher.expected # => "FOO"
142
+ matcher.matches? { object.upcase! } # => true
123
143
 
124
144
  object = "foo"
125
- change = Matchi::Matcher::Change.new(object, :to_s).to("FOO")
126
- change.matches? { object.upcase! } # => true
145
+ matcher = Matchi::Change.new(object, :to_s).to("FOO")
146
+
147
+ matcher.expected # => "FOO"
148
+ matcher.matches? { object.upcase! } # => true
127
149
  ```
128
150
 
129
151
  **Satisfy** matcher:
130
152
 
131
153
  ```ruby
132
- satisfy = Matchi::Matcher::Satisfy.new { |value| value == 42 }
133
- satisfy.matches? { 42 } # => true
154
+ matcher = Matchi::Satisfy.new { |value| value == 42 }
155
+
156
+ matcher.expected # => #<Proc:0x00007fbaafc65540>
157
+ matcher.matches? { 42 } # => true
134
158
  ```
135
159
 
136
160
  ### Custom matchers
137
161
 
138
- Custom matchers can easily be defined for expressing expectations.
139
- They can be any Ruby class that responds to `matches?` instance method with a block.
162
+ Custom matchers can easily be added to express more specific expectations.
140
163
 
141
164
  A **Be the answer** matcher:
142
165
 
143
166
  ```ruby
144
167
  module Matchi
145
- module Matcher
146
- class BeTheAnswer < ::Matchi::Matcher::Base
147
- def matches?
148
- 42.equal?(yield)
149
- end
168
+ class BeTheAnswer
169
+ def expected
170
+ 42
171
+ end
172
+
173
+ def matches?
174
+ expected.equal?(yield)
150
175
  end
151
176
  end
152
177
  end
153
178
 
154
- be_the_answer = Matchi::Matcher::BeTheAnswer.new
155
- be_the_answer.matches? { 42 } # => true
179
+ matcher = Matchi::BeTheAnswer.new
180
+
181
+ matcher.expected # => 42
182
+ matcher.matches? { 42 } # => true
156
183
  ```
157
184
 
158
185
  A **Be prime** matcher:
@@ -161,60 +188,39 @@ A **Be prime** matcher:
161
188
  require "prime"
162
189
 
163
190
  module Matchi
164
- module Matcher
165
- class BePrime < ::Matchi::Matcher::Base
166
- def matches?
167
- Prime.prime?(yield)
168
- end
191
+ class BePrime
192
+ def matches?
193
+ Prime.prime?(yield)
169
194
  end
170
195
  end
171
196
  end
172
197
 
173
- be_prime = Matchi::Matcher::BePrime.new
174
- be_prime.matches? { 42 } # => false
198
+ matcher = Matchi::BePrime.new
199
+
200
+ matcher.matches? { 42 } # => false
175
201
  ```
176
202
 
177
203
  A **Start with** matcher:
178
204
 
179
205
  ```ruby
180
206
  module Matchi
181
- module Matcher
182
- class StartWith < ::Matchi::Matcher::Base
183
- def initialize(expected)
184
- super()
185
- @expected = expected
186
- end
187
-
188
- def matches?
189
- Regexp.new(/\A#{expected}/).match?(yield)
190
- end
191
- end
192
- end
193
- end
207
+ class StartWith
208
+ attr_reader :expected
194
209
 
195
- start_with = Matchi::Matcher::StartWith.new("foo")
196
- start_with.matches? { "foobar" } # => true
197
- ```
198
-
199
- ### Helper methods
200
-
201
- For convenience, it is possible to instantiate a matcher with a method rather than with its class.
202
- To do so, the `Helper` module can be included like this:
203
-
204
- ```ruby
205
- require "matchi/helper"
210
+ def initialize(expected)
211
+ @expected = expected
212
+ end
206
213
 
207
- class MatcherCollection
208
- include ::Matchi::Helper
214
+ def matches?
215
+ Regexp.new(/\A#{expected}/).match?(yield)
216
+ end
217
+ end
209
218
  end
210
- ```
211
219
 
212
- The set of loaded matcher then becomes accessible via a dynamically generated instance method, like these:
220
+ matcher = Matchi::StartWith.new("foo")
213
221
 
214
- ```ruby
215
- matcher = MatcherCollection.new
216
- matcher.equal(42).matches? { 44 } # => false
217
- matcher.be_an_instance_of(:String).matches? { "안녕하세요" } # => true
222
+ matcher.expected # => "foo"
223
+ matcher.matches? { "foobar" } # => true
218
224
  ```
219
225
 
220
226
  ## Contact
@@ -228,7 +234,7 @@ __Matchi__ follows [Semantic Versioning 2.0](https://semver.org/).
228
234
 
229
235
  ## License
230
236
 
231
- The [gem](https://rubygems.org/gems/matchi) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
237
+ The [gem](https://rubygems.org/gems/matchi) is available as open source under the terms of the [MIT License](https://github.com/fixrb/matchi/raw/main/LICENSE.md).
232
238
 
233
239
  ***
234
240
 
data/lib/matchi.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Namespace for the Matchi library.
3
+ # A collection of damn simple expectation matchers.
4
4
  #
5
5
  # @api public
6
6
  module Matchi
7
7
  end
8
8
 
9
- require_relative File.join("matchi", "matcher")
9
+ Dir[File.join(File.dirname(__FILE__), "matchi", "*.rb")].each do |fname|
10
+ require_relative fname
11
+ end
data/lib/matchi/be.rb ADDED
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Identity* matcher.
5
+ class Be
6
+ # @return [#equal?] The expected identical object.
7
+ attr_reader :expected
8
+
9
+ # Initialize the matcher with an object.
10
+ #
11
+ # @example
12
+ # require "matchi/be"
13
+ #
14
+ # Matchi::Be.new(:foo)
15
+ #
16
+ # @param expected [#equal?] The expected identical object.
17
+ def initialize(expected)
18
+ @expected = expected
19
+ end
20
+
21
+ # Boolean comparison between the actual value and the expected value.
22
+ #
23
+ # @example
24
+ # require "matchi/be"
25
+ #
26
+ # matcher = Matchi::Be.new(:foo)
27
+ #
28
+ # matcher.expected # => :foo
29
+ # matcher.matches? { :foo } # => true
30
+ #
31
+ # @yieldreturn [#object_id] The actual value to compare to the expected
32
+ # one.
33
+ #
34
+ # @return [Boolean] Comparison between actual and expected values.
35
+ def matches?
36
+ expected.equal?(yield)
37
+ end
38
+
39
+ # A string containing a human-readable representation of the matcher.
40
+ def inspect
41
+ "#{self.class}(#{expected.inspect})"
42
+ end
43
+
44
+ # Returns a string representing the matcher.
45
+ def to_s
46
+ "be #{expected.inspect}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Type/class* matcher.
5
+ class BeAnInstanceOf
6
+ # @return [String] The expected class name.
7
+ attr_reader :expected
8
+
9
+ # Initialize the matcher with (the name of) a class or module.
10
+ #
11
+ # @example
12
+ # require "matchi/be_an_instance_of"
13
+ #
14
+ # Matchi::BeAnInstanceOf.new(String)
15
+ #
16
+ # @param expected [Class, #to_s] The expected class name.
17
+ def initialize(expected)
18
+ @expected = String(expected)
19
+ end
20
+
21
+ # Boolean comparison between the class of the actual value and the
22
+ # expected class.
23
+ #
24
+ # @example
25
+ # require "matchi/be_an_instance_of"
26
+ #
27
+ # matcher = Matchi::BeAnInstanceOf.new(String)
28
+ #
29
+ # matcher.expected # => "String"
30
+ # matcher.matches? { "foo" } # => true
31
+ #
32
+ # @yieldreturn [#class] the actual value to compare to the expected one.
33
+ #
34
+ # @return [Boolean] Comparison between actual and expected values.
35
+ def matches?
36
+ self.class.const_get(expected).equal?(yield.class)
37
+ end
38
+
39
+ # A string containing a human-readable representation of the matcher.
40
+ def inspect
41
+ "#{self.class}(#{expected})"
42
+ end
43
+
44
+ # Returns a string representing the matcher.
45
+ def to_s
46
+ "be an instance of #{expected}"
47
+ end
48
+ end
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