rspec-expectations 3.0.4 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: effc74da27b6c9c3e1848c1aa153d7cb9bb2741f
4
- data.tar.gz: 273bbb72209bc00f42208e6afbd429e557989229
3
+ metadata.gz: 0d67d8e69dfda8789a69783ab55ae688851cd637
4
+ data.tar.gz: b7eeed6108dc7341331d64f62e1c057c08545c9c
5
5
  SHA512:
6
- metadata.gz: ffda9a9a4e44c478f14a87750353a98ef006a7797d82d45cc86606e9a8ba3affffd45aa095195258808417afb6a8012a8c9698b21fc6f0f0c3051e6c72b9d956
7
- data.tar.gz: da81d97b60f639f6bf33f6d4a14ec76f8f4e6113844753d4cc8829e09cd5c13beeec25a7bf40ed7c7039f43730289cf91ad5d54e77dfc53c0a9c7433382cc746
6
+ metadata.gz: 5b49a1de8f140c99b2bf9fda0af0382c6381f44dfb568626720297c5cd83a9eff033facefa2ff4f312c81c73e9257499bbee9cfe1010f527763d241c8155e2f5
7
+ data.tar.gz: c054e2dee14f838f498918c7b3964381c633fa7d8552242fc16d3d267300cc071aad83e6ef3eedbedda9a9de63553bc27d0d15099fe93c6221eb087b9e02748e
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,34 @@
1
+ ### 3.1.0 / 2014-09-04
2
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.4...v3.1.0)
3
+
4
+ Enhancements:
5
+
6
+ * Add `have_attributes` matcher, that passes if actual's attribute
7
+ values match the expected attributes hash:
8
+ `Person = Struct.new(:name, :age)`
9
+ `person = Person.new("Bob", 32)`
10
+ `expect(person).to have_attributes(:name => "Bob", :age => 32)`.
11
+ (Adam Farhi, #571)
12
+ * Extended compound matcher support to block matchers, for cases like:
13
+ `expect { ... }.to change { x }.to(3).and change { y }.to(4)`. (Myron
14
+ Marston, #567)
15
+ * Include chained methods in custom matcher description and failure message
16
+ when new `include_chain_clauses_in_custom_matcher_descriptions` config
17
+ option is enabled. (Dan Oved, #600)
18
+ * Add `thrice` modifier to `yield_control` matcher as a synonym for
19
+ `exactly(3).times`. (Dennis Taylor, #615)
20
+ * Add `RSpec::Matchers.define_negated_matcher`, which defines a negated
21
+ version of the named matcher. (Adam Farhi, Myron Marston, #618)
22
+ * Document and support negation of `contain_exactly`/`match_array`.
23
+ (Jon Rowe, #626).
24
+
25
+ Bug Fixes:
26
+
27
+ * Rename private `LegacyMacherAdapter` constant to `LegacyMatcherAdapter`
28
+ to fix typo. (Abdelkader Boudih, #563)
29
+ * Fix `all` matcher so that it fails properly (rather than raising a
30
+ `NoMethodError`) when matched against a non-enumerable. (Hao Su, #622)
31
+
1
32
  ### 3.0.4 / 2014-08-14
2
33
  [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.0.3...v3.0.4)
3
34
 
@@ -162,7 +193,7 @@ Enhancements:
162
193
  * Define noun-phrase aliases for built-in matchers, which can be
163
194
  used when creating composed matcher expressions that read better
164
195
  and provide better failure messages. (Myron Marston)
165
- * Add `RSpec::Machers.alias_matcher` so users can define their own
196
+ * Add `RSpec::Matchers.alias_matcher` so users can define their own
166
197
  matcher aliases. The `description` of the matcher will reflect the
167
198
  alternate matcher name. (Myron Marston)
168
199
  * Add explicit `be_between` matcher. `be_between` has worked for a
@@ -107,6 +107,18 @@ module RSpec
107
107
  end
108
108
  end
109
109
 
110
+ # Sets if custom matcher descriptions and failure messages
111
+ # should include clauses from methods defined using `chain`.
112
+ # @param value [Boolean]
113
+ attr_writer :include_chain_clauses_in_custom_matcher_descriptions
114
+
115
+ # Indicates whether or not custom matcher descriptions and failure messages
116
+ # should include clauses from methods defined using `chain`. It is
117
+ # false by default for backwards compatibility.
118
+ def include_chain_clauses_in_custom_matcher_descriptions?
119
+ @include_chain_clauses_in_custom_matcher_descriptions ||= false
120
+ end
121
+
110
122
  # @private
111
123
  def reset_syntaxes_to_default
112
124
  self.syntax = [:should, :expect]
@@ -17,8 +17,8 @@ module RSpec
17
17
  #
18
18
  # @private
19
19
  def self.modern_matcher_from(matcher)
20
- LegacyMacherAdapter::RSpec2.wrap(matcher) ||
21
- LegacyMacherAdapter::RSpec1.wrap(matcher) || matcher
20
+ LegacyMatcherAdapter::RSpec2.wrap(matcher) ||
21
+ LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher
22
22
  end
23
23
 
24
24
  def self.setup(handler, matcher, message)
@@ -95,7 +95,7 @@ module RSpec
95
95
  # order to present the current protocol.
96
96
  #
97
97
  # @private
98
- class LegacyMacherAdapter < Matchers::MatcherDelegator
98
+ class LegacyMatcherAdapter < Matchers::MatcherDelegator
99
99
  def initialize(matcher)
100
100
  super
101
101
  ::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher")
@@ -157,5 +157,11 @@ module RSpec
157
157
  end
158
158
  end
159
159
  end
160
+
161
+ # RSpec 3.0 was released with the class name misspelled. For SemVer compatibility,
162
+ # we will provide this misspelled alias until 4.0.
163
+ # @deprecated Use LegacyMatcherAdapter instead.
164
+ # @private
165
+ LegacyMacherAdapter = LegacyMatcherAdapter
160
166
  end
161
167
  end
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Expectations
3
3
  # @private
4
4
  module Version
5
- STRING = '3.0.4'
5
+ STRING = '3.1.0'
6
6
  end
7
7
  end
8
8
  end
@@ -1,4 +1,5 @@
1
1
  require 'rspec/support'
2
+ RSpec::Support.require_rspec_support 'matcher_definition'
2
3
  RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) }
3
4
 
4
5
  %w[
@@ -15,17 +16,8 @@ RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relat
15
16
  # in the `RSpec::Expectations` and `RSpec::Matchers` namespaces.
16
17
  module RSpec
17
18
  # RSpec::Matchers provides a number of useful matchers we use to define
18
- # expectations. A matcher is any object that responds to the following:
19
- #
20
- # matches?(actual)
21
- # failure_message
22
- #
23
- # These methods are also part of the matcher protocol, but are optional:
24
- #
25
- # does_not_match?(actual)
26
- # failure_message_when_negated
27
- # description
28
- # supports_block_expectations?
19
+ # expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol)
20
+ # can be used as a matcher.
29
21
  #
30
22
  # ## Predicates
31
23
  #
@@ -226,6 +218,8 @@ module RSpec
226
218
  #
227
219
  # @param new_name [Symbol] the new name for the matcher
228
220
  # @param old_name [Symbol] the original name for the matcher
221
+ # @param options [Hash] options for the aliased matcher
222
+ # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
229
223
  # @yield [String] optional block that, when given is used to define the overriden
230
224
  # description. The yielded arg is the original description. If no block is
231
225
  # provided, a default description override is used based on the old and
@@ -249,17 +243,38 @@ module RSpec
249
243
  # @!macro [attach] alias_matcher
250
244
  # @!parse
251
245
  # alias $1 $2
252
- def self.alias_matcher(new_name, old_name, &description_override)
246
+ def self.alias_matcher(new_name, old_name, options={}, &description_override)
253
247
  description_override ||= lambda do |old_desc|
254
248
  old_desc.gsub(Pretty.split_words(old_name), Pretty.split_words(new_name))
255
249
  end
250
+ klass = options.fetch(:klass) { AliasedMatcher }
256
251
 
257
252
  define_method(new_name) do |*args, &block|
258
253
  matcher = __send__(old_name, *args, &block)
259
- AliasedMatcher.new(matcher, description_override)
254
+ klass.new(matcher, description_override)
260
255
  end
261
256
  end
262
257
 
258
+ # Defines a negated matcher. The returned matcher's `description` and `failure_message`
259
+ # will be overriden to reflect the phrasing of the new name, and the match logic will
260
+ # be based on the original matcher but negated.
261
+ #
262
+ # @param negated_name [Symbol] the name for the negated matcher
263
+ # @param base_name [Symbol] the name of the original matcher that will be negated
264
+ # @yield [String] optional block that, when given is used to define the overriden
265
+ # description. The yielded arg is the original description. If no block is
266
+ # provided, a default description override is used based on the old and
267
+ # new names.
268
+ #
269
+ # @example
270
+ #
271
+ # RSpec::Matchers.define_negated_matcher :a_value_not_between, :a_value_between
272
+ # a_value_between(3, 5).description # => "a value between 3 and 5"
273
+ # a_value_not_between(3, 5).description # => "a value not between 3 and 5"
274
+ def self.define_negated_matcher(negated_name, base_name, &description_override)
275
+ alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
276
+ end
277
+
263
278
  # Passes if actual is truthy (anything but false or nil)
264
279
  def be_truthy
265
280
  BuiltIn::BeTruthy.new
@@ -303,7 +318,7 @@ module RSpec
303
318
  def be(*args)
304
319
  args.empty? ? Matchers::BuiltIn::Be.new : equal(*args)
305
320
  end
306
- alias_matcher :a_value, :be
321
+ alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport
307
322
 
308
323
  # passes if target.kind_of?(klass)
309
324
  def be_a(klass)
@@ -382,11 +397,14 @@ module RSpec
382
397
  # You can chain any of the following off of the end to specify details
383
398
  # about the change:
384
399
  #
400
+ # * `from`
401
+ # * `to`
402
+ #
403
+ # or any one of:
404
+ #
385
405
  # * `by`
386
406
  # * `by_at_least`
387
407
  # * `by_at_most`
388
- # * `from`
389
- # * `to`
390
408
  #
391
409
  # @example
392
410
  #
@@ -455,9 +473,6 @@ module RSpec
455
473
  # @note This is also available using the `=~` operator with `should`,
456
474
  # but `=~` is not supported with `expect`.
457
475
  #
458
- # @note This matcher only supports positive expectations.
459
- # `expect(...).not_to contain_exactly(other_array)` is not supported.
460
- #
461
476
  # @example
462
477
  #
463
478
  # expect([1, 2, 3]).to contain_exactly(1, 2, 3)
@@ -560,6 +575,27 @@ module RSpec
560
575
  alias_matcher :an_object_existing, :exist
561
576
  alias_matcher :existing, :exist
562
577
 
578
+ # Passes if actual's attribute values match the expected attributes hash.
579
+ # This works no matter how you define your attribute readers.
580
+ #
581
+ # @example
582
+ #
583
+ # Person = Struct.new(:name, :age)
584
+ # person = Person.new("Bob", 32)
585
+ #
586
+ # expect(person).to have_attributes(:name => "Bob", :age => 32)
587
+ # expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) )
588
+ #
589
+ # @note It will fail if actual doesn't respond to any of the expected attributes.
590
+ #
591
+ # @example
592
+ #
593
+ # expect(person).to have_attributes(:color => "red")
594
+ def have_attributes(expected)
595
+ BuiltIn::HaveAttributes.new(expected)
596
+ end
597
+ alias_matcher :an_object_having_attributes, :have_attributes
598
+
563
599
  # Passes if actual includes expected. This works for
564
600
  # collections and Strings. You can also pass in multiple args
565
601
  # and it will only pass if all args are found in collection.
@@ -906,13 +942,23 @@ module RSpec
906
942
  # @api private
907
943
  def self.is_a_matcher?(obj)
908
944
  return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj
909
- return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
945
+ begin
946
+ return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
947
+ rescue NoMethodError
948
+ # Some objects, like BasicObject, don't implemented standard
949
+ # reflection methods.
950
+ return false
951
+ end
910
952
  return false unless obj.respond_to?(:matches?)
911
953
 
912
954
  obj.respond_to?(:failure_message) ||
913
955
  obj.respond_to?(:failure_message_for_should) # support legacy matchers
914
956
  end
915
957
 
958
+ ::RSpec::Support.register_matcher_definition do |obj|
959
+ is_a_matcher?(obj)
960
+ end
961
+
916
962
  # @api private
917
963
  def self.is_a_describable_matcher?(obj)
918
964
  is_a_matcher?(obj) && obj.respond_to?(:description)
@@ -29,13 +29,57 @@ module RSpec
29
29
 
30
30
  # Provides the description of the aliased matcher. Aliased matchers
31
31
  # are designed to behave identically to the original matcher except
32
- # for this method. The description is different to reflect the aliased
33
- # name.
32
+ # for the description and failure messages. The description is different
33
+ # to reflect the aliased name.
34
34
  #
35
35
  # @api private
36
36
  def description
37
37
  @description_block.call(super)
38
38
  end
39
+
40
+ # Provides the failure_message of the aliased matcher. Aliased matchers
41
+ # are designed to behave identically to the original matcher except
42
+ # for the description and failure messages. The failure_message is different
43
+ # to reflect the aliased name.
44
+ #
45
+ # @api private
46
+ def failure_message
47
+ @description_block.call(super)
48
+ end
49
+
50
+ # Provides the failure_message_when_negated of the aliased matcher. Aliased matchers
51
+ # are designed to behave identically to the original matcher except
52
+ # for the description and failure messages. The failure_message_when_negated is different
53
+ # to reflect the aliased name.
54
+ #
55
+ # @api private
56
+ def failure_message_when_negated
57
+ @description_block.call(super)
58
+ end
59
+ end
60
+
61
+ # Decorator used for matchers that have special implementations of
62
+ # operators like `==` and `===`.
63
+ # @private
64
+ class AliasedMatcherWithOperatorSupport < AliasedMatcher
65
+ # We undef these so that they get delegated via `method_missing`.
66
+ undef ==
67
+ undef ===
68
+ end
69
+
70
+ # @private
71
+ class AliasedNegatedMatcher < AliasedMatcher
72
+ def matches?(*args, &block)
73
+ if @base_matcher.respond_to?(:does_not_match?)
74
+ @base_matcher.does_not_match?(*args, &block)
75
+ else
76
+ !super
77
+ end
78
+ end
79
+
80
+ def does_not_match?(*args, &block)
81
+ @base_matcher.matches?(*args, &block)
82
+ end
39
83
  end
40
84
  end
41
85
  end
@@ -30,6 +30,7 @@ module RSpec
30
30
  autoload :Equal, 'rspec/matchers/built_in/equal'
31
31
  autoload :Exist, 'rspec/matchers/built_in/exist'
32
32
  autoload :Has, 'rspec/matchers/built_in/has'
33
+ autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes'
33
34
  autoload :Include, 'rspec/matchers/built_in/include'
34
35
  autoload :All, 'rspec/matchers/built_in/all'
35
36
  autoload :Match, 'rspec/matchers/built_in/match'
@@ -21,6 +21,10 @@ module RSpec
21
21
  # @api private
22
22
  # @return [String]
23
23
  def failure_message
24
+ unless enumerable?
25
+ return "#{improve_hash_formatting(super)}, but was not enumerable"
26
+ end
27
+
24
28
  all_messages = [improve_hash_formatting(super)]
25
29
  failed_objects.each do |index, matcher_failure_message|
26
30
  all_messages << failure_message_for_item(index, matcher_failure_message)
@@ -37,6 +41,8 @@ module RSpec
37
41
  private
38
42
 
39
43
  def match(_expected, _actual)
44
+ return false unless enumerable?
45
+
40
46
  index_failed_objects
41
47
  failed_objects.empty?
42
48
  end
@@ -69,6 +75,10 @@ module RSpec
69
75
  @matcher = @matcher.clone
70
76
  super
71
77
  end
78
+
79
+ def enumerable?
80
+ Enumerable === @actual
81
+ end
72
82
  end
73
83
  end
74
84
  end
@@ -95,6 +95,11 @@ module RSpec
95
95
  false
96
96
  end
97
97
 
98
+ # @api private
99
+ def expects_call_stack_jump?
100
+ false
101
+ end
102
+
98
103
  private
99
104
 
100
105
  def assert_ivars(*expected_ivars)
@@ -4,9 +4,7 @@ module RSpec
4
4
  # @api private
5
5
  # Provides the implementation for `be_within`.
6
6
  # Not intended to be instantiated directly.
7
- class BeWithin
8
- include Composable
9
-
7
+ class BeWithin < BaseMatcher
10
8
  def initialize(delta)
11
9
  @delta = delta
12
10
  end
@@ -55,11 +53,6 @@ module RSpec
55
53
  "be within #{@delta}#{@unit} of #{@expected}"
56
54
  end
57
55
 
58
- # @private
59
- def supports_block_expectations?
60
- false
61
- end
62
-
63
56
  private
64
57
 
65
58
  def numeric?
@@ -4,9 +4,7 @@ module RSpec
4
4
  # @api private
5
5
  # Provides the implementation for `change`.
6
6
  # Not intended to be instantiated directly.
7
- class Change
8
- include Composable
9
-
7
+ class Change < BaseMatcher
10
8
  # @api public
11
9
  # Specifies the delta of the expected change.
12
10
  def by(expected_delta)
@@ -104,9 +102,7 @@ module RSpec
104
102
 
105
103
  # Used to specify a relative change.
106
104
  # @api private
107
- class ChangeRelatively
108
- include Composable
109
-
105
+ class ChangeRelatively < BaseMatcher
110
106
  def initialize(change_details, expected_delta, relativity, &comparer)
111
107
  @change_details = change_details
112
108
  @expected_delta = expected_delta
@@ -152,8 +148,7 @@ module RSpec
152
148
 
153
149
  # @api private
154
150
  # Base class for specifying a change from and/or to specific values.
155
- class SpecificValuesChange
156
- include Composable
151
+ class SpecificValuesChange < BaseMatcher
157
152
  # @private
158
153
  MATCH_ANYTHING = ::Object.ancestors.last
159
154
 
@@ -14,8 +14,10 @@ module RSpec
14
14
 
15
15
  # @private
16
16
  def does_not_match?(_actual)
17
- raise NotImplementedError, "`expect(...).not_to " \
18
- "matcher.#{conjunction} matcher` is not supported"
17
+ raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \
18
+ "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \
19
+ "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \
20
+ "use `expect(...).to matcher.#{conjunction} matcher`."
19
21
  end
20
22
 
21
23
  # @api private
@@ -24,6 +26,16 @@ module RSpec
24
26
  singleline_message(matcher_1.description, matcher_2.description)
25
27
  end
26
28
 
29
+ def supports_block_expectations?
30
+ matcher_supports_block_expectations?(matcher_1) &&
31
+ matcher_supports_block_expectations?(matcher_2)
32
+ end
33
+
34
+ def expects_call_stack_jump?
35
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
36
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
37
+ end
38
+
27
39
  private
28
40
 
29
41
  def initialize_copy(other)
@@ -32,6 +44,16 @@ module RSpec
32
44
  super
33
45
  end
34
46
 
47
+ def match(_expected, actual)
48
+ evaluator_klass = if supports_block_expectations? && Proc === actual
49
+ NestedEvaluator
50
+ else
51
+ SequentialEvaluator
52
+ end
53
+
54
+ @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2)
55
+ end
56
+
35
57
  def indent_multiline_message(message)
36
58
  message.lines.map do |line|
37
59
  line =~ /\S/ ? ' ' + line : line
@@ -65,15 +87,133 @@ module RSpec
65
87
  [message_1, conjunction, message_2].join(' ')
66
88
  end
67
89
 
90
+ def matcher_1_matches?
91
+ @evaluator.matcher_matches?(matcher_1)
92
+ end
93
+
94
+ def matcher_2_matches?
95
+ @evaluator.matcher_matches?(matcher_2)
96
+ end
97
+
98
+ def matcher_supports_block_expectations?(matcher)
99
+ matcher.supports_block_expectations?
100
+ rescue NoMethodError
101
+ false
102
+ end
103
+
104
+ # For value expectations, we can evaluate the matchers sequentially.
105
+ class SequentialEvaluator
106
+ def initialize(actual, *)
107
+ @actual = actual
108
+ end
109
+
110
+ def matcher_matches?(matcher)
111
+ matcher.matches?(@actual)
112
+ end
113
+ end
114
+
115
+ # Normally, we evaluate the matching sequentially. For an expression like
116
+ # `expect(x).to foo.and bar`, this becomes:
117
+ #
118
+ # expect(x).to foo
119
+ # expect(x).to bar
120
+ #
121
+ # For block expectations, we need to nest them instead, so that
122
+ # `expect { x }.to foo.and bar` becomes:
123
+ #
124
+ # expect {
125
+ # expect { x }.to foo
126
+ # }.to bar
127
+ #
128
+ # This is necessary so that the `expect` block is only executed once.
129
+ class NestedEvaluator
130
+ def initialize(actual, matcher_1, matcher_2)
131
+ @actual = actual
132
+ @matcher_1 = matcher_1
133
+ @matcher_2 = matcher_2
134
+ @match_results = {}
135
+
136
+ inner, outer = order_block_matchers
137
+
138
+ @match_results[outer] = outer.matches?(Proc.new do |*args|
139
+ @match_results[inner] = inner.matches?(inner_matcher_block(args))
140
+ end)
141
+ end
142
+
143
+ def matcher_matches?(matcher)
144
+ @match_results.fetch(matcher)
145
+ end
146
+
147
+ private
148
+
149
+ # Some block matchers (such as `yield_xyz`) pass args to the `expect` block.
150
+ # When such a matcher is used as the outer matcher, we need to forward the
151
+ # the args on to the `expect` block.
152
+ def inner_matcher_block(outer_args)
153
+ return @actual if outer_args.empty?
154
+
155
+ Proc.new do |*inner_args|
156
+ unless inner_args.empty?
157
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
158
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
159
+ "since both matchers pass arguments to the block."
160
+ end
161
+
162
+ @actual.call(*outer_args)
163
+ end
164
+ end
165
+
166
+ # For a matcher like `raise_error` or `throw_symbol`, where the block will jump
167
+ # up the call stack, we need to order things so that it is the inner matcher.
168
+ # For example, we need it to be this:
169
+ #
170
+ # expect {
171
+ # expect {
172
+ # x += 1
173
+ # raise "boom"
174
+ # }.to raise_error("boom")
175
+ # }.to change { x }.by(1)
176
+ #
177
+ # ...rather than:
178
+ #
179
+ # expect {
180
+ # expect {
181
+ # x += 1
182
+ # raise "boom"
183
+ # }.to change { x }.by(1)
184
+ # }.to raise_error("boom")
185
+ #
186
+ # In the latter case, the after-block logic in the `change` matcher would never
187
+ # get executed because the `raise "boom"` line would jump to the `rescue` in the
188
+ # `raise_error` logic, so only the former case will work properly.
189
+ #
190
+ # This method figures out which matcher should be the inner matcher and which
191
+ # should be the outer matcher.
192
+ def order_block_matchers
193
+ return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2)
194
+ return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1)
195
+
196
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
197
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
198
+ "because they both expect a call stack jump."
199
+ end
200
+
201
+ def self.matcher_expects_call_stack_jump?(matcher)
202
+ matcher.expects_call_stack_jump?
203
+ rescue NoMethodError
204
+ false
205
+ end
206
+ end
207
+
68
208
  # @api public
69
209
  # Matcher used to represent a compound `and` expectation.
70
210
  class And < self
71
211
  # @api private
72
212
  # @return [String]
73
213
  def failure_message
74
- if @matcher_1_matches
214
+ if matcher_1_matches?
75
215
  matcher_2.failure_message
76
- elsif @matcher_2_matches
216
+ elsif matcher_2_matches?
77
217
  matcher_1.failure_message
78
218
  else
79
219
  compound_failure_message
@@ -82,11 +222,9 @@ module RSpec
82
222
 
83
223
  private
84
224
 
85
- def match(_expected, actual)
86
- @matcher_1_matches = matcher_1.matches?(actual)
87
- @matcher_2_matches = matcher_2.matches?(actual)
88
-
89
- @matcher_1_matches && @matcher_2_matches
225
+ def match(*)
226
+ super
227
+ matcher_1_matches? && matcher_2_matches?
90
228
  end
91
229
 
92
230
  def conjunction
@@ -105,8 +243,9 @@ module RSpec
105
243
 
106
244
  private
107
245
 
108
- def match(_expected, actual)
109
- matcher_1.matches?(actual) || matcher_2.matches?(actual)
246
+ def match(*)
247
+ super
248
+ matcher_1_matches? || matcher_2_matches?
110
249
  end
111
250
 
112
251
  def conjunction
@@ -23,7 +23,7 @@ module RSpec
23
23
  # @api private
24
24
  # @return [String]
25
25
  def failure_message_when_negated
26
- "`contain_exactly` does not support negation"
26
+ "expected #{actual.inspect} not to contain exactly#{to_sentence(surface_descriptions_in expected)}"
27
27
  end
28
28
 
29
29
  # @api private
@@ -4,9 +4,7 @@ module RSpec
4
4
  # @api private
5
5
  # Provides the implementation for `has_<predicate>`.
6
6
  # Not intended to be instantiated directly.
7
- class Has
8
- include Composable
9
-
7
+ class Has < BaseMatcher
10
8
  def initialize(method_name, *args, &block)
11
9
  @method_name, @args, @block = method_name, args, block
12
10
  end
@@ -43,11 +41,6 @@ module RSpec
43
41
  [method_description, args_description].compact.join(' ')
44
42
  end
45
43
 
46
- # @private
47
- def supports_block_expectations?
48
- false
49
- end
50
-
51
44
  private
52
45
 
53
46
  def predicate_accessible?
@@ -74,7 +67,11 @@ module RSpec
74
67
  end
75
68
 
76
69
  def predicate
77
- @predicate ||= :"has_#{@method_name.to_s.match(Matchers::HAS_REGEX).captures.first}?"
70
+ # On 1.9, there appears to be a bug where String#match can return `false`
71
+ # rather than the match data object. Changing to Regex#match appears to
72
+ # work around this bug. For an example of this bug, see:
73
+ # https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
74
+ @predicate ||= :"has_#{Matchers::HAS_REGEX.match(@method_name.to_s).captures.first}?"
78
75
  end
79
76
 
80
77
  def method_description
@@ -0,0 +1,84 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `have_attributes`.
6
+ # Not intended to be instantiated directly.
7
+ class HaveAttributes < BaseMatcher
8
+ # @private
9
+ attr_reader :respond_to_failed
10
+
11
+ def initialize(expected)
12
+ @expected = expected
13
+ @respond_to_failed = false
14
+ end
15
+
16
+ # @api private
17
+ # @return [Boolean]
18
+ def matches?(actual)
19
+ @actual = actual
20
+ return false unless respond_to_attributes?
21
+ perform_match(:all?)
22
+ end
23
+
24
+ # @api private
25
+ # @return [Boolean]
26
+ def does_not_match?(actual)
27
+ @actual = actual
28
+ return false unless respond_to_attributes?
29
+ perform_match(:none?)
30
+ end
31
+
32
+ # @api private
33
+ # @return [String]
34
+ def description
35
+ described_items = surface_descriptions_in(expected)
36
+ improve_hash_formatting "have attributes #{described_items.inspect}"
37
+ end
38
+
39
+ # @api private
40
+ # @return [String]
41
+ def failure_message
42
+ respond_to_failure_message_or { super }
43
+ end
44
+
45
+ # @api private
46
+ # @return [String]
47
+ def failure_message_when_negated
48
+ respond_to_failure_message_or { super }
49
+ end
50
+
51
+ private
52
+
53
+ def perform_match(predicate)
54
+ expected.__send__(predicate) do |attribute_key, attribute_value|
55
+ actual_has_attribute?(attribute_key, attribute_value)
56
+ end
57
+ end
58
+
59
+ def actual_has_attribute?(attribute_key, attribute_value)
60
+ actual_value = actual.__send__(attribute_key)
61
+ values_match?(attribute_value, actual_value)
62
+ end
63
+
64
+ def respond_to_attributes?
65
+ matches = respond_to_matcher.matches?(actual)
66
+ @respond_to_failed = !matches
67
+ matches
68
+ end
69
+
70
+ def respond_to_matcher
71
+ @respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments
72
+ end
73
+
74
+ def respond_to_failure_message_or
75
+ if respond_to_failed
76
+ respond_to_matcher.failure_message
77
+ else
78
+ improve_hash_formatting(yield)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -8,7 +8,9 @@ module RSpec
8
8
  # Not intended to be instantiated directly.
9
9
  class Output < BaseMatcher
10
10
  def initialize(expected)
11
- @expected = expected
11
+ @expected = expected
12
+ @actual = ""
13
+ @block = nil
12
14
  @stream_capturer = NullCapture
13
15
  end
14
16
 
@@ -4,6 +4,7 @@ module RSpec
4
4
  # @api private
5
5
  # Provides the implementation for `raise_error`.
6
6
  # Not intended to be instantiated directly.
7
+ # rubocop:disable ClassLength
7
8
  class RaiseError
8
9
  include Composable
9
10
 
@@ -66,6 +67,10 @@ module RSpec
66
67
  true
67
68
  end
68
69
 
70
+ def expects_call_stack_jump?
71
+ true
72
+ end
73
+
69
74
  # @api private
70
75
  # @return [String]
71
76
  def failure_message
@@ -163,6 +168,7 @@ module RSpec
163
168
  raise "`expect { }.to raise_error(message).with_message(message)` is not valid. The matcher only allows the expected message to be specified once"
164
169
  end
165
170
  end
171
+ # rubocop:enable ClassLength
166
172
  end
167
173
  end
168
174
  end
@@ -6,9 +6,7 @@ module RSpec
6
6
  # @api private
7
7
  # Provides the implementation for `respond_to`.
8
8
  # Not intended to be instantiated directly.
9
- class RespondTo
10
- include Composable
11
-
9
+ class RespondTo < BaseMatcher
12
10
  def initialize(*names)
13
11
  @names = names
14
12
  @expected_arity = nil
@@ -62,11 +60,6 @@ module RSpec
62
60
  "respond to #{pp_names}#{with_arity}"
63
61
  end
64
62
 
65
- # @private
66
- def supports_block_expectations?
67
- false
68
- end
69
-
70
63
  private
71
64
 
72
65
  def find_failing_method_names(actual, filter_method)
@@ -80,7 +73,7 @@ module RSpec
80
73
  return true unless @expected_arity
81
74
 
82
75
  signature = Support::MethodSignature.new(actual.method(name))
83
- Support::MethodSignatureVerifier.new(signature, Array.new(@expected_arity)).valid?
76
+ Support::StrictSignatureVerifier.new(signature, Array.new(@expected_arity)).valid?
84
77
  end
85
78
 
86
79
  def with_arity
@@ -4,9 +4,7 @@ module RSpec
4
4
  # @api private
5
5
  # Provides the implementation for `satisfy`.
6
6
  # Not intended to be instantiated directly.
7
- class Satisfy
8
- include Composable
9
-
7
+ class Satisfy < BaseMatcher
10
8
  def initialize(&block)
11
9
  @block = block
12
10
  end
@@ -35,11 +33,6 @@ module RSpec
35
33
  def description
36
34
  "satisfy block"
37
35
  end
38
-
39
- # @private
40
- def supports_block_expectations?
41
- false
42
- end
43
36
  end
44
37
  end
45
38
  end
@@ -94,6 +94,10 @@ module RSpec
94
94
  true
95
95
  end
96
96
 
97
+ def expects_call_stack_jump?
98
+ true
99
+ end
100
+
97
101
  private
98
102
 
99
103
  def actual_result
@@ -1,3 +1,5 @@
1
+ RSpec::Support.require_rspec_support "method_signature_verifier"
2
+
1
3
  module RSpec
2
4
  module Matchers
3
5
  module BuiltIn
@@ -68,10 +70,22 @@ module RSpec
68
70
  "are."
69
71
  end
70
72
 
71
- def assert_valid_expect_block!
72
- return if @block.arity == 1
73
- raise "Your expect block must accept an argument to be used with this " \
74
- "matcher. Pass the argument as a block on to the method you are testing."
73
+ if RUBY_VERSION.to_f > 1.8
74
+ def assert_valid_expect_block!
75
+ block_signature = RSpec::Support::BlockSignature.new(@block)
76
+ return if RSpec::Support::StrictSignatureVerifier.new(block_signature, [self]).valid?
77
+ raise "Your expect block must accept an argument to be used with this " \
78
+ "matcher. Pass the argument as a block on to the method you are testing."
79
+ end
80
+ else
81
+ # On 1.8.7, `lambda { }.arity` and `lambda { |*a| }.arity` both return -1,
82
+ # so we can't distinguish between accepting no args and an arg splat.
83
+ # It's OK to skip, this, though; it just provides a nice error message
84
+ # when the user forgets to accept an arg in their block. They'll still get
85
+ # the `assert_used!` error message from above, which is sufficient.
86
+ def assert_valid_expect_block!
87
+ # nothing to do
88
+ end
75
89
  end
76
90
  end
77
91
 
@@ -92,12 +106,19 @@ module RSpec
92
106
  end
93
107
 
94
108
  # @api public
95
- # Specifies that the method is expected to yield once.
109
+ # Specifies that the method is expected to yield twice.
96
110
  def twice
97
111
  exactly(2)
98
112
  self
99
113
  end
100
114
 
115
+ # @api public
116
+ # Specifies that the method is expected to yield thrice.
117
+ def thrice
118
+ exactly(3)
119
+ self
120
+ end
121
+
101
122
  # @api public
102
123
  # Specifies that the method is expected to yield the given number of times.
103
124
  def exactly(number)
@@ -167,6 +188,7 @@ module RSpec
167
188
  when Numeric then n
168
189
  when :once then 1
169
190
  when :twice then 2
191
+ when :thrice then 3
170
192
  end
171
193
  end
172
194
 
@@ -241,9 +263,7 @@ module RSpec
241
263
  # @api private
242
264
  # Provides the implementation for `yield_with_args`.
243
265
  # Not intended to be instantiated directly.
244
- class YieldWithArgs
245
- include Composable
246
-
266
+ class YieldWithArgs < BaseMatcher
247
267
  def initialize(*args)
248
268
  @expected = args
249
269
  end
@@ -330,9 +350,7 @@ module RSpec
330
350
  # @api private
331
351
  # Provides the implementation for `yield_successive_args`.
332
352
  # Not intended to be instantiated directly.
333
- class YieldSuccessiveArgs
334
- include Composable
335
-
353
+ class YieldSuccessiveArgs < BaseMatcher
336
354
  def initialize(*args)
337
355
  @expected = args
338
356
  end
@@ -160,7 +160,10 @@ module RSpec
160
160
  # Convenience for defining methods on this matcher to create a fluent
161
161
  # interface. The trick about fluent interfaces is that each method must
162
162
  # return self in order to chain methods together. `chain` handles that
163
- # for you.
163
+ # for you. If the method is invoked and the
164
+ # `include_chain_clauses_in_custom_matcher_descriptions` config option
165
+ # hash been enabled, the chained method name and args will be added to the
166
+ # default description and failure message.
164
167
  #
165
168
  # @example
166
169
  #
@@ -178,6 +181,7 @@ module RSpec
178
181
  def chain(name, &definition)
179
182
  define_user_override(name, definition) do |*args, &block|
180
183
  super(*args, &block)
184
+ @chained_method_clauses.push([name, args])
181
185
  self
182
186
  end
183
187
  end
@@ -249,17 +253,17 @@ module RSpec
249
253
 
250
254
  # The default description.
251
255
  def description
252
- "#{name_to_sentence}#{to_sentence expected}"
256
+ "#{name_to_sentence}#{to_sentence expected}#{chained_method_clause_sentences}"
253
257
  end
254
258
 
255
259
  # The default failure message for positive expectations.
256
260
  def failure_message
257
- "expected #{actual.inspect} to #{name_to_sentence}#{to_sentence expected}"
261
+ "expected #{actual.inspect} to #{description}"
258
262
  end
259
263
 
260
264
  # The default failure message for negative expectations.
261
265
  def failure_message_when_negated
262
- "expected #{actual.inspect} not to #{name_to_sentence}#{to_sentence expected}"
266
+ "expected #{actual.inspect} not to #{description}"
263
267
  end
264
268
 
265
269
  # Matchers do not support block expectations by default. You
@@ -267,6 +271,21 @@ module RSpec
267
271
  def supports_block_expectations?
268
272
  false
269
273
  end
274
+
275
+ # Most matchers do not expect call stack jumps.
276
+ def expects_call_stack_jump?
277
+ false
278
+ end
279
+
280
+ private
281
+
282
+ def chained_method_clause_sentences
283
+ return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions?
284
+
285
+ @chained_method_clauses.map do |(method_name, method_args)|
286
+ " #{split_words(method_name)}#{to_sentence(method_args)}"
287
+ end.join
288
+ end
270
289
  end
271
290
 
272
291
  # The class used for custom matchers. The block passed to
@@ -304,6 +323,7 @@ module RSpec
304
323
  @actual = nil
305
324
  @expected_as_array = expected
306
325
  @matcher_execution_context = matcher_execution_context
326
+ @chained_method_clauses = []
307
327
 
308
328
  class << self
309
329
  # See `Macros#define_user_override` above, for an explanation.
@@ -3,6 +3,7 @@ module RSpec
3
3
  # Provides the necessary plumbing to wrap a matcher with a decorator.
4
4
  # @private
5
5
  class MatcherDelegator
6
+ include Composable
6
7
  attr_reader :base_matcher
7
8
 
8
9
  def initialize(base_matcher)
@@ -27,10 +28,6 @@ module RSpec
27
28
  @base_matcher = @base_matcher.clone
28
29
  super
29
30
  end
30
-
31
- # So `===` is delegated via `method_missing`.
32
- undef ===
33
- undef ==
34
31
  end
35
32
  end
36
33
  end
@@ -0,0 +1,99 @@
1
+ module RSpec
2
+ module Matchers
3
+ # rspec-expectations can work with any matcher object that implements this protocol.
4
+ #
5
+ # @note This class is not loaded at runtime by rspec-expectations. It exists
6
+ # purely to provide documentation for the matcher protocol.
7
+ class MatcherProtocol
8
+ # @!group Required Methods
9
+
10
+ # @method matches?
11
+ # @param actual [Object] The object being matched against.
12
+ # @yield For an expression like `expect(x).to matcher do...end`, the `do/end`
13
+ # block binds to `to`. It passes that block, if there is one, on to this method.
14
+ # @return [Boolean] true if this matcher matches the provided object.
15
+
16
+ # @method failure_message
17
+ # This will only be called if {#matches?} returns false.
18
+ # @return [String] Explanation for the failure.
19
+
20
+ # @!endgroup
21
+
22
+ # @!group Optional Methods
23
+
24
+ # @method does_not_match?
25
+ # In a negative expectation such as `expect(x).not_to foo`, RSpec will
26
+ # call `foo.does_not_match?(x)` if this method is defined. If it's not
27
+ # defined it will fall back to using `!foo.matches?(x)`. This allows you
28
+ # to provide custom logic for the negative case.
29
+ #
30
+ # @param actual [Object] The object being matched against.
31
+ # @yield For an expression like `expect(x).not_to matcher do...end`, the `do/end`
32
+ # block binds to `not_to`. It passes that block, if there is one, on to this method.
33
+ # @return [Boolean] true if this matcher does not match the provided object.
34
+
35
+ # @method failure_message_when_negated
36
+ # This will only be called when a negative match fails.
37
+ # @return [String] Explanation for the failure.
38
+ # @note This method is listed as optional because matchers do not have to
39
+ # support negation. But if your matcher does support negation, this is a
40
+ # required method -- otherwise, you'll get a `NoMethodError`.
41
+
42
+ # @method description
43
+ # The description is used for two things:
44
+ #
45
+ # * When using RSpec's one-liner syntax
46
+ # (e.g. `it { is_expected.to matcher }`), the description
47
+ # is used to generate the example's doc string since you
48
+ # have not provided one.
49
+ # * In a composed matcher expression, the description is used
50
+ # as part of the failure message (and description) of the outer
51
+ # matcher.
52
+ #
53
+ # @return [String] Description of the matcher.
54
+
55
+ # @method supports_block_expectations?
56
+ # Indicates that this matcher can be used in a block expectation expression,
57
+ # such as `expect { foo }.to raise_error`. Generally speaking, this is
58
+ # only needed for matchers which operate on a side effect of a block, rather
59
+ # than on a particular object.
60
+ # @return [Boolean] true if this matcher can be used in block expressions.
61
+ # @note If not defined, RSpec assumes a value of `false` for this method.
62
+
63
+ # @method expects_call_stack_jump?
64
+ # Indicates that when this matcher is used in a block expectation
65
+ # expression, it expects the block to use a ruby construct that causes
66
+ # a call stack jump (such as raising an error or throwing a symbol).
67
+ #
68
+ # This is used internally for compound block expressions, as matchers
69
+ # which expect call stack jumps must be treated with care to work properly.
70
+ #
71
+ # @return [Boolean] true if the matcher expects a call stack jump
72
+ #
73
+ # @note This method is very rarely used or needed.
74
+ # @note If not defined, RSpec assumes a value of `false` for this method.
75
+
76
+ # @method diffable?
77
+ # @return [Boolean] true if `actual` and `expected` can be diffed.
78
+ # Indicates that this matcher provides `actual` and `expected` attributes,
79
+ # and that the values returned by these can be usefully diffed, which can
80
+ # be included in the output.
81
+
82
+ # @method actual
83
+ # @return [String, Object] If an object (rather than a string) is provided,
84
+ # RSpec will use the `pp` library to convert it to multi-line output in
85
+ # order to diff.
86
+ # The actual value for the purposes of a diff.
87
+ # @note This method is required if `diffable?` returns true.
88
+
89
+ # @method expected
90
+ # @return [String, Object] If an object (rather than a string) is provided,
91
+ # RSpec will use the `pp` library to convert it to multi-line output in
92
+ # order to diff.
93
+ # The expected value for the purposes of a diff.
94
+ # @note This method is required if `diffable?` returns true.
95
+
96
+ # @!endgroup
97
+ end
98
+ end
99
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-expectations
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.4
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Baker
@@ -33,7 +33,7 @@ cert_chain:
33
33
  1yHC1AcSYpvi2dAbOiHT5iQF+krm4wse8KctXgTNnjMsHEoGKulJS2/sZl90jcCz
34
34
  muA=
35
35
  -----END CERTIFICATE-----
36
- date: 2014-08-14 00:00:00.000000000 Z
36
+ date: 2014-09-05 00:00:00.000000000 Z
37
37
  dependencies:
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: rspec-support
@@ -41,14 +41,14 @@ dependencies:
41
41
  requirements:
42
42
  - - "~>"
43
43
  - !ruby/object:Gem::Version
44
- version: 3.0.0
44
+ version: 3.1.0
45
45
  type: :runtime
46
46
  prerelease: false
47
47
  version_requirements: !ruby/object:Gem::Requirement
48
48
  requirements:
49
49
  - - "~>"
50
50
  - !ruby/object:Gem::Version
51
- version: 3.0.0
51
+ version: 3.1.0
52
52
  - !ruby/object:Gem::Dependency
53
53
  name: diff-lcs
54
54
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +164,7 @@ files:
164
164
  - lib/rspec/matchers/built_in/equal.rb
165
165
  - lib/rspec/matchers/built_in/exist.rb
166
166
  - lib/rspec/matchers/built_in/has.rb
167
+ - lib/rspec/matchers/built_in/have_attributes.rb
167
168
  - lib/rspec/matchers/built_in/include.rb
168
169
  - lib/rspec/matchers/built_in/match.rb
169
170
  - lib/rspec/matchers/built_in/operators.rb
@@ -178,6 +179,7 @@ files:
178
179
  - lib/rspec/matchers/dsl.rb
179
180
  - lib/rspec/matchers/generated_descriptions.rb
180
181
  - lib/rspec/matchers/matcher_delegator.rb
182
+ - lib/rspec/matchers/matcher_protocol.rb
181
183
  - lib/rspec/matchers/pretty.rb
182
184
  homepage: http://github.com/rspec/rspec-expectations
183
185
  licenses:
@@ -203,6 +205,6 @@ rubyforge_project: rspec
203
205
  rubygems_version: 2.2.2
204
206
  signing_key:
205
207
  specification_version: 4
206
- summary: rspec-expectations-3.0.4
208
+ summary: rspec-expectations-3.1.0
207
209
  test_files: []
208
210
  has_rdoc:
metadata.gz.sig CHANGED
Binary file