matchi 4.2.0 → 6.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 +4 -4
- data/LICENSE.md +1 -1
- data/README.md +96 -227
- data/lib/matchi/be.rb +10 -65
- data/lib/matchi/be_a_kind_of.rb +21 -116
- data/lib/matchi/be_an_instance_of.rb +24 -113
- data/lib/matchi/be_within/of.rb +57 -0
- data/lib/matchi/be_within.rb +17 -134
- data/lib/matchi/change/delta.rb +56 -0
- data/lib/matchi/change/from.rb +21 -61
- data/lib/matchi/change/from_to.rb +46 -0
- data/lib/matchi/change/to.rb +17 -81
- data/lib/matchi/change.rb +73 -148
- data/lib/matchi/constant.rb +55 -0
- data/lib/matchi/eq.rb +24 -65
- data/lib/matchi/match.rb +29 -64
- data/lib/matchi/number.rb +37 -0
- data/lib/matchi/predicate.rb +40 -139
- data/lib/matchi/raise_exception.rb +83 -105
- data/lib/matchi/satisfy.rb +21 -96
- data/lib/matchi.rb +22 -60
- metadata +18 -20
- data/lib/matchi/change/by.rb +0 -137
- data/lib/matchi/change/by_at_least.rb +0 -158
- data/lib/matchi/change/by_at_most.rb +0 -171
- data/lib/matchi/change/from/to.rb +0 -132
data/lib/matchi/eq.rb
CHANGED
|
@@ -1,85 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Matchi
|
|
4
|
-
#
|
|
4
|
+
# Equivalence matcher: passes when the actual value is of the same class as
|
|
5
|
+
# the expected value and `eql?` to it.
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# The expected value receives the `eql?` call, so the actual value cannot
|
|
8
|
+
# lie. The class check comes first because a few core `eql?` methods, such
|
|
9
|
+
# as `Hash#eql?`, hand the comparison back to an operand of another class;
|
|
10
|
+
# it costs nothing, since `eql?` never holds across classes for core values.
|
|
9
11
|
#
|
|
10
|
-
# @example
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# matcher.match? { "test".dup } # => true
|
|
14
|
-
# matcher.match? { "other" } # => false
|
|
15
|
-
#
|
|
16
|
-
# @example With numbers
|
|
17
|
-
# matcher = Matchi::Eq.new(42)
|
|
18
|
-
# matcher.match? { 42 } # => true
|
|
19
|
-
# matcher.match? { 42.0 } # => false # Different types
|
|
20
|
-
# matcher.match? { 43 } # => false
|
|
21
|
-
#
|
|
22
|
-
# @example With collections
|
|
23
|
-
# array = [1, 2, 3]
|
|
24
|
-
# matcher = Matchi::Eq.new(array)
|
|
25
|
-
# matcher.match? { array.dup } # => true # Same values
|
|
26
|
-
# matcher.match? { array } # => true # Same object
|
|
27
|
-
# matcher.match? { [1, 2, 3] } # => true # Same values
|
|
28
|
-
# matcher.match? { [1, 2] } # => false # Different values
|
|
29
|
-
#
|
|
30
|
-
# @see https://ruby-doc.org/core/Object.html#method-i-eql-3F
|
|
31
|
-
# @see Matchi::Be
|
|
12
|
+
# @example
|
|
13
|
+
# Matchi::Eq.new("foo").match? { "foo" } # => true
|
|
14
|
+
# Matchi::Eq.new(1).match? { 1.0 } # => false (eql? is strict on type)
|
|
32
15
|
class Eq
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
# @api public
|
|
36
|
-
#
|
|
37
|
-
# @param expected [#eql?] The expected equivalent value
|
|
38
|
-
#
|
|
39
|
-
# @return [Eq] a new instance of the matcher
|
|
40
|
-
#
|
|
41
|
-
# @example
|
|
42
|
-
# Eq.new("test") # Match strings with same value
|
|
43
|
-
# Eq.new([1, 2, 3]) # Match arrays with same elements
|
|
16
|
+
# @param expected [#eql?] the expected value
|
|
44
17
|
def initialize(expected)
|
|
45
18
|
@expected = expected
|
|
46
19
|
end
|
|
47
20
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# Two objects are considered equivalent if they have the same value, even if
|
|
52
|
-
# they are different instances.
|
|
53
|
-
#
|
|
54
|
-
# @api public
|
|
55
|
-
#
|
|
56
|
-
# @yield [] Block that returns the object to check
|
|
57
|
-
# @yieldreturn [Object] The object to verify equivalence with
|
|
58
|
-
#
|
|
59
|
-
# @return [Boolean] true if both objects have equivalent values
|
|
60
|
-
#
|
|
61
|
-
# @raise [ArgumentError] if no block is provided
|
|
62
|
-
#
|
|
63
|
-
# @example
|
|
64
|
-
# matcher = Eq.new([1, 2, 3])
|
|
65
|
-
# matcher.match? { [1, 2, 3] } # => true
|
|
66
|
-
# matcher.match? { [1, 2, 3].dup } # => true
|
|
21
|
+
# @yieldreturn [Object] the actual value
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
# @raise [ArgumentError] when no block is given
|
|
67
24
|
def match?
|
|
68
25
|
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
69
26
|
|
|
70
|
-
|
|
27
|
+
Eq.eql?(@expected, yield)
|
|
71
28
|
end
|
|
72
29
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# @api public
|
|
76
|
-
#
|
|
77
|
-
# @return [String] A string describing what this matcher verifies
|
|
78
|
-
#
|
|
79
|
-
# @example
|
|
80
|
-
# Eq.new("test").to_s # => 'eq "test"'
|
|
30
|
+
# @return [String]
|
|
81
31
|
def to_s
|
|
82
32
|
"eq #{@expected.inspect}"
|
|
83
33
|
end
|
|
34
|
+
|
|
35
|
+
# @param expected [#eql?] the value that receives the comparison
|
|
36
|
+
# @param actual [Object] a value from the code under test
|
|
37
|
+
# @return [Boolean] whether actual is of the class of expected and `eql?` to it
|
|
38
|
+
def self.eql?(expected, actual)
|
|
39
|
+
return false unless ::Kernel.instance_method(:class).bind_call(actual).equal?(expected.class)
|
|
40
|
+
|
|
41
|
+
expected.eql?(actual) ? true : false
|
|
42
|
+
end
|
|
84
43
|
end
|
|
85
44
|
end
|
data/lib/matchi/match.rb
CHANGED
|
@@ -1,83 +1,48 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Matchi
|
|
4
|
-
#
|
|
4
|
+
# Regular expression matcher: passes when the actual value is a String or a
|
|
5
|
+
# Symbol that the expected Regexp matches. Any other actual value fails, as
|
|
6
|
+
# does a string with an invalid or incompatible encoding.
|
|
5
7
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
8
|
+
# Only a `Regexp` is accepted as pattern: with a `String` pattern,
|
|
9
|
+
# `String#match?` would turn the actual value into the regular expression,
|
|
10
|
+
# handing the comparison to the code under test. Use {Matchi::Satisfy} for
|
|
11
|
+
# other predicates.
|
|
9
12
|
#
|
|
10
|
-
# @example
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# matcher.match? { "other_string" } # => false
|
|
14
|
-
#
|
|
15
|
-
# @example Case sensitivity
|
|
16
|
-
# matcher = Matchi::Match.new(/^test$/i)
|
|
17
|
-
# matcher.match? { "TEST" } # => true
|
|
18
|
-
# matcher.match? { "Test" } # => true
|
|
19
|
-
# matcher.match? { "testing" } # => false
|
|
20
|
-
#
|
|
21
|
-
# @example Multiline patterns
|
|
22
|
-
# matcher = Matchi::Match.new(/\A\d+\Z/m)
|
|
23
|
-
# matcher.match? { "123" } # => true
|
|
24
|
-
# matcher.match? { "12.3" } # => false
|
|
25
|
-
#
|
|
26
|
-
# @see https://ruby-doc.org/core/Regexp.html#method-i-match-3F
|
|
13
|
+
# @example
|
|
14
|
+
# Matchi::Match.new(/^foo/).match? { "foobar" } # => true
|
|
15
|
+
# Matchi::Match.new(/^foo/).match? { 42 } # => false
|
|
27
16
|
class Match
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# @api public
|
|
31
|
-
#
|
|
32
|
-
# @param expected [#match?] A pattern that responds to match?
|
|
33
|
-
#
|
|
34
|
-
# @raise [ArgumentError] if the pattern doesn't respond to match?
|
|
35
|
-
#
|
|
36
|
-
# @return [Match] a new instance of the matcher
|
|
37
|
-
#
|
|
38
|
-
# @example
|
|
39
|
-
# Match.new(/\d+/) # Match digits
|
|
40
|
-
# Match.new(/^test$/i) # Case-insensitive match
|
|
41
|
-
# Match.new(/\A\w+\Z/) # Full string word characters
|
|
17
|
+
# @param expected [Regexp] the pattern
|
|
18
|
+
# @raise [ArgumentError] when expected is not a Regexp
|
|
42
19
|
def initialize(expected)
|
|
43
|
-
raise ::ArgumentError, "expected must
|
|
20
|
+
raise ::ArgumentError, "expected must be a Regexp (got: #{expected.inspect})" unless ::Regexp === expected
|
|
44
21
|
|
|
45
22
|
@expected = expected
|
|
46
23
|
end
|
|
47
24
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# The match is performed on the entire string unless the pattern specifically
|
|
52
|
-
# allows partial matches.
|
|
53
|
-
#
|
|
54
|
-
# @api public
|
|
55
|
-
#
|
|
56
|
-
# @yield [] Block that returns the value to check
|
|
57
|
-
# @yieldreturn [#to_s] The value to match against the pattern
|
|
58
|
-
#
|
|
59
|
-
# @return [Boolean] true if the value matches the pattern
|
|
60
|
-
#
|
|
61
|
-
# @raise [ArgumentError] if no block is provided
|
|
62
|
-
#
|
|
63
|
-
# @example
|
|
64
|
-
# matcher = Match.new(/^\d{3}-\d{2}-\d{4}$/)
|
|
65
|
-
# matcher.match? { "123-45-6789" } # => true
|
|
66
|
-
# matcher.match? { "123456789" } # => false
|
|
25
|
+
# @yieldreturn [Object] the actual value
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
# @raise [ArgumentError] when no block is given
|
|
67
28
|
def match?
|
|
68
29
|
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
69
30
|
|
|
70
|
-
|
|
31
|
+
actual = yield
|
|
32
|
+
return false unless ::String === actual || ::Symbol === actual
|
|
33
|
+
|
|
34
|
+
# A plain copy: a subclass of String has no say in the comparison.
|
|
35
|
+
string = ::String.new(::Symbol === actual ? actual.name : actual)
|
|
36
|
+
return false unless string.valid_encoding?
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
@expected.match?(string)
|
|
40
|
+
rescue ::Encoding::CompatibilityError
|
|
41
|
+
false
|
|
42
|
+
end
|
|
71
43
|
end
|
|
72
44
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# @api public
|
|
76
|
-
#
|
|
77
|
-
# @return [String] A string describing what this matcher verifies
|
|
78
|
-
#
|
|
79
|
-
# @example
|
|
80
|
-
# Match.new(/^test/).to_s # => "match /^test/"
|
|
45
|
+
# @return [String]
|
|
81
46
|
def to_s
|
|
82
47
|
"match #{@expected.inspect}"
|
|
83
48
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Matchi
|
|
4
|
+
# Validation of the numbers given to matchers: deltas and expected values
|
|
5
|
+
# must be finite and real, so that a `NaN`, an infinity or a `Complex`
|
|
6
|
+
# cannot build a matcher that never, or always, passes.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
module Number
|
|
10
|
+
# @param value [Object]
|
|
11
|
+
# @return [Boolean] whether value is a finite real Numeric
|
|
12
|
+
def self.finite_real?(value)
|
|
13
|
+
::Numeric === value && value.real? && value.finite?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param value [Object]
|
|
17
|
+
# @param role [String] the argument name, for the error message
|
|
18
|
+
# @return [Numeric] value
|
|
19
|
+
# @raise [ArgumentError] when value is not a finite real Numeric
|
|
20
|
+
def self.finite_real!(value, role)
|
|
21
|
+
raise ::ArgumentError, "#{role} must be a finite real Numeric (got: #{value.inspect})" unless finite_real?(value)
|
|
22
|
+
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param value [Object]
|
|
27
|
+
# @param role [String] the argument name, for the error message
|
|
28
|
+
# @return [Numeric] value
|
|
29
|
+
# @raise [ArgumentError] when value is not a finite real non-negative Numeric
|
|
30
|
+
def self.non_negative!(value, role)
|
|
31
|
+
finite_real!(value, role)
|
|
32
|
+
raise ::ArgumentError, "#{role} must be non-negative (got: #{value.inspect})" if value.negative?
|
|
33
|
+
|
|
34
|
+
value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/matchi/predicate.rb
CHANGED
|
@@ -1,172 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "constant"
|
|
4
|
+
|
|
3
5
|
module Matchi
|
|
4
|
-
# Predicate matcher
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# of predicate formats: 'be_*' which converts to '*?' and 'have_*' which converts to 'has_*?'.
|
|
10
|
-
#
|
|
11
|
-
# @example Basic empty check
|
|
12
|
-
# matcher = Matchi::Predicate.new(:be_empty)
|
|
13
|
-
# matcher.match? { [] } # => true
|
|
14
|
-
# matcher.match? { [1, 2] } # => false
|
|
15
|
-
#
|
|
16
|
-
# @example Object property check with arguments
|
|
17
|
-
# matcher = Matchi::Predicate.new(:have_key, :name)
|
|
18
|
-
# matcher.match? { { name: "Alice" } } # => true
|
|
19
|
-
# matcher.match? { { age: 30 } } # => false
|
|
20
|
-
#
|
|
21
|
-
# @example Using keyword arguments
|
|
22
|
-
# class Record
|
|
23
|
-
# def complete?(status: nil)
|
|
24
|
-
# status.nil? || status == :validated
|
|
25
|
-
# end
|
|
26
|
-
# end
|
|
6
|
+
# Predicate matcher: passes when a predicate method of the actual value
|
|
7
|
+
# returns `true`. The matcher name `be_xxx` calls `xxx?`, and `have_xxx`
|
|
8
|
+
# calls `has_xxx?`, with `Kernel#public_send` bound to the actual value.
|
|
9
|
+
# The predicate must return `true` or `false`; any other value is an error,
|
|
10
|
+
# not a truthy pass.
|
|
27
11
|
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
12
|
+
# Since the actual value answers the call, this matcher depends on the
|
|
13
|
+
# honesty of the object under test. Use it for the object's own documented
|
|
14
|
+
# predicates.
|
|
30
15
|
#
|
|
31
|
-
# @example
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# block ? super : empty?
|
|
35
|
-
# end
|
|
36
|
-
# end
|
|
37
|
-
#
|
|
38
|
-
# matcher = Matchi::Predicate.new(:be_all) { |x| x.positive? }
|
|
39
|
-
# matcher.match? { [1, 2, 3] } # => true
|
|
40
|
-
# matcher.match? { [-1, 2, 3] } # => false
|
|
41
|
-
#
|
|
42
|
-
# @see https://ruby-doc.org/core/Object.html#method-i-respond_to-3F
|
|
16
|
+
# @example
|
|
17
|
+
# Matchi::Predicate.new(:be_empty).match? { [] } # => true
|
|
18
|
+
# Matchi::Predicate.new(:have_key, :a).match? { { a: 1 } } # => true
|
|
43
19
|
class Predicate
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
# @api private
|
|
48
|
-
PREFIXES = {
|
|
49
|
-
"be_" => ->(name) { "#{name.gsub(/\A(?:be_)/, "")}?" },
|
|
50
|
-
"have_" => ->(name) { "#{name.gsub(/\A(?:have_)/, "has_")}?" }
|
|
51
|
-
}.freeze
|
|
20
|
+
# A matcher name: `be_` or `have_`, then a letter, then letters, digits
|
|
21
|
+
# or underscores, in any script. It never ends with `?` or `!`.
|
|
22
|
+
NAME = /\A(be|have)_\p{Alpha}[\p{Alnum}_]*\z/
|
|
52
23
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# @
|
|
56
|
-
#
|
|
57
|
-
# @
|
|
58
|
-
# @param args [Array] Optional positional arguments to pass to the predicate method
|
|
59
|
-
# @param kwargs [Hash] Optional keyword arguments to pass to the predicate method
|
|
60
|
-
# @param block [Proc] Optional block to pass to the predicate method
|
|
61
|
-
#
|
|
62
|
-
# @raise [ArgumentError] if the predicate name format is invalid
|
|
63
|
-
#
|
|
64
|
-
# @return [Predicate] a new instance of the matcher
|
|
65
|
-
#
|
|
66
|
-
# @example With simple predicate
|
|
67
|
-
# Predicate.new(:be_empty) # Empty check
|
|
68
|
-
#
|
|
69
|
-
# @example With arguments
|
|
70
|
-
# Predicate.new(:have_key, :id) # Key presence check
|
|
71
|
-
#
|
|
72
|
-
# @example With keyword arguments
|
|
73
|
-
# Predicate.new(:be_valid, status: true) # Conditional validation
|
|
24
|
+
# @param name [Symbol, String] `be_xxx` or `have_xxx`
|
|
25
|
+
# @param args [Array] positional arguments for the predicate
|
|
26
|
+
# @param kwargs [Hash] keyword arguments for the predicate
|
|
27
|
+
# @param block [Proc, nil] a block for the predicate
|
|
28
|
+
# @raise [ArgumentError] when the name does not start with be_ or have_
|
|
74
29
|
def initialize(name, *args, **kwargs, &block)
|
|
75
30
|
@name = String(name)
|
|
76
|
-
raise ::ArgumentError, "
|
|
31
|
+
raise ::ArgumentError, "name must match #{NAME.inspect} (got: #{name.inspect})" unless NAME.match?(@name)
|
|
77
32
|
|
|
33
|
+
@method_name = method_name
|
|
78
34
|
@args = args
|
|
79
35
|
@kwargs = kwargs
|
|
80
36
|
@block = block
|
|
81
37
|
end
|
|
82
38
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
# @api public
|
|
90
|
-
#
|
|
91
|
-
# @yield [] Block that returns the object to check
|
|
92
|
-
# @yieldreturn [Object] The object to call the predicate method on
|
|
93
|
-
#
|
|
94
|
-
# @return [Boolean] true if the predicate method returns true
|
|
95
|
-
#
|
|
96
|
-
# @raise [ArgumentError] if no block is provided
|
|
97
|
-
# @raise [TypeError] if predicate method returns non-boolean value
|
|
98
|
-
#
|
|
99
|
-
# @example Basic usage
|
|
100
|
-
# matcher = Predicate.new(:be_empty)
|
|
101
|
-
# matcher.match? { [] } # => true
|
|
102
|
-
# matcher.match? { [1] } # => false
|
|
103
|
-
#
|
|
104
|
-
# @example With arguments
|
|
105
|
-
# matcher = Predicate.new(:have_key, :id)
|
|
106
|
-
# matcher.match? { { id: 1 } } # => true
|
|
39
|
+
# @yieldreturn [Object] the actual value
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
# @raise [ArgumentError] when no block is given
|
|
42
|
+
# @raise [TypeError] when the predicate returns something other than true or false
|
|
43
|
+
# @raise [NoMethodError] when the actual value has no such public predicate
|
|
107
44
|
def match?
|
|
108
45
|
raise ::ArgumentError, "a block must be provided" unless block_given?
|
|
109
46
|
|
|
110
|
-
|
|
111
|
-
return
|
|
47
|
+
result = ::Kernel.instance_method(:public_send).bind_call(yield, @method_name, *@args, **@kwargs, &@block)
|
|
48
|
+
return result if true.equal?(result) || false.equal?(result)
|
|
112
49
|
|
|
113
|
-
raise ::TypeError, "
|
|
50
|
+
raise ::TypeError, "#{@method_name} must return true or false (got a #{Constant.label(result_class(result))})"
|
|
114
51
|
end
|
|
115
52
|
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# @api public
|
|
119
|
-
#
|
|
120
|
-
# @return [String] A string describing what this matcher verifies
|
|
121
|
-
#
|
|
122
|
-
# @example Simple predicate
|
|
123
|
-
# Predicate.new(:be_empty).to_s # => "be empty"
|
|
124
|
-
#
|
|
125
|
-
# @example With arguments
|
|
126
|
-
# Predicate.new(:have_key, :id).to_s # => "have key :id"
|
|
127
|
-
#
|
|
128
|
-
# @example With keyword arguments
|
|
129
|
-
# Predicate.new(:be_valid, active: true).to_s # => "be valid active: true"
|
|
53
|
+
# @return [String]
|
|
130
54
|
def to_s
|
|
131
|
-
(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
(@block.nil? ? "" : "&block")
|
|
136
|
-
].reject { |i| i.eql?("") }.join(", ")
|
|
137
|
-
).strip
|
|
55
|
+
arguments = @args.map(&:inspect) + @kwargs.map { |key, value| "#{key}: #{value.inspect}" }
|
|
56
|
+
arguments << "&block" if @block
|
|
57
|
+
|
|
58
|
+
[@name.tr("_", " "), arguments.join(", ")].reject(&:empty?).join(" ")
|
|
138
59
|
end
|
|
139
60
|
|
|
140
61
|
private
|
|
141
62
|
|
|
142
|
-
# Converts the predicate name into the actual method name to call.
|
|
143
|
-
#
|
|
144
|
-
# @api private
|
|
145
|
-
#
|
|
146
|
-
# @return [Symbol] The method name to call on the object
|
|
147
|
-
# @raise [ArgumentError] if the predicate prefix is unknown
|
|
148
|
-
#
|
|
149
|
-
# @example
|
|
150
|
-
# # With be_ prefix
|
|
151
|
-
# method_name # => :empty? (from be_empty)
|
|
152
|
-
# # With have_ prefix
|
|
153
|
-
# method_name # => :has_key? (from have_key)
|
|
154
63
|
def method_name
|
|
155
|
-
|
|
156
|
-
return transform.call(@name) if transform
|
|
64
|
+
return "#{@name.delete_prefix("be_")}?" if @name.start_with?("be_")
|
|
157
65
|
|
|
158
|
-
|
|
66
|
+
"has_#{@name.delete_prefix("have_")}?"
|
|
159
67
|
end
|
|
160
68
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
# @api private
|
|
164
|
-
#
|
|
165
|
-
# @return [Boolean] true if the name follows the required format
|
|
166
|
-
def valid_name?
|
|
167
|
-
return false if @name.match?(/[?!]\z/)
|
|
168
|
-
|
|
169
|
-
PREFIXES.keys.any? { |prefix| @name.start_with?(prefix) }
|
|
69
|
+
def result_class(result)
|
|
70
|
+
::Kernel.instance_method(:class).bind_call(result)
|
|
170
71
|
end
|
|
171
72
|
end
|
|
172
73
|
end
|