rspec-expectations 2.14.5 → 2.99.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.
Files changed (63) hide show
  1. checksums.yaml +15 -7
  2. data/Changelog.md +114 -31
  3. data/features/README.md +2 -1
  4. data/features/built_in_matchers/be.feature +40 -40
  5. data/features/step_definitions/additional_cli_steps.rb +10 -0
  6. data/features/test_frameworks/test_unit.feature +40 -0
  7. data/lib/rspec/expectations/caller_filter.rb +60 -0
  8. data/lib/rspec/{matchers → expectations}/configuration.rb +5 -3
  9. data/lib/rspec/expectations/deprecation.rb +11 -15
  10. data/lib/rspec/expectations/expectation_target.rb +75 -8
  11. data/lib/rspec/expectations/handler.rb +5 -1
  12. data/lib/rspec/expectations/syntax.rb +3 -5
  13. data/lib/rspec/expectations/version.rb +1 -2
  14. data/lib/rspec/expectations.rb +1 -1
  15. data/lib/rspec/matchers/be_close.rb +4 -1
  16. data/lib/rspec/matchers/built_in/base_matcher.rb +10 -5
  17. data/lib/rspec/matchers/built_in/be.rb +38 -9
  18. data/lib/rspec/matchers/built_in/be_within.rb +8 -2
  19. data/lib/rspec/matchers/built_in/change.rb +39 -1
  20. data/lib/rspec/matchers/built_in/has.rb +40 -7
  21. data/lib/rspec/matchers/built_in/have.rb +151 -2
  22. data/lib/rspec/matchers/built_in/include.rb +1 -1
  23. data/lib/rspec/matchers/built_in/match_array.rb +1 -1
  24. data/lib/rspec/matchers/built_in/raise_error.rb +7 -1
  25. data/lib/rspec/matchers/built_in/respond_to.rb +7 -1
  26. data/lib/rspec/matchers/built_in/satisfy.rb +7 -1
  27. data/lib/rspec/matchers/built_in/throw_symbol.rb +10 -2
  28. data/lib/rspec/matchers/built_in/yield.rb +25 -3
  29. data/lib/rspec/matchers/built_in.rb +2 -2
  30. data/lib/rspec/matchers/differentiate_block_method_types.rb +55 -0
  31. data/lib/rspec/matchers/dsl.rb +2 -1
  32. data/lib/rspec/matchers/match_aliases.rb +22 -0
  33. data/lib/rspec/matchers/matcher.rb +131 -10
  34. data/lib/rspec/matchers/operator_matcher.rb +70 -70
  35. data/lib/rspec/matchers/pretty.rb +4 -0
  36. data/lib/rspec/matchers/test_unit_integration.rb +22 -5
  37. data/lib/rspec/matchers.rb +41 -7
  38. data/lib/rspec-expectations.rb +5 -0
  39. data/spec/rspec/{matchers → expectations}/configuration_spec.rb +21 -2
  40. data/spec/rspec/expectations/expectation_target_spec.rb +62 -0
  41. data/spec/rspec/expectations/extensions/kernel_spec.rb +4 -0
  42. data/spec/rspec/expectations/handler_spec.rb +1 -1
  43. data/spec/rspec/expectations/syntax_spec.rb +6 -6
  44. data/spec/rspec/expectations_spec.rb +22 -1
  45. data/spec/rspec/matchers/base_matcher_spec.rb +15 -21
  46. data/spec/rspec/matchers/be_close_spec.rb +4 -1
  47. data/spec/rspec/matchers/be_spec.rb +105 -10
  48. data/spec/rspec/matchers/change_spec.rb +76 -1
  49. data/spec/rspec/matchers/description_generation_spec.rb +22 -18
  50. data/spec/rspec/matchers/differentiate_block_method_types_spec.rb +39 -0
  51. data/spec/rspec/matchers/eq_spec.rb +1 -1
  52. data/spec/rspec/matchers/has_spec.rb +24 -0
  53. data/spec/rspec/matchers/have_spec.rb +399 -1
  54. data/spec/rspec/matchers/matcher_spec.rb +213 -24
  55. data/spec/rspec/matchers/operator_matcher_spec.rb +28 -9
  56. data/spec/rspec/matchers/pretty_spec.rb +23 -0
  57. data/spec/rspec/matchers/raise_error_spec.rb +3 -3
  58. data/spec/rspec/matchers/throw_symbol_spec.rb +14 -14
  59. data/spec/spec_helper.rb +4 -2
  60. data/spec/support/helper_methods.rb +42 -0
  61. data/spec/support/shared_examples.rb +42 -0
  62. metadata +85 -64
  63. data/spec/rspec/matchers/matchers_spec.rb +0 -37
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class Have
5
+ include MatchAliases
6
+
5
7
  QUERY_METHODS = [:size, :length, :count].freeze
6
8
 
7
9
  def initialize(expected, relativity=:exactly)
@@ -11,7 +13,11 @@ module RSpec
11
13
  else expected
12
14
  end
13
15
  @relativity = relativity
16
+
14
17
  @actual = @collection_name = @plural_collection_name = nil
18
+ @target_owns_a_collection = false
19
+ @negative_expectation = false
20
+ @expectation_format_method = "to"
15
21
  end
16
22
 
17
23
  def relativities
@@ -29,13 +35,20 @@ module RSpec
29
35
  for query_method in QUERY_METHODS
30
36
  next unless collection.respond_to?(query_method)
31
37
  @actual = collection.__send__(query_method)
32
- break unless @actual.nil?
38
+
39
+ if @actual
40
+ print_deprecation_message(query_method)
41
+ break
42
+ end
33
43
  end
44
+
34
45
  raise not_a_collection if @actual.nil?
35
46
  else
36
47
  query_method = determine_query_method(collection)
37
48
  raise not_a_collection unless query_method
38
49
  @actual = collection.__send__(query_method)
50
+
51
+ print_deprecation_message(query_method)
39
52
  end
40
53
  case @relativity
41
54
  when :at_least then @actual >= @expected
@@ -43,12 +56,19 @@ module RSpec
43
56
  else @actual == @expected
44
57
  end
45
58
  end
46
- alias == matches?
59
+
60
+ def does_not_match?(collection_or_owner)
61
+ @negative_expectation = true
62
+ @expectation_format_method = "to_not"
63
+ !matches?(collection_or_owner)
64
+ end
47
65
 
48
66
  def determine_collection(collection_or_owner)
49
67
  if collection_or_owner.respond_to?(@collection_name)
68
+ @target_owns_a_collection = true
50
69
  collection_or_owner.__send__(@collection_name, *@args, &@block)
51
70
  elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
71
+ @target_owns_a_collection = true
52
72
  collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
53
73
  elsif determine_query_method(collection_or_owner)
54
74
  collection_or_owner
@@ -118,6 +138,135 @@ EOF
118
138
  def enumerator_class
119
139
  RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
120
140
  end
141
+
142
+ def print_deprecation_message(query_method)
143
+ deprecation_message = "the rspec-collection_matchers gem "
144
+ deprecation_message << "or replace your expectation with something like "
145
+ if for_rspec_rails_error_on?
146
+ # It is supposed to be safe to be able to convert the args array to
147
+ # a string. This is because the `errors_on` method only takes two
148
+ # valid arguments: attribute name (symbol/string) and a hash
149
+ deprecated_call = expectation_expression(query_method, "record")
150
+ deprecated_call << "(#{errors_on_args_list})" unless @args.empty?
151
+
152
+ deprecation_message << <<-EOS.gsub(/^\s+\|/, '')
153
+ |
154
+ |
155
+ | #{record_valid_expression}
156
+ | expect(#{record_errors_expression(query_method)}).#{expectation_format_method} #{suggested_matcher_expression}
157
+ |
158
+ EOS
159
+ else
160
+ deprecated_call = expectation_expression(query_method)
161
+ deprecation_message << "`expect(#{cardinality_expression(query_method)}).#{expectation_format_method} #{suggested_matcher_expression}`"
162
+ end
163
+
164
+ RSpec.deprecate("`#{deprecated_call}`",
165
+ :replacement => deprecation_message,
166
+ :type => "the have matcher"
167
+ )
168
+ end
169
+
170
+ def expectation_expression(query_method, target = nil)
171
+ target ||= target_expression
172
+ if @negative_expectation
173
+ RSpec::Expectations::Syntax.negative_expression(target, original_matcher_expression)
174
+ else
175
+ RSpec::Expectations::Syntax.positive_expression(target, original_matcher_expression)
176
+ end
177
+ end
178
+
179
+ def target_expression
180
+ if @target_owns_a_collection
181
+ 'collection_owner'
182
+ else
183
+ 'collection'
184
+ end
185
+ end
186
+
187
+ def original_matcher_expression
188
+ "#{matcher_method}(#{@expected}).#{@collection_name}"
189
+ end
190
+
191
+ def expectation_format_method
192
+ if @relativity == :exactly
193
+ @expectation_format_method
194
+ else
195
+ "to"
196
+ end
197
+ end
198
+
199
+ def cardinality_expression(query_method)
200
+ expression = "#{target_expression}."
201
+ expression << "#{@collection_name}." if @target_owns_a_collection
202
+ expression << String(query_method)
203
+ end
204
+
205
+ def suggested_matcher_expression
206
+ send("suggested_matcher_expression_for_#{@relativity}")
207
+ end
208
+
209
+ def suggested_matcher_expression_for_exactly
210
+ "eq(#{@expected})"
211
+ end
212
+
213
+ def suggested_matcher_expression_for_at_most
214
+ if @negative_expectation
215
+ "be > #{@expected}"
216
+ else
217
+ "be <= #{@expected}"
218
+ end
219
+ end
220
+
221
+ def suggested_matcher_expression_for_at_least
222
+ if @negative_expectation
223
+ "be < #{@expected}"
224
+ else
225
+ "be >= #{@expected}"
226
+ end
227
+ end
228
+
229
+ def matcher_method
230
+ case @relativity
231
+ when :exactly
232
+ "have"
233
+ when :at_most
234
+ "have_at_most"
235
+ when :at_least
236
+ "have_at_least"
237
+ end
238
+ end
239
+
240
+ # RSpec Rails `errors_on` specific helpers
241
+ def for_rspec_rails_error_on?
242
+ defined?(RSpec::Rails) &&
243
+ /\.errors?_on\b/ =~ original_matcher_expression
244
+ end
245
+
246
+ def errors_on_args_list
247
+ list = @args.first.inspect
248
+ context = validation_context
249
+ list << ", :context => #{context}" if context
250
+ list
251
+ end
252
+
253
+ def record_valid_expression
254
+ expression = "record.valid?"
255
+ if on_context = validation_context
256
+ expression << "(#{on_context})"
257
+ end
258
+ expression
259
+ end
260
+
261
+ def validation_context
262
+ return unless Hash === @args.last
263
+ @args.last[:context].inspect
264
+ end
265
+
266
+ def record_errors_expression(query_method)
267
+ attribute = (@args.first || :attr)
268
+ "record.errors[#{attribute.inspect}].#{String(query_method)}"
269
+ end
121
270
  end
122
271
  end
123
272
  end
@@ -17,7 +17,7 @@ module RSpec
17
17
  end
18
18
 
19
19
  def description
20
- "include#{expected_to_sentence}"
20
+ "include#{to_sentence expected}"
21
21
  end
22
22
 
23
23
  def diffable?
@@ -27,7 +27,7 @@ module RSpec
27
27
  end
28
28
 
29
29
  def description
30
- "contain exactly #{_pretty_print(expected)}"
30
+ "contain exactly#{to_sentence(expected)}"
31
31
  end
32
32
 
33
33
  private
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class RaiseError
5
+ include MatchAliases
6
+
5
7
  def initialize(expected_error_or_message=Exception, expected_message=nil, &block)
6
8
  @block = block
7
9
  @actual_error = nil
@@ -51,7 +53,6 @@ module RSpec
51
53
  ensure
52
54
  return (@raised_expected_error & @with_expected_message) ? (@eval_block ? @eval_block_passed : true) : false
53
55
  end
54
- alias == matches?
55
56
 
56
57
  def does_not_match?(given_proc)
57
58
  !matches?(given_proc, :negative_expectation)
@@ -90,6 +91,11 @@ module RSpec
90
91
  "raise #{expected_error}"
91
92
  end
92
93
 
94
+ # @private
95
+ def supports_block_expectations?
96
+ true
97
+ end
98
+
93
99
  private
94
100
 
95
101
  def expected_error
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class RespondTo
5
+ include MatchAliases
6
+
5
7
  def initialize(*names)
6
8
  @names = names
7
9
  @expected_arity = nil
@@ -10,7 +12,6 @@ module RSpec
10
12
  def matches?(actual)
11
13
  find_failing_method_names(actual, :reject).empty?
12
14
  end
13
- alias == matches?
14
15
 
15
16
  def does_not_match?(actual)
16
17
  find_failing_method_names(actual, :select).empty?
@@ -38,6 +39,11 @@ module RSpec
38
39
  end
39
40
  alias :arguments :argument
40
41
 
42
+ # @private
43
+ def supports_block_expectations?
44
+ false
45
+ end
46
+
41
47
  private
42
48
 
43
49
  def find_failing_method_names(actual, filter_method)
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class Satisfy
5
+ include MatchAliases
6
+
5
7
  def initialize(&block)
6
8
  @block = block
7
9
  end
@@ -11,7 +13,6 @@ module RSpec
11
13
  @actual = actual
12
14
  @block.call(actual)
13
15
  end
14
- alias == matches?
15
16
 
16
17
  def failure_message_for_should
17
18
  "expected #{@actual} to satisfy block"
@@ -24,6 +25,11 @@ module RSpec
24
25
  def description
25
26
  "satisfy block"
26
27
  end
28
+
29
+ # @private
30
+ def supports_block_expectations?
31
+ false
32
+ end
27
33
  end
28
34
  end
29
35
  end
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class ThrowSymbol
5
+ include MatchAliases
6
+
5
7
  def initialize(expected_symbol = nil, expected_arg=nil)
6
8
  @expected_symbol = expected_symbol
7
9
  @expected_arg = expected_arg
@@ -51,7 +53,6 @@ module RSpec
51
53
  end
52
54
  end
53
55
  end
54
- alias == matches?
55
56
 
56
57
  def failure_message_for_should
57
58
  "expected #{expected} to be thrown, got #{caught}"
@@ -65,7 +66,14 @@ module RSpec
65
66
  "throw #{expected}"
66
67
  end
67
68
 
68
- private
69
+ # @api private
70
+ # Indicates this matcher matches against a block.
71
+ # @return [True]
72
+ def supports_block_expectations?
73
+ true
74
+ end
75
+
76
+ private
69
77
 
70
78
  def expected(symbol_desc = 'a Symbol')
71
79
  throw_description(@expected_symbol || symbol_desc, @expected_arg)
@@ -121,7 +121,12 @@ module RSpec
121
121
  end
122
122
  end
123
123
 
124
- private
124
+ # @private
125
+ def supports_block_expectations?
126
+ true
127
+ end
128
+
129
+ private
125
130
 
126
131
  def set_expected_yields_count(relativity, n)
127
132
  @expectation_type = relativity
@@ -169,6 +174,11 @@ module RSpec
169
174
  "expected given block not to yield with no arguments, but did"
170
175
  end
171
176
 
177
+ # @private
178
+ def supports_block_expectations?
179
+ true
180
+ end
181
+
172
182
  private
173
183
 
174
184
  def failure_reason
@@ -181,6 +191,8 @@ module RSpec
181
191
  end
182
192
 
183
193
  class YieldWithArgs
194
+ include MatchAliases
195
+
184
196
  def initialize(*args)
185
197
  @expected = args
186
198
  end
@@ -190,7 +202,6 @@ module RSpec
190
202
  @actual = @probe.single_yield_args
191
203
  @probe.yielded_once?(:yield_with_args) && args_match?
192
204
  end
193
- alias == matches?
194
205
 
195
206
  def failure_message_for_should
196
207
  "expected given block to yield with arguments, but #{positive_failure_reason}"
@@ -206,6 +217,11 @@ module RSpec
206
217
  desc
207
218
  end
208
219
 
220
+ # @private
221
+ def supports_block_expectations?
222
+ true
223
+ end
224
+
209
225
  private
210
226
 
211
227
  def positive_failure_reason
@@ -251,6 +267,8 @@ module RSpec
251
267
  end
252
268
 
253
269
  class YieldSuccessiveArgs
270
+ include MatchAliases
271
+
254
272
  def initialize(*args)
255
273
  @expected = args
256
274
  end
@@ -260,7 +278,6 @@ module RSpec
260
278
  @actual = @probe.successive_yield_args
261
279
  args_match?
262
280
  end
263
- alias == matches?
264
281
 
265
282
  def failure_message_for_should
266
283
  "expected given block to yield successively with arguments, but yielded with unexpected arguments" +
@@ -280,6 +297,11 @@ module RSpec
280
297
  desc
281
298
  end
282
299
 
300
+ # @private
301
+ def supports_block_expectations?
302
+ true
303
+ end
304
+
283
305
  private
284
306
 
285
307
  def args_match?
@@ -4,8 +4,8 @@ module RSpec
4
4
  require 'rspec/matchers/built_in/base_matcher'
5
5
  autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
6
6
  autoload :Be, 'rspec/matchers/built_in/be'
7
- autoload :BeTrue, 'rspec/matchers/built_in/be'
8
- autoload :BeFalse, 'rspec/matchers/built_in/be'
7
+ autoload :BeTruthy, 'rspec/matchers/built_in/be'
8
+ autoload :BeFalsey, 'rspec/matchers/built_in/be'
9
9
  autoload :BeNil, 'rspec/matchers/built_in/be'
10
10
  autoload :BeComparedTo, 'rspec/matchers/built_in/be'
11
11
  autoload :BePredicate, 'rspec/matchers/built_in/be'
@@ -0,0 +1,55 @@
1
+ module RSpec
2
+ module Matchers
3
+ # Evaluates a block in order to determine what methods, if any,
4
+ # it defines as instance methods (using `def foo`) vs singleton
5
+ # methods (using `def self.foo`).
6
+ #
7
+ # @api private
8
+ class DifferentiateBlockMethodTypes
9
+ def initialize(*block_args, &block)
10
+ @block_args = block_args
11
+ @block = block
12
+
13
+ ignore_macro_methods
14
+
15
+ capture_added_methods(singletons_singleton_class, singleton_methods)
16
+ capture_added_methods(singleton_class, instance_methods)
17
+
18
+ singleton_class.class_exec(*block_args, &block)
19
+ end
20
+
21
+ def singleton_methods
22
+ @singleton_methods ||= []
23
+ end
24
+
25
+ def instance_methods
26
+ @instance_methods ||= []
27
+ end
28
+
29
+ private
30
+
31
+ def capture_added_methods(object, method_list)
32
+ object.__send__(:define_method, :singleton_method_added) do |method_name|
33
+ method_list << method_name
34
+ end
35
+
36
+ method_list.delete(:singleton_method_added)
37
+ end
38
+
39
+ unless method_defined?(:singleton_class)
40
+ def singleton_class
41
+ class << self; self; end
42
+ end
43
+ end
44
+
45
+ def singletons_singleton_class
46
+ class << singleton_class; self; end
47
+ end
48
+
49
+ def ignore_macro_methods
50
+ def singleton_class.method_missing(*); self; end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -7,7 +7,8 @@ module RSpec
7
7
  matcher_template = RSpec::Matchers::DSL::Matcher.new(name, &declarations)
8
8
  define_method name do |*expected|
9
9
  matcher = matcher_template.for_expected(*expected)
10
- matcher.matcher_execution_context = @matcher_execution_context ||= self
10
+ @matcher_execution_context ||= self
11
+ matcher.instance_variable_set(:@matcher_execution_context, @matcher_execution_context)
11
12
  matcher
12
13
  end
13
14
  end
@@ -0,0 +1,22 @@
1
+ module RSpec
2
+ module Matchers
3
+ module MatchAliases
4
+ def ==(other)
5
+ return true if equal?(other)
6
+
7
+ matched = matches?(other)
8
+
9
+ if matched
10
+ RSpec.deprecate("Using `matcher == value` as an alias for `#matches?`", :replacement => "`matcher === value`")
11
+ end
12
+
13
+ matched
14
+ end
15
+
16
+ def ===(other)
17
+ matches?(other)
18
+ end
19
+ end
20
+ end
21
+ end
22
+