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,96 @@
1
+ # subl lib/rspec/sleeping_king_studios/matchers/core/have_reader_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_property'
6
+
7
+ module RSpec::SleepingKingStudios::Matchers::Core
8
+ # Matcher for testing whether an object has a specific property reader, e.g.
9
+ # responds to :property.
10
+ #
11
+ # @since 1.0.0
12
+ class HaveReaderMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
+ include RSpec::SleepingKingStudios::Matchers::Shared::MatchProperty
14
+
15
+ # (see BaseMatcher#description)
16
+ def description
17
+ value_message = value_to_string
18
+ "have reader :#{@expected}#{@value_set ? " with value #{value_message}" : ''}"
19
+ end # method description
20
+
21
+ # @param [String, Symbol] expected The property to check for on the actual
22
+ # object.
23
+ def initialize expected
24
+ @expected = expected.intern
25
+ end # method initialize
26
+
27
+ # (see BaseMatcher#does_not_match?)
28
+ def does_not_match? actual
29
+ super
30
+
31
+ matches_reader?(:none?)
32
+ end # method does_not_match?
33
+
34
+ # Checks if the object responds to #expected. Additionally, if a value
35
+ # expectation is set, compares the value of #expected to the specified
36
+ # value.
37
+ #
38
+ # @param [Object] actual The object to check.
39
+ #
40
+ # @return [Boolean] true If the object responds to #expected and matches
41
+ # the value expectation (if any); otherwise false.
42
+ def matches? actual
43
+ super
44
+
45
+ matches_reader?(:all?)
46
+ end # method matches?
47
+
48
+ # Sets a value expectation. The matcher will compare the value from
49
+ # #property with the specified value.
50
+ #
51
+ # @param [Object] value The value to compare.
52
+ #
53
+ # @return [HaveReaderMatcher] self
54
+ def with value
55
+ @value = value
56
+ @value_set = true
57
+ self
58
+ end # method with
59
+ alias_method :with_value, :with
60
+
61
+ # (see BaseMatcher#failure_message)
62
+ def failure_message
63
+ message = "expected #{@actual.inspect} to respond to :#{@expected}"
64
+ message << " and return #{value_to_string}" if @value_set
65
+
66
+ if !@matches_reader
67
+ message << ", but did not respond to :#{@expected}"
68
+ elsif !@matches_reader_value
69
+ message << ", but returned #{@actual.send(@expected).inspect}"
70
+ end # if
71
+
72
+ message
73
+ end # method failure_message
74
+
75
+ # (see BaseMatcher#failure_message_when_negated)
76
+ def failure_message_when_negated
77
+ message = "expected #{@actual.inspect} not to respond to :#{@expected}"
78
+ message << " and return #{value_to_string}" if @value_set
79
+
80
+ errors = []
81
+ errors << "responded to :#{@expected}" if @matches_reader
82
+ errors << "returned #{@actual.send(@expected).inspect}" if @matches_reader_value
83
+
84
+ message << ", but #{errors.join(" and ")}"
85
+ message
86
+ end # method failure_message
87
+
88
+ private
89
+
90
+ def matches_reader? filter
91
+ [ responds_to_reader?,
92
+ matches_reader_value?
93
+ ].send(filter) { |bool| bool }
94
+ end # method matches_property?
95
+ end # class
96
+ end # module
@@ -1,64 +1,9 @@
1
- # lib/rspec/sleeping_king_studios/matchers/core/have_mutator.rb
1
+ # lib/rspec/sleeping_king_studios/matchers/core/have_writer.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_property'
3
+ require 'rspec/sleeping_king_studios/matchers/core/have_writer_matcher'
4
+ require 'rspec/sleeping_king_studios/matchers/macros'
6
5
 
7
- module RSpec::SleepingKingStudios::Matchers::Core
8
- # Matcher for testing whether an object has a specific property writer, e.g.
9
- # responds to :property= and updates the state.
10
- #
11
- # @since 1.0.0
12
- class HaveWriterMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
- include RSpec::SleepingKingStudios::Matchers::Shared::MatchProperty
14
-
15
- # Generates a description of the matcher expectation.
16
- #
17
- # @return [String] The matcher description.
18
- def description
19
- "have writer :#{@expected}"
20
- end # method description
21
-
22
- # @param [String, Symbol] expected the property to check for on the actual
23
- # object
24
- def initialize expected
25
- @expected = expected.to_s.gsub(/=$/,'').intern
26
- end # method initialize
27
-
28
- # Checks if the object responds to :expected=. Additionally, if a value
29
- # expectation is set, assigns the value via :expected= and compares the
30
- # subsequent value to the specified value using :expected or the block
31
- # provided to #with.
32
- #
33
- # @param [Object] actual the object to check
34
- #
35
- # @return [Boolean] true if the object responds to :expected= and matches
36
- # the value expectation (if any); otherwise false
37
- def matches? actual
38
- super
39
-
40
- responds_to_writer?
41
- end # method matches?
42
-
43
- # @see BaseMatcher#failure_message
44
- def failure_message
45
- message = "expected #{@actual.inspect} to respond to :#{@expected}="
46
-
47
- if !@matches_writer
48
- message << ", but did not respond to :#{@expected}="
49
- end # if
50
-
51
- message
52
- end # method failure_message
53
-
54
- # @see BaseMatcher#failure_message_when_negated
55
- def failure_message_when_negated
56
- "expected #{@actual.inspect} not to respond to :#{@expected}="
57
- end # method failure_message
58
- end # class
59
- end # module
60
-
61
- module RSpec::SleepingKingStudios::Matchers
6
+ module RSpec::SleepingKingStudios::Matchers::Macros
62
7
  # @see RSpec::SleepingKingStudios::Matchers::Core::HaveWriterMatcher#matches?
63
8
  def have_writer expected
64
9
  RSpec::SleepingKingStudios::Matchers::Core::HaveWriterMatcher.new expected
@@ -0,0 +1,55 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/core/have_writer_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_property'
6
+
7
+ module RSpec::SleepingKingStudios::Matchers::Core
8
+ # Matcher for testing whether an object has a specific property writer, e.g.
9
+ # responds to :property= and updates the state.
10
+ #
11
+ # @since 1.0.0
12
+ class HaveWriterMatcher < RSpec::SleepingKingStudios::Matchers::BaseMatcher
13
+ include RSpec::SleepingKingStudios::Matchers::Shared::MatchProperty
14
+
15
+ # Generates a description of the matcher expectation.
16
+ #
17
+ # @return [String] The matcher description.
18
+ def description
19
+ "have writer :#{@expected}"
20
+ end # method description
21
+
22
+ # @param [String, Symbol] expected the property to check for on the actual
23
+ # object
24
+ def initialize expected
25
+ @expected = expected.to_s.gsub(/=$/,'').intern
26
+ end # method initialize
27
+
28
+ # Checks if the object responds to :expected=. Additionally, if a value
29
+ # expectation is set, assigns the value via :expected= and compares the
30
+ # subsequent value to the specified value using :expected or the block
31
+ # provided to #with.
32
+ #
33
+ # @param [Object] actual the object to check
34
+ #
35
+ # @return [Boolean] true if the object responds to :expected= and matches
36
+ # the value expectation (if any); otherwise false
37
+ def matches? actual
38
+ super
39
+
40
+ responds_to_writer?
41
+ end # method matches?
42
+
43
+ # @see BaseMatcher#failure_message
44
+ def failure_message
45
+ "expected #{@actual.inspect} to respond to :#{@expected}="\
46
+ ", but did not respond to :#{@expected}="
47
+ end # method failure_message
48
+
49
+ # @see BaseMatcher#failure_message_when_negated
50
+ def failure_message_when_negated
51
+ "expected #{@actual.inspect} not to respond to :#{@expected}="\
52
+ ", but responded to :#{@expected}="
53
+ end # method failure_message
54
+ end # class
55
+ end # module
@@ -0,0 +1,62 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/description.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers'
4
+
5
+ require 'sleeping_king_studios/tools/array_tools'
6
+ require 'sleeping_king_studios/tools/string_tools'
7
+
8
+ module RSpec::SleepingKingStudios::Matchers
9
+ # Reusable logic for building a matcher description across different versions
10
+ # of the base RSpec library.
11
+ #
12
+ # @since 2.2.0
13
+ module Description
14
+ # @api private
15
+ DEFAULT_EXPECTED_ITEMS = Object.new
16
+
17
+ # A short string that describes the purpose of the matcher.
18
+ #
19
+ # @return [String] the matcher description
20
+ def description
21
+ desc = matcher_name
22
+
23
+ desc << format_expected_items
24
+
25
+ desc
26
+ end # method description
27
+
28
+ private
29
+
30
+ def expected_items_for_description
31
+ defined?(@expected) ? @expected : DEFAULT_EXPECTED_ITEMS
32
+ end # method expected_items_for_description
33
+
34
+ def format_expected_items
35
+ expected_items = expected_items_for_description
36
+
37
+ return '' if expected_items == DEFAULT_EXPECTED_ITEMS
38
+
39
+ if defined?(RSpec::Matchers::EnglishPhrasing)
40
+ # RSpec 3.4+
41
+ RSpec::Matchers::EnglishPhrasing.list(expected_items)
42
+ elsif defined?(to_sentence)
43
+ # RSpec 3.0-3.3
44
+ to_sentence(expected_items)
45
+ else
46
+ array_tools = ::SleepingKingStudios::Tools::ArrayTools
47
+ processed = [expected_items].flatten.map(&:inspect)
48
+
49
+ ' ' << array_tools.humanize_list(processed)
50
+ end # if-elsif-else
51
+ end # method format_expected_items
52
+
53
+ def matcher_name
54
+ return @matcher_name if @matcher_name
55
+
56
+ string_tools = ::SleepingKingStudios::Tools::StringTools
57
+ name = string_tools.underscore(self.class.name.split('::').last)
58
+
59
+ @matcher_name = name.tr('_', ' ').sub(/ matcher\z/, '')
60
+ end # method matcher_name
61
+ end # module
62
+ end # module
@@ -0,0 +1,32 @@
1
+ # lib/rspec/sleeping_king_studios/matchers/macros.rb
2
+
3
+ require 'rspec/sleeping_king_studios/matchers'
4
+
5
+ module RSpec::SleepingKingStudios::Matchers
6
+ module Macros
7
+ # @see RSpec::Matchers::alias_matcher
8
+ def self.alias_matcher(new_name, old_name, options = {}, &description_override)
9
+ description_override ||= if defined?(RSpec::Matchers::Pretty)
10
+ ->(str) { str.gsub(RSpec::Matchers::Pretty.split_words(old_name), RSpec::Matchers::Pretty.split_words(new_name)) }
11
+ elsif defined?(RSpec::Matchers::EnglishPhrasing)
12
+ ->(str) { str.gsub(RSpec::Matchers::EnglishPhrasing.split_words(old_name), RSpec::Matchers::EnglishPhrasing.split_words(new_name)) }
13
+ else
14
+ ->(str) { str }
15
+ end # if-elsif-else
16
+
17
+ klass = (options.is_a?(Hash) ? options[:klass] : nil) || RSpec::Matchers::AliasedMatcher
18
+ define_method(new_name) do |*args, &block|
19
+ matcher = __send__(old_name, *args, &block)
20
+
21
+ klass.new(matcher, description_override)
22
+ end # define_method
23
+ end # class method alias_matcher
24
+ end # module
25
+
26
+ # Ensure macros are defined on parent module for compatibility reasons.
27
+ include Macros
28
+ end # module
29
+
30
+ RSpec.configure do |config|
31
+ config.include RSpec::SleepingKingStudios::Matchers::Macros
32
+ end # configuration
@@ -1,89 +1,191 @@
1
1
  # lib/rspec/sleeping_king_studios/matchers/shared/parameters_matcher.rb
2
2
 
3
3
  require 'rspec/sleeping_king_studios/matchers'
4
+ require 'sleeping_king_studios/tools/array_tools'
5
+ require 'rspec/sleeping_king_studios/support/method_signature_expectation'
4
6
 
5
7
  module RSpec::SleepingKingStudios::Matchers::Shared
6
8
  # Helper methods for checking the parameters and keywords (Ruby 2.0 only) of
7
9
  # a method.
8
10
  module MatchParameters
9
- # Checks whether the method accepts the specified number or range of
10
- # arguments.
11
+ # Convenience method for more fluent specs. Does nothing and returns self.
11
12
  #
12
- # @param [Method] method the method to check
13
- # @param [Integer, Range] arity the expected number or range of parameters
13
+ # @return self
14
+ def argument
15
+ self
16
+ end # method argument
17
+ alias_method :arguments, :argument
18
+
19
+ # Adds a parameter count expectation and/or one or more keyword
20
+ # expectations.
14
21
  #
15
- # @return [Boolean] true if the method accepts the specified number or both
16
- # the specified minimum and maximum number of parameters; otherwise false
17
- def check_method_arity method, arity, expect_unlimited_arguments: false
18
- parameters = method.parameters
19
- required = parameters.count { |type, | :req == type }
20
- optional = parameters.count { |type, | :opt == type }
21
- variadic = parameters.count { |type, | :rest == type }
22
- reasons = {}
23
-
24
- reasons[:expected_unlimited_arguments] = { count: required + optional } if 0 == variadic && expect_unlimited_arguments
25
-
26
- min, max = arity.is_a?(Range) ?
27
- [arity.begin, arity.end] :
28
- [arity, arity]
29
-
30
- if min && min < required
31
- reasons[:not_enough_args] = { arity: min, count: required }
32
- elsif max && 0 == variadic && max > required + optional
33
- reasons[:too_many_args] = { arity: max, count: required + optional }
34
- end # if
22
+ # @overload with count
23
+ # Adds a parameter count expectation.
24
+ #
25
+ # @param [Integer, Range, nil] count (optional) The number of expected
26
+ # parameters.
27
+ #
28
+ # @return [RespondToMatcher] self
29
+ # @overload with *keywords
30
+ # Adds one or more keyword expectations.
31
+ #
32
+ # @param [Array<String, Symbol>] keywords List of keyword arguments
33
+ # accepted by the method.
34
+ #
35
+ # @return self
36
+ # @overload with count, *keywords
37
+ # Adds a parameter count expectation and one or more keyword
38
+ # expectations.
39
+ #
40
+ # @param [Integer, Range, nil] count (optional) The number of expected
41
+ # parameters.
42
+ # @param [Array<String, Symbol>] keywords List of keyword arguments
43
+ # accepted by the method.
44
+ #
45
+ # @return self
46
+ def with *keywords
47
+ case keywords.first
48
+ when Range
49
+ arity = keywords.shift
50
+
51
+ method_signature_expectation.min_arguments = arity.begin
52
+ method_signature_expectation.max_arguments = arity.end
53
+ when Integer
54
+ arity = keywords.shift
55
+
56
+ method_signature_expectation.min_arguments = arity
57
+ method_signature_expectation.max_arguments = arity
58
+ end # case
35
59
 
36
- reasons.empty? ? nil : reasons
37
- end # method check_method_arity
60
+ method_signature_expectation.keywords = keywords
38
61
 
39
- # Checks whether the method accepts the specified keywords.
62
+ self
63
+ end # method with
64
+
65
+ # Adds a block expectation. The actual object will only match a block
66
+ # expectation if it expects a parameter of the form &block.
40
67
  #
41
- # @param [Method] method the method to check
42
- # @param [Array<String, Symbol>] keywords the expected keywords
68
+ # @return self
69
+ def with_a_block
70
+ method_signature_expectation.block_argument = true
71
+
72
+ self
73
+ end # method with_a_block
74
+ alias_method :and_a_block, :with_a_block
75
+
76
+ # Adds an arbitrary keyword expectation, e.g. that the method supports
77
+ # any keywords with splatted hash arguments of the form **kwargs.
78
+ def with_arbitrary_keywords
79
+ method_signature_expectation.any_keywords = true
80
+
81
+ self
82
+ end # method with_arbitrary_keywords
83
+ alias_method :and_arbitrary_keywords, :with_arbitrary_keywords
84
+ alias_method :with_any_keywords, :with_arbitrary_keywords
85
+ alias_method :and_any_keywords, :with_any_keywords
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 self
93
+ def with_keywords *keywords
94
+ method_signature_expectation.keywords = keywords
95
+
96
+ self
97
+ end # method with_keywords
98
+ alias_method :and_keywords, :with_keywords
99
+
100
+ # Adds an unlimited parameter count expectation, e.g. that the method
101
+ # supports splatted array arguments of the form *args.
43
102
  #
44
- # @return [Boolean] true if the method accepts the specified keywords;
45
- # otherwise false
46
- def check_method_keywords method, keywords, expect_arbitrary_keywords: false
47
- keywords ||= []
48
- parameters = method.parameters
49
- reasons = {}
50
-
51
- # Check for missing required keywords.
52
- if RUBY_VERSION >= "2.1.0"
53
- missing = []
54
- parameters.select { |type, _| :keyreq == type }.each do |_, keyword|
55
- missing << keyword unless keywords.include?(keyword)
56
- end # each
57
-
58
- reasons[:missing_keywords] = missing unless missing.empty?
103
+ # @return self
104
+ def with_unlimited_arguments
105
+ method_signature_expectation.unlimited_arguments = true
106
+
107
+ self
108
+ end # method with_unlimited_arguments
109
+ alias_method :and_unlimited_arguments, :with_unlimited_arguments
110
+
111
+ private
112
+
113
+ # @api private
114
+ def check_method_signature method
115
+ method_signature_expectation.matches?(method)
116
+ end # method check_method_signature
117
+
118
+ # @api private
119
+ def format_errors errors
120
+ messages = []
121
+
122
+ # TODO: Replace this with " expected :arity arguments, but method can "\
123
+ # "receive :count arguments", with :count being one of the following:
124
+ # - an integer (for methods that do not have optional or variadic params)
125
+ # - between :min and :max (for methods with optional but not variadic
126
+ # params)
127
+ # - at least :min (for methods with variadic params)
128
+ if hsh = errors.fetch(:not_enough_args, false)
129
+ messages << " expected at least #{hsh[:expected]} arguments, but received #{hsh[:received]}"
59
130
  end # if
60
131
 
61
- unless 0 < parameters.count { |type, _| :keyrest == type }
62
- reasons[:expected_arbitrary_keywords] = true if expect_arbitrary_keywords
132
+ if hsh = errors.fetch(:too_many_args, false)
133
+ messages << " expected at most #{hsh[:expected]} arguments, but received #{hsh[:received]}"
134
+ end # if
63
135
 
64
- mismatch = []
65
- keywords.each do |keyword|
66
- mismatch << keyword unless
67
- parameters.include?([:key, keyword]) ||
68
- parameters.include?([:keyreq, keyword])
69
- end # each
136
+ # TODO: Replace this with " expected method to receive unlimited "\
137
+ # "arguments, but method can receive at most :max arguments"
138
+ if hsh = errors.fetch(:no_variadic_args, false)
139
+ messages << " expected at most #{hsh[:expected]} arguments, but received unlimited arguments"
140
+ end # if
70
141
 
71
- reasons[:unexpected_keywords] = mismatch unless mismatch.empty?
72
- end # unless
142
+ # TODO: Replace this with " expected method to receive arbitrary "\
143
+ # "keywords, but the method can receive :keyword_list", with
144
+ # :keyword_list being a comma-separated list. If the method cannot
145
+ # receive keywords, replace last fragment with ", but the method cannot"\
146
+ # " receive keywords"
147
+ if errors.fetch(:no_variadic_keywords, false)
148
+ messages << " expected arbitrary keywords"
149
+ end # if
73
150
 
74
- reasons.empty? ? nil : reasons
75
- end # method check_method_keywords
151
+ # TODO: Replace this with " expected method to receive keywords "\
152
+ # ":received_list, but the method requires keywords :required_list"
153
+ if ary = errors.fetch(:missing_keywords, false)
154
+ tools = ::SleepingKingStudios::Tools::ArrayTools
76
155
 
77
- # Checks whether the method expects a block.
78
- #
79
- # @param [Method] method the method to check
80
- #
81
- # @return [Boolean] true if the method expects a block argument; otherwise
82
- # false
83
- def check_method_block method
84
- 0 == method.parameters.count { |type, | :block == type } ? { :expected_block => true } : nil
85
- end # method check_method_block
156
+ messages <<
157
+ " missing keyword#{ary.count == 1 ? '' : 's'} "\
158
+ "#{tools.humanize_list ary.map(&:inspect)}"
159
+ end # if
160
+
161
+ # TODO: Replace this with " expected method to receive keywords "\
162
+ # ":received_list, but the method can receive :keyword_list"
163
+ if ary = errors.fetch(:unexpected_keywords, false)
164
+ tools = ::SleepingKingStudios::Tools::ArrayTools
165
+
166
+ messages <<
167
+ " unexpected keyword#{ary.count == 1 ? '' : 's'} "\
168
+ "#{tools.humanize_list ary.map(&:inspect)}"
169
+ end # if
170
+
171
+ # TODO: Replace this with " expected method to receive a block "\
172
+ # "argument, but the method signature does not specify a block argument"
173
+ if errors.fetch(:no_block_argument, false)
174
+ messages << " unexpected block"
175
+ end # if
176
+
177
+ messages.join "\n"
178
+ end # method format_errors
179
+
180
+ # @api private
181
+ def method_signature_expectation
182
+ @method_signature_expectation ||=
183
+ ::RSpec::SleepingKingStudios::Support::MethodSignatureExpectation.new
184
+ end # method_signature_expectation
86
185
 
87
- private :check_method_arity, :check_method_block, :check_method_keywords
186
+ # @api private
187
+ def method_signature_expectation?
188
+ !!@method_signature_expectation
189
+ end # method_signature_expectation
88
190
  end # module
89
191
  end # module
@@ -5,6 +5,23 @@ module RSpec::SleepingKingStudios::Matchers::Shared
5
5
  module MatchProperty
6
6
  private
7
7
 
8
+ # Checks whether the value of the predicate matches the expected value. If
9
+ # the value looks like an RSpec matcher (it responds to :matches?), runs
10
+ # value.matches?(); otherwise checks for equality using :==.
11
+ #
12
+ # @return [Boolean] true if the value matches the expected value; otherwise
13
+ # false.
14
+ def matches_predicate_value?
15
+ return false unless responds_to_predicate?
16
+ return true unless @value_set
17
+
18
+ actual_value = @actual.send(:"#{@expected}?")
19
+
20
+ @matches_predicate_value = (@value.respond_to?(:matches?) && @value.respond_to?(:description)) ?
21
+ @value.matches?(actual_value) :
22
+ @value == actual_value
23
+ end # method matches_reader_value?
24
+
8
25
  # Checks whether the value of the reader matches the expected value. If the
9
26
  # value looks like an RSpec matcher (it responds to :matches?), runs
10
27
  # value.matches?(); otherwise checks for equality using :==.
@@ -22,6 +39,14 @@ module RSpec::SleepingKingStudios::Matchers::Shared
22
39
  @value == actual_value
23
40
  end # method matches_reader_value?
24
41
 
42
+ # Checks whether the object responds to the predicate method :#{property}?.
43
+ #
44
+ # @return [Boolean] true if the object responds to the method; otherwise
45
+ # false.
46
+ def responds_to_predicate?
47
+ @matches_predicate = @actual.respond_to?(:"#{@expected}?")
48
+ end # method responds_to_predicate?
49
+
25
50
  # Checks whether the object responds to the reader method :#{property}.
26
51
  #
27
52
  # @return [Boolean] true if the object responds to the method; otherwise
@@ -6,7 +6,3 @@ module RSpec::SleepingKingStudios
6
6
  # Custom matchers for use with RSpec::Expectations.
7
7
  module Matchers; end
8
8
  end # module
9
-
10
- RSpec.configure do |config|
11
- config.include RSpec::SleepingKingStudios::Matchers
12
- end # configuration
@@ -0,0 +1,51 @@
1
+ # lib/rspec/sleeping_king_studios/support/method_signature.rb
2
+
3
+ require 'rspec/sleeping_king_studios/support'
4
+
5
+ module RSpec::SleepingKingStudios::Support
6
+ # @api private
7
+ class MethodSignature
8
+ def initialize method
9
+ parameters = method.parameters
10
+
11
+ required = parameters.count { |type, _| :req == type }
12
+ optional = parameters.count { |type, _| :opt == type }
13
+ variadic = parameters.count { |type, _| :rest == type }
14
+
15
+ @min_arguments = required
16
+ @max_arguments = required + optional
17
+ @unlimited_arguments = variadic > 0
18
+
19
+ required = parameters.select { |type, _| :keyreq == type }.map { |_, keyword| keyword }
20
+ optional = parameters.select { |type, _| :key == type }.map { |_, keyword| keyword }
21
+ variadic = parameters.count { |type, _| :keyrest == type }
22
+
23
+ @required_keywords = required
24
+ @optional_keywords = optional
25
+ @any_keywords = variadic > 0
26
+
27
+ @block_argument = parameters.count { |type, _| :block == type } > 0
28
+ end # method initialize
29
+
30
+ attr_reader :min_arguments,
31
+ :max_arguments,
32
+ :optional_keywords,
33
+ :required_keywords
34
+
35
+ def any_keywords?
36
+ !!@any_keywords
37
+ end # method any_keywords?
38
+
39
+ def block_argument?
40
+ !!@block_argument
41
+ end # method block_argument?
42
+
43
+ def keywords
44
+ @optional_keywords + @required_keywords
45
+ end # method keywords
46
+
47
+ def unlimited_arguments?
48
+ !!@unlimited_arguments
49
+ end # method unlimited_arguments?
50
+ end # class
51
+ end # module