rspec-expectations 3.0.0.beta1 → 3.0.0.beta2

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 (122) hide show
  1. data.tar.gz.sig +2 -2
  2. data/.yardopts +1 -0
  3. data/Changelog.md +138 -0
  4. data/README.md +75 -8
  5. data/features/README.md +2 -2
  6. data/features/built_in_matchers/README.md +12 -9
  7. data/features/built_in_matchers/comparisons.feature +2 -2
  8. data/features/built_in_matchers/contain_exactly.feature +46 -0
  9. data/features/built_in_matchers/expect_change.feature +2 -2
  10. data/features/built_in_matchers/include.feature +0 -48
  11. data/features/built_in_matchers/output.feature +70 -0
  12. data/features/composing_matchers.feature +250 -0
  13. data/features/compound_expectations.feature +45 -0
  14. data/features/custom_matchers/access_running_example.feature +1 -1
  15. data/features/custom_matchers/define_matcher.feature +6 -6
  16. data/features/custom_matchers/define_matcher_outside_rspec.feature +4 -8
  17. data/features/test_frameworks/{test_unit.feature → minitest.feature} +11 -11
  18. data/lib/rspec/expectations.rb +31 -42
  19. data/lib/rspec/expectations/diff_presenter.rb +141 -0
  20. data/lib/rspec/expectations/differ.rb +22 -132
  21. data/lib/rspec/expectations/encoded_string.rb +56 -0
  22. data/lib/rspec/expectations/expectation_target.rb +0 -30
  23. data/lib/rspec/expectations/fail_with.rb +2 -2
  24. data/lib/rspec/expectations/handler.rb +128 -31
  25. data/lib/rspec/expectations/minitest_integration.rb +16 -0
  26. data/lib/rspec/expectations/syntax.rb +4 -58
  27. data/lib/rspec/expectations/version.rb +1 -1
  28. data/lib/rspec/matchers.rb +298 -60
  29. data/lib/rspec/matchers/aliased_matcher.rb +35 -0
  30. data/lib/rspec/matchers/built_in.rb +37 -33
  31. data/lib/rspec/matchers/built_in/base_matcher.rb +25 -15
  32. data/lib/rspec/matchers/built_in/be.rb +23 -31
  33. data/lib/rspec/matchers/built_in/be_between.rb +55 -0
  34. data/lib/rspec/matchers/built_in/be_within.rb +15 -11
  35. data/lib/rspec/matchers/built_in/change.rb +198 -81
  36. data/lib/rspec/matchers/built_in/compound.rb +106 -0
  37. data/lib/rspec/matchers/built_in/contain_exactly.rb +245 -0
  38. data/lib/rspec/matchers/built_in/eq.rb +43 -4
  39. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  40. data/lib/rspec/matchers/built_in/equal.rb +35 -18
  41. data/lib/rspec/matchers/built_in/has.rb +16 -15
  42. data/lib/rspec/matchers/built_in/include.rb +45 -23
  43. data/lib/rspec/matchers/built_in/match.rb +6 -3
  44. data/lib/rspec/matchers/built_in/operators.rb +103 -0
  45. data/lib/rspec/matchers/built_in/output.rb +108 -0
  46. data/lib/rspec/matchers/built_in/raise_error.rb +9 -15
  47. data/lib/rspec/matchers/built_in/respond_to.rb +5 -4
  48. data/lib/rspec/matchers/built_in/satisfy.rb +4 -3
  49. data/lib/rspec/matchers/built_in/start_and_end_with.rb +37 -16
  50. data/lib/rspec/matchers/built_in/throw_symbol.rb +6 -5
  51. data/lib/rspec/matchers/built_in/yield.rb +31 -29
  52. data/lib/rspec/matchers/composable.rb +138 -0
  53. data/lib/rspec/matchers/dsl.rb +330 -0
  54. data/lib/rspec/matchers/generated_descriptions.rb +6 -6
  55. data/lib/rspec/matchers/matcher_delegator.rb +33 -0
  56. data/lib/rspec/matchers/pretty.rb +13 -2
  57. data/spec/rspec/expectations/{differ_spec.rb → diff_presenter_spec.rb} +56 -36
  58. data/spec/rspec/expectations/encoded_string_spec.rb +74 -0
  59. data/spec/rspec/expectations/extensions/kernel_spec.rb +11 -11
  60. data/spec/rspec/expectations/fail_with_spec.rb +8 -8
  61. data/spec/rspec/expectations/handler_spec.rb +27 -49
  62. data/spec/rspec/expectations/minitest_integration_spec.rb +27 -0
  63. data/spec/rspec/expectations/syntax_spec.rb +17 -67
  64. data/spec/rspec/expectations_spec.rb +7 -52
  65. data/spec/rspec/matchers/aliased_matcher_spec.rb +48 -0
  66. data/spec/rspec/matchers/aliases_spec.rb +449 -0
  67. data/spec/rspec/matchers/{base_matcher_spec.rb → built_in/base_matcher_spec.rb} +24 -3
  68. data/spec/rspec/matchers/built_in/be_between_spec.rb +159 -0
  69. data/spec/rspec/matchers/{be_instance_of_spec.rb → built_in/be_instance_of_spec.rb} +0 -0
  70. data/spec/rspec/matchers/{be_kind_of_spec.rb → built_in/be_kind_of_spec.rb} +0 -0
  71. data/spec/rspec/matchers/{be_spec.rb → built_in/be_spec.rb} +76 -32
  72. data/spec/rspec/matchers/{be_within_spec.rb → built_in/be_within_spec.rb} +6 -2
  73. data/spec/rspec/matchers/{change_spec.rb → built_in/change_spec.rb} +310 -69
  74. data/spec/rspec/matchers/built_in/compound_spec.rb +292 -0
  75. data/spec/rspec/matchers/built_in/contain_exactly_spec.rb +441 -0
  76. data/spec/rspec/matchers/{cover_spec.rb → built_in/cover_spec.rb} +0 -0
  77. data/spec/rspec/matchers/built_in/eq_spec.rb +156 -0
  78. data/spec/rspec/matchers/{eql_spec.rb → built_in/eql_spec.rb} +2 -2
  79. data/spec/rspec/matchers/built_in/equal_spec.rb +106 -0
  80. data/spec/rspec/matchers/{exist_spec.rb → built_in/exist_spec.rb} +1 -1
  81. data/spec/rspec/matchers/{has_spec.rb → built_in/has_spec.rb} +39 -0
  82. data/spec/rspec/matchers/{include_spec.rb → built_in/include_spec.rb} +118 -109
  83. data/spec/rspec/matchers/{match_spec.rb → built_in/match_spec.rb} +30 -2
  84. data/spec/rspec/matchers/{operator_matcher_spec.rb → built_in/operators_spec.rb} +26 -26
  85. data/spec/rspec/matchers/built_in/output_spec.rb +165 -0
  86. data/spec/rspec/matchers/{raise_error_spec.rb → built_in/raise_error_spec.rb} +81 -11
  87. data/spec/rspec/matchers/{respond_to_spec.rb → built_in/respond_to_spec.rb} +0 -0
  88. data/spec/rspec/matchers/{satisfy_spec.rb → built_in/satisfy_spec.rb} +0 -0
  89. data/spec/rspec/matchers/{start_with_end_with_spec.rb → built_in/start_and_end_with_spec.rb} +82 -15
  90. data/spec/rspec/matchers/{throw_symbol_spec.rb → built_in/throw_symbol_spec.rb} +29 -10
  91. data/spec/rspec/matchers/{yield_spec.rb → built_in/yield_spec.rb} +90 -0
  92. data/spec/rspec/matchers/configuration_spec.rb +7 -39
  93. data/spec/rspec/matchers/description_generation_spec.rb +22 -6
  94. data/spec/rspec/matchers/dsl_spec.rb +838 -0
  95. data/spec/rspec/matchers/legacy_spec.rb +101 -0
  96. data/spec/rspec/matchers_spec.rb +74 -0
  97. data/spec/spec_helper.rb +35 -21
  98. data/spec/support/shared_examples.rb +26 -4
  99. metadata +172 -116
  100. metadata.gz.sig +3 -4
  101. checksums.yaml +0 -15
  102. checksums.yaml.gz.sig +0 -0
  103. data/features/built_in_matchers/match_array.feature +0 -37
  104. data/lib/rspec/expectations/errors.rb +0 -9
  105. data/lib/rspec/expectations/extensions.rb +0 -1
  106. data/lib/rspec/expectations/extensions/object.rb +0 -29
  107. data/lib/rspec/matchers/built_in/match_array.rb +0 -51
  108. data/lib/rspec/matchers/compatibility.rb +0 -14
  109. data/lib/rspec/matchers/matcher.rb +0 -301
  110. data/lib/rspec/matchers/method_missing.rb +0 -12
  111. data/lib/rspec/matchers/operator_matcher.rb +0 -99
  112. data/lib/rspec/matchers/test_unit_integration.rb +0 -11
  113. data/spec/rspec/matchers/eq_spec.rb +0 -60
  114. data/spec/rspec/matchers/equal_spec.rb +0 -78
  115. data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
  116. data/spec/rspec/matchers/match_array_spec.rb +0 -194
  117. data/spec/rspec/matchers/matcher_spec.rb +0 -706
  118. data/spec/rspec/matchers/matchers_spec.rb +0 -36
  119. data/spec/rspec/matchers/method_missing_spec.rb +0 -28
  120. data/spec/support/classes.rb +0 -56
  121. data/spec/support/in_sub_process.rb +0 -37
  122. data/spec/support/ruby_version.rb +0 -10
@@ -0,0 +1,35 @@
1
+ module RSpec
2
+ module Matchers
3
+ # Decorator that wraps a matcher and overrides `description`
4
+ # using the provided block in order to support an alias
5
+ # of a matcher. This is intended for use when composing
6
+ # matchers, so that you can use an expression like
7
+ # `include( a_value_within(0.1).of(3) )` rather than
8
+ # `include( be_within(0.1).of(3) )`, and have the corresponding
9
+ # description read naturally.
10
+ #
11
+ # @api private
12
+ class AliasedMatcher < MatcherDelegator
13
+ def initialize(base_matcher, description_block)
14
+ @description_block = description_block
15
+ super(base_matcher)
16
+ end
17
+
18
+ # Forward messages on to the wrapped matcher.
19
+ # Since many matchers provide a fluent interface
20
+ # (e.g. `a_value_within(0.1).of(3)`), we need to wrap
21
+ # the returned value if it responds to `description`,
22
+ # so that our override can be applied when it is eventually
23
+ # used.
24
+ def method_missing(*)
25
+ return_val = super
26
+ return return_val unless return_val.respond_to?(:description)
27
+ AliasedMatcher.new(return_val, @description_block)
28
+ end
29
+
30
+ def description
31
+ @description_block.call(super)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,39 +1,43 @@
1
+ require 'rspec/matchers/built_in/base_matcher'
2
+
1
3
  module RSpec
2
4
  module Matchers
3
5
  module BuiltIn
4
- require 'rspec/matchers/built_in/base_matcher'
5
- autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
6
- autoload :Be, 'rspec/matchers/built_in/be'
7
- autoload :BeTruthy, 'rspec/matchers/built_in/be'
8
- autoload :BeFalsey, 'rspec/matchers/built_in/be'
9
- autoload :BeNil, 'rspec/matchers/built_in/be'
10
- autoload :BeComparedTo, 'rspec/matchers/built_in/be'
11
- autoload :BePredicate, 'rspec/matchers/built_in/be'
12
- autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
13
- autoload :BeWithin, 'rspec/matchers/built_in/be_within'
14
- autoload :Change, 'rspec/matchers/built_in/change'
15
- autoload :Cover, 'rspec/matchers/built_in/cover' if (1..2).respond_to?(:cover?)
16
- autoload :Eq, 'rspec/matchers/built_in/eq'
17
- autoload :Eql, 'rspec/matchers/built_in/eql'
18
- autoload :Equal, 'rspec/matchers/built_in/equal'
19
- autoload :Exist, 'rspec/matchers/built_in/exist'
20
- autoload :Has, 'rspec/matchers/built_in/has'
21
- autoload :Have, 'rspec/matchers/built_in/have'
22
- autoload :Include, 'rspec/matchers/built_in/include'
23
- autoload :Match, 'rspec/matchers/built_in/match'
24
- autoload :MatchArray, 'rspec/matchers/built_in/match_array'
25
- autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
26
- autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
27
- autoload :StartWith, 'rspec/matchers/built_in/start_and_end_with'
28
- autoload :EndWith, 'rspec/matchers/built_in/start_and_end_with'
29
- autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
30
- autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
31
- autoload :YieldControl, 'rspec/matchers/built_in/yield'
32
- autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
33
- autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
34
- autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
6
+ autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
7
+ autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
8
+ autoload :BeBetween, 'rspec/matchers/built_in/be_between'
9
+ autoload :Be, 'rspec/matchers/built_in/be'
10
+ autoload :BeComparedTo, 'rspec/matchers/built_in/be'
11
+ autoload :BeFalsey, 'rspec/matchers/built_in/be'
12
+ autoload :BeNil, 'rspec/matchers/built_in/be'
13
+ autoload :BePredicate, 'rspec/matchers/built_in/be'
14
+ autoload :BeTruthy, 'rspec/matchers/built_in/be'
15
+ autoload :BeWithin, 'rspec/matchers/built_in/be_within'
16
+ autoload :Change, 'rspec/matchers/built_in/change'
17
+ autoload :Compound, 'rspec/matchers/built_in/compound'
18
+ autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
19
+ autoload :Cover, 'rspec/matchers/built_in/cover'
20
+ autoload :EndWith, 'rspec/matchers/built_in/start_and_end_with'
21
+ autoload :Eq, 'rspec/matchers/built_in/eq'
22
+ autoload :Eql, 'rspec/matchers/built_in/eql'
23
+ autoload :Equal, 'rspec/matchers/built_in/equal'
24
+ autoload :Exist, 'rspec/matchers/built_in/exist'
25
+ autoload :Has, 'rspec/matchers/built_in/has'
26
+ autoload :Include, 'rspec/matchers/built_in/include'
27
+ autoload :Match, 'rspec/matchers/built_in/match'
28
+ autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators'
29
+ autoload :OperatorMatcher, 'rspec/matchers/built_in/operators'
30
+ autoload :Output, 'rspec/matchers/built_in/output'
31
+ autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators'
32
+ autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
33
+ autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
34
+ autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
35
+ autoload :StartWith, 'rspec/matchers/built_in/start_and_end_with'
36
+ autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
37
+ autoload :YieldControl, 'rspec/matchers/built_in/yield'
38
+ autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
39
+ autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
40
+ autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
35
41
  end
36
42
  end
37
43
  end
38
-
39
-
@@ -13,11 +13,14 @@ module RSpec
13
13
  # class. If/when this changes, we will announce it and remove this warning.
14
14
  class BaseMatcher
15
15
  include RSpec::Matchers::Pretty
16
+ include RSpec::Matchers::Composable
17
+
18
+ UNDEFINED = Object.new.freeze
16
19
 
17
20
  attr_reader :actual, :expected, :rescued_exception
18
21
 
19
- def initialize(expected = nil)
20
- @expected = expected
22
+ def initialize(expected = UNDEFINED)
23
+ @expected = expected unless UNDEFINED.equal?(expected)
21
24
  end
22
25
 
23
26
  def matches?(actual)
@@ -35,32 +38,39 @@ module RSpec
35
38
  end
36
39
  end
37
40
 
38
- def failure_message_for_should
39
- assert_ivars :@actual, :@expected
40
- "expected #{@actual.inspect} to #{name_to_sentence}#{expected_to_sentence}"
41
+ def failure_message
42
+ assert_ivars :@actual
43
+ "expected #{@actual.inspect} to #{description}"
41
44
  end
42
45
 
43
- def failure_message_for_should_not
44
- assert_ivars :@actual, :@expected
45
- "expected #{@actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"
46
+ def failure_message_when_negated
47
+ assert_ivars :@actual
48
+ "expected #{@actual.inspect} not to #{description}"
46
49
  end
47
50
 
48
51
  def description
49
- expected ? "#{name_to_sentence} #{@expected.inspect}" : name_to_sentence
52
+ return name_to_sentence unless defined?(@expected)
53
+ "#{name_to_sentence}#{to_sentence @expected}"
50
54
  end
51
55
 
52
56
  def diffable?
53
57
  false
54
58
  end
55
59
 
56
- def ==(other)
57
- matches?(other)
58
- end
60
+ private
59
61
 
60
- private
62
+ def assert_ivars(*expected_ivars)
63
+ if (expected_ivars - present_ivars).any?
64
+ raise "#{self.class.name} needs to supply#{to_sentence expected_ivars}"
65
+ end
66
+ end
61
67
 
62
- def assert_ivars *ivars
63
- raise "#{self.class.name} needs to supply #{to_sentence ivars}" unless ivars.all? { |v| instance_variables.map(&:intern).include? v }
68
+ if RUBY_VERSION.to_f < 1.9
69
+ def present_ivars
70
+ instance_variables.map { |v| v.to_sym }
71
+ end
72
+ else
73
+ alias present_ivars instance_variables
64
74
  end
65
75
  end
66
76
  end
@@ -8,11 +8,11 @@ module RSpec
8
8
  !!actual
9
9
  end
10
10
 
11
- def failure_message_for_should
11
+ def failure_message
12
12
  "expected: truthy value\n got: #{actual.inspect}"
13
13
  end
14
14
 
15
- def failure_message_for_should_not
15
+ def failure_message_when_negated
16
16
  "expected: falsey value\n got: #{actual.inspect}"
17
17
  end
18
18
  end
@@ -22,11 +22,11 @@ module RSpec
22
22
  !actual
23
23
  end
24
24
 
25
- def failure_message_for_should
25
+ def failure_message
26
26
  "expected: falsey value\n got: #{actual.inspect}"
27
27
  end
28
28
 
29
- def failure_message_for_should_not
29
+ def failure_message_when_negated
30
30
  "expected: truthy value\n got: #{actual.inspect}"
31
31
  end
32
32
  end
@@ -36,11 +36,11 @@ module RSpec
36
36
  actual.nil?
37
37
  end
38
38
 
39
- def failure_message_for_should
39
+ def failure_message
40
40
  "expected: nil\n got: #{actual.inspect}"
41
41
  end
42
42
 
43
- def failure_message_for_should_not
43
+ def failure_message_when_negated
44
44
  "expected: not nil\n got: nil"
45
45
  end
46
46
  end
@@ -80,11 +80,11 @@ module RSpec
80
80
  !!actual
81
81
  end
82
82
 
83
- def failure_message_for_should
83
+ def failure_message
84
84
  "expected #{@actual.inspect} to evaluate to true"
85
85
  end
86
86
 
87
- def failure_message_for_should_not
87
+ def failure_message_when_negated
88
88
  "expected #{@actual.inspect} to evaluate to false"
89
89
  end
90
90
 
@@ -95,10 +95,12 @@ module RSpec
95
95
  end
96
96
  end
97
97
 
98
- class BeComparedTo < Be
98
+ class BeComparedTo < BaseMatcher
99
+ include BeHelpers
100
+
99
101
  def initialize(operand, operator)
100
102
  @expected, @operator = operand, operator
101
- @args = []
103
+ @args = []
102
104
  end
103
105
 
104
106
  def matches?(actual)
@@ -106,28 +108,22 @@ module RSpec
106
108
  @actual.__send__ @operator, @expected
107
109
  end
108
110
 
109
- def failure_message_for_should
111
+ def failure_message
110
112
  "expected: #{@operator} #{@expected.inspect}\n got: #{@operator.to_s.gsub(/./, ' ')} #{@actual.inspect}"
111
113
  end
112
114
 
113
- def failure_message_for_should_not
114
- message = <<-MESSAGE
115
- `#{negative_expectation_expression}` not only FAILED,
116
- it is a bit confusing.
117
- MESSAGE
118
-
119
- raise message << ([:===,:==].include?(@operator) ?
120
- "It might be more clearly expressed without the \"be\"?" :
121
- "It might be more clearly expressed in the positive?")
115
+ def failure_message_when_negated
116
+ message = "`expect(#{@actual.inspect}).not_to be #{@operator} #{@expected.inspect}`"
117
+ if [:<, :>, :<=, :>=].include?(@operator)
118
+ message + " not only FAILED, it is a bit confusing."
119
+ else
120
+ message
121
+ end
122
122
  end
123
123
 
124
124
  def description
125
125
  "be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
126
126
  end
127
-
128
- def negative_expectation_expression
129
- Expectations::Syntax.negative_expression("actual", "be #{@operator} #{@expected}")
130
- end
131
127
  end
132
128
 
133
129
  class BePredicate < BaseMatcher
@@ -149,7 +145,6 @@ it is a bit confusing.
149
145
  begin
150
146
  return @result = actual.__send__(predicate, *@args, &@block)
151
147
  rescue NameError => predicate_missing_error
152
- "this needs to be here or rcov will not count this branch even though it's executed in a code example"
153
148
  end
154
149
 
155
150
  begin
@@ -159,13 +154,11 @@ it is a bit confusing.
159
154
  end
160
155
  end
161
156
 
162
- alias === matches?
163
-
164
- def failure_message_for_should
157
+ def failure_message
165
158
  "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
166
159
  end
167
160
 
168
- def failure_message_for_should_not
161
+ def failure_message_when_negated
169
162
  "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}"
170
163
  end
171
164
 
@@ -200,8 +193,7 @@ it is a bit confusing.
200
193
  end
201
194
 
202
195
  def prefix_and_expected(symbol)
203
- symbol.to_s =~ /^(be_(an?_)?)(.*)/
204
- return $1, $3
196
+ Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
205
197
  end
206
198
 
207
199
  def prefix_to_sentence
@@ -0,0 +1,55 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ class BeBetween < BaseMatcher
5
+ def initialize(min, max)
6
+ @min, @max = min, max
7
+ inclusive
8
+ end
9
+
10
+ def inclusive
11
+ @less_than_operator = :<=
12
+ @greater_than_operator = :>=
13
+ @mode = :inclusive
14
+ self
15
+ end
16
+
17
+ def exclusive
18
+ @less_than_operator = :<
19
+ @greater_than_operator = :>
20
+ @mode = :exclusive
21
+ self
22
+ end
23
+
24
+ def matches?(actual)
25
+ @actual = actual
26
+ comparable? && compare
27
+ rescue ArgumentError
28
+ false
29
+ end
30
+
31
+ def failure_message
32
+ "#{super}#{not_comparable_clause}"
33
+ end
34
+
35
+ def description
36
+ "be between #{@min.inspect} and #{@max.inspect} (#{@mode})"
37
+ end
38
+
39
+ private
40
+
41
+ def comparable?
42
+ @actual.respond_to?(@less_than_operator) && @actual.respond_to?(@greater_than_operator)
43
+ end
44
+
45
+ def not_comparable_clause
46
+ ", but it does not respond to `#{@less_than_operator}` and `#{@greater_than_operator}`" unless comparable?
47
+ end
48
+
49
+ def compare
50
+ @actual.__send__(@greater_than_operator, @min) && @actual.__send__(@less_than_operator, @max)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -2,17 +2,17 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class BeWithin
5
+ include Composable
6
+
5
7
  def initialize(delta)
6
8
  @delta = delta
7
9
  end
8
10
 
9
11
  def matches?(actual)
10
12
  @actual = actual
11
- raise needs_expected unless defined? @expected
12
- raise needs_subtractable unless @actual.respond_to? :-
13
- (@actual - @expected).abs <= @tolerance
13
+ raise needs_expected unless defined? @expected
14
+ numeric? && (@actual - @expected).abs <= @tolerance
14
15
  end
15
- alias == matches?
16
16
 
17
17
  def of(expected)
18
18
  @expected = expected
@@ -28,27 +28,31 @@ module RSpec
28
28
  self
29
29
  end
30
30
 
31
- def failure_message_for_should
32
- "expected #{@actual} to #{description}"
31
+ def failure_message
32
+ "expected #{@actual.inspect} to #{description}#{not_numeric_clause}"
33
33
  end
34
34
 
35
- def failure_message_for_should_not
36
- "expected #{@actual} not to #{description}"
35
+ def failure_message_when_negated
36
+ "expected #{@actual.inspect} not to #{description}"
37
37
  end
38
38
 
39
39
  def description
40
40
  "be within #{@delta}#{@unit} of #{@expected}"
41
41
  end
42
42
 
43
- private
43
+ private
44
44
 
45
- def needs_subtractable
46
- ArgumentError.new "The actual value (#{@actual.inspect}) must respond to `-`"
45
+ def numeric?
46
+ @actual.respond_to?(:-)
47
47
  end
48
48
 
49
49
  def needs_expected
50
50
  ArgumentError.new "You must set an expected value using #of: be_within(#{@delta}).of(expected_value)"
51
51
  end
52
+
53
+ def not_numeric_clause
54
+ ", but it could not be treated as a numeric value" unless numeric?
55
+ end
52
56
  end
53
57
  end
54
58
  end
@@ -1,139 +1,256 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # Describes an expected mutation.
4
5
  class Change
5
- def initialize(receiver=nil, message=nil, &block)
6
- @message = message
7
- @value_proc = block || lambda {receiver.__send__(message)}
8
- @expected_after = @expected_before = @minimum = @maximum = @expected_delta = nil
9
- @eval_before = @eval_after = false
6
+ include Composable
7
+
8
+ # Specifies the delta of the expected change.
9
+ def by(expected_delta)
10
+ ChangeRelatively.new(@change_details, expected_delta, :by) do |actual_delta|
11
+ values_match?(expected_delta, actual_delta)
12
+ end
13
+ end
14
+
15
+ # Specifies a minimum delta of the expected change.
16
+ def by_at_least(minimum)
17
+ ChangeRelatively.new(@change_details, minimum, :by_at_least) do |actual_delta|
18
+ actual_delta >= minimum
19
+ end
20
+ end
21
+
22
+ # Specifies a maximum delta of the expected change.
23
+ def by_at_most(maximum)
24
+ ChangeRelatively.new(@change_details, maximum, :by_at_most) do |actual_delta|
25
+ actual_delta <= maximum
26
+ end
27
+ end
28
+
29
+ # Specifies the new value you expect.
30
+ def to(value)
31
+ ChangeToValue.new(@change_details, value)
10
32
  end
11
33
 
34
+ # Specifies the original value.
35
+ def from(value)
36
+ ChangeFromValue.new(@change_details, value)
37
+ end
38
+
39
+ # @api private
12
40
  def matches?(event_proc)
13
41
  raise_block_syntax_error if block_given?
42
+ @change_details.perform_change(event_proc)
43
+ @change_details.changed?
44
+ end
14
45
 
15
- @actual_before = evaluate_value_proc
16
- event_proc.call
17
- @actual_after = evaluate_value_proc
46
+ # @api private
47
+ def failure_message
48
+ "expected #{@change_details.message} to have changed, but is still #{description_of @change_details.actual_before}"
49
+ end
18
50
 
19
- (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?
51
+ # @api private
52
+ def failure_message_when_negated
53
+ "expected #{@change_details.message} not to have changed, but did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
20
54
  end
21
- alias == matches?
22
55
 
23
- def raise_block_syntax_error
24
- raise SyntaxError.new(<<-MESSAGE)
25
- block passed to should or should_not change must use {} instead of do/end
26
- MESSAGE
56
+ # @api private
57
+ def description
58
+ "change #{@change_details.message}"
27
59
  end
28
60
 
29
- def evaluate_value_proc
30
- case val = @value_proc.call
31
- when Enumerable, String
32
- val.dup
33
- else
34
- val
35
- end
61
+ private
62
+
63
+ def initialize(receiver=nil, message=nil, &block)
64
+ @change_details = ChangeDetails.new(receiver, message, &block)
36
65
  end
37
66
 
38
- def failure_message_for_should
39
- if @eval_before && !expected_matches_actual?(@expected_before, @actual_before)
40
- "#{message} should have initially been #{@expected_before.inspect}, but was #{@actual_before.inspect}"
41
- elsif @eval_after && !expected_matches_actual?(@expected_after, @actual_after)
42
- "#{message} should have been changed to #{failure_message_for_expected_after}, but is now #{@actual_after.inspect}"
43
- elsif @expected_delta
44
- "#{message} should have been changed by #{@expected_delta.inspect}, but was changed by #{actual_delta.inspect}"
45
- elsif @minimum
46
- "#{message} should have been changed by at least #{@minimum.inspect}, but was changed by #{actual_delta.inspect}"
47
- elsif @maximum
48
- "#{message} should have been changed by at most #{@maximum.inspect}, but was changed by #{actual_delta.inspect}"
49
- else
50
- "#{message} should have changed, but is still #{@actual_before.inspect}"
51
- end
67
+ def raise_block_syntax_error
68
+ raise SyntaxError,
69
+ "The block passed to the `change` matcher must use `{ ... }` instead of do/end"
52
70
  end
71
+ end
53
72
 
54
- def actual_delta
55
- @actual_after - @actual_before
73
+ # Used to specify a relative change.
74
+ # @api private
75
+ class ChangeRelatively
76
+ include Composable
77
+
78
+ def initialize(change_details, expected_delta, relativity, &comparer)
79
+ @change_details = change_details
80
+ @expected_delta = expected_delta
81
+ @relativity = relativity
82
+ @comparer = comparer
56
83
  end
57
84
 
58
- def failure_message_for_should_not
59
- "#{message} should not have changed, but did change from #{@actual_before.inspect} to #{@actual_after.inspect}"
85
+ def failure_message
86
+ "expected #{@change_details.message} to have changed #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}, " +
87
+ "but was changed by #{description_of @change_details.actual_delta}"
60
88
  end
61
89
 
62
- def by(expected_delta)
63
- @expected_delta = expected_delta
64
- self
90
+ def matches?(event_proc)
91
+ @change_details.perform_change(event_proc)
92
+ @comparer.call(@change_details.actual_delta)
65
93
  end
66
94
 
67
- def by_at_least(minimum)
68
- @minimum = minimum
69
- self
95
+ def does_not_match?(event_proc)
96
+ raise NotImplementedError, "`expect { }.not_to change { }.#{@relativity}()` is not supported"
70
97
  end
71
98
 
72
- def by_at_most(maximum)
73
- @maximum = maximum
74
- self
99
+ # @api private
100
+ def description
101
+ "change #{@change_details.message} #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}"
75
102
  end
103
+ end
76
104
 
77
- def to(to)
78
- @eval_after = true
79
- @expected_after = to
80
- self
105
+ # Base class for specifying a change from and/or to specific values.
106
+ # @api private
107
+ class SpecificValuesChange
108
+ include Composable
109
+ MATCH_ANYTHING = ::Object.ancestors.last
110
+
111
+ def initialize(change_details, from, to)
112
+ @change_details = change_details
113
+ @expected_before = from
114
+ @expected_after = to
81
115
  end
82
116
 
83
- def from (before)
84
- @eval_before = true
85
- @expected_before = before
86
- self
117
+ def matches?(event_proc)
118
+ @change_details.perform_change(event_proc)
119
+ @change_details.changed? && matches_before? && matches_after?
87
120
  end
88
121
 
89
122
  def description
90
- "change ##{message}"
123
+ "change #{@change_details.message} #{change_description}"
91
124
  end
92
125
 
93
- private
126
+ def failure_message
127
+ return before_value_failure unless matches_before?
128
+ return did_not_change_failure unless @change_details.changed?
129
+ after_value_failure
130
+ end
94
131
 
95
- def failure_message_for_expected_after
96
- if RSpec::Matchers.is_a_matcher?(@expected_after)
97
- @expected_after.description
98
- else
99
- @expected_after.inspect
132
+ private
133
+
134
+ def matches_before?
135
+ values_match?(@expected_before, @change_details.actual_before)
136
+ end
137
+
138
+ def matches_after?
139
+ values_match?(@expected_after, @change_details.actual_after)
140
+ end
141
+
142
+ def before_value_failure
143
+ "expected #{@change_details.message} to have initially been #{description_of @expected_before}, but was #{description_of @change_details.actual_before}"
144
+ end
145
+
146
+ def after_value_failure
147
+ "expected #{@change_details.message} to have changed to #{description_of @expected_after}, but is now #{description_of @change_details.actual_after}"
148
+ end
149
+
150
+ def did_not_change_failure
151
+ "expected #{@change_details.message} to have changed #{change_description}, but did not change"
152
+ end
153
+
154
+ def did_change_failure
155
+ "expected #{@change_details.message} not to have changed, but did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
156
+ end
157
+ end
158
+
159
+ # Used to specify a change from a specific value
160
+ # (and, optionally, to a specific value).
161
+ # @api private
162
+ class ChangeFromValue < SpecificValuesChange
163
+ def initialize(change_details, expected_before)
164
+ @description_suffix = nil
165
+ super(change_details, expected_before, MATCH_ANYTHING)
166
+ end
167
+
168
+ def to(value)
169
+ @expected_after = value
170
+ @description_suffix = " to #{description_of value}"
171
+ self
172
+ end
173
+
174
+ def does_not_match?(event_proc)
175
+ if @description_suffix
176
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
100
177
  end
178
+
179
+ @change_details.perform_change(event_proc)
180
+ !@change_details.changed? && matches_before?
101
181
  end
102
182
 
103
- def message
104
- @message || "result"
183
+ def failure_message_when_negated
184
+ return before_value_failure unless matches_before?
185
+ did_change_failure
105
186
  end
106
187
 
107
- def change_expected?
108
- @expected_delta != 0
188
+ private
189
+
190
+ def change_description
191
+ "from #{description_of @expected_before}#{@description_suffix}"
109
192
  end
193
+ end
110
194
 
111
- def changed?
112
- @actual_before != @actual_after
195
+ # Used to specify a change to a specific value
196
+ # (and, optionally, from a specific value).
197
+ # @api private
198
+ class ChangeToValue < SpecificValuesChange
199
+ def initialize(change_details, expected_after)
200
+ @description_suffix = nil
201
+ super(change_details, MATCH_ANYTHING, expected_after)
113
202
  end
114
203
 
115
- def matches_before?
116
- @eval_before ? expected_matches_actual?(@expected_before, @actual_before) : true
204
+ def from(value)
205
+ @expected_before = value
206
+ @description_suffix = " from #{description_of value}"
207
+ self
117
208
  end
118
209
 
119
- def matches_after?
120
- @eval_after ? expected_matches_actual?(@expected_after, @actual_after) : true
210
+ def does_not_match?(event_proc)
211
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
121
212
  end
122
213
 
123
- def matches_expected_delta?
124
- @expected_delta ? (@actual_before + @expected_delta == @actual_after) : true
214
+ private
215
+
216
+ def change_description
217
+ "to #{description_of @expected_after}#{@description_suffix}"
125
218
  end
219
+ end
220
+
221
+ # Encapsulates the details of the before/after values.
222
+ # @api private
223
+ class ChangeDetails
224
+ attr_reader :message, :actual_before, :actual_after
126
225
 
127
- def matches_min?
128
- @minimum ? (@actual_after - @actual_before >= @minimum) : true
226
+ def initialize(receiver=nil, message=nil, &block)
227
+ @message = message ? "##{message}" : "result"
228
+ @value_proc = block || lambda { receiver.__send__(message) }
129
229
  end
130
230
 
131
- def matches_max?
132
- @maximum ? (@actual_after - @actual_before <= @maximum) : true
231
+ def perform_change(event_proc)
232
+ @actual_before = evaluate_value_proc
233
+ event_proc.call
234
+ @actual_after = evaluate_value_proc
133
235
  end
134
236
 
135
- def expected_matches_actual?(expected, actual)
136
- expected === actual || actual == expected
237
+ def changed?
238
+ @actual_before != @actual_after
239
+ end
240
+
241
+ def actual_delta
242
+ @actual_after - @actual_before
243
+ end
244
+
245
+ private
246
+
247
+ def evaluate_value_proc
248
+ case val = @value_proc.call
249
+ when Enumerable, String
250
+ val.dup
251
+ else
252
+ val
253
+ end
137
254
  end
138
255
  end
139
256
  end