rspec-sleeping_king_studios 2.1.1 → 2.2.0.rc.1

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -0
  3. data/DEVELOPMENT.md +26 -17
  4. data/LICENSE +1 -1
  5. data/README.md +198 -19
  6. data/lib/rspec/sleeping_king_studios/configuration.rb +63 -2
  7. data/lib/rspec/sleeping_king_studios/examples/property_examples.rb +115 -27
  8. data/lib/rspec/sleeping_king_studios/examples/rspec_matcher_examples.rb +66 -51
  9. data/lib/rspec/sleeping_king_studios/matchers/active_model/have_errors.rb +8 -269
  10. data/lib/rspec/sleeping_king_studios/matchers/active_model/have_errors_matcher.rb +268 -0
  11. data/lib/rspec/sleeping_king_studios/matchers/base_matcher.rb +5 -15
  12. data/lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of.rb +3 -63
  13. data/lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of_matcher.rb +65 -0
  14. data/lib/rspec/sleeping_king_studios/matchers/built_in/include.rb +3 -108
  15. data/lib/rspec/sleeping_king_studios/matchers/built_in/include_matcher.rb +134 -0
  16. data/lib/rspec/sleeping_king_studios/matchers/built_in/respond_to.rb +3 -258
  17. data/lib/rspec/sleeping_king_studios/matchers/built_in/respond_to_matcher.rb +116 -0
  18. data/lib/rspec/sleeping_king_studios/matchers/core/alias_method.rb +11 -0
  19. data/lib/rspec/sleeping_king_studios/matchers/core/alias_method_matcher.rb +107 -0
  20. data/lib/rspec/sleeping_king_studios/matchers/core/be_boolean.rb +9 -36
  21. data/lib/rspec/sleeping_king_studios/matchers/core/be_boolean_matcher.rb +37 -0
  22. data/lib/rspec/sleeping_king_studios/matchers/core/construct.rb +3 -210
  23. data/lib/rspec/sleeping_king_studios/matchers/core/construct_matcher.rb +113 -0
  24. data/lib/rspec/sleeping_king_studios/matchers/core/delegate_method.rb +11 -0
  25. data/lib/rspec/sleeping_king_studios/matchers/core/delegate_method_matcher.rb +311 -0
  26. data/lib/rspec/sleeping_king_studios/matchers/core/have_constant.rb +16 -0
  27. data/lib/rspec/sleeping_king_studios/matchers/core/have_constant_matcher.rb +225 -0
  28. data/lib/rspec/sleeping_king_studios/matchers/core/have_predicate.rb +11 -0
  29. data/lib/rspec/sleeping_king_studios/matchers/core/have_predicate_matcher.rb +97 -0
  30. data/lib/rspec/sleeping_king_studios/matchers/core/have_property.rb +3 -104
  31. data/lib/rspec/sleeping_king_studios/matchers/core/have_property_matcher.rb +108 -0
  32. data/lib/rspec/sleeping_king_studios/matchers/core/have_reader.rb +3 -74
  33. data/lib/rspec/sleeping_king_studios/matchers/core/have_reader_matcher.rb +96 -0
  34. data/lib/rspec/sleeping_king_studios/matchers/core/have_writer.rb +4 -59
  35. data/lib/rspec/sleeping_king_studios/matchers/core/have_writer_matcher.rb +55 -0
  36. data/lib/rspec/sleeping_king_studios/matchers/description.rb +62 -0
  37. data/lib/rspec/sleeping_king_studios/matchers/macros.rb +32 -0
  38. data/lib/rspec/sleeping_king_studios/matchers/shared/match_parameters.rb +168 -66
  39. data/lib/rspec/sleeping_king_studios/matchers/shared/match_property.rb +25 -0
  40. data/lib/rspec/sleeping_king_studios/matchers.rb +0 -4
  41. data/lib/rspec/sleeping_king_studios/support/method_signature.rb +51 -0
  42. data/lib/rspec/sleeping_king_studios/support/method_signature_expectation.rb +158 -0
  43. data/lib/rspec/sleeping_king_studios/support.rb +9 -0
  44. data/lib/rspec/sleeping_king_studios/version.rb +10 -4
  45. metadata +46 -30
@@ -0,0 +1,268 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/active_model/have_errors_matcher.rb
2
+ require 'rspec/sleeping_king_studios/matchers/base_matcher'
3
+ require 'rspec/sleeping_king_studios/matchers/active_model'
4
+ require 'rspec/sleeping_king_studios/matchers/active_model/have_errors/error_expectation'
5
+ require 'sleeping_king_studios/tools/array_tools'
6
+ require 'sleeping_king_studios/tools/string_tools'
7
+
8
+ module RSpec::SleepingKingStudios::Matchers::ActiveModel
9
+ # Matcher for testing ActiveModel object validations.
10
+ #
11
+ # @since 1.0.0
12
+ class HaveErrorsMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
+ include RSpec::SleepingKingStudios::Matchers::ActiveModel::HaveErrors
14
+
15
+ def initialize
16
+ super
17
+
18
+ # The error and message expectations are set up through #on and
19
+ # #with_message.
20
+ @error_expectations = []
21
+ end # constructor
22
+
23
+ # (see BaseMatcher#description)
24
+ def description
25
+ message = 'have errors'
26
+
27
+ attribute_messages = @error_expectations.select(&:expected).map do |expectation|
28
+ attribute_message = ":#{expectation.attribute}"
29
+
30
+ error_messages = expectation.messages.select(&:expected).map do |message_expectation|
31
+ %{"#{message_expectation.message}"}
32
+ end # map
33
+
34
+ unless error_messages.empty?
35
+ tools = ::SleepingKingStudios::Tools::ArrayTools
36
+
37
+ attribute_message <<
38
+ " with message#{error_messages.count == 1 ? '' : 's'} "\
39
+ "#{tools.humanize_list error_messages}"
40
+ end # unless
41
+
42
+ attribute_message
43
+ end # each
44
+ message << " on #{attribute_messages.join(", and on ")}" unless attribute_messages.empty?
45
+
46
+ message
47
+ end # method description
48
+
49
+ # Checks if the object can be validated, whether the object is valid, and
50
+ # checks the errors on the object against the expected errors and messages
51
+ # from #on and #with_message, if any.
52
+ #
53
+ # @param [Object] actual The object to test against the matcher.
54
+ #
55
+ # @return [Boolean] True if the object responds to :valid? and is valid
56
+ # or object.errors does not match the specified errors and messages (if
57
+ # any); otherwise false.
58
+ #
59
+ # @see #matches?
60
+ def does_not_match? actual
61
+ super
62
+
63
+ @negative_expectation = true
64
+
65
+ return false unless @validates = actual.respond_to?(:valid?)
66
+
67
+ !matches?(actual)
68
+ end # method does_not_match?
69
+
70
+ # Checks if the object can be validated, whether the object is valid, and
71
+ # checks the errors on the object against the expected errors and messages
72
+ # from #on and #with_message, if any.
73
+ #
74
+ # @param [Object] actual The object to test against the matcher.
75
+ #
76
+ # @return [Boolean] True if the object responds to :valid?, is not valid,
77
+ # and object.errors matches the specified errors and messages (if any);
78
+ # otherwise false.
79
+ #
80
+ # @see RSpec::SleepingKingStudios::Matchers::BaseMatcher#matches?
81
+ def matches? actual
82
+ super
83
+
84
+ return false unless @validates = actual.respond_to?(:valid?)
85
+
86
+ !@actual.valid? && attributes_have_errors?
87
+ end # method matches?
88
+
89
+ # Adds an error expectation. If the actual object does not have an error on
90
+ # the specified attribute, #matches? will return false.
91
+ #
92
+ # @param [String, Symbol] attribute
93
+ #
94
+ # @return [HaveErrorsMatcher] self
95
+ #
96
+ # @example Setting an error expectation
97
+ # expect(actual).to have_errors.on(:foo)
98
+ def on attribute
99
+ @error_expectations << ErrorExpectation.new(attribute)
100
+
101
+ self
102
+ end # method on
103
+
104
+ # Adds a message expectation for the most recently added error attribute.
105
+ # If the actual object does not have an error on the that attribute with
106
+ # the specified message, #matches? will return false.
107
+ #
108
+ # @param [String, Regexp] message The expected error message. If a string,
109
+ # matcher will check for an exact match; if a regular expression, matcher
110
+ # will check if the message matches the regexp.
111
+ #
112
+ # @raise [ArgumentError] If no error attribute has been added.
113
+ #
114
+ # @return [HaveErrorsMatcher] self
115
+ #
116
+ # @example Setting an error and a message expectation
117
+ # expect(actual).to have_errors.on(:foo).with("can't be blank")
118
+ #
119
+ # @see #on
120
+ def with_message message
121
+ raise ArgumentError.new "no attribute specified for error message" if
122
+ @error_expectations.empty?
123
+
124
+ @error_expectations.last.messages << MessageExpectation.new(message)
125
+
126
+ self
127
+ end # method with_message
128
+
129
+ # Adds a set of message expectations for the most recently added error
130
+ # attribute.
131
+ #
132
+ # @param [Array<String, Regexp>] messages
133
+ #
134
+ # @see #with_message
135
+ def with_messages *messages
136
+ messages.each do |message| self.with_message(message); end
137
+
138
+ self
139
+ end # method with_message
140
+ alias_method :with, :with_messages
141
+
142
+ # (see BaseMatcher#failure_message)
143
+ def failure_message
144
+ # Failure cases:
145
+ # * object is not a model ("to respond to valid")
146
+ # * expected one or more errors, but received none ("to have errors")
147
+ # * expected one or more messages on :attribute, but received none or a
148
+ # subset ("to have errors on")
149
+
150
+ if !@validates
151
+ "expected #{@actual.inspect} to respond to :valid?"
152
+ elsif expected_errors.empty?
153
+ "expected #{@actual.inspect} to have errors"
154
+ else
155
+ "expected #{@actual.inspect} to have errors#{expected_errors_message}#{received_errors_message}"
156
+ end # if-elsif-else
157
+ end # method failure_message
158
+
159
+ # (see BaseMatcher#failure_message_when_negated)
160
+ def failure_message_when_negated
161
+ # Failure cases:
162
+ # * object is not a model ("to respond to valid")
163
+ # * expected one or more errors, received one or more ("not to have
164
+ # errors")
165
+ # * expected one or more messages on attribute, received one or more
166
+ # ("not to have errors on")
167
+ # * expected specific messages on attribute, received all ("not to have
168
+ # errors on")
169
+
170
+ if !@validates
171
+ "expected #{@actual.inspect} to respond to :valid?"
172
+ elsif expected_errors.empty?
173
+ return "expected #{@actual.inspect} not to have errors#{received_errors_message}"
174
+ else
175
+ return "expected #{@actual.inspect} not to have errors#{expected_errors_message}#{received_errors_message}"
176
+ end # if-else
177
+ end # method failure_message_when_negated
178
+
179
+ private
180
+
181
+ def attributes_have_errors?
182
+ # Iterate through the received errors and match them against the expected
183
+ # errors and messages.
184
+ @actual.errors.messages.each do |attribute, messages|
185
+ # Find the matching error expectation, if any.
186
+ error_expectation = @error_expectations.detect do |error_expectation|
187
+ error_expectation.attribute == attribute
188
+ end # detect
189
+
190
+ if error_expectation
191
+ error_expectation.received = true
192
+
193
+ # If the error includes message expectations, iterate through the
194
+ # received messages.
195
+ unless error_expectation.messages.empty?
196
+ messages.each do |message|
197
+ # Find the matching message expectation, if any.
198
+ message_expectation = error_expectation.messages.detect do |message_expectation|
199
+ if Regexp === message_expectation.message
200
+ message =~ message_expectation.message
201
+ else
202
+ message == message_expectation.message
203
+ end # if-else
204
+ end # detect
205
+
206
+ if message_expectation
207
+ message_expectation.received = true
208
+ else
209
+ error_expectation.messages << MessageExpectation.new(message, false, true)
210
+ end # if-else
211
+ end # each
212
+ end # unless
213
+ else
214
+ error_expectation = ErrorExpectation.new attribute, false, true
215
+ messages.each do |message|
216
+ error_expectation.messages << MessageExpectation.new(message, false, true)
217
+ end # each
218
+
219
+ @error_expectations << error_expectation
220
+ end # if-else
221
+ end # each
222
+
223
+ missing_errors.empty? && missing_messages.empty?
224
+ end # method attributes_have_errors
225
+
226
+ def expected_errors
227
+ @error_expectations.select do |error_expectation|
228
+ error_expectation.expected
229
+ end # select
230
+ end # method expected_errors
231
+
232
+ def missing_errors
233
+ @error_expectations.select do |error_expectation|
234
+ error_expectation.expected && !error_expectation.received
235
+ end # select
236
+ end # method missing_errors
237
+
238
+ def missing_messages
239
+ @error_expectations.select do |error_expectation|
240
+ !error_expectation.messages.missing.empty?
241
+ end # select
242
+ end # method missing_messages
243
+
244
+ def unexpected_errors
245
+ @error_expectations.select do |error_expectation|
246
+ !error_expectation.expected && error_expectation.received
247
+ end # select
248
+ end # method unexpected_errors
249
+
250
+ def expected_errors_message
251
+ "\n expected errors:" + expected_errors.map do |error_expectation|
252
+ "\n #{error_expectation.attribute}: " + (error_expectation.messages.empty? ?
253
+ "(#{@negative_expectation ? 'none' : 'any'})" :
254
+ error_expectation.messages.expected.map(&:message).map(&:inspect).join(", "))
255
+ end.join # map
256
+ end # method expected_errors_message
257
+
258
+ def received_errors_message
259
+ return "" unless @validates
260
+
261
+ return "\n received errors:\n (none)" if @actual.errors.messages.empty?
262
+
263
+ "\n received errors:" + @actual.errors.messages.map do |attr, ary|
264
+ "\n #{attr}: " + ary.map(&:inspect).join(", ")
265
+ end.join # map
266
+ end # method received_errors_message
267
+ end # class
268
+ end # module
@@ -1,21 +1,18 @@
1
1
  # lib/rspec/sleeping_king_studios/matchers/base_matcher.rb
2
2
 
3
3
  require 'rspec/sleeping_king_studios/matchers'
4
+ require 'rspec/sleeping_king_studios/matchers/description'
5
+
6
+ require 'sleeping_king_studios/tools/string_tools'
4
7
 
5
8
  module RSpec::SleepingKingStudios::Matchers
6
9
  # Minimal implementation of the RSpec matcher interface.
7
10
  #
8
11
  # @since 1.0.0
9
12
  class BaseMatcher
10
- attr_reader :actual
13
+ include RSpec::SleepingKingStudios::Matchers::Description
11
14
 
12
- # A short string that describes the purpose of the matcher.
13
- #
14
- # @return [String] the matcher description
15
- def description
16
- return name_to_sentence unless defined?(@expected)
17
- "#{name_to_sentence}#{to_sentence @expected}"
18
- end # method description
15
+ attr_reader :actual
19
16
 
20
17
  # Inverse of #matches? method.
21
18
  #
@@ -51,12 +48,5 @@ module RSpec::SleepingKingStudios::Matchers
51
48
  def failure_message_when_negated
52
49
  "expected #{@actual.inspect} not to #{description}"
53
50
  end # method failure_message_when_negated
54
-
55
- private
56
-
57
- # @api private
58
- def name_to_sentence
59
- 'match'
60
- end # method name_to_sentence
61
51
  end # class
62
52
  end # module
@@ -1,69 +1,9 @@
1
1
  # lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of.rb
2
2
 
3
- require 'rspec/sleeping_king_studios/matchers/built_in'
4
- require 'sleeping_king_studios/tools/enumerable_tools'
3
+ require 'rspec/sleeping_king_studios/matchers/built_in/be_kind_of_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
5
5
 
6
- module RSpec::SleepingKingStudios::Matchers::BuiltIn
7
- # Extensions to the built-in RSpec #be_kind_of matcher.
8
- class BeAKindOfMatcher < RSpec::Matchers::BuiltIn::BeAKindOf
9
- include SleepingKingStudios::Tools::EnumerableTools
10
-
11
- # (see BaseMatcher#description)
12
- def description
13
- message = "be #{type_string}"
14
- end # method description
15
-
16
- # Checks if the object matches one of the specified types. Allows an
17
- # expected value of nil as a shortcut for expecting an instance of
18
- # NilClass.
19
- #
20
- # @param [Module, nil, Array<Module, nil>] expected The type or types to
21
- # check the object against.
22
- # @param [Object] actual The object to check.
23
- #
24
- # @return [Boolean] True if the object matches one of the specified types,
25
- # otherwise false.
26
- def match expected, actual
27
- match_type? expected
28
- end # method match
29
-
30
- # (see BaseMatcher#failure_message)
31
- def failure_message
32
- "expected #{@actual.inspect} to be #{type_string}"
33
- end # method failure_message
34
-
35
- # (see BaseMatcher#failure_message_when_negated)
36
- def failure_message_when_negated
37
- "expected #{@actual.inspect} not to be #{type_string}"
38
- end # method failure_message_when_negated
39
-
40
- private
41
-
42
- def match_type? expected
43
- case
44
- when expected.nil?
45
- @actual.nil?
46
- when expected.is_a?(Enumerable)
47
- expected.reduce(false) { |memo, obj| memo || match_type?(obj) }
48
- else
49
- @actual.kind_of? expected
50
- end # case
51
- end # method match_type?
52
-
53
- def type_string
54
- case
55
- when @expected.nil?
56
- @expected.inspect
57
- when @expected.is_a?(Enumerable) && 1 < @expected.count
58
- "a #{humanize_list @expected.map { |value| value.nil? ? 'nil' : value }, :last_separator => ' or '}"
59
- else
60
- "a #{expected}"
61
- end # case
62
- end # method type_string
63
- end # class
64
- end # module
65
-
66
- module RSpec::SleepingKingStudios::Matchers
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
67
7
  # @see RSpec::SleepingKingStudios::Matchers::BuiltIn::BeAKindOfMatcher#match
68
8
  def be_kind_of expected
69
9
  RSpec::SleepingKingStudios::Matchers::BuiltIn::BeAKindOfMatcher.new expected
@@ -0,0 +1,65 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of_matcher.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/built_in'
4
+ require 'sleeping_king_studios/tools/array_tools'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::BuiltIn
7
+ # Extensions to the built-in RSpec #be_kind_of matcher.
8
+ class BeAKindOfMatcher < RSpec::Matchers::BuiltIn::BeAKindOf
9
+ # (see BaseMatcher#description)
10
+ def description
11
+ message = "be #{type_string}"
12
+ end # method description
13
+
14
+ # Checks if the object matches one of the specified types. Allows an
15
+ # expected value of nil as a shortcut for expecting an instance of
16
+ # NilClass.
17
+ #
18
+ # @param [Module, nil, Array<Module, nil>] expected The type or types to
19
+ # check the object against.
20
+ # @param [Object] actual The object to check.
21
+ #
22
+ # @return [Boolean] True if the object matches one of the specified types,
23
+ # otherwise false.
24
+ def match expected, actual
25
+ match_type? expected
26
+ end # method match
27
+
28
+ # (see BaseMatcher#failure_message)
29
+ def failure_message
30
+ "expected #{@actual.inspect} to be #{type_string}"
31
+ end # method failure_message
32
+
33
+ # (see BaseMatcher#failure_message_when_negated)
34
+ def failure_message_when_negated
35
+ "expected #{@actual.inspect} not to be #{type_string}"
36
+ end # method failure_message_when_negated
37
+
38
+ private
39
+
40
+ def match_type? expected
41
+ case
42
+ when expected.nil?
43
+ @actual.nil?
44
+ when expected.is_a?(Enumerable)
45
+ expected.reduce(false) { |memo, obj| memo || match_type?(obj) }
46
+ else
47
+ @actual.kind_of? expected
48
+ end # case
49
+ end # method match_type?
50
+
51
+ def type_string
52
+ case
53
+ when @expected.nil?
54
+ @expected.inspect
55
+ when @expected.is_a?(Enumerable) && 1 < @expected.count
56
+ tools = ::SleepingKingStudios::Tools::ArrayTools
57
+ items = @expected.map { |value| value.nil? ? 'nil' : value }
58
+
59
+ "a #{tools.humanize_list items, :last_separator => ' or '}"
60
+ else
61
+ "a #{expected}"
62
+ end # case
63
+ end # method type_string
64
+ end # class
65
+ end # module
@@ -1,114 +1,9 @@
1
1
  # lib/rspec/sleeping_king_studios/matchers/built_in/be_kind_of.rb
2
2
 
3
- require 'rspec/sleeping_king_studios/matchers/built_in'
3
+ require 'rspec/sleeping_king_studios/matchers/built_in/include_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
4
5
 
5
- module RSpec::SleepingKingStudios::Matchers::BuiltIn
6
- # Extensions to the built-in RSpec #include matcher.
7
- class IncludeMatcher < RSpec::Matchers::BuiltIn::Include
8
- # @param [Array<Hash, Proc, Object>] expected the items expected to be
9
- # matched by the actual object
10
- #
11
- # @yield If a block is provided, the block is converted to a proc and
12
- # appended to the item expectations.
13
- # @yieldparam [Object] item An item from the actual object; yield(item)
14
- # should return true if and only if the item matches the desired
15
- # predicate.
16
- def initialize *expected, &block
17
- expected << block if block_given?
18
-
19
- super *expected
20
- end # constructor
21
-
22
- # @api private
23
- #
24
- # @return [Boolean]
25
- def matches?(actual)
26
- perform_match(actual) { |v| v }
27
- end # method matches?
28
-
29
- # @api private
30
- #
31
- # @return [Boolean]
32
- def does_not_match?(actual)
33
- perform_match(actual) { |v| !v }
34
- end # method does_not_match?
35
-
36
- # @api private
37
- #
38
- # Converts the expected item to a human-readable string. Retained for
39
- # pre-3.3 compatibility.
40
- def to_word expected_item
41
- case
42
- when is_matcher_with_description?(expected_item)
43
- expected_item.description
44
- when Proc === expected_item
45
- "an item matching the block"
46
- else
47
- expected_item.inspect
48
- end # case
49
- end # method to_word
50
-
51
- # (see BaseMatcher#failure_message)
52
- def failure_message
53
- message = super.sub ':__block_comparison__', 'an item matching the block'
54
-
55
- message << ", but it does not respond to `include?`" unless actual.respond_to?(:include?) || message =~ /does not respond to/
56
-
57
- message
58
- end # method failure_message_for_should
59
-
60
- # (see BaseMatcher#failure_message_when_negated)
61
- def failure_message_when_negated
62
- message = super.sub ':__block_comparison__', 'an item matching the block'
63
-
64
- message << ", but it does not respond to `include?`" unless actual.respond_to?(:include?) || message =~ /does not respond to/
65
-
66
- message
67
- end # method
68
-
69
- private
70
-
71
- # @api private
72
- def perform_match(actual, &block)
73
- @actual = actual
74
- @divergent_items = excluded_from_actual(&block)
75
- actual.respond_to?(:include?) && @divergent_items.empty?
76
- end # method perform_match
77
-
78
- # @api private
79
- def excluded_from_actual
80
- return [] unless @actual.respond_to?(:include?)
81
-
82
- expected.inject([]) do |memo, expected_item|
83
- if comparing_proc?(expected_item)
84
- memo << :__block_comparison__ unless yield actual_matches_proc?(expected_item)
85
- elsif comparing_hash_to_a_subset?(expected_item)
86
- expected_item.each do |(key, value)|
87
- memo << { key => value } unless yield actual_hash_includes?(key, value)
88
- end # each
89
- elsif comparing_hash_keys?(expected_item)
90
- memo << expected_item unless yield actual_hash_has_key?(expected_item)
91
- else
92
- memo << expected_item unless yield actual_collection_includes?(expected_item)
93
- end # if-elsif-else
94
-
95
- memo
96
- end # inject
97
- end # method excluded_from_actual
98
-
99
- # @api private
100
- def actual_matches_proc? expected_item
101
- !!actual.detect(&expected_item)
102
- end # method actual_matches_proc?
103
-
104
- # @api private
105
- def comparing_proc? expected_item
106
- expected_item.is_a?(Proc)
107
- end # method comparing_proc?
108
- end # class
109
- end # module
110
-
111
- module RSpec::SleepingKingStudios::Matchers
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
112
7
  # @see RSpec::SleepingKingStudios::Matchers::BuiltIn::IncludeMatcher#matches?
113
8
  def include *expected, &block
114
9
  RSpec::SleepingKingStudios::Matchers::BuiltIn::IncludeMatcher.new *expected, &block
@@ -0,0 +1,134 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/built_in/include_matcher.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/built_in'
4
+ require 'rspec/sleeping_king_studios/matchers/description'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::BuiltIn
7
+ # Extensions to the built-in RSpec #include matcher.
8
+ class IncludeMatcher < RSpec::Matchers::BuiltIn::Include
9
+ include RSpec::SleepingKingStudios::Matchers::Description
10
+
11
+ # @param [Array<Hash, Proc, Object>] expected the items expected to be
12
+ # matched by the actual object
13
+ #
14
+ # @yield If a block is provided, the block is converted to a proc and
15
+ # appended to the item expectations.
16
+ # @yieldparam [Object] item An item from the actual object; yield(item)
17
+ # should return true if and only if the item matches the desired
18
+ # predicate.
19
+ def initialize *expected, &block
20
+ expected << block if block_given?
21
+
22
+ super *expected
23
+ end # constructor
24
+
25
+ # (see BaseMatcher#description)
26
+ def description
27
+ desc = super
28
+
29
+ # Format hash expectations.
30
+ desc = desc.gsub(/(\S)=>(\S)/, '\1 => \2')
31
+
32
+ # Replace processed block expectation stub with proper description.
33
+ desc = desc.gsub ':__block_comparison__', 'an item matching the block'
34
+
35
+ desc
36
+ end # method description
37
+
38
+ # @api private
39
+ #
40
+ # @return [Boolean]
41
+ def matches?(actual)
42
+ @actual = actual
43
+
44
+ perform_match(actual) { |v| v }
45
+ end # method matches?
46
+
47
+ # @api private
48
+ #
49
+ # @return [Boolean]
50
+ def does_not_match?(actual)
51
+ @actual = actual
52
+
53
+ perform_match(actual) { |v| !v }
54
+ end # method does_not_match?
55
+
56
+ # (see BaseMatcher#failure_message)
57
+ def failure_message
58
+ message = super.sub ':__block_comparison__', 'an item matching the block'
59
+
60
+ message << ", but it does not respond to `include?`" unless actual.respond_to?(:include?) || message =~ /does not respond to/
61
+
62
+ message
63
+ end # method failure_message_for_should
64
+
65
+ # (see BaseMatcher#failure_message_when_negated)
66
+ def failure_message_when_negated
67
+ message = super.sub ':__block_comparison__', 'an item matching the block'
68
+
69
+ message << ", but it does not respond to `include?`" unless actual.respond_to?(:include?) || message =~ /does not respond to/
70
+
71
+ message
72
+ end # method
73
+
74
+ private
75
+
76
+ def actual_matches_proc? expected_item
77
+ if actual.respond_to?(:detect)
78
+ !!actual.detect(&expected_item)
79
+ else
80
+ !!expected_item.call(actual)
81
+ end # if-else
82
+ end # method actual_matches_proc?
83
+
84
+ def comparing_proc? expected_item
85
+ expected_item.is_a?(Proc)
86
+ end # method comparing_proc?
87
+
88
+ def excluded_from_actual
89
+ return [] unless @actual.respond_to?(:include?)
90
+
91
+ expected.inject([]) do |memo, expected_item|
92
+ if comparing_proc?(expected_item)
93
+ memo << :__block_comparison__ unless yield actual_matches_proc?(expected_item)
94
+ elsif comparing_hash_to_a_subset?(expected_item)
95
+ expected_item.each do |(key, value)|
96
+ memo << { key => value } unless yield actual_hash_includes?(key, value)
97
+ end # each
98
+ elsif comparing_hash_keys?(expected_item)
99
+ memo << expected_item unless yield actual_hash_has_key?(expected_item)
100
+ else
101
+ memo << expected_item unless yield actual_collection_includes?(expected_item)
102
+ end # if-elsif-else
103
+
104
+ memo
105
+ end # inject
106
+ end # method excluded_from_actual
107
+
108
+ def expected_items_for_description
109
+ # Preprocess items to stub out block expectations.
110
+ @expected.map { |item| item.is_a?(Proc) ? :__block_comparison__ : item }
111
+ end # method expected_items_for_description
112
+
113
+ def perform_match(actual, &block)
114
+ @actual = actual
115
+ @divergent_items = excluded_from_actual(&block)
116
+ actual.respond_to?(:include?) && @divergent_items.empty?
117
+ end # method perform_match
118
+
119
+ # @api private
120
+ #
121
+ # Converts the expected item to a human-readable string. Retained for
122
+ # pre-3.3 compatibility.
123
+ def to_word expected_item
124
+ case
125
+ when is_matcher_with_description?(expected_item)
126
+ expected_item.description
127
+ when Proc === expected_item
128
+ "an item matching the block"
129
+ else
130
+ expected_item.inspect
131
+ end # case
132
+ end # method to_word
133
+ end # class
134
+ end # module