matchi 2.3.1 → 3.1.1

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: d0160e87dd2bb35e34120f11d16c0ebb5a5d4b4c984612e37a8139cb65814ed6
4
- data.tar.gz: 10ce199e0511fda23304b1f8b64bf1c7911512e77c2af6b2d90ad078d56caed0
3
+ metadata.gz: 294443e7f304a5c154aa48af9c20145bba9ff2caaf090bb7336c9bb7538be19d
4
+ data.tar.gz: c89f84d45eaf0923a7a023bd11b9cf54830054a178ce93e1074aea63cb7a9658
5
5
  SHA512:
6
- metadata.gz: 5bb3da470e32e8332b4fb4106ebec65d06f23c92f9fdc18ce6de655b0fdc3408142210aebfcfe45d30e79ce0b3856488658f3c107c5dab7164125561e2a44bd0
7
- data.tar.gz: 55dce8aca5a1b3aeacb396da47e41e008bde7c189b9acc389a39ba03beb9a3eb883732b5dfaf476158662d1c396fd7a454f0b0a10cd8ed87489b88440771158c
6
+ metadata.gz: 9fe569142636d1acbba97f47dc7efbe38da3c02e9b19e77ab7ad723b6c03a52c03c3d8cd730a9948b4c45b21cdb6e807c29c181fd5d27f77aeee11c92c9738f4
7
+ data.tar.gz: 2bd24bb8f545496e7bbb115f9bd1577f60d03e67198a0452cd2393f977f063ac0e168f699930c6819a0c478a9c208627db75f7c49a63e5f5905256ba0ef3c8ca
data/README.md CHANGED
@@ -6,9 +6,14 @@
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.
12
17
 
13
18
  ## Installation
14
19
 
@@ -44,91 +49,131 @@ require "matchi"
44
49
 
45
50
  All examples here assume that this has been done.
46
51
 
52
+ ### Anatomy of a matcher
53
+
54
+ A __Matchi__ matcher is an object that must respond to the `matches?` method with a block as argument, and return a boolean.
55
+
56
+ To facilitate the integration of the matchers in other tools, it is recommended to expose the expected value via the `expected` method.
57
+
58
+ That's all it is.
59
+
60
+ Let's see some examples.
61
+
47
62
  ### Built-in matchers
48
63
 
64
+ Here is the collection of useful generic matchers.
65
+
49
66
  **Equivalence** matcher:
50
67
 
51
68
  ```ruby
52
- eql = Matchi::Matcher::Eql.new("foo")
53
- eql.matches? { "foo" } # => true
69
+ matcher = Matchi::Eq.new("foo")
70
+
71
+ matcher.expected # => "foo"
72
+ matcher.matches? { "foo" } # => true
54
73
  ```
55
74
 
56
75
  **Identity** matcher:
57
76
 
58
77
  ```ruby
59
- equal = Matchi::Matcher::Equal.new(:foo)
60
- equal.matches? { :foo } # => true
78
+ matcher = Matchi::Be.new(:foo)
79
+
80
+ matcher.expected # => :foo
81
+ matcher.matches? { :foo } # => true
61
82
  ```
62
83
 
63
84
  **Regular expressions** matcher:
64
85
 
65
86
  ```ruby
66
- match = Matchi::Matcher::Match.new(/^foo$/)
67
- match.matches? { "foo" } # => true
87
+ matcher = Matchi::Match.new(/^foo$/)
88
+
89
+ matcher.expected # => /^foo$/
90
+ matcher.matches? { "foo" } # => true
68
91
  ```
69
92
 
70
93
  **Expecting errors** matcher:
71
94
 
72
95
  ```ruby
73
- raise_exception = Matchi::Matcher::RaiseException.new(NameError)
74
- raise_exception.matches? { Boom } # => true
96
+ matcher = Matchi::RaiseException.new(:NameError)
97
+
98
+ matcher.expected # => "NameError"
99
+ matcher.matches? { Boom } # => true
75
100
  ```
76
101
 
77
- **Truth** matcher:
102
+ **Type/class** matcher:
78
103
 
79
104
  ```ruby
80
- be_true = Matchi::Matcher::BeTrue.new
81
- be_true.matches? { true } # => true
105
+ matcher = Matchi::BeAnInstanceOf.new(:String)
106
+
107
+ matcher.expected # => "String"
108
+ matcher.matches? { "foo" } # => true
82
109
  ```
83
110
 
84
- **Untruth** matcher:
111
+ **Change** matcher:
85
112
 
86
113
  ```ruby
87
- be_false = Matchi::Matcher::BeFalse.new
88
- be_false.matches? { false } # => true
89
- ```
114
+ object = []
115
+ matcher = Matchi::Change.new(object, :length).by(1)
90
116
 
91
- **Nil** matcher:
117
+ matcher.expected # => 1
118
+ matcher.matches? { object << 1 } # => true
92
119
 
93
- ```ruby
94
- be_nil = Matchi::Matcher::BeNil.new
95
- be_nil.matches? { nil } # => true
96
- ```
120
+ object = []
121
+ matcher = Matchi::Change.new(object, :length).by_at_least(1)
97
122
 
98
- **Type/class** matcher:
123
+ matcher.expected # => 1
124
+ matcher.matches? { object << 1 } # => true
99
125
 
100
- ```ruby
101
- be_an_instance_of = Matchi::Matcher::BeAnInstanceOf.new(:String)
102
- be_an_instance_of.matches? { "foo" } # => true
126
+ object = []
127
+ matcher = Matchi::Change.new(object, :length).by_at_most(1)
128
+
129
+ matcher.expected # => 1
130
+ matcher.matches? { object << 1 } # => true
131
+
132
+ object = "foo"
133
+ matcher = Matchi::Change.new(object, :to_s).from("foo").to("FOO")
134
+
135
+ matcher.expected # => "FOO"
136
+ matcher.matches? { object.upcase! } # => true
137
+
138
+ object = "foo"
139
+ matcher = Matchi::Change.new(object, :to_s).to("FOO")
140
+
141
+ matcher.expected # => "FOO"
142
+ matcher.matches? { object.upcase! } # => true
103
143
  ```
104
144
 
105
145
  **Satisfy** matcher:
106
146
 
107
147
  ```ruby
108
- satisfy = Matchi::Matcher::Satisfy.new { |value| value == 42 }
109
- satisfy.matches? { 42 } # => true
148
+ matcher = Matchi::Satisfy.new { |value| value == 42 }
149
+
150
+ matcher.expected # => #<Proc:0x00007fbaafc65540>
151
+ matcher.matches? { 42 } # => true
110
152
  ```
111
153
 
112
154
  ### Custom matchers
113
155
 
114
- Custom matchers can easily be defined for expressing expectations.
115
- They can be any Ruby class that responds to `matches?` instance method with a block.
156
+ Custom matchers can easily be added to express more specific expectations.
116
157
 
117
158
  A **Be the answer** matcher:
118
159
 
119
160
  ```ruby
120
161
  module Matchi
121
- module Matcher
122
- class BeTheAnswer < ::Matchi::Matcher::Base
123
- def matches?
124
- 42.equal?(yield)
125
- end
162
+ class BeTheAnswer
163
+ def expected
164
+ 42
165
+ end
166
+
167
+ def matches?
168
+ expected.equal?(yield)
126
169
  end
127
170
  end
128
171
  end
129
172
 
130
- be_the_answer = Matchi::Matcher::BeTheAnswer.new
131
- be_the_answer.matches? { 42 } # => true
173
+ matcher = Matchi::BeTheAnswer.new
174
+
175
+ matcher.expected # => 42
176
+ matcher.matches? { 42 } # => true
132
177
  ```
133
178
 
134
179
  A **Be prime** matcher:
@@ -137,60 +182,42 @@ A **Be prime** matcher:
137
182
  require "prime"
138
183
 
139
184
  module Matchi
140
- module Matcher
141
- class BePrime < ::Matchi::Matcher::Base
142
- def matches?
143
- Prime.prime?(yield)
144
- end
185
+ class BePrime
186
+ attr_reader :expected
187
+
188
+ def matches?
189
+ Prime.prime?(yield)
145
190
  end
146
191
  end
147
192
  end
148
193
 
149
- be_prime = Matchi::Matcher::BePrime.new
150
- be_prime.matches? { 42 } # => false
194
+ matcher = Matchi::BePrime.new
195
+
196
+ matcher.expected # => nil
197
+ matcher.matches? { 42 } # => false
151
198
  ```
152
199
 
153
200
  A **Start with** matcher:
154
201
 
155
202
  ```ruby
156
203
  module Matchi
157
- module Matcher
158
- class StartWith < ::Matchi::Matcher::Base
159
- def initialize(expected)
160
- super()
161
- @expected = expected
162
- end
163
-
164
- def matches?
165
- Regexp.new(/\A#{expected}/).match?(yield)
166
- end
167
- end
168
- end
169
- end
204
+ class StartWith
205
+ attr_reader :expected
170
206
 
171
- start_with = Matchi::Matcher::StartWith.new("foo")
172
- start_with.matches? { "foobar" } # => true
173
- ```
174
-
175
- ### Helper methods
176
-
177
- For convenience, it is possible to instantiate a matcher with a method rather than with its class.
178
- To do so, the `Helper` module can be included like this:
179
-
180
- ```ruby
181
- require "matchi/helper"
207
+ def initialize(expected)
208
+ @expected = expected
209
+ end
182
210
 
183
- class MatcherCollection
184
- include ::Matchi::Helper
211
+ def matches?
212
+ Regexp.new(/\A#{expected}/).match?(yield)
213
+ end
214
+ end
185
215
  end
186
- ```
187
216
 
188
- The set of loaded matcher then becomes accessible via a dynamically generated instance method, like these:
217
+ matcher = Matchi::StartWith.new("foo")
189
218
 
190
- ```ruby
191
- matcher = MatcherCollection.new
192
- matcher.equal(42).matches? { 44 } # => false
193
- matcher.be_an_instance_of(:String).matches? { "안녕하세요" } # => true
219
+ matcher.expected # => "foo"
220
+ matcher.matches? { "foobar" } # => true
194
221
  ```
195
222
 
196
223
  ## Contact
@@ -204,7 +231,7 @@ __Matchi__ follows [Semantic Versioning 2.0](https://semver.org/).
204
231
 
205
232
  ## License
206
233
 
207
- The [gem](https://rubygems.org/gems/matchi) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
234
+ 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).
208
235
 
209
236
  ***
210
237
 
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,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative File.join("change", "by_at_least")
4
+ require_relative File.join("change", "by_at_most")
5
+ require_relative File.join("change", "by")
6
+ require_relative File.join("change", "from")
7
+ require_relative File.join("change", "to")
8
+
9
+ module Matchi
10
+ # Wraps the target of a change matcher.
11
+ class Change
12
+ # Initialize a wrapper of the change matcher with an object and the name of
13
+ # one of its methods.
14
+ #
15
+ # @example
16
+ # require "matchi/change"
17
+ #
18
+ # Matchi::Change.new("foo", :to_s)
19
+ #
20
+ # @param object [#object_id] An object.
21
+ # @param method [Symbol] The name of a method.
22
+ # @param args [Array] A list of arguments.
23
+ # @param kwargs [Hash] A list of keyword arguments.
24
+ def initialize(object, method, *args, **kwargs, &block)
25
+ @state = -> { object.send(method, *args, **kwargs, &block) }
26
+ end
27
+
28
+ # Specifies a minimum delta of the expected change.
29
+ #
30
+ # @example
31
+ # require "matchi/change"
32
+ #
33
+ # object = []
34
+ #
35
+ # change_wrapper = Matchi::Change.new(object, :length)
36
+ # change_wrapper.by_at_least(1)
37
+ #
38
+ # @param minimum_delta [#object_id] The minimum delta of the expected change.
39
+ #
40
+ # @return [#matches?] A *change by at least* matcher.
41
+ def by_at_least(minimum_delta)
42
+ ByAtLeast.new(minimum_delta, &@state)
43
+ end
44
+
45
+ # Specifies a maximum delta of the expected change.
46
+ #
47
+ # @example
48
+ # require "matchi/change"
49
+ #
50
+ # object = []
51
+ #
52
+ # change_wrapper = Matchi::Change.new(object, :length)
53
+ # change_wrapper.by_at_most(1)
54
+ #
55
+ # @param maximum_delta [#object_id] The maximum delta of the expected change.
56
+ #
57
+ # @return [#matches?] A *change by at most* matcher.
58
+ def by_at_most(maximum_delta)
59
+ ByAtMost.new(maximum_delta, &@state)
60
+ end
61
+
62
+ # Specifies the delta of the expected change.
63
+ #
64
+ # @example
65
+ # require "matchi/change"
66
+ #
67
+ # object = []
68
+ #
69
+ # change_wrapper = Matchi::Change.new(object, :length)
70
+ # change_wrapper.by(1)
71
+ #
72
+ # @param delta [#object_id] The delta of the expected change.
73
+ #
74
+ # @return [#matches?] A *change by* matcher.
75
+ def by(delta)
76
+ By.new(delta, &@state)
77
+ end
78
+
79
+ # Specifies the original value.
80
+ #
81
+ # @example
82
+ # require "matchi/change"
83
+ #
84
+ # change_wrapper = Matchi::Change.new("foo", :to_s)
85
+ # change_wrapper.from("foo")
86
+ #
87
+ # @param old_value [#object_id] The original value.
88
+ #
89
+ # @return [#matches?] A *change from* wrapper.
90
+ def from(old_value)
91
+ From.new(old_value, &@state)
92
+ end
93
+
94
+ # Specifies the new value to expect.
95
+ #
96
+ # @example
97
+ # require "matchi/change"
98
+ #
99
+ # change_wrapper = Matchi::Change.new("foo", :to_s)
100
+ # change_wrapper.to("FOO")
101
+ #
102
+ # @param new_value [#object_id] The new value to expect.
103
+ #
104
+ # @return [#matches?] A *change to* matcher.
105
+ def to(new_value)
106
+ To.new(new_value, &@state)
107
+ end
108
+ end
109
+ end