rspec-expectations 3.9.2 → 3.10.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.
@@ -16,8 +16,9 @@ module RSpec
16
16
  autoload :Be, 'rspec/matchers/built_in/be'
17
17
  autoload :BeComparedTo, 'rspec/matchers/built_in/be'
18
18
  autoload :BeFalsey, 'rspec/matchers/built_in/be'
19
+ autoload :BeHelpers, 'rspec/matchers/built_in/be'
19
20
  autoload :BeNil, 'rspec/matchers/built_in/be'
20
- autoload :BePredicate, 'rspec/matchers/built_in/be'
21
+ autoload :BePredicate, 'rspec/matchers/built_in/has'
21
22
  autoload :BeTruthy, 'rspec/matchers/built_in/be'
22
23
  autoload :BeWithin, 'rspec/matchers/built_in/be_within'
23
24
  autoload :Change, 'rspec/matchers/built_in/change'
@@ -143,8 +143,13 @@ module RSpec
143
143
  end
144
144
 
145
145
  def matches?(actual)
146
- @actual = actual
147
- @actual.__send__ @operator, @expected
146
+ perform_match(actual)
147
+ rescue ArgumentError, NoMethodError
148
+ false
149
+ end
150
+
151
+ def does_not_match?(actual)
152
+ !perform_match(actual)
148
153
  rescue ArgumentError, NoMethodError
149
154
  false
150
155
  end
@@ -173,114 +178,12 @@ module RSpec
173
178
  def description
174
179
  "be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
175
180
  end
176
- end
177
-
178
- # @api private
179
- # Provides the implementation of `be_<predicate>`.
180
- # Not intended to be instantiated directly.
181
- class BePredicate < BaseMatcher
182
- include BeHelpers
183
-
184
- def initialize(*args, &block)
185
- @expected = parse_expected(args.shift)
186
- @args = args
187
- @block = block
188
- end
189
-
190
- def matches?(actual, &block)
191
- @actual = actual
192
- @block ||= block
193
- predicate_accessible? && predicate_matches?
194
- end
195
-
196
- def does_not_match?(actual, &block)
197
- @actual = actual
198
- @block ||= block
199
- predicate_accessible? && !predicate_matches?
200
- end
201
-
202
- # @api private
203
- # @return [String]
204
- def failure_message
205
- failure_message_expecting(true)
206
- end
207
-
208
- # @api private
209
- # @return [String]
210
- def failure_message_when_negated
211
- failure_message_expecting(false)
212
- end
213
-
214
- # @api private
215
- # @return [String]
216
- def description
217
- "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
218
- end
219
181
 
220
182
  private
221
183
 
222
- def predicate_accessible?
223
- actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
224
- end
225
-
226
- # support 1.8.7, evaluate once at load time for performance
227
- if String === methods.first
228
- # :nocov:
229
- def private_predicate?
230
- @actual.private_methods.include? predicate.to_s
231
- end
232
- # :nocov:
233
- else
234
- def private_predicate?
235
- @actual.private_methods.include? predicate
236
- end
237
- end
238
-
239
- def predicate_matches?
240
- method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
241
- @predicate_matches = actual.__send__(method_name, *@args, &@block)
242
- end
243
-
244
- def predicate
245
- :"#{@expected}?"
246
- end
247
-
248
- def present_tense_predicate
249
- :"#{@expected}s?"
250
- end
251
-
252
- def parse_expected(expected)
253
- @prefix, expected = prefix_and_expected(expected)
254
- expected
255
- end
256
-
257
- def prefix_and_expected(symbol)
258
- Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
259
- end
260
-
261
- def prefix_to_sentence
262
- EnglishPhrasing.split_words(@prefix)
263
- end
264
-
265
- def failure_message_expecting(value)
266
- validity_message ||
267
- "expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
268
- end
269
-
270
- def validity_message
271
- return nil if predicate_accessible?
272
-
273
- msg = "expected #{actual_formatted} to respond to `#{predicate}`".dup
274
-
275
- if private_predicate?
276
- msg << " but `#{predicate}` is a private method"
277
- elsif predicate == :true?
278
- msg << " or perhaps you meant `be true` or `be_truthy`"
279
- elsif predicate == :false?
280
- msg << " or perhaps you meant `be false` or `be_falsey`"
281
- end
282
-
283
- msg
184
+ def perform_match(actual)
185
+ @actual = actual
186
+ @actual.__send__ @operator, @expected
284
187
  end
285
188
  end
286
189
  end
@@ -23,7 +23,7 @@ module RSpec
23
23
  # a percent comparison.
24
24
  def percent_of(expected)
25
25
  @expected = expected
26
- @tolerance = @delta * @expected.abs / 100.0
26
+ @tolerance = @expected.abs * @delta / 100.0
27
27
  @unit = '%'
28
28
  self
29
29
  end
@@ -50,7 +50,7 @@ module RSpec
50
50
  # @api private
51
51
  # @return [String]
52
52
  def description
53
- "be within #{@delta}#{@unit} of #{@expected}"
53
+ "be within #{@delta}#{@unit} of #{expected_formatted}"
54
54
  end
55
55
 
56
56
  private
@@ -0,0 +1,169 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Asbtract class to implement `once`, `at_least` and other
6
+ # count constraints.
7
+ module CountExpectation
8
+ # @api public
9
+ # Specifies that the method is expected to match once.
10
+ def once
11
+ exactly(1)
12
+ end
13
+
14
+ # @api public
15
+ # Specifies that the method is expected to match twice.
16
+ def twice
17
+ exactly(2)
18
+ end
19
+
20
+ # @api public
21
+ # Specifies that the method is expected to match thrice.
22
+ def thrice
23
+ exactly(3)
24
+ end
25
+
26
+ # @api public
27
+ # Specifies that the method is expected to match the given number of times.
28
+ def exactly(number)
29
+ set_expected_count(:==, number)
30
+ self
31
+ end
32
+
33
+ # @api public
34
+ # Specifies the maximum number of times the method is expected to match
35
+ def at_most(number)
36
+ set_expected_count(:<=, number)
37
+ self
38
+ end
39
+
40
+ # @api public
41
+ # Specifies the minimum number of times the method is expected to match
42
+ def at_least(number)
43
+ set_expected_count(:>=, number)
44
+ self
45
+ end
46
+
47
+ # @api public
48
+ # No-op. Provides syntactic sugar.
49
+ def times
50
+ self
51
+ end
52
+
53
+ protected
54
+ # @api private
55
+ attr_reader :count_expectation_type, :expected_count
56
+
57
+ private
58
+
59
+ if RUBY_VERSION.to_f > 1.8
60
+ def cover?(count, number)
61
+ count.cover?(number)
62
+ end
63
+ else
64
+ def cover?(count, number)
65
+ number >= count.first && number <= count.last
66
+ end
67
+ end
68
+
69
+ def expected_count_matches?(actual_count)
70
+ @actual_count = actual_count
71
+ return @actual_count > 0 unless count_expectation_type
72
+ return cover?(expected_count, actual_count) if count_expectation_type == :<=>
73
+
74
+ @actual_count.__send__(count_expectation_type, expected_count)
75
+ end
76
+
77
+ def has_expected_count?
78
+ !!count_expectation_type
79
+ end
80
+
81
+ def set_expected_count(relativity, n)
82
+ raise_unsupported_count_expectation if unsupported_count_expectation?(relativity)
83
+
84
+ count = count_constraint_to_number(n)
85
+
86
+ if count_expectation_type == :<= && relativity == :>=
87
+ raise_impossible_count_expectation(count) if count > expected_count
88
+ @count_expectation_type = :<=>
89
+ @expected_count = count..expected_count
90
+ elsif count_expectation_type == :>= && relativity == :<=
91
+ raise_impossible_count_expectation(count) if count < expected_count
92
+ @count_expectation_type = :<=>
93
+ @expected_count = expected_count..count
94
+ else
95
+ @count_expectation_type = relativity
96
+ @expected_count = count
97
+ end
98
+ end
99
+
100
+ def raise_impossible_count_expectation(count)
101
+ text =
102
+ case count_expectation_type
103
+ when :<= then "at_least(#{count}).at_most(#{expected_count})"
104
+ when :>= then "at_least(#{expected_count}).at_most(#{count})"
105
+ end
106
+ raise ArgumentError, "The constraint #{text} is not possible"
107
+ end
108
+
109
+ def raise_unsupported_count_expectation
110
+ text =
111
+ case count_expectation_type
112
+ when :<= then "at_least"
113
+ when :>= then "at_most"
114
+ when :<=> then "at_least/at_most combination"
115
+ else "count"
116
+ end
117
+ raise ArgumentError, "Multiple #{text} constraints are not supported"
118
+ end
119
+
120
+ def count_constraint_to_number(n)
121
+ case n
122
+ when Numeric then n
123
+ when :once then 1
124
+ when :twice then 2
125
+ when :thrice then 3
126
+ else
127
+ raise ArgumentError, "Expected a number, :once, :twice or :thrice," \
128
+ " but got #{n}"
129
+ end
130
+ end
131
+
132
+ def unsupported_count_expectation?(relativity)
133
+ return true if count_expectation_type == :==
134
+ return true if count_expectation_type == :<=>
135
+ (count_expectation_type == :<= && relativity == :<=) ||
136
+ (count_expectation_type == :>= && relativity == :>=)
137
+ end
138
+
139
+ def count_expectation_description
140
+ "#{human_readable_expectation_type}#{human_readable_count(expected_count)}"
141
+ end
142
+
143
+ def count_failure_reason(action)
144
+ "#{count_expectation_description}" \
145
+ " but #{action}#{human_readable_count(@actual_count)}"
146
+ end
147
+
148
+ def human_readable_expectation_type
149
+ case count_expectation_type
150
+ when :<= then ' at most'
151
+ when :>= then ' at least'
152
+ when :<=> then ' between'
153
+ else ''
154
+ end
155
+ end
156
+
157
+ def human_readable_count(count)
158
+ case count
159
+ when Range then " #{count.first} and #{count.last} times"
160
+ when nil then ''
161
+ when 1 then ' once'
162
+ when 2 then ' twice'
163
+ else " #{count} times"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -2,12 +2,15 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  # @api private
5
- # Provides the implementation for `has_<predicate>`.
6
- # Not intended to be instantiated directly.
7
- class Has < BaseMatcher
5
+ # Provides the implementation for dynamic predicate matchers.
6
+ # Not intended to be inherited directly.
7
+ class DynamicPredicate < BaseMatcher
8
+ include BeHelpers
9
+
8
10
  def initialize(method_name, *args, &block)
9
11
  @method_name, @args, @block = method_name, args, block
10
12
  end
13
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
11
14
 
12
15
  # @private
13
16
  def matches?(actual, &block)
@@ -20,31 +23,31 @@ module RSpec
20
23
  def does_not_match?(actual, &block)
21
24
  @actual = actual
22
25
  @block ||= block
23
- predicate_accessible? && !predicate_matches?
26
+ predicate_accessible? && predicate_matches?(false)
24
27
  end
25
28
 
26
29
  # @api private
27
30
  # @return [String]
28
31
  def failure_message
29
- validity_message || "expected ##{predicate}#{failure_message_args_description} to return true, got false"
32
+ failure_message_expecting(true)
30
33
  end
31
34
 
32
35
  # @api private
33
36
  # @return [String]
34
37
  def failure_message_when_negated
35
- validity_message || "expected ##{predicate}#{failure_message_args_description} to return false, got true"
38
+ failure_message_expecting(false)
36
39
  end
37
40
 
38
41
  # @api private
39
42
  # @return [String]
40
43
  def description
41
- [method_description, args_description].compact.join(' ')
44
+ "#{method_description}#{args_to_sentence}"
42
45
  end
43
46
 
44
47
  private
45
48
 
46
49
  def predicate_accessible?
47
- !private_predicate? && predicate_exists?
50
+ @actual.respond_to? predicate
48
51
  end
49
52
 
50
53
  # support 1.8.7, evaluate once at load time for performance
@@ -60,44 +63,105 @@ module RSpec
60
63
  end
61
64
  end
62
65
 
63
- def predicate_exists?
64
- @actual.respond_to? predicate
66
+ def predicate_result
67
+ @predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
65
68
  end
66
69
 
67
- def predicate_matches?
68
- @actual.__send__(predicate, *@args, &@block)
70
+ def predicate_method_name
71
+ predicate
69
72
  end
70
73
 
71
- def predicate
74
+ def predicate_matches?(value=true)
75
+ if RSpec::Expectations.configuration.strict_predicate_matchers?
76
+ value == predicate_result
77
+ else
78
+ value == !!predicate_result
79
+ end
80
+ end
81
+
82
+ def root
72
83
  # On 1.9, there appears to be a bug where String#match can return `false`
73
84
  # rather than the match data object. Changing to Regex#match appears to
74
85
  # work around this bug. For an example of this bug, see:
75
86
  # https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
76
- @predicate ||= :"has_#{Matchers::HAS_REGEX.match(@method_name.to_s).captures.first}?"
87
+ self.class::REGEX.match(@method_name.to_s).captures.first
77
88
  end
78
89
 
79
90
  def method_description
80
- @method_name.to_s.tr('_', ' ')
91
+ EnglishPhrasing.split_words(@method_name)
81
92
  end
82
93
 
83
- def args_description
84
- return nil if @args.empty?
85
- @args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(', ')
94
+ def failure_message_expecting(value)
95
+ validity_message ||
96
+ "expected `#{actual_formatted}.#{predicate}#{args_to_s}` to #{expectation_of value}, got #{description_of @predicate_result}"
86
97
  end
87
98
 
88
- def failure_message_args_description
89
- desc = args_description
90
- "(#{desc})" if desc
99
+ def expectation_of(value)
100
+ if RSpec::Expectations.configuration.strict_predicate_matchers?
101
+ "return #{value}"
102
+ elsif value
103
+ "be truthy"
104
+ else
105
+ "be falsey"
106
+ end
91
107
  end
92
108
 
93
109
  def validity_message
110
+ return nil if predicate_accessible?
111
+
112
+ "expected #{actual_formatted} to respond to `#{predicate}`#{failure_to_respond_explanation}"
113
+ end
114
+
115
+ def failure_to_respond_explanation
94
116
  if private_predicate?
95
- "expected #{@actual} to respond to `#{predicate}` but `#{predicate}` is a private method"
96
- elsif !predicate_exists?
97
- "expected #{@actual} to respond to `#{predicate}`"
117
+ " but `#{predicate}` is a private method"
98
118
  end
99
119
  end
100
120
  end
121
+
122
+ # @api private
123
+ # Provides the implementation for `has_<predicate>`.
124
+ # Not intended to be instantiated directly.
125
+ class Has < DynamicPredicate
126
+ # :nodoc:
127
+ REGEX = Matchers::HAS_REGEX
128
+ private
129
+ def predicate
130
+ @predicate ||= :"has_#{root}?"
131
+ end
132
+ end
133
+
134
+ # @api private
135
+ # Provides the implementation of `be_<predicate>`.
136
+ # Not intended to be instantiated directly.
137
+ class BePredicate < DynamicPredicate
138
+ # :nodoc:
139
+ REGEX = Matchers::BE_PREDICATE_REGEX
140
+ private
141
+ def predicate
142
+ @predicate ||= :"#{root}?"
143
+ end
144
+
145
+ def predicate_method_name
146
+ actual.respond_to?(predicate) ? predicate : present_tense_predicate
147
+ end
148
+
149
+ def failure_to_respond_explanation
150
+ super || if predicate == :true?
151
+ " or perhaps you meant `be true` or `be_truthy`"
152
+ elsif predicate == :false?
153
+ " or perhaps you meant `be false` or `be_falsey`"
154
+ end
155
+ end
156
+
157
+ def predicate_accessible?
158
+ super || actual.respond_to?(present_tense_predicate)
159
+ end
160
+
161
+ def present_tense_predicate
162
+ :"#{root}s?"
163
+ end
164
+ end
101
165
  end
102
166
  end
103
167
  end