rspec-expectations 3.9.3 → 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'
@@ -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