matchi 2.2.1 → 3.0.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: 43879fd564f4fe9dfab4116b3e59e452edd5e700dd4cd0f71164965214a17f47
4
- data.tar.gz: c4f2eda14d387063449b71e42ea80af8a61888a676b986106c40062735c19342
3
+ metadata.gz: 16b38ee69034c01d5f2961a5d4e14b6c150cad89a9368f7c0cf84071761e2999
4
+ data.tar.gz: f76f7a3286443d49f6e449af9ffefdb2f38b9d974aada733e1a611d75c627199
5
5
  SHA512:
6
- metadata.gz: fd575e3e83c363e04d34ea37ed2f66cb5d7a84dfb63c4b18752347ad105bea9074a0c1489a598a9904295d06e5bf051d5126a0d785231ca9df2762f188db7c1d
7
- data.tar.gz: b9e90e939b96c10c27796d58577e2c8db4269233e689bb17e1845526da2c5e83668b1608a478ffdec4321d7852750eec9f21b92bd5f4932d6190a85caa11c491
6
+ metadata.gz: ed8abd4dd002520dc4740fc80b91573098e3242319e74b807e6621482beb131d4a40e299584fc507bacbb806867a00691a3f4bdcebf611dd2c45eb1b90e9e76e
7
+ data.tar.gz: 3fd0b621816f91d0569fdebeb4a8c80c55890f83250ed87b0df2cd1b7683e02f390df159f372f6e87f62aab853b7d8042d438f1efc31f83fd3b169a12eab2b8e
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
+ * Provide a collection of useful generic matchers.
16
+ * Adding matchers should be as simple as possible.
17
+ * Being framework agnostic and easy to integrate.
12
18
 
13
19
  ## Installation
14
20
 
@@ -44,84 +50,97 @@ 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 just an object that responds to the `matches?` method with a block as argument, and returns a boolean. That's all it is.
56
+
57
+ But let's see some examples.
58
+
47
59
  ### Built-in matchers
48
60
 
49
61
  **Equivalence** matcher:
50
62
 
51
63
  ```ruby
52
- eql = Matchi::Matcher::Eql.new("foo")
53
- eql.matches? { "foo" } # => true
64
+ matcher = Matchi::Eq.new("foo")
65
+ matcher.matches? { "foo" } # => true
54
66
  ```
55
67
 
56
68
  **Identity** matcher:
57
69
 
58
70
  ```ruby
59
- equal = Matchi::Matcher::Equal.new(:foo)
60
- equal.matches? { :foo } # => true
71
+ matcher = Matchi::Be.new(:foo)
72
+ matcher.matches? { :foo } # => true
61
73
  ```
62
74
 
63
75
  **Regular expressions** matcher:
64
76
 
65
77
  ```ruby
66
- match = Matchi::Matcher::Match.new(/^foo$/)
67
- match.matches? { "foo" } # => true
78
+ matcher = Matchi::Match.new(/^foo$/)
79
+ matcher.matches? { "foo" } # => true
68
80
  ```
69
81
 
70
82
  **Expecting errors** matcher:
71
83
 
72
84
  ```ruby
73
- raise_exception = Matchi::Matcher::RaiseException.new(NameError)
74
- raise_exception.matches? { Boom } # => true
85
+ matcher = Matchi::RaiseException.new(NameError)
86
+ matcher.matches? { Boom } # => true
75
87
  ```
76
88
 
77
- **Truth** matcher:
89
+ **Type/class** matcher:
78
90
 
79
91
  ```ruby
80
- be_true = Matchi::Matcher::BeTrue.new
81
- be_true.matches? { true } # => true
92
+ matcher = Matchi::BeAnInstanceOf.new(:String)
93
+ matcher.matches? { "foo" } # => true
82
94
  ```
83
95
 
84
- **Untruth** matcher:
96
+ **Change** matcher:
85
97
 
86
98
  ```ruby
87
- be_false = Matchi::Matcher::BeFalse.new
88
- be_false.matches? { false } # => true
89
- ```
99
+ object = []
100
+ matcher = Matchi::Change.new(object, :length).by(1)
101
+ matcher.matches? { object << 1 } # => true
90
102
 
91
- **Nil** matcher:
103
+ object = []
104
+ matcher = Matchi::Change.new(object, :length).by_at_least(1)
105
+ matcher.matches? { object << 1 } # => true
92
106
 
93
- ```ruby
94
- be_nil = Matchi::Matcher::BeNil.new
95
- be_nil.matches? { nil } # => true
107
+ object = []
108
+ matcher = Matchi::Change.new(object, :length).by_at_most(1)
109
+ matcher.matches? { object << 1 } # => true
110
+
111
+ object = "foo"
112
+ matcher = Matchi::Change.new(object, :to_s).from("foo").to("FOO")
113
+ matcher.matches? { object.upcase! } # => true
114
+
115
+ object = "foo"
116
+ matcher = Matchi::Change.new(object, :to_s).to("FOO")
117
+ matcher.matches? { object.upcase! } # => true
96
118
  ```
97
119
 
98
- **Type/class** matcher:
120
+ **Satisfy** matcher:
99
121
 
100
122
  ```ruby
101
- be_an_instance_of = Matchi::Matcher::BeAnInstanceOf.new(:String)
102
- be_an_instance_of.matches? { "foo" } # => true
123
+ matcher = Matchi::Satisfy.new { |value| value == 42 }
124
+ matcher.matches? { 42 } # => true
103
125
  ```
104
126
 
105
127
  ### Custom matchers
106
128
 
107
- Custom matchers can easily be defined for expressing expectations.
108
- They can be any Ruby class that responds to `matches?` instance method with a block.
129
+ Custom matchers can easily be added to express more specific expectations.
109
130
 
110
131
  A **Be the answer** matcher:
111
132
 
112
133
  ```ruby
113
134
  module Matchi
114
- module Matcher
115
- class BeTheAnswer < ::Matchi::Matcher::Base
116
- def matches?
117
- 42.equal?(yield)
118
- end
135
+ class BeTheAnswer
136
+ def matches?
137
+ 42.equal?(yield)
119
138
  end
120
139
  end
121
140
  end
122
141
 
123
- be_the_answer = Matchi::Matcher::BeTheAnswer.new
124
- be_the_answer.matches? { 42 } # => true
142
+ matcher = Matchi::BeTheAnswer.new
143
+ matcher.matches? { 42 } # => true
125
144
  ```
126
145
 
127
146
  A **Be prime** matcher:
@@ -130,60 +149,36 @@ A **Be prime** matcher:
130
149
  require "prime"
131
150
 
132
151
  module Matchi
133
- module Matcher
134
- class BePrime < ::Matchi::Matcher::Base
135
- def matches?
136
- Prime.prime?(yield)
137
- end
152
+ class BePrime
153
+ def matches?
154
+ Prime.prime?(yield)
138
155
  end
139
156
  end
140
157
  end
141
158
 
142
- be_prime = Matchi::Matcher::BePrime.new
143
- be_prime.matches? { 42 } # => false
159
+ matcher = Matchi::BePrime.new
160
+ matcher.matches? { 42 } # => false
144
161
  ```
145
162
 
146
163
  A **Start with** matcher:
147
164
 
148
165
  ```ruby
149
166
  module Matchi
150
- module Matcher
151
- class StartWith < ::Matchi::Matcher::Base
152
- def initialize(expected)
153
- super()
154
- @expected = expected
155
- end
156
-
157
- def matches?
158
- Regexp.new(/\A#{expected}/).match?(yield)
159
- end
160
- end
161
- end
162
- end
163
-
164
- start_with = Matchi::Matcher::StartWith.new("foo")
165
- start_with.matches? { "foobar" } # => true
166
- ```
167
+ class StartWith
168
+ attr_reader :expected
167
169
 
168
- ### Helper methods
169
-
170
- For convenience, it is possible to instantiate a matcher with a method rather than with its class.
171
- To do so, the `Helper` module can be included like this:
172
-
173
- ```ruby
174
- require "matchi/helper"
170
+ def initialize(expected)
171
+ @expected = expected
172
+ end
175
173
 
176
- class MatcherCollection
177
- include ::Matchi::Helper
174
+ def matches?
175
+ Regexp.new(/\A#{expected}/).match?(yield)
176
+ end
177
+ end
178
178
  end
179
- ```
180
-
181
- The set of loaded matcher then becomes accessible via a dynamically generated instance method, like these:
182
179
 
183
- ```ruby
184
- matcher = MatcherCollection.new
185
- matcher.equal(42).matches? { 44 } # => false
186
- matcher.be_an_instance_of(:String).matches? { "안녕하세요" } # => true
180
+ matcher = Matchi::StartWith.new("foo")
181
+ matcher.matches? { "foobar" } # => true
187
182
  ```
188
183
 
189
184
  ## Contact
@@ -197,7 +192,7 @@ __Matchi__ follows [Semantic Versioning 2.0](https://semver.org/).
197
192
 
198
193
  ## License
199
194
 
200
- The [gem](https://rubygems.org/gems/matchi) is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
195
+ 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).
201
196
 
202
197
  ***
203
198
 
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,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Identity* matcher.
5
+ class Be
6
+ # Initialize the matcher with an object.
7
+ #
8
+ # @example
9
+ # require "matchi/be"
10
+ #
11
+ # Matchi::Be.new(:foo)
12
+ #
13
+ # @param expected [#equal?] The expected identical object.
14
+ def initialize(expected)
15
+ @expected = expected
16
+ end
17
+
18
+ # Boolean comparison between the actual value and the expected value.
19
+ #
20
+ # @example
21
+ # require "matchi/be"
22
+ #
23
+ # matcher = Matchi::Be.new(:foo)
24
+ # matcher.matches? { :foo } # => true
25
+ #
26
+ # @yieldreturn [#object_id] The actual value to compare to the expected
27
+ # one.
28
+ #
29
+ # @return [Boolean] Comparison between actual and expected values.
30
+ def matches?(*, **)
31
+ @expected.equal?(yield)
32
+ end
33
+
34
+ # A string containing a human-readable representation of the matcher.
35
+ def inspect
36
+ "#{self.class}(#{@expected.inspect})"
37
+ end
38
+
39
+ # Returns a string representing the matcher.
40
+ def to_s
41
+ "be #{@expected.inspect}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Matchi
4
+ # *Type/class* matcher.
5
+ class BeAnInstanceOf
6
+ # Initialize the matcher with (the name of) a class or module.
7
+ #
8
+ # @example
9
+ # require "matchi/be_an_instance_of"
10
+ #
11
+ # Matchi::BeAnInstanceOf.new(String)
12
+ #
13
+ # @param expected [#to_s] A (name of a) class or module.
14
+ def initialize(expected)
15
+ @expected = String(expected).to_sym
16
+ end
17
+
18
+ # Boolean comparison between the class of the actual value and the
19
+ # expected class.
20
+ #
21
+ # @example
22
+ # require "matchi/be_an_instance_of"
23
+ #
24
+ # matcher = Matchi::BeAnInstanceOf.new(String)
25
+ # matcher.matches? { "foo" } # => true
26
+ #
27
+ # @yieldreturn [#class] the actual value to compare to the expected one.
28
+ #
29
+ # @return [Boolean] Comparison between actual and expected values.
30
+ def matches?(*, **)
31
+ self.class.const_get(@expected).equal?(yield.class)
32
+ end
33
+
34
+ # A string containing a human-readable representation of the matcher.
35
+ def inspect
36
+ "#{self.class}(#{@expected})"
37
+ end
38
+
39
+ # Returns a string representing the matcher.
40
+ def to_s
41
+ "be an instance of #{@expected}"
42
+ end
43
+ end
44
+ 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 = Matchi::Change.new(object, :length)
36
+ # change.by_at_least(1)
37
+ #
38
+ # @param expected [#object_id] The minimum delta of the expected change.
39
+ #
40
+ # @return [#matches?] A *change by at least* matcher.
41
+ def by_at_least(expected)
42
+ ByAtLeast.new(expected, &@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 = Matchi::Change.new(object, :length)
53
+ # change.by_at_most(1)
54
+ #
55
+ # @param expected [#object_id] The maximum delta of the expected change.
56
+ #
57
+ # @return [#matches?] A *change by at most* matcher.
58
+ def by_at_most(expected)
59
+ ByAtMost.new(expected, &@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 = Matchi::Change.new(object, :length)
70
+ # change.by(1)
71
+ #
72
+ # @param expected [#object_id] The delta of the expected change.
73
+ #
74
+ # @return [#matches?] A *change by* matcher.
75
+ def by(expected)
76
+ By.new(expected, &@state)
77
+ end
78
+
79
+ # Specifies the original value.
80
+ #
81
+ # @example
82
+ # require "matchi/change"
83
+ #
84
+ # change = Matchi::Change.new("foo", :to_s)
85
+ # change.from("foo")
86
+ #
87
+ # @param expected [#object_id] The original value.
88
+ #
89
+ # @return [#matches?] A *change from* wrapper.
90
+ def from(expected)
91
+ From.new(expected, &@state)
92
+ end
93
+
94
+ # Specifies the new value to expect.
95
+ #
96
+ # @example
97
+ # require "matchi/change"
98
+ #
99
+ # change = Matchi::Change.new("foo", :to_s)
100
+ # change.to("FOO")
101
+ #
102
+ # @param expected [#object_id] The new value to expect.
103
+ #
104
+ # @return [#matches?] A *change to* matcher.
105
+ def to(expected)
106
+ To.new(expected, &@state)
107
+ end
108
+ end
109
+ end