rspec-expectations 2.11.3 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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