rspec-expectations 2.11.3 → 3.11.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.
Files changed (152) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +1026 -21
  6. data/{License.txt → LICENSE.md} +5 -3
  7. data/README.md +174 -78
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +230 -0
  10. data/lib/rspec/expectations/expectation_target.rb +130 -55
  11. data/lib/rspec/expectations/fail_with.rb +17 -33
  12. data/lib/rspec/expectations/failure_aggregator.rb +212 -0
  13. data/lib/rspec/expectations/handler.rb +163 -29
  14. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  15. data/lib/rspec/expectations/syntax.rb +68 -54
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +59 -24
  18. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  19. data/lib/rspec/matchers/built_in/all.rb +86 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +150 -20
  21. data/lib/rspec/matchers/built_in/be.rb +115 -109
  22. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +16 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +10 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +43 -17
  26. data/lib/rspec/matchers/built_in/change.rb +392 -75
  27. data/lib/rspec/matchers/built_in/compound.rb +290 -0
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/cover.rb +3 -0
  31. data/lib/rspec/matchers/built_in/eq.rb +26 -8
  32. data/lib/rspec/matchers/built_in/eql.rb +19 -8
  33. data/lib/rspec/matchers/built_in/equal.rb +56 -19
  34. data/lib/rspec/matchers/built_in/exist.rb +74 -10
  35. data/lib/rspec/matchers/built_in/has.rb +141 -22
  36. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  37. data/lib/rspec/matchers/built_in/include.rb +175 -20
  38. data/lib/rspec/matchers/built_in/match.rb +95 -1
  39. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  40. data/lib/rspec/matchers/built_in/output.rb +207 -0
  41. data/lib/rspec/matchers/built_in/raise_error.rb +212 -38
  42. data/lib/rspec/matchers/built_in/respond_to.rb +155 -29
  43. data/lib/rspec/matchers/built_in/satisfy.rb +39 -9
  44. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  45. data/lib/rspec/matchers/built_in/throw_symbol.rb +58 -14
  46. data/lib/rspec/matchers/built_in/yield.rb +252 -98
  47. data/lib/rspec/matchers/built_in.rb +47 -33
  48. data/lib/rspec/matchers/composable.rb +171 -0
  49. data/lib/rspec/matchers/dsl.rb +530 -10
  50. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  51. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  52. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  53. data/lib/rspec/matchers/generated_descriptions.rb +15 -10
  54. data/lib/rspec/matchers/matcher_delegator.rb +35 -0
  55. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  56. data/lib/rspec/matchers.rb +604 -252
  57. data.tar.gz.sig +0 -0
  58. metadata +178 -278
  59. metadata.gz.sig +0 -0
  60. data/features/README.md +0 -49
  61. data/features/Upgrade.md +0 -53
  62. data/features/built_in_matchers/README.md +0 -90
  63. data/features/built_in_matchers/be.feature +0 -173
  64. data/features/built_in_matchers/be_within.feature +0 -46
  65. data/features/built_in_matchers/cover.feature +0 -45
  66. data/features/built_in_matchers/end_with.feature +0 -46
  67. data/features/built_in_matchers/equality.feature +0 -145
  68. data/features/built_in_matchers/exist.feature +0 -43
  69. data/features/built_in_matchers/expect_change.feature +0 -59
  70. data/features/built_in_matchers/expect_error.feature +0 -138
  71. data/features/built_in_matchers/have.feature +0 -103
  72. data/features/built_in_matchers/include.feature +0 -121
  73. data/features/built_in_matchers/match.feature +0 -50
  74. data/features/built_in_matchers/operators.feature +0 -221
  75. data/features/built_in_matchers/predicates.feature +0 -128
  76. data/features/built_in_matchers/respond_to.feature +0 -78
  77. data/features/built_in_matchers/satisfy.feature +0 -31
  78. data/features/built_in_matchers/start_with.feature +0 -46
  79. data/features/built_in_matchers/throw_symbol.feature +0 -85
  80. data/features/built_in_matchers/types.feature +0 -114
  81. data/features/built_in_matchers/yield.feature +0 -146
  82. data/features/custom_matchers/access_running_example.feature +0 -53
  83. data/features/custom_matchers/define_diffable_matcher.feature +0 -27
  84. data/features/custom_matchers/define_matcher.feature +0 -340
  85. data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
  86. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
  87. data/features/customized_message.feature +0 -22
  88. data/features/diffing.feature +0 -85
  89. data/features/implicit_docstrings.feature +0 -52
  90. data/features/step_definitions/additional_cli_steps.rb +0 -22
  91. data/features/support/env.rb +0 -5
  92. data/features/syntax_configuration.feature +0 -68
  93. data/features/test_frameworks/test_unit.feature +0 -46
  94. data/lib/rspec/expectations/deprecation.rb +0 -38
  95. data/lib/rspec/expectations/differ.rb +0 -81
  96. data/lib/rspec/expectations/errors.rb +0 -9
  97. data/lib/rspec/expectations/extensions/array.rb +0 -9
  98. data/lib/rspec/expectations/extensions/object.rb +0 -39
  99. data/lib/rspec/expectations/extensions.rb +0 -2
  100. data/lib/rspec/matchers/be_close.rb +0 -9
  101. data/lib/rspec/matchers/built_in/have.rb +0 -108
  102. data/lib/rspec/matchers/built_in/match_array.rb +0 -45
  103. data/lib/rspec/matchers/built_in/start_and_end_with.rb +0 -48
  104. data/lib/rspec/matchers/compatibility.rb +0 -14
  105. data/lib/rspec/matchers/configuration.rb +0 -66
  106. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  107. data/lib/rspec/matchers/matcher.rb +0 -299
  108. data/lib/rspec/matchers/method_missing.rb +0 -12
  109. data/lib/rspec/matchers/operator_matcher.rb +0 -84
  110. data/lib/rspec/matchers/pretty.rb +0 -60
  111. data/lib/rspec-expectations.rb +0 -1
  112. data/spec/rspec/expectations/differ_spec.rb +0 -153
  113. data/spec/rspec/expectations/expectation_target_spec.rb +0 -65
  114. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  115. data/spec/rspec/expectations/fail_with_spec.rb +0 -70
  116. data/spec/rspec/expectations/handler_spec.rb +0 -206
  117. data/spec/rspec/matchers/base_matcher_spec.rb +0 -60
  118. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  119. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -40
  120. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -37
  121. data/spec/rspec/matchers/be_spec.rb +0 -452
  122. data/spec/rspec/matchers/be_within_spec.rb +0 -80
  123. data/spec/rspec/matchers/change_spec.rb +0 -528
  124. data/spec/rspec/matchers/configuration_spec.rb +0 -202
  125. data/spec/rspec/matchers/cover_spec.rb +0 -69
  126. data/spec/rspec/matchers/description_generation_spec.rb +0 -176
  127. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  128. data/spec/rspec/matchers/eq_spec.rb +0 -54
  129. data/spec/rspec/matchers/eql_spec.rb +0 -41
  130. data/spec/rspec/matchers/equal_spec.rb +0 -60
  131. data/spec/rspec/matchers/exist_spec.rb +0 -110
  132. data/spec/rspec/matchers/has_spec.rb +0 -118
  133. data/spec/rspec/matchers/have_spec.rb +0 -461
  134. data/spec/rspec/matchers/include_spec.rb +0 -367
  135. data/spec/rspec/matchers/match_array_spec.rb +0 -124
  136. data/spec/rspec/matchers/match_spec.rb +0 -61
  137. data/spec/rspec/matchers/matcher_spec.rb +0 -434
  138. data/spec/rspec/matchers/matchers_spec.rb +0 -31
  139. data/spec/rspec/matchers/method_missing_spec.rb +0 -24
  140. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -221
  141. data/spec/rspec/matchers/raise_error_spec.rb +0 -344
  142. data/spec/rspec/matchers/respond_to_spec.rb +0 -295
  143. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  144. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -182
  145. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  146. data/spec/rspec/matchers/yield_spec.rb +0 -402
  147. data/spec/spec_helper.rb +0 -27
  148. data/spec/support/classes.rb +0 -56
  149. data/spec/support/in_sub_process.rb +0 -31
  150. data/spec/support/matchers.rb +0 -22
  151. data/spec/support/ruby_version.rb +0 -10
  152. data/spec/support/shared_examples.rb +0 -13
@@ -1,74 +1,78 @@
1
- require 'rspec/matchers/dsl'
2
-
3
1
  module RSpec
4
2
  module Matchers
5
3
  module BuiltIn
6
- class BeTrue < BaseMatcher
7
- def match(_, actual)
8
- !!actual
4
+ # @api private
5
+ # Provides the implementation for `be_truthy`.
6
+ # Not intended to be instantiated directly.
7
+ class BeTruthy < BaseMatcher
8
+ # @api private
9
+ # @return [String]
10
+ def failure_message
11
+ "expected: truthy value\n got: #{actual_formatted}"
9
12
  end
10
13
 
11
- def failure_message_for_should
12
- "expected: true value\n got: #{actual.inspect}"
14
+ # @api private
15
+ # @return [String]
16
+ def failure_message_when_negated
17
+ "expected: falsey value\n got: #{actual_formatted}"
13
18
  end
14
19
 
15
- def failure_message_for_should_not
16
- "expected: non-true value\n got: #{actual.inspect}"
17
- end
18
- end
20
+ private
19
21
 
20
- class BeFalse < BaseMatcher
21
22
  def match(_, actual)
22
- !actual
23
+ !!actual
23
24
  end
25
+ end
24
26
 
25
- def failure_message_for_should
26
- "expected: false value\n got: #{actual.inspect}"
27
+ # @api private
28
+ # Provides the implementation for `be_falsey`.
29
+ # Not intended to be instantiated directly.
30
+ class BeFalsey < BaseMatcher
31
+ # @api private
32
+ # @return [String]
33
+ def failure_message
34
+ "expected: falsey value\n got: #{actual_formatted}"
27
35
  end
28
36
 
29
- def failure_message_for_should_not
30
- "expected: non-false value\n got: #{actual.inspect}"
37
+ # @api private
38
+ # @return [String]
39
+ def failure_message_when_negated
40
+ "expected: truthy value\n got: #{actual_formatted}"
31
41
  end
32
- end
33
42
 
34
- class BeNil < BaseMatcher
43
+ private
44
+
35
45
  def match(_, actual)
36
- actual.nil?
46
+ !actual
37
47
  end
48
+ end
38
49
 
39
- def failure_message_for_should
40
- "expected: nil\n got: #{actual.inspect}"
50
+ # @api private
51
+ # Provides the implementation for `be_nil`.
52
+ # Not intended to be instantiated directly.
53
+ class BeNil < BaseMatcher
54
+ # @api private
55
+ # @return [String]
56
+ def failure_message
57
+ "expected: nil\n got: #{actual_formatted}"
41
58
  end
42
59
 
43
- def failure_message_for_should_not
60
+ # @api private
61
+ # @return [String]
62
+ def failure_message_when_negated
44
63
  "expected: not nil\n got: nil"
45
64
  end
46
- end
47
65
 
48
- class Be < BaseMatcher
49
- def initialize(*args, &block)
50
- @args = args
51
- end
66
+ private
52
67
 
53
68
  def match(_, actual)
54
- !!actual
55
- end
56
-
57
- def failure_message_for_should
58
- "expected #{@actual.inspect} to evaluate to true"
59
- end
60
-
61
- def failure_message_for_should_not
62
- "expected #{@actual.inspect} to evaluate to false"
63
- end
64
-
65
- [:==, :<, :<=, :>=, :>, :===].each do |operator|
66
- define_method operator do |operand|
67
- BeComparedTo.new(operand, operator)
68
- end
69
+ actual.nil?
69
70
  end
71
+ end
70
72
 
71
- private
73
+ # @private
74
+ module BeHelpers
75
+ private
72
76
 
73
77
  def args_to_s
74
78
  @args.empty? ? "" : parenthesize(inspected_args.join(', '))
@@ -79,105 +83,107 @@ module RSpec
79
83
  end
80
84
 
81
85
  def inspected_args
82
- @args.collect{|a| a.inspect}
86
+ @args.map { |a| RSpec::Support::ObjectFormatter.format(a) }
83
87
  end
84
88
 
85
89
  def expected_to_sentence
86
- split_words(@expected)
90
+ EnglishPhrasing.split_words(@expected)
87
91
  end
88
92
 
89
93
  def args_to_sentence
90
- to_sentence(@args)
94
+ EnglishPhrasing.list(@args)
91
95
  end
92
96
  end
93
97
 
94
- class BeComparedTo < Be
95
- def initialize(operand, operator)
96
- @expected, @operator = operand, operator
97
- @args = []
98
- end
99
-
100
- def matches?(actual)
101
- @actual = actual
102
- @actual.send @operator, @expected
103
- end
98
+ # @api private
99
+ # Provides the implementation for `be`.
100
+ # Not intended to be instantiated directly.
101
+ class Be < BaseMatcher
102
+ include BeHelpers
104
103
 
105
- def failure_message_for_should
106
- "expected: #{@operator} #{@expected.inspect}\n got: #{@operator.to_s.gsub(/./, ' ')} #{@actual.inspect}"
104
+ def initialize(*args)
105
+ @args = args
107
106
  end
108
107
 
109
- def failure_message_for_should_not
110
- message = <<-MESSAGE
111
- 'should_not be #{@operator} #{@expected}' not only FAILED,
112
- it is a bit confusing.
113
- MESSAGE
114
-
115
- raise message << ([:===,:==].include?(@operator) ?
116
- "It might be more clearly expressed without the \"be\"?" :
117
- "It might be more clearly expressed in the positive?")
108
+ # @api private
109
+ # @return [String]
110
+ def failure_message
111
+ "expected #{actual_formatted} to evaluate to true"
118
112
  end
119
113
 
120
- def description
121
- "be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
114
+ # @api private
115
+ # @return [String]
116
+ def failure_message_when_negated
117
+ "expected #{actual_formatted} to evaluate to false"
122
118
  end
123
- end
124
119
 
125
- class BePredicate < Be
126
- def initialize(*args, &block)
127
- @expected = parse_expected(args.shift)
128
- @args = args
129
- @block = block
120
+ [:==, :<, :<=, :>=, :>, :===, :=~].each do |operator|
121
+ define_method operator do |operand|
122
+ BeComparedTo.new(operand, operator)
123
+ end
130
124
  end
131
125
 
132
- def matches?(actual)
133
- @actual = actual
134
- begin
135
- return @result = actual.__send__(predicate, *@args, &@block)
136
- rescue NameError => predicate_missing_error
137
- "this needs to be here or rcov will not count this branch even though it's executed in a code example"
138
- end
126
+ private
139
127
 
140
- begin
141
- return @result = actual.__send__(present_tense_predicate, *@args, &@block)
142
- rescue NameError
143
- raise predicate_missing_error
144
- end
128
+ def match(_, actual)
129
+ !!actual
145
130
  end
131
+ end
146
132
 
147
- def failure_message_for_should
148
- "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
149
- end
133
+ # @api private
134
+ # Provides the implementation of `be <operator> value`.
135
+ # Not intended to be instantiated directly.
136
+ class BeComparedTo < BaseMatcher
137
+ include BeHelpers
150
138
 
151
- def failure_message_for_should_not
152
- "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}"
139
+ def initialize(operand, operator)
140
+ @expected = operand
141
+ @operator = operator
142
+ @args = []
153
143
  end
154
144
 
155
- def description
156
- "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
145
+ def matches?(actual)
146
+ perform_match(actual)
147
+ rescue ArgumentError, NoMethodError
148
+ false
157
149
  end
158
150
 
159
- private
160
-
161
- def predicate
162
- "#{@expected}?".to_sym
151
+ def does_not_match?(actual)
152
+ !perform_match(actual)
153
+ rescue ArgumentError, NoMethodError
154
+ false
163
155
  end
164
156
 
165
- def present_tense_predicate
166
- "#{@expected}s?".to_sym
157
+ # @api private
158
+ # @return [String]
159
+ def failure_message
160
+ "expected: #{@operator} #{expected_formatted}\n" \
161
+ " got: #{@operator.to_s.gsub(/./, ' ')} #{actual_formatted}"
167
162
  end
168
163
 
169
- def parse_expected(expected)
170
- @prefix, expected = prefix_and_expected(expected)
171
- expected
164
+ # @api private
165
+ # @return [String]
166
+ def failure_message_when_negated
167
+ message = "`expect(#{actual_formatted}).not_to " \
168
+ "be #{@operator} #{expected_formatted}`"
169
+ if [:<, :>, :<=, :>=].include?(@operator)
170
+ message + " not only FAILED, it is a bit confusing."
171
+ else
172
+ message
173
+ end
172
174
  end
173
175
 
174
- def prefix_and_expected(symbol)
175
- symbol.to_s =~ /^(be_(an?_)?)(.*)/
176
- return $1, $3
176
+ # @api private
177
+ # @return [String]
178
+ def description
179
+ "be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
177
180
  end
178
181
 
179
- def prefix_to_sentence
180
- split_words(@prefix)
182
+ private
183
+
184
+ def perform_match(actual)
185
+ @actual = actual
186
+ @actual.__send__ @operator, @expected
181
187
  end
182
188
  end
183
189
  end
@@ -0,0 +1,77 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `be_between`.
6
+ # Not intended to be instantiated directly.
7
+ class BeBetween < BaseMatcher
8
+ def initialize(min, max)
9
+ @min, @max = min, max
10
+ inclusive
11
+ end
12
+
13
+ # @api public
14
+ # Makes the between comparison inclusive.
15
+ #
16
+ # @example
17
+ # expect(3).to be_between(2, 3).inclusive
18
+ #
19
+ # @note The matcher is inclusive by default; this simply provides
20
+ # a way to be more explicit about it.
21
+ def inclusive
22
+ @less_than_operator = :<=
23
+ @greater_than_operator = :>=
24
+ @mode = :inclusive
25
+ self
26
+ end
27
+
28
+ # @api public
29
+ # Makes the between comparison exclusive.
30
+ #
31
+ # @example
32
+ # expect(3).to be_between(2, 4).exclusive
33
+ def exclusive
34
+ @less_than_operator = :<
35
+ @greater_than_operator = :>
36
+ @mode = :exclusive
37
+ self
38
+ end
39
+
40
+ # @api private
41
+ # @return [Boolean]
42
+ def matches?(actual)
43
+ @actual = actual
44
+ comparable? && compare
45
+ rescue ArgumentError
46
+ false
47
+ end
48
+
49
+ # @api private
50
+ # @return [String]
51
+ def failure_message
52
+ "#{super}#{not_comparable_clause}"
53
+ end
54
+
55
+ # @api private
56
+ # @return [String]
57
+ def description
58
+ "be between #{description_of @min} and #{description_of @max} (#{@mode})"
59
+ end
60
+
61
+ private
62
+
63
+ def comparable?
64
+ @actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator)
65
+ end
66
+
67
+ def not_comparable_clause
68
+ ", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable?
69
+ end
70
+
71
+ def compare
72
+ @actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,9 +1,24 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `be_an_instance_of`.
6
+ # Not intended to be instantiated directly.
4
7
  class BeAnInstanceOf < BaseMatcher
8
+ # @api private
9
+ # @return [String]
10
+ def description
11
+ "be an instance of #{expected}"
12
+ end
13
+
14
+ private
15
+
5
16
  def match(expected, actual)
6
- actual.instance_of? expected
17
+ actual.instance_of?(expected)
18
+ rescue NoMethodError
19
+ raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
20
+ "the actual object responds to #instance_of? method " \
21
+ "but a `NoMethodError` was encountered instead."
7
22
  end
8
23
  end
9
24
  end
@@ -1,9 +1,18 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `be_a_kind_of`.
6
+ # Not intended to be instantiated directly.
4
7
  class BeAKindOf < BaseMatcher
8
+ private
9
+
5
10
  def match(expected, actual)
6
- actual.kind_of? expected
11
+ actual.kind_of?(expected)
12
+ rescue NoMethodError
13
+ raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
14
+ "the actual object responds to #kind_of? method " \
15
+ "but a `NoMethodError` was encountered instead."
7
16
  end
8
17
  end
9
18
  end
@@ -1,45 +1,71 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
- class BeWithin
4
+ # @api private
5
+ # Provides the implementation for `be_within`.
6
+ # Not intended to be instantiated directly.
7
+ class BeWithin < BaseMatcher
5
8
  def initialize(delta)
6
9
  @delta = delta
7
10
  end
8
11
 
9
- def matches?(actual)
10
- @actual = actual
11
- raise needs_expected unless defined? @expected
12
- raise needs_subtractable unless @actual.respond_to? :-
13
- (@actual - @expected).abs <= @delta
12
+ # @api public
13
+ # Sets the expected value.
14
+ def of(expected)
15
+ @expected = expected
16
+ @tolerance = @delta
17
+ @unit = ''
18
+ self
14
19
  end
15
- alias == matches?
16
20
 
17
- def of(expected)
18
- @expected = expected
21
+ # @api public
22
+ # Sets the expected value, and makes the matcher do
23
+ # a percent comparison.
24
+ def percent_of(expected)
25
+ @expected = expected
26
+ @tolerance = @expected.abs * @delta / 100.0
27
+ @unit = '%'
19
28
  self
20
29
  end
21
30
 
22
- def failure_message_for_should
23
- "expected #{@actual} to #{description}"
31
+ # @private
32
+ def matches?(actual)
33
+ @actual = actual
34
+ raise needs_expected unless defined? @expected
35
+ numeric? && (@actual - @expected).abs <= @tolerance
36
+ end
37
+
38
+ # @api private
39
+ # @return [String]
40
+ def failure_message
41
+ "expected #{actual_formatted} to #{description}#{not_numeric_clause}"
24
42
  end
25
43
 
26
- def failure_message_for_should_not
27
- "expected #{@actual} not to #{description}"
44
+ # @api private
45
+ # @return [String]
46
+ def failure_message_when_negated
47
+ "expected #{actual_formatted} not to #{description}"
28
48
  end
29
49
 
50
+ # @api private
51
+ # @return [String]
30
52
  def description
31
- "be within #{@delta} of #{@expected}"
53
+ "be within #{@delta}#{@unit} of #{expected_formatted}"
32
54
  end
33
55
 
34
- private
56
+ private
35
57
 
36
- def needs_subtractable
37
- ArgumentError.new "The actual value (#{@actual.inspect}) must respond to `-`"
58
+ def numeric?
59
+ @actual.respond_to?(:-)
38
60
  end
39
61
 
40
62
  def needs_expected
41
63
  ArgumentError.new "You must set an expected value using #of: be_within(#{@delta}).of(expected_value)"
42
64
  end
65
+
66
+ def not_numeric_clause
67
+ ", but it could not be treated as a numeric value" unless numeric?
68
+ end
43
69
  end
44
70
  end
45
71
  end