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
@@ -1,27 +1,23 @@
1
+ require 'rspec/expectations/caller_filter' unless defined?(::RSpec::CallerFilter)
2
+
1
3
  module RSpec
2
4
  module Expectations
3
5
  module Deprecation
4
- RSPEC_LIBS = %w[
5
- core
6
- mocks
7
- expectations
8
- matchers
9
- rails
10
- ]
11
-
12
- ADDITIONAL_TOP_LEVEL_FILES = %w[ autorun ]
13
-
14
- LIB_REGEX = %r{/lib/rspec/(#{(RSPEC_LIBS + ADDITIONAL_TOP_LEVEL_FILES).join('|')})(\.rb|/)}
15
-
16
6
  # @private
17
7
  #
18
8
  # Used internally to print deprecation warnings
19
9
  def deprecate(deprecated, options={})
20
- call_site = caller.find { |line| line !~ LIB_REGEX }
21
-
22
10
  message = "DEPRECATION: #{deprecated} is deprecated."
23
11
  message << " Use #{options[:replacement]} instead." if options[:replacement]
24
- message << " Called from #{call_site}."
12
+ message << " Called from #{CallerFilter.first_non_rspec_line}."
13
+ warn message
14
+ end
15
+
16
+ # @private
17
+ #
18
+ # Used internally to print deprecation warnings
19
+ def warn_deprecation(warning)
20
+ message = "\nDEPRECATION: #{warning}\n"
25
21
  warn message
26
22
  end
27
23
  end
@@ -1,23 +1,48 @@
1
1
  module RSpec
2
2
  module Expectations
3
3
  # Wraps the target of an expectation.
4
+ #
4
5
  # @example
5
- # expect(something) # => ExpectationTarget wrapping something
6
+ # expect(something) # => ExpectationTarget wrapping something
7
+ # expect { do_something } # => ExpectationTarget wrapping the block
6
8
  #
7
9
  # # used with `to`
8
10
  # expect(actual).to eq(3)
9
11
  #
10
12
  # # with `not_to`
11
13
  # expect(actual).not_to eq(3)
14
+ #
15
+ # @note `ExpectationTarget` is not intended to be instantiated
16
+ # directly by users. Use `expect` instead.
12
17
  class ExpectationTarget
13
18
  class << self
14
19
  attr_accessor :deprecated_should_enabled
15
20
  alias deprecated_should_enabled? deprecated_should_enabled
16
21
  end
17
22
 
23
+ # @private
24
+ # Used as a sentinel value to be able to tell when the user
25
+ # did not pass an argument. We can't use `nil` for that because
26
+ # `nil` is a valid value to pass.
27
+ UndefinedValue = Module.new
28
+
18
29
  # @api private
19
- def initialize(target)
20
- @target = target
30
+ def initialize(value)
31
+ @target = value
32
+ end
33
+
34
+ # @private
35
+ def self.for(value, block)
36
+ if UndefinedValue.equal?(value)
37
+ unless block
38
+ raise ArgumentError, "You must pass either an argument or a block to `expect`."
39
+ end
40
+ BlockExpectationTarget.new(block)
41
+ elsif block
42
+ raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
43
+ else
44
+ new(value)
45
+ end
21
46
  end
22
47
 
23
48
  # Runs the given expectation, passing if `matcher` returns true.
@@ -30,7 +55,7 @@ module RSpec
30
55
  # @return [Boolean] true if the expectation succeeds (else raises)
31
56
  # @see RSpec::Matchers
32
57
  def to(matcher=nil, message=nil, &block)
33
- prevent_operator_matchers(:to, matcher)
58
+ prevent_operator_matchers(:to) unless matcher
34
59
  RSpec::Expectations::PositiveExpectationHandler.handle_matcher(@target, matcher, message, &block)
35
60
  end
36
61
 
@@ -43,7 +68,7 @@ module RSpec
43
68
  # @return [Boolean] false if the negative expectation succeeds (else raises)
44
69
  # @see RSpec::Matchers
45
70
  def not_to(matcher=nil, message=nil, &block)
46
- prevent_operator_matchers(:not_to, matcher)
71
+ prevent_operator_matchers(:not_to) unless matcher
47
72
  RSpec::Expectations::NegativeExpectationHandler.handle_matcher(@target, matcher, message, &block)
48
73
  end
49
74
  alias to_not not_to
@@ -75,13 +100,55 @@ module RSpec
75
100
 
76
101
  private
77
102
 
78
- def prevent_operator_matchers(verb, matcher)
79
- return if matcher
80
-
103
+ def prevent_operator_matchers(verb)
81
104
  raise ArgumentError, "The expect syntax does not support operator matchers, " +
82
105
  "so you must pass a matcher to `##{verb}`."
83
106
  end
84
107
  end
108
+
109
+ # @private
110
+ # Validates the provided matcher to ensure it supports block
111
+ # expectations, in order to avoid user confusion when they
112
+ # use a block thinking the expectation will be on the return
113
+ # value of the block rather than the block itself.
114
+ class BlockExpectationTarget < ExpectationTarget
115
+ def to(matcher, message=nil, &block)
116
+ enforce_block_expectation(matcher)
117
+ super
118
+ end
119
+
120
+ def not_to(matcher, message=nil, &block)
121
+ enforce_block_expectation(matcher)
122
+ super
123
+ end
124
+ alias to_not not_to
125
+
126
+ private
127
+
128
+ def enforce_block_expectation(matcher)
129
+ return if supports_block_expectations?(matcher)
130
+
131
+ RSpec.deprecate("Using a matcher in a block expectation expression " +
132
+ "(e.g. `expect { }.to matcher`) that does not implement " +
133
+ "`supports_block_expectations?`",
134
+ :replacement => "a value expectation expression " +
135
+ "(e.g. `expect(value).to matcher`) or implement " +
136
+ "`supports_block_expectations?` on the provided matcher " +
137
+ "(#{description_of matcher})")
138
+ end
139
+
140
+ def supports_block_expectations?(matcher)
141
+ matcher.supports_block_expectations?
142
+ rescue NoMethodError
143
+ false
144
+ end
145
+
146
+ def description_of(matcher)
147
+ matcher.description
148
+ rescue NoMethodError
149
+ matcher.inspect
150
+ end
151
+ end
85
152
  end
86
153
  end
87
154
 
@@ -31,7 +31,11 @@ module RSpec
31
31
  matcher.failure_message
32
32
 
33
33
  if matcher.respond_to?(:diffable?) && matcher.diffable?
34
- ::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
34
+ if RSpec::Matchers::DSL::Matcher === matcher
35
+ ::RSpec::Expectations.fail_with message, matcher.expected_as_array, matcher.actual
36
+ else
37
+ ::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
38
+ end
35
39
  else
36
40
  ::RSpec::Expectations.fail_with message
37
41
  end
@@ -79,11 +79,9 @@ module RSpec
79
79
  def enable_expect(syntax_host = ::RSpec::Matchers)
80
80
  return if expect_enabled?(syntax_host)
81
81
 
82
- syntax_host.module_eval do
83
- def expect(*target, &target_block)
84
- target << target_block if block_given?
85
- raise ArgumentError.new("You must pass an argument or a block to #expect but not both.") unless target.size == 1
86
- ::RSpec::Expectations::ExpectationTarget.new(target.first)
82
+ syntax_host.module_exec do
83
+ def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
84
+ ::RSpec::Expectations::ExpectationTarget.for(value, block)
87
85
  end
88
86
  end
89
87
 
@@ -2,8 +2,7 @@ module RSpec
2
2
  module Expectations
3
3
  # @private
4
4
  module Version
5
- STRING = '2.14.5'
5
+ STRING = '2.99.0'
6
6
  end
7
7
  end
8
8
  end
9
-
@@ -1,7 +1,7 @@
1
1
  require 'rspec/expectations/extensions'
2
2
  require 'rspec/matchers'
3
3
  require 'rspec/expectations/expectation_target'
4
- require 'rspec/matchers/configuration'
4
+ require 'rspec/expectations/configuration'
5
5
  require 'rspec/expectations/fail_with'
6
6
  require 'rspec/expectations/errors'
7
7
  require 'rspec/expectations/deprecation'
@@ -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})", :replacement => "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,6 +13,7 @@ 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
16
17
 
17
18
  UNDEFINED = Object.new.freeze
18
19
 
@@ -39,12 +40,12 @@ module RSpec
39
40
 
40
41
  def failure_message_for_should
41
42
  assert_ivars :@actual
42
- "expected #{@actual.inspect} to #{name_to_sentence}#{expected_to_sentence}"
43
+ "expected #{@actual.inspect} to #{name_to_sentence}#{to_sentence expected}"
43
44
  end
44
45
 
45
46
  def failure_message_for_should_not
46
47
  assert_ivars :@actual
47
- "expected #{@actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"
48
+ "expected #{@actual.inspect} not to #{name_to_sentence}#{to_sentence expected}"
48
49
  end
49
50
 
50
51
  def description
@@ -55,11 +56,15 @@ module RSpec
55
56
  false
56
57
  end
57
58
 
58
- def ==(other)
59
- 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
60
65
  end
61
66
 
62
- private
67
+ private
63
68
 
64
69
  def assert_ivars *ivars
65
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
 
@@ -141,14 +141,25 @@ it is a bit confusing.
141
141
 
142
142
  def matches?(actual)
143
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
+
144
151
  begin
145
- return @result = actual.__send__(predicate, *@args, &@block)
152
+ @result = actual.__send__(predicate, *@args, &@block)
153
+ check_respond_to(predicate)
154
+ return @result
146
155
  rescue NameError => predicate_missing_error
147
156
  "this needs to be here or rcov will not count this branch even though it's executed in a code example"
148
157
  end
149
158
 
150
159
  begin
151
- 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
152
163
  rescue NameError
153
164
  raise predicate_missing_error
154
165
  end
@@ -168,7 +179,18 @@ it is a bit confusing.
168
179
  "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
169
180
  end
170
181
 
171
- 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
172
194
 
173
195
  def predicate
174
196
  "#{@expected}?".to_sym
@@ -191,6 +213,13 @@ it is a bit confusing.
191
213
  def prefix_to_sentence
192
214
  split_words(@prefix)
193
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
194
223
  end
195
224
  end
196
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
@@ -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)
@@ -90,6 +124,10 @@ 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
 
95
133
  def failure_message_for_expected_after
@@ -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