rspec-expectations 2.14.5 → 2.99.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,7 +11,11 @@ module RSpec
11
11
  else expected
12
12
  end
13
13
  @relativity = relativity
14
+
14
15
  @actual = @collection_name = @plural_collection_name = nil
16
+ @target_owns_a_collection = false
17
+ @negative_expectation = false
18
+ @expectation_format_method = "to"
15
19
  end
16
20
 
17
21
  def relativities
@@ -29,13 +33,20 @@ module RSpec
29
33
  for query_method in QUERY_METHODS
30
34
  next unless collection.respond_to?(query_method)
31
35
  @actual = collection.__send__(query_method)
32
- break unless @actual.nil?
36
+
37
+ if @actual
38
+ print_deprecation_message(query_method)
39
+ break
40
+ end
33
41
  end
42
+
34
43
  raise not_a_collection if @actual.nil?
35
44
  else
36
45
  query_method = determine_query_method(collection)
37
46
  raise not_a_collection unless query_method
38
47
  @actual = collection.__send__(query_method)
48
+
49
+ print_deprecation_message(query_method)
39
50
  end
40
51
  case @relativity
41
52
  when :at_least then @actual >= @expected
@@ -45,10 +56,18 @@ module RSpec
45
56
  end
46
57
  alias == matches?
47
58
 
59
+ def does_not_match?(collection_or_owner)
60
+ @negative_expectation = true
61
+ @expectation_format_method = "to_not"
62
+ !matches?(collection_or_owner)
63
+ end
64
+
48
65
  def determine_collection(collection_or_owner)
49
66
  if collection_or_owner.respond_to?(@collection_name)
67
+ @target_owns_a_collection = true
50
68
  collection_or_owner.__send__(@collection_name, *@args, &@block)
51
69
  elsif (@plural_collection_name && collection_or_owner.respond_to?(@plural_collection_name))
70
+ @target_owns_a_collection = true
52
71
  collection_or_owner.__send__(@plural_collection_name, *@args, &@block)
53
72
  elsif determine_query_method(collection_or_owner)
54
73
  collection_or_owner
@@ -118,6 +137,83 @@ EOF
118
137
  def enumerator_class
119
138
  RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
120
139
  end
140
+
141
+ def print_deprecation_message(query_method)
142
+ deprecation_message = "the rspec-collection_matchers gem "
143
+ deprecation_message << "or replace your expectation with something like "
144
+ deprecation_message << "`expect(#{cardinality_expression(query_method)}).#{expectation_format_method} #{suggested_matcher_expression}`"
145
+
146
+ RSpec.deprecate("`#{expectation_expression(query_method)}`", :replacement => deprecation_message)
147
+ end
148
+
149
+ def expectation_expression(query_method)
150
+ if @negative_expectation
151
+ RSpec::Expectations::Syntax.negative_expression(target_expression, original_matcher_expression)
152
+ else
153
+ RSpec::Expectations::Syntax.positive_expression(target_expression, original_matcher_expression)
154
+ end
155
+ end
156
+
157
+ def target_expression
158
+ if @target_owns_a_collection
159
+ 'collection_owner'
160
+ else
161
+ 'collection'
162
+ end
163
+ end
164
+
165
+ def original_matcher_expression
166
+ "#{matcher_method}(#{@expected}).#{@collection_name}"
167
+ end
168
+
169
+ def expectation_format_method
170
+ if @relativity == :exactly
171
+ @expectation_format_method
172
+ else
173
+ "to"
174
+ end
175
+ end
176
+
177
+ def cardinality_expression(query_method)
178
+ expression = "#{target_expression}."
179
+ expression << "#{@collection_name}." if @target_owns_a_collection
180
+ expression << String(query_method)
181
+ end
182
+
183
+ def suggested_matcher_expression
184
+ send("suggested_matcher_expression_for_#{@relativity}")
185
+ end
186
+
187
+ def suggested_matcher_expression_for_exactly
188
+ "eq(#{@expected})"
189
+ end
190
+
191
+ def suggested_matcher_expression_for_at_most
192
+ if @negative_expectation
193
+ "be > #{@expected}"
194
+ else
195
+ "be <= #{@expected}"
196
+ end
197
+ end
198
+
199
+ def suggested_matcher_expression_for_at_least
200
+ if @negative_expectation
201
+ "be < #{@expected}"
202
+ else
203
+ "be >= #{@expected}"
204
+ end
205
+ end
206
+
207
+ def matcher_method
208
+ case @relativity
209
+ when :exactly
210
+ "have"
211
+ when :at_most
212
+ "have_at_most"
213
+ when :at_least
214
+ "have_at_least"
215
+ end
216
+ end
121
217
  end
122
218
  end
123
219
  end
@@ -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
+
@@ -1,4 +1,5 @@
1
1
  require 'set'
2
+ require 'rspec/matchers/differentiate_block_method_types'
2
3
 
3
4
  module RSpec
4
5
  module Matchers
@@ -22,6 +23,9 @@ module RSpec
22
23
  @expected_exception, @rescued_exception = nil, nil
23
24
  @match_for_should_not_block = nil
24
25
  @messages = {}
26
+ @define_block_executed = false
27
+ @block_method_differentiator = nil
28
+ @deprecated_methods = Set.new
25
29
  end
26
30
 
27
31
  PERSISTENT_INSTANCE_VARIABLES = [
@@ -38,9 +42,14 @@ module RSpec
38
42
  instance_variable_set(ivar, nil) unless (PERSISTENT_INSTANCE_VARIABLES + [:@expected]).include?(ivar)
39
43
  end
40
44
  @messages = {}
45
+ @deprecated_methods = Set.new
46
+
47
+ @block_method_differentiator = DifferentiateBlockMethodTypes.new(*@expected, &@declarations)
41
48
  making_declared_methods_public do
42
49
  instance_eval_with_args(*@expected, &@declarations)
43
50
  end
51
+
52
+ @define_block_executed = true
44
53
  self
45
54
  end
46
55
  end
@@ -237,8 +246,38 @@ module RSpec
237
246
  end
238
247
  end
239
248
 
240
- def include(*args)
241
- singleton_class.__send__(:include, *args)
249
+ def include(*modules)
250
+ return_value = singleton_class.__send__(:include, *modules)
251
+
252
+ modules.each do |mod|
253
+ mod.instance_methods.each do |name|
254
+ add_deprecation_warning_to(name,
255
+ "Calling a helper method (`#{name}`) from a module included in a custom matcher as a macro",
256
+ "`extend #{mod.name || "TheModule"}`",
257
+ "included in the custom matcher",
258
+ :unless
259
+ )
260
+ end
261
+ end
262
+
263
+ return_value
264
+ end
265
+
266
+ def extend(*modules)
267
+ return_value = super
268
+
269
+ modules.each do |mod|
270
+ mod.instance_methods.each do |name|
271
+ add_deprecation_warning_to(name,
272
+ "Calling a helper method (`#{name}`) from a module extended onto a custom matcher",
273
+ "`include #{mod.name || "TheModule"}`",
274
+ "extended onto the custom matcher",
275
+ :if
276
+ )
277
+ end
278
+ end unless @define_block_executed
279
+
280
+ return_value
242
281
  end
243
282
 
244
283
  def define_method(name, &block)
@@ -294,6 +333,50 @@ module RSpec
294
333
  class << self; self; end
295
334
  end
296
335
  end
336
+
337
+ def singleton_method_added(name)
338
+ return unless @block_method_differentiator
339
+
340
+ if @block_method_differentiator.instance_methods.include?(name)
341
+ add_deprecation_warning_to(name,
342
+ "Calling a helper method (`#{name}`) defined as an instance method (using `def #{name}`) as a macro from a custom matcher `define` block",
343
+ "`def self.#{name}` (to define it as a singleton method)",
344
+ "defined in the custom matcher definition block",
345
+ :unless
346
+ )
347
+ elsif @block_method_differentiator.singleton_methods.include?(name)
348
+ add_deprecation_warning_to(name,
349
+ "Calling a helper method (`#{name}`) defined as a singleton method (using `def self.#{name}`) on a custom matcher",
350
+ "`def #{name}` (to define it as an instance method)",
351
+ "defined in the custom matcher definition block",
352
+ :if
353
+ )
354
+ end
355
+ end
356
+
357
+ def add_deprecation_warning_to(method_name, msg, replacement, extra_call_site_msg, condition)
358
+ return if @deprecated_methods.include?(method_name)
359
+ @deprecated_methods << method_name
360
+
361
+ aliased_name = aliased_name_for(method_name)
362
+ singleton_class.__send__(:alias_method, aliased_name, method_name)
363
+
364
+ singleton_class.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
365
+ def #{method_name}(*a, &b)
366
+ ::RSpec.deprecate(#{msg.inspect},
367
+ :replacement => #{replacement.inspect},
368
+ :call_site => CallerFilter.first_non_rspec_line + " and #{extra_call_site_msg} at #{CallerFilter.first_non_rspec_line}"
369
+ ) #{condition} @define_block_executed
370
+
371
+ __send__(#{aliased_name.inspect}, *a, &b)
372
+ end
373
+ EOS
374
+ end
375
+
376
+ def aliased_name_for(method_name)
377
+ target, punctuation = method_name.to_s.sub(/([?!=])$/, ''), $1
378
+ "#{target}_without_rspec_deprecation_warning#{punctuation}"
379
+ end
297
380
  end
298
381
  end
299
382
  end
@@ -140,7 +140,7 @@ module RSpec
140
140
  actual = Object.new
141
141
  matcher.should_receive(:matches?).with(actual).and_return(false)
142
142
  matcher.stub(:negative_failure_message).and_return("ignore")
143
- expect(RSpec::Expectations::NegativeExpectationHandler.handle_matcher(actual, matcher)).to be_false
143
+ expect(RSpec::Expectations::NegativeExpectationHandler.handle_matcher(actual, matcher)).to be_falsey
144
144
  end
145
145
 
146
146
 
@@ -91,8 +91,8 @@ module RSpec
91
91
 
92
92
  context "when only :expect is enabled" do
93
93
  before do
94
- expect(Syntax.should_enabled?).to be_false
95
- expect(Syntax.expect_enabled?).to be_true
94
+ expect(Syntax.should_enabled?).to be_falsey
95
+ expect(Syntax.expect_enabled?).to be_truthy
96
96
  end
97
97
 
98
98
  it 'generates a positive expression using the expect syntax' do
@@ -106,8 +106,8 @@ module RSpec
106
106
 
107
107
  context "when both :should and :expect are enabled", :uses_should do
108
108
  before do
109
- expect(Syntax.should_enabled?).to be_true
110
- expect(Syntax.expect_enabled?).to be_true
109
+ expect(Syntax.should_enabled?).to be_truthy
110
+ expect(Syntax.expect_enabled?).to be_truthy
111
111
  end
112
112
 
113
113
  it 'generates a positive expression using the expect syntax' do
@@ -121,8 +121,8 @@ module RSpec
121
121
 
122
122
  context "when only :should is enabled", :uses_only_should do
123
123
  before do
124
- Syntax.should_enabled?.should be_true
125
- Syntax.expect_enabled?.should be_false
124
+ Syntax.should_enabled?.should be_truthy
125
+ Syntax.expect_enabled?.should be_falsey
126
126
  end
127
127
 
128
128
  it 'generates a positive expression using the expect syntax' do
@@ -1,5 +1,19 @@
1
1
  module RSpec
2
2
  describe Expectations do
3
+ def file_contents_for(lib, filename)
4
+ # http://rubular.com/r/HYpUMftlG2
5
+ path = $LOAD_PATH.find { |p| p.match(/\/rspec-#{lib}(-[a-f0-9]+)?\/lib/) }
6
+ file = File.join(path, filename)
7
+ File.read(file)
8
+ end
9
+
10
+ it 'has an up-to-date caller_filter file' do
11
+ expectations = file_contents_for("expectations", "rspec/expectations/caller_filter.rb")
12
+ core = file_contents_for("core", "rspec/core/caller_filter.rb")
13
+
14
+ expect(expectations).to eq(core)
15
+ end
16
+
3
17
  describe '.method_handle_for(object, method_name)' do
4
18
 
5
19
  class UntamperedClass
@@ -8,21 +8,21 @@ module RSpec::Matchers::BuiltIn
8
8
  end
9
9
 
10
10
  it "returns true if there are no errors" do
11
- expect(matcher.match_unless_raises {}).to be_true
11
+ expect(matcher.match_unless_raises {}).to be_truthy
12
12
  end
13
13
 
14
14
  it "returns false if there is an error" do
15
- expect(matcher.match_unless_raises { raise }).to be_false
15
+ expect(matcher.match_unless_raises { raise }).to be_falsey
16
16
  end
17
17
 
18
18
  it "returns false if the only submitted error is raised" do
19
- expect(matcher.match_unless_raises(RuntimeError){ raise "foo" }).to be_false
19
+ expect(matcher.match_unless_raises(RuntimeError){ raise "foo" }).to be_falsey
20
20
  end
21
21
 
22
22
  it "returns false if any of several errors submitted is raised" do
23
- expect(matcher.match_unless_raises(RuntimeError, ArgumentError, NameError) { raise "foo" }).to be_false
24
- expect(matcher.match_unless_raises(RuntimeError, ArgumentError, NameError) { raise ArgumentError.new('') }).to be_false
25
- expect(matcher.match_unless_raises(RuntimeError, ArgumentError, NameError) { raise NameError.new('') }).to be_false
23
+ expect(matcher.match_unless_raises(RuntimeError, ArgumentError, NameError) { raise "foo" }).to be_falsey
24
+ expect(matcher.match_unless_raises(RuntimeError, ArgumentError, NameError) { raise ArgumentError.new('') }).to be_falsey
25
+ expect(matcher.match_unless_raises(RuntimeError, ArgumentError, NameError) { raise NameError.new('') }).to be_falsey
26
26
  end
27
27
 
28
28
  it "re-raises any error other than one of those specified" do
@@ -39,27 +39,6 @@ module RSpec::Matchers::BuiltIn
39
39
 
40
40
  end
41
41
 
42
- describe "#failure_message_for_should" do
43
- context "when the parameter to .new is omitted" do
44
- it "describes what was expected" do
45
- matcher_class = Class.new(BaseMatcher) do
46
- def name=(name)
47
- @name = name
48
- end
49
-
50
- def match(expected, actual)
51
- false
52
- end
53
- end
54
-
55
- matcher = matcher_class.new
56
- matcher.name = "be something"
57
- matcher.matches?("foo")
58
- expect(matcher.failure_message_for_should).to eq('expected "foo" to be something')
59
- end
60
- end
61
- end
62
-
63
42
  describe "#==" do
64
43
  it "responds the same way as matches?" do
65
44
  matcher = Class.new(BaseMatcher) do
@@ -72,10 +51,10 @@ module RSpec::Matchers::BuiltIn
72
51
  end
73
52
  end
74
53
 
75
- expect(matcher.new(3).matches?(3)).to be_true
54
+ expect(matcher.new(3).matches?(3)).to be_truthy
76
55
  expect(matcher.new(3)).to eq(3)
77
56
 
78
- expect(matcher.new(3).matches?(4)).to be_false
57
+ expect(matcher.new(3).matches?(4)).to be_falsey
79
58
  expect(matcher.new(3)).not_to eq(4)
80
59
  end
81
60
  end
@@ -31,6 +31,21 @@ describe "expect(...).to be_predicate" do
31
31
  }.to raise_error(NameError, /happy\?/)
32
32
  end
33
33
 
34
+ it 'warns of deprecation when :predicate? is private' do
35
+ privately_happy = Class.new do
36
+ private
37
+ def happy?
38
+ true
39
+ end
40
+ end
41
+ expect(RSpec).to receive(:deprecate).with(
42
+ "matching with be_happy on private method happy?",
43
+ :replacement => "`expect(object.send(:happy?)).to be_true` or change the method's visibility to public",
44
+ :call_site => RSpec::Mocks::ArgumentMatchers::RegexpMatcher.new(/#{__FILE__}:#{__LINE__ + 2}/)
45
+ )
46
+ expect(privately_happy.new).to be_happy
47
+ end
48
+
34
49
  it "fails on error other than NameError" do
35
50
  actual = double("actual")
36
51
  actual.should_receive(:foo?).and_raise("aaaah")
@@ -50,7 +65,7 @@ describe "expect(...).to be_predicate" do
50
65
  it "does not support operator chaining like a basic `be` matcher does" do
51
66
  matcher = be_happy
52
67
  value = double(:happy? => false)
53
- expect(be_happy == value).to be false
68
+ expect(matcher == value).to be false
54
69
  end
55
70
  end
56
71
 
@@ -231,35 +246,94 @@ describe "expect(...).not_to be_predicate(*args, &block)" do
231
246
  end
232
247
  end
233
248
 
234
- describe "expect(...).to be_true" do
249
+ describe "expect(...).to be_truthy" do
235
250
  it "passes when actual equal?(true)" do
236
- expect(true).to be_true
251
+ expect(true).to be_truthy
237
252
  end
238
253
 
239
254
  it "passes when actual is 1" do
240
- expect(1).to be_true
255
+ expect(1).to be_truthy
241
256
  end
242
257
 
243
258
  it "fails when actual equal?(false)" do
244
259
  expect {
245
- expect(false).to be_true
246
- }.to fail_with("expected: true value\n got: false")
260
+ expect(false).to be_truthy
261
+ }.to fail_with("expected: truthy value\n got: false")
247
262
  end
248
263
  end
249
264
 
250
265
  describe "expect(...).to be_false" do
251
- it "passes when actual equal?(false)" do
266
+ it "is deprecated" do
267
+ expect(RSpec).to receive(:deprecate).with(/be_false/, :replacement => match(/falsey.*false/))
268
+ expect(false).to be_false
269
+ end
270
+
271
+ it "has the correct call site in the deprecation message" do
272
+ expect_deprecation_with_call_site(__FILE__, __LINE__ + 1)
252
273
  expect(false).to be_false
253
274
  end
254
275
 
276
+ it "still passes" do
277
+ allow(RSpec).to receive(:deprecate)
278
+ expect(false).to be_false
279
+ end
280
+
281
+ it "still fails" do
282
+ allow(RSpec).to receive(:deprecate)
283
+ expect {
284
+ expect(true).to be_false
285
+ }.to fail_with(/expected: falsey value/)
286
+ end
287
+ end
288
+
289
+ describe "expect(...).to be_true" do
290
+ it "is deprecated" do
291
+ expect(RSpec).to receive(:deprecate).with(/be_true/, :replacement => match(/truthy.*true/))
292
+ expect(true).to be_true
293
+ end
294
+
295
+ it "still passes" do
296
+ allow(RSpec).to receive(:deprecate)
297
+ expect(true).to be_true
298
+ end
299
+
300
+ it "still fails" do
301
+ allow(RSpec).to receive(:deprecate)
302
+ expect {
303
+ expect(false).to be_true
304
+ }.to fail_with(/expected: truthy value/)
305
+ end
306
+ end
307
+
308
+ describe "expect(...).to be_falsey" do
309
+ it "passes when actual equal?(false)" do
310
+ expect(false).to be_falsey
311
+ end
312
+
255
313
  it "passes when actual equal?(nil)" do
256
- expect(nil).to be_false
314
+ expect(nil).to be_falsey
257
315
  end
258
316
 
259
317
  it "fails when actual equal?(true)" do
260
318
  expect {
261
- expect(true).to be_false
262
- }.to fail_with("expected: false value\n got: true")
319
+ expect(true).to be_falsey
320
+ }.to fail_with("expected: falsey value\n got: true")
321
+ end
322
+ end
323
+
324
+ describe "expect(...).to be_falsy" do
325
+ it "passes when actual equal?(false)" do
326
+ expect(false).to be_falsy
327
+ end
328
+
329
+ it "passes when actual equal?(nil)" do
330
+ expect(nil).to be_falsy
331
+ end
332
+
333
+ it "fails when actual equal?(true)" do
334
+ expect {
335
+ expect(true).to be_falsy
336
+ }.to fail_with("expected: falsey value\n got: true")
263
337
  end
264
338
  end
265
339