matchi 3.3.2 → 4.1.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 +4 -4
- data/README.md +201 -90
- data/lib/matchi/be.rb +7 -13
- data/lib/matchi/be_a_kind_of.rb +81 -0
- data/lib/matchi/be_an_instance_of.rb +91 -21
- data/lib/matchi/be_within/of.rb +11 -13
- data/lib/matchi/be_within.rb +4 -1
- data/lib/matchi/change/by.rb +11 -14
- data/lib/matchi/change/by_at_least.rb +12 -14
- data/lib/matchi/change/by_at_most.rb +12 -14
- data/lib/matchi/change/from/to.rb +10 -14
- data/lib/matchi/change/from.rb +3 -1
- data/lib/matchi/change/to.rb +10 -14
- data/lib/matchi/change.rb +8 -7
- data/lib/matchi/eq.rb +7 -13
- data/lib/matchi/match.rb +9 -13
- data/lib/matchi/predicate.rb +8 -19
- data/lib/matchi/raise_exception.rb +30 -17
- data/lib/matchi/satisfy.rb +8 -12
- data/lib/matchi.rb +300 -0
- metadata +4 -6
@@ -1,49 +1,119 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Matchi
|
4
|
-
# *Type/class* matcher.
|
4
|
+
# *Type/class* matcher with enhanced class checking.
|
5
|
+
#
|
6
|
+
# This matcher aims to provide a more reliable way to check if an object is an exact
|
7
|
+
# instance of a specific class (not a subclass). While not foolproof, it uses a more
|
8
|
+
# robust method to get the actual class of an object that helps resist common
|
9
|
+
# attempts at type checking manipulation.
|
10
|
+
#
|
11
|
+
# @example Basic usage
|
12
|
+
# require "matchi/be_an_instance_of"
|
13
|
+
#
|
14
|
+
# matcher = Matchi::BeAnInstanceOf.new(String)
|
15
|
+
# matcher.match? { "foo" } # => true
|
16
|
+
# matcher.match? { :foo } # => false
|
17
|
+
#
|
18
|
+
# @example Enhanced class checking in practice
|
19
|
+
# # Consider a class that attempts to masquerade as String by overriding
|
20
|
+
# # common type checking methods:
|
21
|
+
# class MaliciousString
|
22
|
+
# def class
|
23
|
+
# ::String
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# def instance_of?(klass)
|
27
|
+
# self.class == klass
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def is_a?(klass)
|
31
|
+
# "".is_a?(klass) # Delegates to a real String
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def kind_of?(klass)
|
35
|
+
# is_a?(klass) # Maintains Ruby's kind_of? alias for is_a?
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# obj = MaliciousString.new
|
40
|
+
# obj.class # => String
|
41
|
+
# obj.is_a?(String) # => true
|
42
|
+
# obj.kind_of?(String) # => true
|
43
|
+
# obj.instance_of?(String) # => true
|
44
|
+
#
|
45
|
+
# # Using our enhanced checking approach:
|
46
|
+
# matcher = Matchi::BeAnInstanceOf.new(String)
|
47
|
+
# matcher.match? { obj } # => false
|
5
48
|
class BeAnInstanceOf
|
6
|
-
# @return [String] The expected class name.
|
7
|
-
attr_reader :expected
|
8
|
-
|
9
49
|
# Initialize the matcher with (the name of) a class or module.
|
10
50
|
#
|
11
51
|
# @example
|
12
52
|
# require "matchi/be_an_instance_of"
|
13
53
|
#
|
14
54
|
# Matchi::BeAnInstanceOf.new(String)
|
55
|
+
# Matchi::BeAnInstanceOf.new("String")
|
56
|
+
# Matchi::BeAnInstanceOf.new(:String)
|
15
57
|
#
|
16
|
-
# @param expected [Class, #to_s] The expected class name
|
58
|
+
# @param expected [Class, #to_s] The expected class name
|
59
|
+
# @raise [ArgumentError] if the class name doesn't start with an uppercase letter
|
17
60
|
def initialize(expected)
|
18
61
|
@expected = String(expected)
|
62
|
+
return if /\A[A-Z]/.match?(@expected)
|
63
|
+
|
64
|
+
raise ::ArgumentError,
|
65
|
+
"expected must start with an uppercase letter (got: #{@expected})"
|
19
66
|
end
|
20
67
|
|
21
|
-
#
|
22
|
-
# expected class.
|
68
|
+
# Securely checks if the yielded object is an instance of the expected class.
|
23
69
|
#
|
24
|
-
#
|
25
|
-
#
|
70
|
+
# This method uses a specific Ruby reflection technique to get the true class of
|
71
|
+
# an object, bypassing potential method overrides:
|
26
72
|
#
|
27
|
-
#
|
73
|
+
# 1. ::Object.instance_method(:class) retrieves the original, unoverridden 'class'
|
74
|
+
# method from the Object class
|
75
|
+
# 2. .bind_call(obj) binds this original method to our object and calls it,
|
76
|
+
# ensuring we get the real class regardless of method overrides
|
28
77
|
#
|
29
|
-
#
|
30
|
-
#
|
78
|
+
# This approach is more reliable than obj.class because it uses Ruby's method
|
79
|
+
# binding mechanism to call the original implementation directly. While not
|
80
|
+
# completely foolproof, it provides better protection against type check spoofing
|
81
|
+
# than using regular method calls which can be overridden.
|
31
82
|
#
|
32
|
-
# @
|
83
|
+
# @example Basic class check
|
84
|
+
# matcher = Matchi::BeAnInstanceOf.new(String)
|
85
|
+
# matcher.match? { "test" } # => true
|
86
|
+
# matcher.match? { StringIO.new } # => false
|
33
87
|
#
|
34
|
-
# @
|
35
|
-
|
36
|
-
|
37
|
-
|
88
|
+
# @see https://ruby-doc.org/core/Method.html#method-i-bind_call
|
89
|
+
# @see https://ruby-doc.org/core/UnboundMethod.html
|
90
|
+
#
|
91
|
+
# @yieldreturn [Object] the actual value to check
|
92
|
+
# @return [Boolean] true if the object's actual class is exactly the expected class
|
93
|
+
# @raise [ArgumentError] if no block is provided
|
94
|
+
def match?
|
95
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
38
96
|
|
39
|
-
|
40
|
-
|
41
|
-
"#{self.class}(#{expected})"
|
97
|
+
actual_class = ::Object.instance_method(:class).bind_call(yield)
|
98
|
+
expected_class == actual_class
|
42
99
|
end
|
43
100
|
|
44
101
|
# Returns a string representing the matcher.
|
102
|
+
#
|
103
|
+
# @return [String] a human-readable description of the matcher
|
45
104
|
def to_s
|
46
|
-
"be an instance of #{expected}"
|
105
|
+
"be an instance of #{@expected}"
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Resolves the expected class name to an actual Class object.
|
111
|
+
# This method handles both string and symbol class names through constant resolution.
|
112
|
+
#
|
113
|
+
# @return [Class] the resolved class
|
114
|
+
# @raise [NameError] if the class doesn't exist
|
115
|
+
def expected_class
|
116
|
+
::Object.const_get(@expected)
|
47
117
|
end
|
48
118
|
end
|
49
119
|
end
|
data/lib/matchi/be_within/of.rb
CHANGED
@@ -4,9 +4,6 @@ module Matchi
|
|
4
4
|
class BeWithin
|
5
5
|
# *BeWithin of* matcher.
|
6
6
|
class Of
|
7
|
-
# @return [Numeric] An expected value.
|
8
|
-
attr_reader :expected
|
9
|
-
|
10
7
|
# Initialize the matcher with a delta and an expected value.
|
11
8
|
#
|
12
9
|
# @example
|
@@ -17,6 +14,10 @@ module Matchi
|
|
17
14
|
# @param delta [Numeric] The accepted variation of the actual value.
|
18
15
|
# @param expected [Numeric] The expected value.
|
19
16
|
def initialize(delta, expected)
|
17
|
+
raise ::ArgumentError, "delta must be a Numeric" unless delta.is_a?(::Numeric)
|
18
|
+
raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
|
19
|
+
raise ::ArgumentError, "delta must be non-negative" if delta.negative?
|
20
|
+
|
20
21
|
@delta = delta
|
21
22
|
@expected = expected
|
22
23
|
end
|
@@ -28,25 +29,22 @@ module Matchi
|
|
28
29
|
# require "matchi/be_within/of"
|
29
30
|
#
|
30
31
|
# matcher = Matchi::BeWithin::Of.new(1, 41)
|
31
|
-
#
|
32
|
-
# matcher.expected # => 41
|
33
|
-
# matcher.matches? { 42 } # => true
|
32
|
+
# matcher.match? { 42 } # => true
|
34
33
|
#
|
35
34
|
# @yieldreturn [Numeric] The block of code to execute.
|
36
35
|
#
|
37
36
|
# @return [Boolean] Comparison between the actual and the expected values.
|
38
|
-
def
|
39
|
-
|
40
|
-
end
|
37
|
+
def match?
|
38
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
41
39
|
|
42
|
-
|
43
|
-
def inspect
|
44
|
-
"#{self.class}(#{@delta}, #{expected})"
|
40
|
+
(@expected - yield).abs <= @delta
|
45
41
|
end
|
46
42
|
|
47
43
|
# Returns a string representing the matcher.
|
44
|
+
#
|
45
|
+
# @return [String] a human-readable description of the matcher
|
48
46
|
def to_s
|
49
|
-
"be within #{@delta} of #{expected}"
|
47
|
+
"be within #{@delta} of #{@expected}"
|
50
48
|
end
|
51
49
|
end
|
52
50
|
end
|
data/lib/matchi/be_within.rb
CHANGED
@@ -14,6 +14,9 @@ module Matchi
|
|
14
14
|
#
|
15
15
|
# @param delta [Numeric] A numeric value.
|
16
16
|
def initialize(delta)
|
17
|
+
raise ::ArgumentError, "delta must be a Numeric" unless delta.is_a?(::Numeric)
|
18
|
+
raise ::ArgumentError, "delta must be non-negative" if delta.negative?
|
19
|
+
|
17
20
|
@delta = delta
|
18
21
|
end
|
19
22
|
|
@@ -27,7 +30,7 @@ module Matchi
|
|
27
30
|
#
|
28
31
|
# @param expected [Numeric] The expected value.
|
29
32
|
#
|
30
|
-
# @return [#
|
33
|
+
# @return [#match?] A *be_within of* matcher.
|
31
34
|
def of(expected)
|
32
35
|
Of.new(@delta, expected)
|
33
36
|
end
|
data/lib/matchi/change/by.rb
CHANGED
@@ -4,9 +4,6 @@ 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
|
-
|
10
7
|
# Initialize the matcher with an object and a block.
|
11
8
|
#
|
12
9
|
# @example
|
@@ -20,6 +17,9 @@ module Matchi
|
|
20
17
|
# @param state [Proc] A block of code to execute to get the
|
21
18
|
# state of the object.
|
22
19
|
def initialize(expected, &state)
|
20
|
+
raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
|
21
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
22
|
+
|
23
23
|
@expected = expected
|
24
24
|
@state = state
|
25
25
|
end
|
@@ -33,30 +33,27 @@ module Matchi
|
|
33
33
|
# object = []
|
34
34
|
#
|
35
35
|
# matcher = Matchi::Change::By.new(1) { object.length }
|
36
|
-
#
|
37
|
-
# matcher.expected # => 1
|
38
|
-
# matcher.matches? { object << "foo" } # => true
|
36
|
+
# matcher.match? { object << "foo" } # => true
|
39
37
|
#
|
40
38
|
# @yieldreturn [#object_id] The block of code to execute.
|
41
39
|
#
|
42
40
|
# @return [Boolean] Comparison between the value before and after the
|
43
41
|
# code execution.
|
44
|
-
def
|
42
|
+
def match?
|
43
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
44
|
+
|
45
45
|
value_before = @state.call
|
46
46
|
yield
|
47
47
|
value_after = @state.call
|
48
48
|
|
49
|
-
expected == (value_after - value_before)
|
50
|
-
end
|
51
|
-
|
52
|
-
# A string containing a human-readable representation of the matcher.
|
53
|
-
def inspect
|
54
|
-
"#{self.class}(#{expected.inspect})"
|
49
|
+
@expected == (value_after - value_before)
|
55
50
|
end
|
56
51
|
|
57
52
|
# Returns a string representing the matcher.
|
53
|
+
#
|
54
|
+
# @return [String] a human-readable description of the matcher
|
58
55
|
def to_s
|
59
|
-
"change by #{expected.inspect}"
|
56
|
+
"change by #{@expected.inspect}"
|
60
57
|
end
|
61
58
|
end
|
62
59
|
end
|
@@ -4,9 +4,6 @@ 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
|
-
|
10
7
|
# Initialize the matcher with an object and a block.
|
11
8
|
#
|
12
9
|
# @example
|
@@ -20,6 +17,10 @@ module Matchi
|
|
20
17
|
# @param state [Proc] A block of code to execute to get the
|
21
18
|
# state of the object.
|
22
19
|
def initialize(expected, &state)
|
20
|
+
raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
|
21
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
22
|
+
raise ::ArgumentError, "expected must be non-negative" if expected.negative?
|
23
|
+
|
23
24
|
@expected = expected
|
24
25
|
@state = state
|
25
26
|
end
|
@@ -33,30 +34,27 @@ module Matchi
|
|
33
34
|
# object = []
|
34
35
|
#
|
35
36
|
# matcher = Matchi::Change::ByAtLeast.new(1) { object.length }
|
36
|
-
#
|
37
|
-
# matcher.expected # => 1
|
38
|
-
# matcher.matches? { object << "foo" } # => true
|
37
|
+
# matcher.match? { object << "foo" } # => true
|
39
38
|
#
|
40
39
|
# @yieldreturn [#object_id] The block of code to execute.
|
41
40
|
#
|
42
41
|
# @return [Boolean] Comparison between the value before and after the
|
43
42
|
# code execution.
|
44
|
-
def
|
43
|
+
def match?
|
44
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
45
|
+
|
45
46
|
value_before = @state.call
|
46
47
|
yield
|
47
48
|
value_after = @state.call
|
48
49
|
|
49
|
-
expected <= (value_after - value_before)
|
50
|
-
end
|
51
|
-
|
52
|
-
# A string containing a human-readable representation of the matcher.
|
53
|
-
def inspect
|
54
|
-
"#{self.class}(#{expected.inspect})"
|
50
|
+
@expected <= (value_after - value_before)
|
55
51
|
end
|
56
52
|
|
57
53
|
# Returns a string representing the matcher.
|
54
|
+
#
|
55
|
+
# @return [String] a human-readable description of the matcher
|
58
56
|
def to_s
|
59
|
-
"change by at least #{expected.inspect}"
|
57
|
+
"change by at least #{@expected.inspect}"
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|
@@ -4,9 +4,6 @@ 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
|
-
|
10
7
|
# Initialize the matcher with an object and a block.
|
11
8
|
#
|
12
9
|
# @example
|
@@ -20,6 +17,10 @@ module Matchi
|
|
20
17
|
# @param state [Proc] A block of code to execute to get the
|
21
18
|
# state of the object.
|
22
19
|
def initialize(expected, &state)
|
20
|
+
raise ::ArgumentError, "expected must be a Numeric" unless expected.is_a?(::Numeric)
|
21
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
22
|
+
raise ::ArgumentError, "expected must be non-negative" if expected.negative?
|
23
|
+
|
23
24
|
@expected = expected
|
24
25
|
@state = state
|
25
26
|
end
|
@@ -33,30 +34,27 @@ module Matchi
|
|
33
34
|
# object = []
|
34
35
|
#
|
35
36
|
# matcher = Matchi::Change::ByAtMost.new(1) { object.length }
|
36
|
-
#
|
37
|
-
# matcher.expected # => 1
|
38
|
-
# matcher.matches? { object << "foo" } # => true
|
37
|
+
# matcher.match? { object << "foo" } # => true
|
39
38
|
#
|
40
39
|
# @yieldreturn [#object_id] The block of code to execute.
|
41
40
|
#
|
42
41
|
# @return [Boolean] Comparison between the value before and after the
|
43
42
|
# code execution.
|
44
|
-
def
|
43
|
+
def match?
|
44
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
45
|
+
|
45
46
|
value_before = @state.call
|
46
47
|
yield
|
47
48
|
value_after = @state.call
|
48
49
|
|
49
|
-
expected >= (value_after - value_before)
|
50
|
-
end
|
51
|
-
|
52
|
-
# A string containing a human-readable representation of the matcher.
|
53
|
-
def inspect
|
54
|
-
"#{self.class}(#{expected.inspect})"
|
50
|
+
@expected >= (value_after - value_before)
|
55
51
|
end
|
56
52
|
|
57
53
|
# Returns a string representing the matcher.
|
54
|
+
#
|
55
|
+
# @return [String] a human-readable description of the matcher
|
58
56
|
def to_s
|
59
|
-
"change by at most #{expected.inspect}"
|
57
|
+
"change by at most #{@expected.inspect}"
|
60
58
|
end
|
61
59
|
end
|
62
60
|
end
|
@@ -5,9 +5,6 @@ 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
|
-
|
11
8
|
# Initialize the matcher with two objects and a block.
|
12
9
|
#
|
13
10
|
# @example
|
@@ -22,6 +19,8 @@ module Matchi
|
|
22
19
|
# @param state [Proc] A block of code to execute to
|
23
20
|
# get the state of the object.
|
24
21
|
def initialize(expected_init, expected_new_value, &state)
|
22
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
23
|
+
|
25
24
|
@expected_init = expected_init
|
26
25
|
@expected = expected_new_value
|
27
26
|
@state = state
|
@@ -36,32 +35,29 @@ module Matchi
|
|
36
35
|
# object = "foo"
|
37
36
|
#
|
38
37
|
# matcher = Matchi::Change::From::To.new("foo", "FOO") { object.to_s }
|
39
|
-
#
|
40
|
-
# matcher.expected # => "FOO"
|
41
|
-
# matcher.matches? { object.upcase! } # => true
|
38
|
+
# matcher.match? { object.upcase! } # => true
|
42
39
|
#
|
43
40
|
# @yieldreturn [#object_id] The block of code to execute.
|
44
41
|
#
|
45
42
|
# @return [Boolean] Comparison between the value before and after the
|
46
43
|
# code execution.
|
47
|
-
def
|
44
|
+
def match?
|
45
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
46
|
+
|
48
47
|
value_before = @state.call
|
49
48
|
return false unless @expected_init == value_before
|
50
49
|
|
51
50
|
yield
|
52
51
|
value_after = @state.call
|
53
52
|
|
54
|
-
expected == value_after
|
55
|
-
end
|
56
|
-
|
57
|
-
# A string containing a human-readable representation of the matcher.
|
58
|
-
def inspect
|
59
|
-
"#{self.class}(#{@expected_init.inspect}, #{expected.inspect})"
|
53
|
+
@expected == value_after
|
60
54
|
end
|
61
55
|
|
62
56
|
# Returns a string representing the matcher.
|
57
|
+
#
|
58
|
+
# @return [String] a human-readable description of the matcher
|
63
59
|
def to_s
|
64
|
-
"change from #{@expected_init.inspect} to #{expected.inspect}"
|
60
|
+
"change from #{@expected_init.inspect} to #{@expected.inspect}"
|
65
61
|
end
|
66
62
|
end
|
67
63
|
end
|
data/lib/matchi/change/from.rb
CHANGED
@@ -19,6 +19,8 @@ module Matchi
|
|
19
19
|
# @param state [Proc] A block of code to execute to get the
|
20
20
|
# state of the object.
|
21
21
|
def initialize(expected, &state)
|
22
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
23
|
+
|
22
24
|
@expected = expected
|
23
25
|
@state = state
|
24
26
|
end
|
@@ -35,7 +37,7 @@ module Matchi
|
|
35
37
|
#
|
36
38
|
# @param expected_new_value [#object_id] The new value to expect.
|
37
39
|
#
|
38
|
-
# @return [#
|
40
|
+
# @return [#match?] A *change from to* matcher.
|
39
41
|
def to(expected_new_value)
|
40
42
|
To.new(@expected, expected_new_value, &@state)
|
41
43
|
end
|
data/lib/matchi/change/to.rb
CHANGED
@@ -4,9 +4,6 @@ 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
|
-
|
10
7
|
# Initialize the matcher with an object and a block.
|
11
8
|
#
|
12
9
|
# @example
|
@@ -20,6 +17,8 @@ module Matchi
|
|
20
17
|
# @param state [Proc] A block of code to execute to get the
|
21
18
|
# state of the object.
|
22
19
|
def initialize(expected, &state)
|
20
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
21
|
+
|
23
22
|
@expected = expected
|
24
23
|
@state = state
|
25
24
|
end
|
@@ -33,29 +32,26 @@ module Matchi
|
|
33
32
|
# object = "foo"
|
34
33
|
#
|
35
34
|
# matcher = Matchi::Change::To.new("FOO") { object.to_s }
|
36
|
-
#
|
37
|
-
# matcher.expected # => "FOO"
|
38
|
-
# matcher.matches? { object.upcase! } # => true
|
35
|
+
# matcher.match? { object.upcase! } # => true
|
39
36
|
#
|
40
37
|
# @yieldreturn [#object_id] The block of code to execute.
|
41
38
|
#
|
42
39
|
# @return [Boolean] Comparison between the value before and after the
|
43
40
|
# code execution.
|
44
|
-
def
|
41
|
+
def match?
|
42
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
43
|
+
|
45
44
|
yield
|
46
45
|
value_after = @state.call
|
47
46
|
|
48
|
-
expected == value_after
|
49
|
-
end
|
50
|
-
|
51
|
-
# A string containing a human-readable representation of the matcher.
|
52
|
-
def inspect
|
53
|
-
"#{self.class}(#{expected.inspect})"
|
47
|
+
@expected == value_after
|
54
48
|
end
|
55
49
|
|
56
50
|
# Returns a string representing the matcher.
|
51
|
+
#
|
52
|
+
# @return [String] a human-readable description of the matcher
|
57
53
|
def to_s
|
58
|
-
"change to #{expected.inspect}"
|
54
|
+
"change to #{@expected.inspect}"
|
59
55
|
end
|
60
56
|
end
|
61
57
|
end
|
data/lib/matchi/change.rb
CHANGED
@@ -19,9 +19,10 @@ module Matchi
|
|
19
19
|
#
|
20
20
|
# @param object [#object_id] An object.
|
21
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
22
|
def initialize(object, method, ...)
|
23
|
+
raise ::ArgumentError, "method must be a Symbol" unless method.is_a?(::Symbol)
|
24
|
+
raise ::ArgumentError, "object must respond to method" unless object.respond_to?(method)
|
25
|
+
|
25
26
|
@state = -> { object.send(method, ...) }
|
26
27
|
end
|
27
28
|
|
@@ -37,7 +38,7 @@ module Matchi
|
|
37
38
|
#
|
38
39
|
# @param minimum_delta [#object_id] The minimum delta of the expected change.
|
39
40
|
#
|
40
|
-
# @return [#
|
41
|
+
# @return [#match?] A *change by at least* matcher.
|
41
42
|
def by_at_least(minimum_delta)
|
42
43
|
ByAtLeast.new(minimum_delta, &@state)
|
43
44
|
end
|
@@ -54,7 +55,7 @@ module Matchi
|
|
54
55
|
#
|
55
56
|
# @param maximum_delta [#object_id] The maximum delta of the expected change.
|
56
57
|
#
|
57
|
-
# @return [#
|
58
|
+
# @return [#match?] A *change by at most* matcher.
|
58
59
|
def by_at_most(maximum_delta)
|
59
60
|
ByAtMost.new(maximum_delta, &@state)
|
60
61
|
end
|
@@ -71,7 +72,7 @@ module Matchi
|
|
71
72
|
#
|
72
73
|
# @param delta [#object_id] The delta of the expected change.
|
73
74
|
#
|
74
|
-
# @return [#
|
75
|
+
# @return [#match?] A *change by* matcher.
|
75
76
|
def by(delta)
|
76
77
|
By.new(delta, &@state)
|
77
78
|
end
|
@@ -86,7 +87,7 @@ module Matchi
|
|
86
87
|
#
|
87
88
|
# @param old_value [#object_id] The original value.
|
88
89
|
#
|
89
|
-
# @return [#
|
90
|
+
# @return [#match?] A *change from* wrapper.
|
90
91
|
def from(old_value)
|
91
92
|
From.new(old_value, &@state)
|
92
93
|
end
|
@@ -101,7 +102,7 @@ module Matchi
|
|
101
102
|
#
|
102
103
|
# @param new_value [#object_id] The new value to expect.
|
103
104
|
#
|
104
|
-
# @return [#
|
105
|
+
# @return [#match?] A *change to* matcher.
|
105
106
|
def to(new_value)
|
106
107
|
To.new(new_value, &@state)
|
107
108
|
end
|
data/lib/matchi/eq.rb
CHANGED
@@ -3,9 +3,6 @@
|
|
3
3
|
module Matchi
|
4
4
|
# *Equivalence* matcher.
|
5
5
|
class Eq
|
6
|
-
# @return [#eql?] An expected equivalent object.
|
7
|
-
attr_reader :expected
|
8
|
-
|
9
6
|
# Initialize the matcher with an object.
|
10
7
|
#
|
11
8
|
# @example
|
@@ -24,26 +21,23 @@ module Matchi
|
|
24
21
|
# require "matchi/eq"
|
25
22
|
#
|
26
23
|
# matcher = Matchi::Eq.new("foo")
|
27
|
-
#
|
28
|
-
# matcher.expected # => "foo"
|
29
|
-
# matcher.matches? { "foo" } # => true
|
24
|
+
# matcher.match? { "foo" } # => true
|
30
25
|
#
|
31
26
|
# @yieldreturn [#object_id] The actual value to compare to the expected
|
32
27
|
# one.
|
33
28
|
#
|
34
29
|
# @return [Boolean] Comparison between actual and expected values.
|
35
|
-
def
|
36
|
-
|
37
|
-
end
|
30
|
+
def match?
|
31
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
38
32
|
|
39
|
-
|
40
|
-
def inspect
|
41
|
-
"#{self.class}(#{expected.inspect})"
|
33
|
+
@expected.eql?(yield)
|
42
34
|
end
|
43
35
|
|
44
36
|
# Returns a string representing the matcher.
|
37
|
+
#
|
38
|
+
# @return [String] a human-readable description of the matcher
|
45
39
|
def to_s
|
46
|
-
"eq #{expected.inspect}"
|
40
|
+
"eq #{@expected.inspect}"
|
47
41
|
end
|
48
42
|
end
|
49
43
|
end
|
data/lib/matchi/match.rb
CHANGED
@@ -3,9 +3,6 @@
|
|
3
3
|
module Matchi
|
4
4
|
# *Regular expressions* matcher.
|
5
5
|
class Match
|
6
|
-
# @return [#match] A regular expression.
|
7
|
-
attr_reader :expected
|
8
|
-
|
9
6
|
# Initialize the matcher with an instance of Regexp.
|
10
7
|
#
|
11
8
|
# @example
|
@@ -15,6 +12,8 @@ module Matchi
|
|
15
12
|
#
|
16
13
|
# @param expected [#match] A regular expression.
|
17
14
|
def initialize(expected)
|
15
|
+
raise ::ArgumentError, "expected must respond to match?" unless expected.respond_to?(:match?)
|
16
|
+
|
18
17
|
@expected = expected
|
19
18
|
end
|
20
19
|
|
@@ -24,26 +23,23 @@ module Matchi
|
|
24
23
|
# require "matchi/match"
|
25
24
|
#
|
26
25
|
# matcher = Matchi::Match.new(/^foo$/)
|
27
|
-
#
|
28
|
-
# matcher.expected # => /^foo$/
|
29
|
-
# matcher.matches? { "foo" } # => true
|
26
|
+
# matcher.match? { "foo" } # => true
|
30
27
|
#
|
31
28
|
# @yieldreturn [#object_id] The actual value to compare to the expected
|
32
29
|
# one.
|
33
30
|
#
|
34
31
|
# @return [Boolean] Comparison between actual and expected values.
|
35
|
-
def
|
36
|
-
|
37
|
-
end
|
32
|
+
def match?
|
33
|
+
raise ::ArgumentError, "a block must be provided" unless block_given?
|
38
34
|
|
39
|
-
|
40
|
-
def inspect
|
41
|
-
"#{self.class}(#{expected.inspect})"
|
35
|
+
@expected.match?(yield)
|
42
36
|
end
|
43
37
|
|
44
38
|
# Returns a string representing the matcher.
|
39
|
+
#
|
40
|
+
# @return [String] a human-readable description of the matcher
|
45
41
|
def to_s
|
46
|
-
"match #{expected.inspect}"
|
42
|
+
"match #{@expected.inspect}"
|
47
43
|
end
|
48
44
|
end
|
49
45
|
end
|