rspec-expectations 2.13.0 → 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 (96) hide show
  1. checksums.yaml +15 -0
  2. data/Changelog.md +204 -23
  3. data/README.md +43 -87
  4. data/features/README.md +10 -10
  5. data/features/built_in_matchers/README.md +41 -41
  6. data/features/built_in_matchers/be.feature +40 -40
  7. data/features/built_in_matchers/be_within.feature +3 -3
  8. data/features/built_in_matchers/expect_change.feature +6 -6
  9. data/features/built_in_matchers/expect_error.feature +2 -2
  10. data/features/built_in_matchers/start_with.feature +1 -1
  11. data/features/built_in_matchers/throw_symbol.feature +11 -11
  12. data/features/built_in_matchers/yield.feature +18 -3
  13. data/features/custom_matchers/define_diffable_matcher.feature +1 -1
  14. data/features/custom_matchers/define_matcher_outside_rspec.feature +6 -6
  15. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +1 -1
  16. data/features/customized_message.feature +1 -1
  17. data/features/diffing.feature +1 -1
  18. data/features/step_definitions/additional_cli_steps.rb +10 -0
  19. data/features/support/env.rb +10 -1
  20. data/features/support/rubinius.rb +6 -0
  21. data/features/syntax_configuration.feature +3 -0
  22. data/features/test_frameworks/test_unit.feature +55 -17
  23. data/lib/rspec/expectations/caller_filter.rb +60 -0
  24. data/lib/rspec/{matchers → expectations}/configuration.rb +5 -3
  25. data/lib/rspec/expectations/deprecation.rb +20 -31
  26. data/lib/rspec/expectations/differ.rb +48 -9
  27. data/lib/rspec/expectations/expectation_target.rb +81 -15
  28. data/lib/rspec/expectations/extensions/object.rb +2 -12
  29. data/lib/rspec/expectations/fail_with.rb +11 -1
  30. data/lib/rspec/expectations/handler.rb +16 -7
  31. data/lib/rspec/expectations/syntax.rb +5 -7
  32. data/lib/rspec/expectations/version.rb +1 -1
  33. data/lib/rspec/expectations.rb +28 -2
  34. data/lib/rspec/matchers/be_close.rb +4 -1
  35. data/lib/rspec/matchers/built_in/base_matcher.rb +17 -10
  36. data/lib/rspec/matchers/built_in/be.rb +69 -32
  37. data/lib/rspec/matchers/built_in/be_within.rb +9 -3
  38. data/lib/rspec/matchers/built_in/change.rb +49 -3
  39. data/lib/rspec/matchers/built_in/has.rb +40 -7
  40. data/lib/rspec/matchers/built_in/have.rb +170 -5
  41. data/lib/rspec/matchers/built_in/include.rb +3 -11
  42. data/lib/rspec/matchers/built_in/match.rb +5 -0
  43. data/lib/rspec/matchers/built_in/match_array.rb +1 -1
  44. data/lib/rspec/matchers/built_in/raise_error.rb +34 -8
  45. data/lib/rspec/matchers/built_in/respond_to.rb +7 -1
  46. data/lib/rspec/matchers/built_in/satisfy.rb +7 -1
  47. data/lib/rspec/matchers/built_in/throw_symbol.rb +10 -2
  48. data/lib/rspec/matchers/built_in/yield.rb +102 -5
  49. data/lib/rspec/matchers/built_in.rb +2 -2
  50. data/lib/rspec/matchers/differentiate_block_method_types.rb +55 -0
  51. data/lib/rspec/matchers/dsl.rb +2 -1
  52. data/lib/rspec/matchers/match_aliases.rb +22 -0
  53. data/lib/rspec/matchers/matcher.rb +131 -10
  54. data/lib/rspec/matchers/operator_matcher.rb +70 -66
  55. data/lib/rspec/matchers/pretty.rb +11 -1
  56. data/lib/rspec/matchers/test_unit_integration.rb +28 -0
  57. data/lib/rspec/matchers.rb +175 -146
  58. data/lib/rspec-expectations.rb +5 -0
  59. data/spec/rspec/{matchers → expectations}/configuration_spec.rb +78 -91
  60. data/spec/rspec/expectations/differ_spec.rb +65 -6
  61. data/spec/rspec/expectations/expectation_target_spec.rb +72 -3
  62. data/spec/rspec/expectations/extensions/kernel_spec.rb +9 -5
  63. data/spec/rspec/expectations/fail_with_spec.rb +19 -0
  64. data/spec/rspec/expectations/handler_spec.rb +43 -22
  65. data/spec/rspec/expectations/syntax_spec.rb +51 -9
  66. data/spec/rspec/expectations_spec.rb +71 -0
  67. data/spec/rspec/matchers/base_matcher_spec.rb +33 -18
  68. data/spec/rspec/matchers/be_close_spec.rb +9 -6
  69. data/spec/rspec/matchers/be_spec.rb +146 -45
  70. data/spec/rspec/matchers/be_within_spec.rb +8 -0
  71. data/spec/rspec/matchers/change_spec.rb +107 -7
  72. data/spec/rspec/matchers/description_generation_spec.rb +38 -20
  73. data/spec/rspec/matchers/differentiate_block_method_types_spec.rb +39 -0
  74. data/spec/rspec/matchers/eq_spec.rb +1 -1
  75. data/spec/rspec/matchers/equal_spec.rb +26 -0
  76. data/spec/rspec/matchers/exist_spec.rb +9 -9
  77. data/spec/rspec/matchers/has_spec.rb +25 -1
  78. data/spec/rspec/matchers/have_spec.rb +411 -3
  79. data/spec/rspec/matchers/include_matcher_integration_spec.rb +2 -2
  80. data/spec/rspec/matchers/include_spec.rb +4 -4
  81. data/spec/rspec/matchers/match_array_spec.rb +1 -1
  82. data/spec/rspec/matchers/match_spec.rb +14 -1
  83. data/spec/rspec/matchers/matcher_spec.rb +213 -24
  84. data/spec/rspec/matchers/operator_matcher_spec.rb +56 -8
  85. data/spec/rspec/matchers/pretty_spec.rb +23 -0
  86. data/spec/rspec/matchers/raise_error_spec.rb +242 -102
  87. data/spec/rspec/matchers/respond_to_spec.rb +4 -4
  88. data/spec/rspec/matchers/satisfy_spec.rb +1 -1
  89. data/spec/rspec/matchers/start_with_end_with_spec.rb +2 -2
  90. data/spec/rspec/matchers/throw_symbol_spec.rb +14 -14
  91. data/spec/rspec/matchers/yield_spec.rb +81 -4
  92. data/spec/spec_helper.rb +5 -3
  93. data/spec/support/helper_methods.rb +42 -0
  94. data/spec/support/shared_examples.rb +42 -0
  95. metadata +40 -44
  96. data/spec/rspec/matchers/matchers_spec.rb +0 -37
@@ -2,7 +2,10 @@ module RSpec
2
2
  module Matchers
3
3
  # @deprecated use +be_within+ instead.
4
4
  def be_close(expected, delta)
5
- RSpec.deprecate("be_close(#{expected}, #{delta})", "be_within(#{delta}).of(#{expected})")
5
+ RSpec.deprecate("be_close(#{expected}, #{delta})",
6
+ :replacement => "be_within(#{delta}).of(#{expected})",
7
+ :type => 'the be_close matcher'
8
+ )
6
9
  be_within(delta).of(expected)
7
10
  end
8
11
  end
@@ -13,11 +13,14 @@ module RSpec
13
13
  # class. If/when this changes, we will announce it and remove this warning.
14
14
  class BaseMatcher
15
15
  include RSpec::Matchers::Pretty
16
+ include RSpec::Matchers::MatchAliases
17
+
18
+ UNDEFINED = Object.new.freeze
16
19
 
17
20
  attr_reader :actual, :expected, :rescued_exception
18
21
 
19
- def initialize(expected = nil)
20
- @expected = expected
22
+ def initialize(expected = UNDEFINED)
23
+ @expected = expected unless UNDEFINED.equal?(expected)
21
24
  end
22
25
 
23
26
  def matches?(actual)
@@ -36,28 +39,32 @@ module RSpec
36
39
  end
37
40
 
38
41
  def failure_message_for_should
39
- assert_ivars :@actual, :@expected
40
- "expected #{@actual.inspect} to #{name_to_sentence}#{expected_to_sentence}"
42
+ assert_ivars :@actual
43
+ "expected #{@actual.inspect} to #{name_to_sentence}#{to_sentence expected}"
41
44
  end
42
45
 
43
46
  def failure_message_for_should_not
44
- assert_ivars :@actual, :@expected
45
- "expected #{@actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"
47
+ assert_ivars :@actual
48
+ "expected #{@actual.inspect} not to #{name_to_sentence}#{to_sentence expected}"
46
49
  end
47
50
 
48
51
  def description
49
- expected ? "#{name_to_sentence} #{@expected.inspect}" : name_to_sentence
52
+ defined?(@expected) ? "#{name_to_sentence} #{@expected.inspect}" : name_to_sentence
50
53
  end
51
54
 
52
55
  def diffable?
53
56
  false
54
57
  end
55
58
 
56
- def ==(other)
57
- matches?(other)
59
+ # @api private
60
+ # Most matchers are value matchers (i.e. meant to work with `expect(value)`)
61
+ # rather than block matchers (i.e. meant to work with `expect { }`), so
62
+ # this defaults to false. Block matchers must override this to return true.
63
+ def supports_block_expectations?
64
+ false
58
65
  end
59
66
 
60
- private
67
+ private
61
68
 
62
69
  def assert_ivars *ivars
63
70
  raise "#{self.class.name} needs to supply #{to_sentence ivars}" unless ivars.all? { |v| instance_variables.map(&:intern).include? v }
@@ -3,31 +3,31 @@ require 'rspec/matchers/dsl'
3
3
  module RSpec
4
4
  module Matchers
5
5
  module BuiltIn
6
- class BeTrue < BaseMatcher
6
+ class BeTruthy < BaseMatcher
7
7
  def match(_, actual)
8
8
  !!actual
9
9
  end
10
10
 
11
11
  def failure_message_for_should
12
- "expected: true value\n got: #{actual.inspect}"
12
+ "expected: truthy value\n got: #{actual.inspect}"
13
13
  end
14
14
 
15
15
  def failure_message_for_should_not
16
- "expected: non-true value\n got: #{actual.inspect}"
16
+ "expected: falsey value\n got: #{actual.inspect}"
17
17
  end
18
18
  end
19
19
 
20
- class BeFalse < BaseMatcher
20
+ class BeFalsey < BaseMatcher
21
21
  def match(_, actual)
22
22
  !actual
23
23
  end
24
24
 
25
25
  def failure_message_for_should
26
- "expected: false value\n got: #{actual.inspect}"
26
+ "expected: falsey value\n got: #{actual.inspect}"
27
27
  end
28
28
 
29
29
  def failure_message_for_should_not
30
- "expected: non-false value\n got: #{actual.inspect}"
30
+ "expected: truthy value\n got: #{actual.inspect}"
31
31
  end
32
32
  end
33
33
 
@@ -45,7 +45,33 @@ module RSpec
45
45
  end
46
46
  end
47
47
 
48
+ module BeHelpers
49
+ private
50
+
51
+ def args_to_s
52
+ @args.empty? ? "" : parenthesize(inspected_args.join(', '))
53
+ end
54
+
55
+ def parenthesize(string)
56
+ "(#{string})"
57
+ end
58
+
59
+ def inspected_args
60
+ @args.collect{|a| a.inspect}
61
+ end
62
+
63
+ def expected_to_sentence
64
+ split_words(@expected)
65
+ end
66
+
67
+ def args_to_sentence
68
+ to_sentence(@args)
69
+ end
70
+ end
71
+
48
72
  class Be < BaseMatcher
73
+ include BeHelpers
74
+
49
75
  def initialize(*args, &block)
50
76
  @args = args
51
77
  end
@@ -67,28 +93,6 @@ module RSpec
67
93
  BeComparedTo.new(operand, operator)
68
94
  end
69
95
  end
70
-
71
- private
72
-
73
- def args_to_s
74
- @args.empty? ? "" : parenthesize(inspected_args.join(', '))
75
- end
76
-
77
- def parenthesize(string)
78
- "(#{string})"
79
- end
80
-
81
- def inspected_args
82
- @args.collect{|a| a.inspect}
83
- end
84
-
85
- def expected_to_sentence
86
- split_words(@expected)
87
- end
88
-
89
- def args_to_sentence
90
- to_sentence(@args)
91
- end
92
96
  end
93
97
 
94
98
  class BeComparedTo < Be
@@ -126,7 +130,9 @@ it is a bit confusing.
126
130
  end
127
131
  end
128
132
 
129
- class BePredicate < Be
133
+ class BePredicate < BaseMatcher
134
+ include BeHelpers
135
+
130
136
  def initialize(*args, &block)
131
137
  @expected = parse_expected(args.shift)
132
138
  @args = args
@@ -135,19 +141,32 @@ it is a bit confusing.
135
141
 
136
142
  def matches?(actual)
137
143
  @actual = actual
144
+
145
+ if is_private_on?( @actual )
146
+ RSpec.deprecate "matching with be_#{predicate.to_s.gsub(/\?$/,'')} on private method #{predicate}",
147
+ :replacement => "`expect(object.send(#{predicate.inspect})).to be_true` or change the method's visibility to public",
148
+ :call_site => caller(0)[3]
149
+ end
150
+
138
151
  begin
139
- return @result = actual.__send__(predicate, *@args, &@block)
152
+ @result = actual.__send__(predicate, *@args, &@block)
153
+ check_respond_to(predicate)
154
+ return @result
140
155
  rescue NameError => predicate_missing_error
141
156
  "this needs to be here or rcov will not count this branch even though it's executed in a code example"
142
157
  end
143
158
 
144
159
  begin
145
- return @result = actual.__send__(present_tense_predicate, *@args, &@block)
160
+ @result = actual.__send__(present_tense_predicate, *@args, &@block)
161
+ check_respond_to(present_tense_predicate)
162
+ return @result
146
163
  rescue NameError
147
164
  raise predicate_missing_error
148
165
  end
149
166
  end
150
167
 
168
+ alias === matches?
169
+
151
170
  def failure_message_for_should
152
171
  "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
153
172
  end
@@ -160,7 +179,18 @@ it is a bit confusing.
160
179
  "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
161
180
  end
162
181
 
163
- private
182
+ private
183
+
184
+ # support 1.8.7
185
+ if String === methods.first
186
+ def is_private_on? actual
187
+ actual.private_methods.include? predicate.to_s
188
+ end
189
+ else
190
+ def is_private_on? actual
191
+ actual.private_methods.include? predicate
192
+ end
193
+ end
164
194
 
165
195
  def predicate
166
196
  "#{@expected}?".to_sym
@@ -183,6 +213,13 @@ it is a bit confusing.
183
213
  def prefix_to_sentence
184
214
  split_words(@prefix)
185
215
  end
216
+
217
+ def check_respond_to(method)
218
+ RSpec.deprecate(
219
+ "Matching with #{@prefix}#{@expected} on an object that doesn't respond to `#{method}`",
220
+ :replacement => "`respond_to_missing?` or `respond_to?` on your object"
221
+ ) unless actual.respond_to?(method)
222
+ end
186
223
  end
187
224
  end
188
225
  end
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class BeWithin
5
+ include MatchAliases
6
+
5
7
  def initialize(delta)
6
8
  @delta = delta
7
9
  end
@@ -12,7 +14,6 @@ module RSpec
12
14
  raise needs_subtractable unless @actual.respond_to? :-
13
15
  (@actual - @expected).abs <= @tolerance
14
16
  end
15
- alias == matches?
16
17
 
17
18
  def of(expected)
18
19
  @expected = expected
@@ -23,7 +24,7 @@ module RSpec
23
24
 
24
25
  def percent_of(expected)
25
26
  @expected = expected
26
- @tolerance = @delta * @expected / 100
27
+ @tolerance = @delta * @expected.abs / 100.0
27
28
  @unit = '%'
28
29
  self
29
30
  end
@@ -40,7 +41,12 @@ module RSpec
40
41
  "be within #{@delta}#{@unit} of #{@expected}"
41
42
  end
42
43
 
43
- private
44
+ # @private
45
+ def supports_block_expectations?
46
+ false
47
+ end
48
+
49
+ private
44
50
 
45
51
  def needs_subtractable
46
52
  ArgumentError.new "The actual value (#{@actual.inspect}) must respond to `-`"
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class Change
5
+ include MatchAliases
6
+
5
7
  def initialize(receiver=nil, message=nil, &block)
6
8
  @message = message
7
9
  @value_proc = block || lambda {receiver.__send__(message)}
@@ -18,7 +20,39 @@ module RSpec
18
20
 
19
21
  (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?
20
22
  end
21
- alias == matches?
23
+
24
+ def does_not_match?(event_proc, &block)
25
+ expression = if @expected_delta
26
+ "by()"
27
+ elsif @minimum
28
+ "by_at_least()"
29
+ elsif @maximum
30
+ "by_at_most()"
31
+ elsif @eval_after
32
+ "to()"
33
+ end
34
+
35
+ if expression
36
+ RSpec.deprecate("`expect { }.not_to change { }.#{expression}`")
37
+ end
38
+
39
+ matched_positively = matches?(event_proc, &block)
40
+
41
+ unless matches_before?
42
+ RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''))
43
+ |The semantics of `expect { }.not_to change { }.from()` are changing
44
+ |in RSpec 3. In RSpec 2.x, this would pass if the value changed but
45
+ |the starting value was not what you specified with `from()`. In
46
+ |RSpec 3, this will only pass if the starting value matches your
47
+ |`from()` value _and_ it has not changed.
48
+ |
49
+ |You have an expectation that relies upon the old RSpec 2.x semantics
50
+ |at: #{CallerFilter.first_non_rspec_line}"
51
+ EOS
52
+ end
53
+
54
+ !matched_positively
55
+ end
22
56
 
23
57
  def raise_block_syntax_error
24
58
  raise SyntaxError.new(<<-MESSAGE)
@@ -39,7 +73,7 @@ MESSAGE
39
73
  if @eval_before && !expected_matches_actual?(@expected_before, @actual_before)
40
74
  "#{message} should have initially been #{@expected_before.inspect}, but was #{@actual_before.inspect}"
41
75
  elsif @eval_after && !expected_matches_actual?(@expected_after, @actual_after)
42
- "#{message} should have been changed to #{@expected_after.inspect}, but is now #{@actual_after.inspect}"
76
+ "#{message} should have been changed to #{failure_message_for_expected_after}, but is now #{@actual_after.inspect}"
43
77
  elsif @expected_delta
44
78
  "#{message} should have been changed by #{@expected_delta.inspect}, but was changed by #{actual_delta.inspect}"
45
79
  elsif @minimum
@@ -90,8 +124,20 @@ MESSAGE
90
124
  "change ##{message}"
91
125
  end
92
126
 
127
+ def supports_block_expectations?
128
+ true
129
+ end
130
+
93
131
  private
94
132
 
133
+ def failure_message_for_expected_after
134
+ if RSpec::Matchers.is_a_matcher?(@expected_after)
135
+ @expected_after.description
136
+ else
137
+ @expected_after.inspect
138
+ end
139
+ end
140
+
95
141
  def message
96
142
  @message || "result"
97
143
  end
@@ -125,7 +171,7 @@ MESSAGE
125
171
  end
126
172
 
127
173
  def expected_matches_actual?(expected, actual)
128
- expected === actual
174
+ expected === actual || actual == expected
129
175
  end
130
176
  end
131
177
  end
@@ -2,31 +2,57 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class Has
5
+ include MatchAliases
6
+
5
7
  def initialize(expected, *args)
6
8
  @expected, @args = expected, args
7
9
  end
8
10
 
9
11
  def matches?(actual)
10
- actual.__send__(predicate(@expected), *@args)
12
+ method = predicate
13
+
14
+ if is_private_on?(actual)
15
+ RSpec.deprecate "matching with #{@expected} on private method #{predicate}",
16
+ :replacement => "`expect(object.send(#{predicate.inspect})).to be_true` or change the method's visibility to public"
17
+ end
18
+
19
+ result = actual.__send__(method, *@args)
20
+ check_respond_to(actual, method)
21
+ result
11
22
  end
12
- alias == matches?
13
23
 
14
24
  def failure_message_for_should
15
- "expected ##{predicate(@expected)}#{failure_message_args_description} to return true, got false"
25
+ "expected ##{predicate}#{failure_message_args_description} to return true, got false"
16
26
  end
17
27
 
18
28
  def failure_message_for_should_not
19
- "expected ##{predicate(@expected)}#{failure_message_args_description} to return false, got true"
29
+ "expected ##{predicate}#{failure_message_args_description} to return false, got true"
20
30
  end
21
31
 
22
32
  def description
23
33
  [method_description(@expected), args_description].compact.join(' ')
24
34
  end
25
35
 
26
- private
36
+ # @private
37
+ def supports_block_expectations?
38
+ false
39
+ end
40
+
41
+ private
27
42
 
28
- def predicate(sym)
29
- "#{sym.to_s.sub("have_","has_")}?".to_sym
43
+ # support 1.8.7
44
+ if String === methods.first
45
+ def is_private_on? actual
46
+ actual.private_methods.include? predicate.to_s
47
+ end
48
+ else
49
+ def is_private_on? actual
50
+ actual.private_methods.include? predicate
51
+ end
52
+ end
53
+
54
+ def predicate
55
+ "#{@expected.to_s.sub("have_","has_")}?".to_sym
30
56
  end
31
57
 
32
58
  def method_description(method)
@@ -42,6 +68,13 @@ module RSpec
42
68
  desc = args_description
43
69
  "(#{desc})" if desc
44
70
  end
71
+
72
+ def check_respond_to(actual, method)
73
+ RSpec.deprecate(
74
+ "Matching with #{@expected} on an object that doesn't respond to `#{method}`",
75
+ :replacement => "`respond_to_missing?` or `respond_to?` on your object"
76
+ ) unless actual.respond_to?(method)
77
+ end
45
78
  end
46
79
  end
47
80
  end
@@ -2,6 +2,10 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class Have
5
+ include MatchAliases
6
+
7
+ QUERY_METHODS = [:size, :length, :count].freeze
8
+
5
9
  def initialize(expected, relativity=:exactly)
6
10
  @expected = case expected
7
11
  when :no then 0
@@ -9,7 +13,11 @@ module RSpec
9
13
  else expected
10
14
  end
11
15
  @relativity = relativity
16
+
12
17
  @actual = @collection_name = @plural_collection_name = nil
18
+ @target_owns_a_collection = false
19
+ @negative_expectation = false
20
+ @expectation_format_method = "to"
13
21
  end
14
22
 
15
23
  def relativities
@@ -22,21 +30,45 @@ module RSpec
22
30
 
23
31
  def matches?(collection_or_owner)
24
32
  collection = determine_collection(collection_or_owner)
25
- query_method = determine_query_method(collection)
26
- raise not_a_collection unless query_method
27
- @actual = collection.__send__(query_method)
33
+ case collection
34
+ when enumerator_class
35
+ for query_method in QUERY_METHODS
36
+ next unless collection.respond_to?(query_method)
37
+ @actual = collection.__send__(query_method)
38
+
39
+ if @actual
40
+ print_deprecation_message(query_method)
41
+ break
42
+ end
43
+ end
44
+
45
+ raise not_a_collection if @actual.nil?
46
+ else
47
+ query_method = determine_query_method(collection)
48
+ raise not_a_collection unless query_method
49
+ @actual = collection.__send__(query_method)
50
+
51
+ print_deprecation_message(query_method)
52
+ end
28
53
  case @relativity
29
54
  when :at_least then @actual >= @expected
30
55
  when :at_most then @actual <= @expected
31
56
  else @actual == @expected
32
57
  end
33
58
  end
34
- 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
35
65
 
36
66
  def determine_collection(collection_or_owner)
37
67
  if collection_or_owner.respond_to?(@collection_name)
68
+ @target_owns_a_collection = true
38
69
  collection_or_owner.__send__(@collection_name, *@args, &@block)
39
70
  elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
71
+ @target_owns_a_collection = true
40
72
  collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
41
73
  elsif determine_query_method(collection_or_owner)
42
74
  collection_or_owner
@@ -46,7 +78,7 @@ module RSpec
46
78
  end
47
79
 
48
80
  def determine_query_method(collection)
49
- [:size, :length, :count].detect {|m| collection.respond_to?(m)}
81
+ QUERY_METHODS.detect {|m| collection.respond_to?(m)}
50
82
  end
51
83
 
52
84
  def not_a_collection
@@ -102,6 +134,139 @@ EOF
102
134
  def relative_expectation
103
135
  "#{relativities[@relativity]}#{@expected}"
104
136
  end
137
+
138
+ def enumerator_class
139
+ RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
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
105
270
  end
106
271
  end
107
272
  end