/
have.rb
220 lines (189 loc) · 7.14 KB
/
have.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
module RSpec
module Matchers
module BuiltIn
class Have
QUERY_METHODS = [:size, :length, :count].freeze
def initialize(expected, relativity=:exactly)
@expected = case expected
when :no then 0
when String then expected.to_i
else expected
end
@relativity = relativity
@actual = @collection_name = @plural_collection_name = nil
@target_owns_a_collection = false
@negative_expectation = false
@expectation_format_method = "to"
end
def relativities
@relativities ||= {
:exactly => "",
:at_least => "at least ",
:at_most => "at most "
}
end
def matches?(collection_or_owner)
collection = determine_collection(collection_or_owner)
case collection
when enumerator_class
for query_method in QUERY_METHODS
next unless collection.respond_to?(query_method)
@actual = collection.__send__(query_method)
if @actual
print_deprecation_message(query_method)
break
end
end
raise not_a_collection if @actual.nil?
else
query_method = determine_query_method(collection)
raise not_a_collection unless query_method
@actual = collection.__send__(query_method)
print_deprecation_message(query_method)
end
case @relativity
when :at_least then @actual >= @expected
when :at_most then @actual <= @expected
else @actual == @expected
end
end
alias == matches?
def does_not_match?(collection_or_owner)
@negative_expectation = true
@expectation_format_method = "to_not"
!matches?(collection_or_owner)
end
def determine_collection(collection_or_owner)
if collection_or_owner.respond_to?(@collection_name)
@target_owns_a_collection = true
collection_or_owner.__send__(@collection_name, *@args, &@block)
elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
@target_owns_a_collection = true
collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
elsif determine_query_method(collection_or_owner)
collection_or_owner
else
collection_or_owner.__send__(@collection_name, *@args, &@block)
end
end
def determine_query_method(collection)
QUERY_METHODS.detect {|m| collection.respond_to?(m)}
end
def not_a_collection
"expected #{@collection_name} to be a collection but it does not respond to #length, #size or #count"
end
def failure_message_for_should
"expected #{relative_expectation} #{@collection_name}, got #{@actual}"
end
def failure_message_for_should_not
if @relativity == :exactly
return "expected target not to have #{@expected} #{@collection_name}, got #{@actual}"
elsif @relativity == :at_most
return <<-EOF
Isn't life confusing enough?
Instead of having to figure out the meaning of this:
#{Expectations::Syntax.negative_expression("actual", "have_at_most(#{@expected}).#{@collection_name}")}
We recommend that you use this instead:
#{Expectations::Syntax.positive_expression("actual", "have_at_least(#{@expected + 1}).#{@collection_name}")}
EOF
elsif @relativity == :at_least
return <<-EOF
Isn't life confusing enough?
Instead of having to figure out the meaning of this:
#{Expectations::Syntax.negative_expression("actual", "have_at_least(#{@expected}).#{@collection_name}")}
We recommend that you use this instead:
#{Expectations::Syntax.positive_expression("actual", "have_at_most(#{@expected - 1}).#{@collection_name}")}
EOF
end
end
def description
"have #{relative_expectation} #{@collection_name}"
end
def respond_to?(m)
@expected.respond_to?(m) || super
end
private
def method_missing(method, *args, &block)
@collection_name = method
if inflector = (defined?(ActiveSupport::Inflector) && ActiveSupport::Inflector.respond_to?(:pluralize) ? ActiveSupport::Inflector : (defined?(Inflector) ? Inflector : nil))
@plural_collection_name = inflector.pluralize(method.to_s)
end
@args = args
@block = block
self
end
def relative_expectation
"#{relativities[@relativity]}#{@expected}"
end
def enumerator_class
RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
end
def print_deprecation_message(query_method)
deprecation_message = "the rspec-collection_matchers gem "
deprecation_message << "or replace your expectation with something like "
deprecation_message << "`expect(#{cardinality_expression(query_method)}).#{expectation_format_method} #{suggested_matcher_expression}`"
RSpec.deprecate("`#{expectation_expression(query_method)}`", :replacement => deprecation_message)
end
def expectation_expression(query_method)
if @negative_expectation
RSpec::Expectations::Syntax.negative_expression(target_expression, original_matcher_expression)
else
RSpec::Expectations::Syntax.positive_expression(target_expression, original_matcher_expression)
end
end
def target_expression
if @target_owns_a_collection
'collection_owner'
else
'collection'
end
end
def original_matcher_expression
"#{matcher_method}(#{@expected}).#{@collection_name}"
end
def expectation_format_method
if @relativity == :exactly
@expectation_format_method
else
"to"
end
end
def cardinality_expression(query_method)
expression = "#{target_expression}."
expression << "#{@collection_name}." if @target_owns_a_collection
expression << String(query_method)
end
def suggested_matcher_expression
send("suggested_matcher_expression_for_#{@relativity}")
end
def suggested_matcher_expression_for_exactly
"eq(#{@expected})"
end
def suggested_matcher_expression_for_at_most
if @negative_expectation
"be > #{@expected}"
else
"be <= #{@expected}"
end
end
def suggested_matcher_expression_for_at_least
if @negative_expectation
"be < #{@expected}"
else
"be >= #{@expected}"
end
end
def matcher_method
case @relativity
when :exactly
"have"
when :at_most
"have_at_most"
when :at_least
"have_at_least"
end
end
end
end
end
end