rspec-expectations 3.9.1 → 3.10.1

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'
@@ -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