rspec-expectations 3.0.4 → 3.1.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 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