rspec-expectations 3.0.0.beta1 → 3.0.0.beta2

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