rspec-expectations 3.9.3 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'
@@ -186,139 +186,6 @@ module RSpec
186
186
  @actual.__send__ @operator, @expected
187
187
  end
188
188
  end
189
-
190
- # @api private
191
- # Provides the implementation of `be_<predicate>`.
192
- # Not intended to be instantiated directly.
193
- class BePredicate < BaseMatcher
194
- include BeHelpers
195
-
196
- if RSpec::Support::RubyFeatures.kw_args_supported?
197
- binding.eval(<<-CODE, __FILE__, __LINE__)
198
- def initialize(*args, **kwargs, &block)
199
- @expected = parse_expected(args.shift)
200
- @args = args
201
- @kwargs = kwargs
202
- @block = block
203
- end
204
- CODE
205
- else
206
- def initialize(*args, &block)
207
- @expected = parse_expected(args.shift)
208
- @args = args
209
- @block = block
210
- end
211
- end
212
-
213
- def matches?(actual, &block)
214
- @actual = actual
215
- @block ||= block
216
- predicate_accessible? && predicate_matches?
217
- end
218
-
219
- def does_not_match?(actual, &block)
220
- @actual = actual
221
- @block ||= block
222
- predicate_accessible? && !predicate_matches?
223
- end
224
-
225
- # @api private
226
- # @return [String]
227
- def failure_message
228
- failure_message_expecting(true)
229
- end
230
-
231
- # @api private
232
- # @return [String]
233
- def failure_message_when_negated
234
- failure_message_expecting(false)
235
- end
236
-
237
- # @api private
238
- # @return [String]
239
- def description
240
- "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
241
- end
242
-
243
- private
244
-
245
- def predicate_accessible?
246
- actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
247
- end
248
-
249
- # support 1.8.7, evaluate once at load time for performance
250
- if String === methods.first
251
- # :nocov:
252
- def private_predicate?
253
- @actual.private_methods.include? predicate.to_s
254
- end
255
- # :nocov:
256
- else
257
- def private_predicate?
258
- @actual.private_methods.include? predicate
259
- end
260
- end
261
-
262
- if RSpec::Support::RubyFeatures.kw_args_supported?
263
- binding.eval(<<-CODE, __FILE__, __LINE__)
264
- def predicate_matches?
265
- method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
266
- if @kwargs.empty?
267
- @predicate_matches = actual.__send__(method_name, *@args, &@block)
268
- else
269
- @predicate_matches = actual.__send__(method_name, *@args, **@kwargs, &@block)
270
- end
271
- end
272
- CODE
273
- else
274
- def predicate_matches?
275
- method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
276
- @predicate_matches = actual.__send__(method_name, *@args, &@block)
277
- end
278
- end
279
-
280
- def predicate
281
- :"#{@expected}?"
282
- end
283
-
284
- def present_tense_predicate
285
- :"#{@expected}s?"
286
- end
287
-
288
- def parse_expected(expected)
289
- @prefix, expected = prefix_and_expected(expected)
290
- expected
291
- end
292
-
293
- def prefix_and_expected(symbol)
294
- Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
295
- end
296
-
297
- def prefix_to_sentence
298
- EnglishPhrasing.split_words(@prefix)
299
- end
300
-
301
- def failure_message_expecting(value)
302
- validity_message ||
303
- "expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
304
- end
305
-
306
- def validity_message
307
- return nil if predicate_accessible?
308
-
309
- msg = "expected #{actual_formatted} to respond to `#{predicate}`".dup
310
-
311
- if private_predicate?
312
- msg << " but `#{predicate}` is a private method"
313
- elsif predicate == :true?
314
- msg << " or perhaps you meant `be true` or `be_truthy`"
315
- elsif predicate == :false?
316
- msg << " or perhaps you meant `be false` or `be_falsey`"
317
- end
318
-
319
- msg
320
- end
321
- end
322
189
  end
323
190
  end
324
191
  end
@@ -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,20 +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
8
- if RSpec::Support::RubyFeatures.kw_args_supported?
9
- binding.eval(<<-CODE, __FILE__, __LINE__)
10
- def initialize(method_name, *args, **kwargs, &block)
11
- @method_name, @args, @kwargs, @block = method_name, args, kwargs, block
12
- end
13
- CODE
14
- else
15
- def initialize(method_name, *args, &block)
16
- @method_name, @args, @block = method_name, args, block
17
- end
5
+ # Provides the implementation for dynamic predicate matchers.
6
+ # Not intended to be inherited directly.
7
+ class DynamicPredicate < BaseMatcher
8
+ include BeHelpers
9
+
10
+ def initialize(method_name, *args, &block)
11
+ @method_name, @args, @block = method_name, args, block
18
12
  end
13
+ ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
19
14
 
20
15
  # @private
21
16
  def matches?(actual, &block)
@@ -28,31 +23,31 @@ module RSpec
28
23
  def does_not_match?(actual, &block)
29
24
  @actual = actual
30
25
  @block ||= block
31
- predicate_accessible? && !predicate_matches?
26
+ predicate_accessible? && predicate_matches?(false)
32
27
  end
33
28
 
34
29
  # @api private
35
30
  # @return [String]
36
31
  def failure_message
37
- validity_message || "expected ##{predicate}#{failure_message_args_description} to return true, got false"
32
+ failure_message_expecting(true)
38
33
  end
39
34
 
40
35
  # @api private
41
36
  # @return [String]
42
37
  def failure_message_when_negated
43
- validity_message || "expected ##{predicate}#{failure_message_args_description} to return false, got true"
38
+ failure_message_expecting(false)
44
39
  end
45
40
 
46
41
  # @api private
47
42
  # @return [String]
48
43
  def description
49
- [method_description, args_description].compact.join(' ')
44
+ "#{method_description}#{args_to_sentence}"
50
45
  end
51
46
 
52
47
  private
53
48
 
54
49
  def predicate_accessible?
55
- !private_predicate? && predicate_exists?
50
+ @actual.respond_to? predicate
56
51
  end
57
52
 
58
53
  # support 1.8.7, evaluate once at load time for performance
@@ -68,56 +63,105 @@ module RSpec
68
63
  end
69
64
  end
70
65
 
71
- def predicate_exists?
72
- @actual.respond_to? predicate
66
+ def predicate_result
67
+ @predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
73
68
  end
74
69
 
75
- if RSpec::Support::RubyFeatures.kw_args_supported?
76
- binding.eval(<<-CODE, __FILE__, __LINE__)
77
- def predicate_matches?
78
- if @kwargs.empty?
79
- @actual.__send__(predicate, *@args, &@block)
80
- else
81
- @actual.__send__(predicate, *@args, **@kwargs, &@block)
82
- end
83
- end
84
- CODE
85
- else
86
- def predicate_matches?
87
- @actual.__send__(predicate, *@args, &@block)
70
+ def predicate_method_name
71
+ predicate
72
+ end
73
+
74
+ def predicate_matches?(value=true)
75
+ if RSpec::Expectations.configuration.strict_predicate_matchers?
76
+ value == predicate_result
77
+ else
78
+ value == !!predicate_result
88
79
  end
89
80
  end
90
81
 
91
- def predicate
82
+ def root
92
83
  # On 1.9, there appears to be a bug where String#match can return `false`
93
84
  # rather than the match data object. Changing to Regex#match appears to
94
85
  # work around this bug. For an example of this bug, see:
95
86
  # https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
96
- @predicate ||= :"has_#{Matchers::HAS_REGEX.match(@method_name.to_s).captures.first}?"
87
+ self.class::REGEX.match(@method_name.to_s).captures.first
97
88
  end
98
89
 
99
90
  def method_description
100
- @method_name.to_s.tr('_', ' ')
91
+ EnglishPhrasing.split_words(@method_name)
101
92
  end
102
93
 
103
- def args_description
104
- return nil if @args.empty?
105
- @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}"
106
97
  end
107
98
 
108
- def failure_message_args_description
109
- desc = args_description
110
- "(#{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
111
107
  end
112
108
 
113
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
114
116
  if private_predicate?
115
- "expected #{@actual} to respond to `#{predicate}` but `#{predicate}` is a private method"
116
- elsif !predicate_exists?
117
- "expected #{@actual} to respond to `#{predicate}`"
117
+ " but `#{predicate}` is a private method"
118
118
  end
119
119
  end
120
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
121
165
  end
122
166
  end
123
167
  end