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.
- checksums.yaml +15 -0
- data/Changelog.md +204 -23
- data/README.md +43 -87
- data/features/README.md +10 -10
- data/features/built_in_matchers/README.md +41 -41
- data/features/built_in_matchers/be.feature +40 -40
- data/features/built_in_matchers/be_within.feature +3 -3
- data/features/built_in_matchers/expect_change.feature +6 -6
- data/features/built_in_matchers/expect_error.feature +2 -2
- data/features/built_in_matchers/start_with.feature +1 -1
- data/features/built_in_matchers/throw_symbol.feature +11 -11
- data/features/built_in_matchers/yield.feature +18 -3
- data/features/custom_matchers/define_diffable_matcher.feature +1 -1
- data/features/custom_matchers/define_matcher_outside_rspec.feature +6 -6
- data/features/custom_matchers/define_matcher_with_fluent_interface.feature +1 -1
- data/features/customized_message.feature +1 -1
- data/features/diffing.feature +1 -1
- data/features/step_definitions/additional_cli_steps.rb +10 -0
- data/features/support/env.rb +10 -1
- data/features/support/rubinius.rb +6 -0
- data/features/syntax_configuration.feature +3 -0
- data/features/test_frameworks/test_unit.feature +55 -17
- data/lib/rspec/expectations/caller_filter.rb +60 -0
- data/lib/rspec/{matchers → expectations}/configuration.rb +5 -3
- data/lib/rspec/expectations/deprecation.rb +20 -31
- data/lib/rspec/expectations/differ.rb +48 -9
- data/lib/rspec/expectations/expectation_target.rb +81 -15
- data/lib/rspec/expectations/extensions/object.rb +2 -12
- data/lib/rspec/expectations/fail_with.rb +11 -1
- data/lib/rspec/expectations/handler.rb +16 -7
- data/lib/rspec/expectations/syntax.rb +5 -7
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/expectations.rb +28 -2
- data/lib/rspec/matchers/be_close.rb +4 -1
- data/lib/rspec/matchers/built_in/base_matcher.rb +17 -10
- data/lib/rspec/matchers/built_in/be.rb +69 -32
- data/lib/rspec/matchers/built_in/be_within.rb +9 -3
- data/lib/rspec/matchers/built_in/change.rb +49 -3
- data/lib/rspec/matchers/built_in/has.rb +40 -7
- data/lib/rspec/matchers/built_in/have.rb +170 -5
- data/lib/rspec/matchers/built_in/include.rb +3 -11
- data/lib/rspec/matchers/built_in/match.rb +5 -0
- data/lib/rspec/matchers/built_in/match_array.rb +1 -1
- data/lib/rspec/matchers/built_in/raise_error.rb +34 -8
- data/lib/rspec/matchers/built_in/respond_to.rb +7 -1
- data/lib/rspec/matchers/built_in/satisfy.rb +7 -1
- data/lib/rspec/matchers/built_in/throw_symbol.rb +10 -2
- data/lib/rspec/matchers/built_in/yield.rb +102 -5
- data/lib/rspec/matchers/built_in.rb +2 -2
- data/lib/rspec/matchers/differentiate_block_method_types.rb +55 -0
- data/lib/rspec/matchers/dsl.rb +2 -1
- data/lib/rspec/matchers/match_aliases.rb +22 -0
- data/lib/rspec/matchers/matcher.rb +131 -10
- data/lib/rspec/matchers/operator_matcher.rb +70 -66
- data/lib/rspec/matchers/pretty.rb +11 -1
- data/lib/rspec/matchers/test_unit_integration.rb +28 -0
- data/lib/rspec/matchers.rb +175 -146
- data/lib/rspec-expectations.rb +5 -0
- data/spec/rspec/{matchers → expectations}/configuration_spec.rb +78 -91
- data/spec/rspec/expectations/differ_spec.rb +65 -6
- data/spec/rspec/expectations/expectation_target_spec.rb +72 -3
- data/spec/rspec/expectations/extensions/kernel_spec.rb +9 -5
- data/spec/rspec/expectations/fail_with_spec.rb +19 -0
- data/spec/rspec/expectations/handler_spec.rb +43 -22
- data/spec/rspec/expectations/syntax_spec.rb +51 -9
- data/spec/rspec/expectations_spec.rb +71 -0
- data/spec/rspec/matchers/base_matcher_spec.rb +33 -18
- data/spec/rspec/matchers/be_close_spec.rb +9 -6
- data/spec/rspec/matchers/be_spec.rb +146 -45
- data/spec/rspec/matchers/be_within_spec.rb +8 -0
- data/spec/rspec/matchers/change_spec.rb +107 -7
- data/spec/rspec/matchers/description_generation_spec.rb +38 -20
- data/spec/rspec/matchers/differentiate_block_method_types_spec.rb +39 -0
- data/spec/rspec/matchers/eq_spec.rb +1 -1
- data/spec/rspec/matchers/equal_spec.rb +26 -0
- data/spec/rspec/matchers/exist_spec.rb +9 -9
- data/spec/rspec/matchers/has_spec.rb +25 -1
- data/spec/rspec/matchers/have_spec.rb +411 -3
- data/spec/rspec/matchers/include_matcher_integration_spec.rb +2 -2
- data/spec/rspec/matchers/include_spec.rb +4 -4
- data/spec/rspec/matchers/match_array_spec.rb +1 -1
- data/spec/rspec/matchers/match_spec.rb +14 -1
- data/spec/rspec/matchers/matcher_spec.rb +213 -24
- data/spec/rspec/matchers/operator_matcher_spec.rb +56 -8
- data/spec/rspec/matchers/pretty_spec.rb +23 -0
- data/spec/rspec/matchers/raise_error_spec.rb +242 -102
- data/spec/rspec/matchers/respond_to_spec.rb +4 -4
- data/spec/rspec/matchers/satisfy_spec.rb +1 -1
- data/spec/rspec/matchers/start_with_end_with_spec.rb +2 -2
- data/spec/rspec/matchers/throw_symbol_spec.rb +14 -14
- data/spec/rspec/matchers/yield_spec.rb +81 -4
- data/spec/spec_helper.rb +5 -3
- data/spec/support/helper_methods.rb +42 -0
- data/spec/support/shared_examples.rb +42 -0
- metadata +40 -44
- 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})",
|
|
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 =
|
|
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
|
|
40
|
-
"expected #{@actual.inspect} to #{name_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
|
|
45
|
-
"expected #{@actual.inspect} not to #{name_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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
12
|
+
"expected: truthy value\n got: #{actual.inspect}"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def failure_message_for_should_not
|
|
16
|
-
"expected:
|
|
16
|
+
"expected: falsey value\n got: #{actual.inspect}"
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
class
|
|
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:
|
|
26
|
+
"expected: falsey value\n got: #{actual.inspect}"
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def failure_message_for_should_not
|
|
30
|
-
"expected:
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 #{
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|