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
@@ -1,216 +1,9 @@
1
1
  # spec/rspec/sleeping_king_studios/matchers/core/construct.rb
2
2
 
3
- require 'rspec/sleeping_king_studios/matchers/base_matcher'
4
- require 'rspec/sleeping_king_studios/matchers/core'
5
- require 'rspec/sleeping_king_studios/matchers/shared/match_parameters'
6
- require 'sleeping_king_studios/tools/enumerable_tools'
7
- require 'sleeping_king_studios/tools/string_tools'
3
+ require 'rspec/sleeping_king_studios/matchers/core/construct_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
8
5
 
9
- module RSpec::SleepingKingStudios::Matchers::Core
10
- # Matcher for checking whether an object can be constructed via #new and
11
- # #initialize, and the parameters accepted by #initialize.
12
- #
13
- # @since 1.0.0
14
- class ConstructMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
15
- include RSpec::SleepingKingStudios::Matchers::Shared::MatchParameters
16
- include SleepingKingStudios::Tools::EnumerableTools
17
- include SleepingKingStudios::Tools::StringTools
18
-
19
- # (see BaseMatcher#description)
20
- def description
21
- expected_message = format_expected_arguments
22
- "construct#{expected_message.empty? ? '' : " with #{expected_message}"}"
23
- end # method description
24
-
25
- # Checks if the object responds to :new. If so, allocates an instance and
26
- # checks the parameters expected by #initialize against the expected
27
- # parameters, if any.
28
- #
29
- # @param [Object] actual The object to check.
30
- #
31
- # @return [Boolean] True if the object responds to :new and accepts the
32
- # specified parameters for #initialize; otherwise false.
33
- def matches? actual
34
- @actual = actual
35
- @failing_method_reasons = {}
36
- @actual.respond_to?(:new) &&
37
- matches_arity?(actual) &&
38
- matches_keywords?(actual)
39
- end # method matches?
40
-
41
- # Adds a parameter count expectation and/or one or more keyword
42
- # expectations.
43
- #
44
- # @overload with(count)
45
- # Adds a parameter count expectation and removes any keyword
46
- # expectations.
47
- #
48
- # @param [Integer, Range] count The number of expected parameters.
49
- #
50
- # @return [ConstructMatcher] self
51
- #
52
- # @overload with(count, *keywords)
53
- # Adds a parameter count expectation and one or more keyword
54
- # expectations.
55
- #
56
- # @param [Integer, Range] count The number of expected parameters.
57
- # @param [Array<String, Symbol>] keywords List of keyword arguments
58
- # accepted by the method.
59
- #
60
- # @return [ConstructMatcher] self
61
- #
62
- # @overload with(*keywords)
63
- # Removes a parameter count expectation (if any) and adds one or more
64
- # keyword expectations.
65
- #
66
- # @param [Array<String, Symbol>] keywords List of keyword arguments
67
- # accepted by the method.
68
- #
69
- # @return [ConstructMatcher] self
70
- def with *keywords
71
- @expected_arity = keywords.shift if Integer === keywords.first || Range === keywords.first
72
- @expected_keywords = keywords
73
- self
74
- end # method with
75
-
76
- # Adds an unlimited parameter count expectation, e.g. that the method
77
- # supports splatted array arguments of the form *args.
78
- #
79
- # @return [RespondToMatcher] self
80
- def with_unlimited_arguments
81
- @expect_unlimited_arguments = true
82
-
83
- self
84
- end # method with_unlimited_arguments
85
- alias_method :and_unlimited_arguments, :with_unlimited_arguments
86
-
87
- # Adds one or more keyword expectations.
88
- #
89
- # @param [Array<String, Symbol>] keywords List of keyword arguments
90
- # accepted by the method.
91
- #
92
- # @return [RespondToMatcher] self
93
- def with_keywords *keywords
94
- (@expected_keywords ||= []).concat(keywords)
95
-
96
- self
97
- end # method with_keywords
98
- alias_method :and_keywords, :with_keywords
99
-
100
- # Adds an arbitrary keyword expectation, e.g. that the method supports
101
- # any keywords with splatted hash arguments of the form **kwargs.
102
- def with_arbitrary_keywords
103
- @expect_arbitrary_keywords = true
104
-
105
- self
106
- end # method with_arbitrary_keywords
107
- alias_method :and_arbitrary_keywords, :with_arbitrary_keywords
108
-
109
- # Convenience method for more fluent specs. Does nothing and returns self.
110
- #
111
- # @return [ConstructMatcher] self
112
- #
113
- # @since 2.0.0
114
- def argument
115
- self
116
- end # method argument
117
- alias_method :arguments, :argument
118
-
119
- # (see BaseMatcher#failure_message)
120
- def failure_message
121
- message = "expected #{@actual.inspect} to be constructible"
122
- message << " with arguments:\n#{format_errors}" if @actual.respond_to?(:new)
123
- message
124
- end # method failure_message
125
-
126
- # (see BaseMatcher#failure_message_when_negated)
127
- def failure_message_when_negated
128
- message = "expected #{@actual.inspect} not to be constructible"
129
- unless (formatted = format_expected_arguments).empty?
130
- message << " with #{formatted}"
131
- end # unless
132
- message
133
- end # method failure_message_when_negated
134
-
135
- private
136
-
137
- def matches_arity? actual
138
- return true unless @expected_arity || @expect_unlimited_arguments
139
-
140
- if result = check_method_arity(actual.allocate.method(:initialize), @expected_arity, expect_unlimited_arguments: @expect_unlimited_arguments)
141
- @failing_method_reasons.update result
142
- return false
143
- end # if
144
-
145
- true
146
- end # method matches_arity?
147
-
148
- def matches_keywords? actual
149
- return true unless @expected_keywords ||
150
- @expect_arbitrary_keywords ||
151
- (@expected_arity && RUBY_VERSION >= "2.1.0")
152
-
153
- if result = check_method_keywords(actual.allocate.method(:initialize), @expected_keywords, expect_arbitrary_keywords: @expect_arbitrary_keywords)
154
- @failing_method_reasons.update result
155
- return false
156
- end # if
157
-
158
- true
159
- end # method matches_keywords?
160
-
161
- def format_expected_arguments
162
- messages = []
163
-
164
- if !@expected_arity.nil?
165
- messages << "#{@expected_arity.inspect} argument#{1 == @expected_arity ? "" : "s"}"
166
- end # if
167
-
168
- if @expect_unlimited_arguments
169
- messages << 'unlimited arguments'
170
- end # if
171
-
172
- if !(@expected_keywords.nil? || @expected_keywords.empty?)
173
- messages << "#{pluralize @expected_keywords.count, 'keyword', 'keywords'} #{humanize_list @expected_keywords.map(&:inspect)}"
174
- end # if
175
-
176
- if @expect_arbitrary_keywords
177
- messages << 'arbitrary keywords'
178
- end # if
179
-
180
- humanize_list messages
181
- end # method format_expected_arguments
182
-
183
- def format_errors
184
- reasons, messages = @failing_method_reasons, []
185
-
186
- if hsh = reasons.fetch(:not_enough_args, false)
187
- messages << " expected at least #{hsh[:count]} arguments, but received #{hsh[:arity]}"
188
- elsif hsh = reasons.fetch(:too_many_args, false)
189
- messages << " expected at most #{hsh[:count]} arguments, but received #{hsh[:arity]}"
190
- end # if-elsif
191
-
192
- if hsh = reasons.fetch(:expected_unlimited_arguments, false)
193
- messages << " expected at most #{hsh[:count]} arguments, but received unlimited arguments"
194
- end # if
195
-
196
- if ary = reasons.fetch(:missing_keywords, false)
197
- messages << " missing #{pluralize ary.count, 'keyword', 'keywords'} #{humanize_list ary.map(&:inspect)}"
198
- end # if
199
-
200
- if ary = reasons.fetch(:unexpected_keywords, false)
201
- messages << " unexpected #{pluralize ary.count, 'keyword', 'keywords'} #{humanize_list ary.map(&:inspect)}"
202
- end # if
203
-
204
- if reasons.fetch(:expected_arbitrary_keywords, false)
205
- messages << " expected arbitrary keywords"
206
- end # if
207
-
208
- messages.join "\n"
209
- end # method format_errors
210
- end # class
211
- end # module
212
-
213
- module RSpec::SleepingKingStudios::Matchers
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
214
7
  # @see RSpec::SleepingKingStudios::Matchers::Core::ConstructMatcher#matches?
215
8
  def construct
216
9
  RSpec::SleepingKingStudios::Matchers::Core::ConstructMatcher.new
@@ -0,0 +1,113 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/core/construct_matcher.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/base_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/core'
5
+ require 'rspec/sleeping_king_studios/matchers/shared/match_parameters'
6
+
7
+ module RSpec::SleepingKingStudios::Matchers::Core
8
+ # Matcher for checking whether an object can be constructed via #new and
9
+ # #initialize, and the parameters accepted by #initialize.
10
+ #
11
+ # @since 1.0.0
12
+ class ConstructMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
+ include RSpec::SleepingKingStudios::Matchers::Shared::MatchParameters
14
+
15
+ # (see BaseMatcher#description)
16
+ def description
17
+ message = 'construct'
18
+
19
+ if method_signature_expectation?
20
+ message << ' ' << method_signature_expectation.description
21
+ end # if
22
+
23
+ message
24
+ end # method description
25
+
26
+ # Checks if the object responds to :new. If so, allocates an instance and
27
+ # checks the parameters expected by #initialize against the expected
28
+ # parameters, if any.
29
+ #
30
+ # @param [Object] actual The object to check.
31
+ #
32
+ # @return [Boolean] True if the object responds to :new and accepts the
33
+ # specified parameters for #initialize; otherwise false.
34
+ def matches? actual
35
+ @actual = actual
36
+ @failing_method_reasons = {}
37
+
38
+ unless @actual.respond_to?(:new, @include_all)
39
+ @failing_method_reasons = {
40
+ :does_not_respond_to_new => true
41
+ } # end hash
42
+
43
+ return false
44
+ end # unless
45
+
46
+ instance =
47
+ begin; actual.allocate; rescue NoMethodError; nil; end ||
48
+ begin; actual.new; rescue StandardError; nil; end
49
+
50
+ if instance.nil?
51
+ @failing_method_reasons = {
52
+ :unable_to_create_instance => true
53
+ } # end hash
54
+
55
+ return false
56
+ end # unless
57
+
58
+ constructor =
59
+ begin; instance.method(:initialize); rescue NameError; nil; end
60
+
61
+ unless constructor.is_a?(Method)
62
+ @failing_method_reasons = {
63
+ :constructor_is_not_a_method => true
64
+ } # end hash
65
+
66
+ return false
67
+ end # unless
68
+
69
+ return true unless method_signature_expectation?
70
+
71
+ unless check_method_signature(constructor)
72
+ @failing_method_reasons = method_signature_expectation.errors
73
+
74
+ return false
75
+ end # unless
76
+
77
+ true
78
+ end # method matches?
79
+
80
+ # (see BaseMatcher#failure_message)
81
+ def failure_message
82
+ message = "expected #{@actual.inspect} to be constructible"
83
+
84
+ if @failing_method_reasons.key?(:does_not_respond_to_new)
85
+ message << ", but #{@actual.inspect} does not respond to ::new"
86
+ elsif @failing_method_reasons.key?(:unable_to_create_instance)
87
+ message << ", but was unable to allocate an instance of #{@actual.inspect} with ::allocate or ::new"
88
+ elsif @failing_method_reasons.key?(:constructor_is_not_a_method)
89
+ message <<
90
+ ", but was unable to reflect on constructor because :initialize is not a method on #{@actual.inspect}"
91
+ else
92
+ errors = @failing_method_reasons
93
+
94
+ # TODO: Replace this with ", but received arguments did not match "\
95
+ # " method signature:"
96
+ message << " with arguments:\n" << format_errors(errors)
97
+ end # if-elsif-else
98
+
99
+ message
100
+ end # method failure_message
101
+
102
+ # (see BaseMatcher#failure_message_when_negated)
103
+ def failure_message_when_negated
104
+ message = "expected #{@actual.inspect} not to be constructible"
105
+
106
+ if method_signature_expectation?
107
+ message << ' ' << method_signature_expectation.description
108
+ end # if
109
+
110
+ message
111
+ end # method failure_message_when_negated
112
+ end # class
113
+ end # module
@@ -0,0 +1,11 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/core/delegate_method.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/core/delegate_method_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
7
+ # @see RSpec::SleepingKingStudios::Matchers::Core::DelegateMethodMatcher#matches?
8
+ def delegate_method *method_names
9
+ RSpec::SleepingKingStudios::Matchers::Core::DelegateMethodMatcher.new *method_names
10
+ end # method delegate_method
11
+ end # module
@@ -0,0 +1,311 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/core/delegate_method_matcher.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/base_matcher'
4
+
5
+ require 'sleeping_king_studios/tools/array_tools'
6
+
7
+ module RSpec::SleepingKingStudios::Matchers::Core
8
+ # Matcher for testing whether an object delegates a method to the specified
9
+ # other object.
10
+ #
11
+ # @since 2.2.0
12
+ class DelegateMethodMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
+ include RSpec::Mocks::ExampleMethods
14
+
15
+ # @api private
16
+ DEFAULT_EXPECTED_RETURN = Object.new.freeze
17
+
18
+ # @param expected [Array<String, Symbol>] The names of the methods that
19
+ # should be delegated to the target.
20
+ def initialize expected
21
+ @expected = expected
22
+ @expected_arguments = []
23
+ @expected_keywords = {}
24
+ @expected_block = false
25
+ @received_block = false
26
+ @expected_return_values = []
27
+ @received_return_values = []
28
+ @errors = {}
29
+ end # method initialize
30
+
31
+ attr_reader :expected
32
+ alias_method :method_name, :expected
33
+
34
+ # Specifies that the actual method, when called, will return the specified
35
+ # value. If more than one return value is specified, the method will be
36
+ # called one time for each return value, and on the Nth call must return
37
+ # the Nth specified return value.
38
+ #
39
+ # @param return_values [Array<Object>] The expected values to be returned
40
+ # from calling the method on the actual object.
41
+ #
42
+ # @return [DelegateMethodMatcher] self
43
+ def and_return *return_values
44
+ @expected_return_values = return_values
45
+
46
+ self
47
+ end # method and_return
48
+
49
+ # (see BaseMatcher#description)
50
+ def description
51
+ str = 'delegate method'
52
+ str << " :#{@expected}" if @expected
53
+ str << " to #{@target.inspect}" if @target
54
+ str
55
+ end # method description
56
+
57
+ # (see BaseMatcher#failure_message)
58
+ def failure_message
59
+ message =
60
+ "expected #{@actual.inspect} to delegate :#{@expected} to "\
61
+ "#{@target.inspect}"
62
+
63
+ if @errors.key?(:actual_does_not_respond_to)
64
+ message << ", but #{@actual.inspect} does not respond to :#{@expected}"
65
+
66
+ return message
67
+ end # if
68
+
69
+ if @errors.key?(:target_does_not_respond_to)
70
+ message << ", but #{@target.inspect} does not respond to :#{@expected}"
71
+
72
+ return message
73
+ end # if
74
+
75
+ if @errors.key?(:raised_exception)
76
+ exception = @errors[:raised_exception]
77
+
78
+ message << format_arguments << format_return_values <<
79
+ ", but ##{@expected} raised #{exception.class.name}: "\
80
+ "#{exception.message}"
81
+
82
+ return message
83
+ end # if
84
+
85
+ if @errors.key?(:actual_does_not_delegate_to_target)
86
+ message <<
87
+ ", but calling ##{@expected} on the object does not call "\
88
+ "##{@expected} on the delegate"
89
+
90
+ return message
91
+ end # if
92
+
93
+ message << format_arguments << format_return_values
94
+
95
+ if @errors.key?(:unexpected_arguments)
96
+ message << ", but #{@errors[:unexpected_arguments]}"
97
+
98
+ return message
99
+ end # if
100
+
101
+ if @errors.key?(:block_not_received)
102
+ message << ', but the block was not passed to the delegate'
103
+
104
+ return message
105
+ end # if
106
+
107
+ if @errors.key?(:unexpected_return)
108
+ values = @errors[:unexpected_return].map &:inspect
109
+
110
+ message << ', but returned ' <<
111
+ SleepingKingStudios::Tools::ArrayTools.humanize_list(values)
112
+ end # if
113
+
114
+ message
115
+ end # method failure_message
116
+
117
+ # (see BaseMatcher#failure_message_when_negated)
118
+ def failure_message_when_negated
119
+ message =
120
+ "expected #{@actual.inspect} not to delegate :#{@expected} to "\
121
+ "#{@target.inspect}"
122
+
123
+ message << format_arguments << format_return_values
124
+ end # method failure_message_when_negated
125
+
126
+ # (see BaseMatcher#matches?)
127
+ def matches? actual
128
+ super
129
+
130
+ raise ArgumentError.new('must specify a target') if @target.nil?
131
+
132
+ responds_to_method? && delegates_method?
133
+ end # method matches?
134
+
135
+ # Specifies the target object. The expected methods should be delegated
136
+ # from the actual object to the target.
137
+ #
138
+ # @param target [Object] The target object.
139
+ #
140
+ # @return [DelegateMethodMatcher] self
141
+ def to target
142
+ @target = target
143
+
144
+ self
145
+ end # method to
146
+
147
+ # Specifies that a block argument must be passed in to the target when
148
+ # calling the method on the actual object.
149
+ #
150
+ # @return [DelegateMethodMatcher] self
151
+ def with_a_block
152
+ @expected_block = true
153
+
154
+ self
155
+ end # method with_a_block
156
+ alias_method :and_a_block, :with_a_block
157
+
158
+ # Specifies a list of arguments. The provided arguments are passed in to the
159
+ # method call when calling the method on the actual object, and must be
160
+ # passed on to the target.
161
+ #
162
+ # @param arguments [Array<Object>] The arguments to be passed in to the
163
+ # method and which must be forwarded to the target.
164
+ #
165
+ # @return [DelegateMethodMatcher] self
166
+ def with_arguments *arguments
167
+ @expected_arguments = arguments
168
+
169
+ self
170
+ end # method with_arguments
171
+ alias_method :and_arguments, :with_arguments
172
+
173
+ # Specifies a hash of keywords and values. The provided keywords are passed
174
+ # in to the method call when calling the method on the actual object, and
175
+ # must be passed on to the target.
176
+ #
177
+ # @param keywords [Hash] The keywords to be passed in to the
178
+ # method and which must be forwarded to the target.
179
+ #
180
+ # @return [DelegateMethodMatcher] self
181
+ def with_keywords **keywords
182
+ @expected_keywords = keywords
183
+
184
+ self
185
+ end # method with_keywords
186
+ alias_method :and_keywords, :with_keywords
187
+
188
+ private
189
+
190
+ def call_method arguments, expected_return = DEFAULT_EXPECTED_RETURN
191
+ if @expected_block
192
+ @received_block = false
193
+ block = ->(*args, **kwargs, &block) {}
194
+
195
+ return_value = @actual.send(@expected, *arguments, &block)
196
+ else
197
+ return_value = @actual.send(@expected, *arguments)
198
+ end
199
+
200
+ @received_return_values << return_value
201
+
202
+ if @expected_block && !@received_block
203
+ @errors[:block_not_received] = true
204
+ end # if
205
+
206
+ return if expected_return == DEFAULT_EXPECTED_RETURN
207
+
208
+ unless return_value == expected_return
209
+ @errors[:unexpected_return] = @received_return_values
210
+ end # unless
211
+ rescue StandardError => exception
212
+ @errors[:raised_exception] = exception
213
+ end # method call_method
214
+
215
+ def delegates_method?
216
+ stub_target!
217
+
218
+ args = @expected_arguments.dup
219
+ args << @expected_keywords unless @expected_keywords.empty?
220
+
221
+ if @expected_return_values.empty?
222
+ call_method(args)
223
+ else
224
+ @expected_return_values.each do |return_value|
225
+ call_method(args, return_value)
226
+ end # each
227
+ end # if-else
228
+
229
+ matcher = RSpec::Mocks::Matchers::HaveReceived.new(@expected)
230
+ matcher = matcher.with(*args) unless args.empty?
231
+
232
+ unless @expected_return_values.empty?
233
+ matcher = matcher.exactly(@expected_return_values.count).times
234
+ end # unless
235
+
236
+ unless matcher.matches? @target
237
+ if matcher.failure_message =~ /with unexpected arguments/
238
+ @errors[:unexpected_arguments] = matcher.failure_message
239
+ else
240
+ @errors[:actual_does_not_delegate_to_target] = true
241
+ end # if
242
+ end # unless
243
+
244
+ @errors.empty?
245
+ end # method delegates_method?
246
+
247
+ def format_arguments
248
+ fragments = []
249
+
250
+ unless @expected_arguments.empty?
251
+ args = SleepingKingStudios::Tools::ArrayTools.humanize_list(@expected_arguments.map &:inspect)
252
+
253
+ fragments << ('arguments ' << args)
254
+ end # unless
255
+
256
+ unless @expected_keywords.empty?
257
+ kwargs = @expected_keywords.map { |key, value| "#{key.inspect}=>#{value.inspect}" }
258
+ kwargs = SleepingKingStudios::Tools::ArrayTools.humanize_list(kwargs)
259
+
260
+ fragments << ('keywords ' << kwargs)
261
+ end # unless
262
+
263
+ if fragments.empty?
264
+ arguments = ' with no arguments'
265
+ else
266
+ arguments = ' with ' << fragments.join(' and ')
267
+ end # if-else
268
+
269
+ arguments << ' and yield a block' if @expected_block
270
+
271
+ arguments
272
+ end # method format_arguments
273
+
274
+ def format_return_values
275
+ return '' if @expected_return_values.empty?
276
+
277
+ values = @expected_return_values.map &:inspect
278
+
279
+ ' and return ' << SleepingKingStudios::Tools::ArrayTools.humanize_list(values)
280
+ end # method format_return_values
281
+
282
+ def responds_to_method?
283
+ unless @actual.respond_to?(@expected)
284
+ @errors[:actual_does_not_respond_to] = true
285
+ end # unless
286
+
287
+ unless @target.respond_to?(@expected)
288
+ @errors[:target_does_not_respond_to] = true
289
+ end # unless
290
+
291
+ @errors.empty?
292
+ end # method responds_to_methods?
293
+
294
+ def stub_target!
295
+ original_method = @target.method(@expected)
296
+
297
+ receive_matcher = receive(@expected)
298
+ receive_matcher.with(any_args) do |*args, **kwargs, &block|
299
+ @received_block = !!block
300
+
301
+ if kwargs.empty?
302
+ original_method.call(*args, &block)
303
+ else
304
+ original_method.call(*args, **kwargs, &block)
305
+ end # if-else
306
+ end # matcher
307
+
308
+ allow(@target).to receive_matcher
309
+ end # metod stub_target!
310
+ end # class
311
+ end # module
@@ -0,0 +1,16 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/core/have_constant.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers/core/have_constant_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
5
+
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
7
+ # @see RSpec::SleepingKingStudios::Matchers::Core::HaveConstantMatcher#matches?
8
+ def have_constant expected
9
+ RSpec::SleepingKingStudios::Matchers::Core::HaveConstantMatcher.new expected
10
+ end # method have_reader
11
+
12
+ # @see RSpec::SleepingKingStudios::Matchers::Core::HaveConstantMatcher#immutable
13
+ def have_immutable_constant expected
14
+ have_constant(expected).immutable
15
+ end # method have_reader
16
+ end # module