rspec-expectations 2.13.0 → 3.0.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 (142) hide show
  1. checksums.yaml +15 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/Changelog.md +413 -23
  4. data/License.txt +1 -0
  5. data/README.md +109 -80
  6. data/lib/rspec/{matchers → expectations}/configuration.rb +44 -14
  7. data/lib/rspec/expectations/expectation_target.rb +71 -38
  8. data/lib/rspec/expectations/fail_with.rb +12 -44
  9. data/lib/rspec/expectations/handler.rb +135 -35
  10. data/lib/rspec/expectations/minitest_integration.rb +16 -0
  11. data/lib/rspec/expectations/syntax.rb +60 -94
  12. data/lib/rspec/expectations/version.rb +1 -1
  13. data/lib/rspec/expectations.rb +46 -24
  14. data/lib/rspec/matchers/aliased_matcher.rb +41 -0
  15. data/lib/rspec/matchers/built_in/all.rb +78 -0
  16. data/lib/rspec/matchers/built_in/base_matcher.rb +63 -15
  17. data/lib/rspec/matchers/built_in/be.rb +170 -79
  18. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  19. data/lib/rspec/matchers/built_in/be_instance_of.rb +11 -3
  20. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -0
  21. data/lib/rspec/matchers/built_in/be_within.rb +40 -16
  22. data/lib/rspec/matchers/built_in/change.rb +275 -74
  23. data/lib/rspec/matchers/built_in/compound.rb +122 -0
  24. data/lib/rspec/matchers/built_in/contain_exactly.rb +256 -0
  25. data/lib/rspec/matchers/built_in/cover.rb +3 -0
  26. data/lib/rspec/matchers/built_in/eq.rb +60 -6
  27. data/lib/rspec/matchers/built_in/eql.rb +17 -5
  28. data/lib/rspec/matchers/built_in/equal.rb +55 -21
  29. data/lib/rspec/matchers/built_in/exist.rb +74 -10
  30. data/lib/rspec/matchers/built_in/has.rb +71 -15
  31. data/lib/rspec/matchers/built_in/include.rb +58 -29
  32. data/lib/rspec/matchers/built_in/match.rb +19 -1
  33. data/lib/rspec/matchers/built_in/operators.rb +119 -0
  34. data/lib/rspec/matchers/built_in/output.rb +150 -0
  35. data/lib/rspec/matchers/built_in/raise_error.rb +91 -33
  36. data/lib/rspec/matchers/built_in/respond_to.rb +42 -20
  37. data/lib/rspec/matchers/built_in/satisfy.rb +19 -3
  38. data/lib/rspec/matchers/built_in/start_and_end_with.rb +54 -18
  39. data/lib/rspec/matchers/built_in/throw_symbol.rb +38 -8
  40. data/lib/rspec/matchers/built_in/yield.rb +237 -55
  41. data/lib/rspec/matchers/built_in.rb +45 -33
  42. data/lib/rspec/matchers/composable.rb +179 -0
  43. data/lib/rspec/matchers/dsl.rb +362 -5
  44. data/lib/rspec/matchers/generated_descriptions.rb +13 -6
  45. data/lib/rspec/matchers/matcher_delegator.rb +37 -0
  46. data/lib/rspec/matchers/pretty.rb +33 -20
  47. data/lib/rspec/matchers.rb +450 -227
  48. data.tar.gz.sig +1 -0
  49. metadata +100 -215
  50. metadata.gz.sig +0 -0
  51. data/features/README.md +0 -49
  52. data/features/Upgrade.md +0 -53
  53. data/features/built_in_matchers/README.md +0 -90
  54. data/features/built_in_matchers/be.feature +0 -175
  55. data/features/built_in_matchers/be_within.feature +0 -48
  56. data/features/built_in_matchers/cover.feature +0 -47
  57. data/features/built_in_matchers/end_with.feature +0 -48
  58. data/features/built_in_matchers/equality.feature +0 -139
  59. data/features/built_in_matchers/exist.feature +0 -45
  60. data/features/built_in_matchers/expect_change.feature +0 -59
  61. data/features/built_in_matchers/expect_error.feature +0 -144
  62. data/features/built_in_matchers/have.feature +0 -109
  63. data/features/built_in_matchers/include.feature +0 -174
  64. data/features/built_in_matchers/match.feature +0 -52
  65. data/features/built_in_matchers/operators.feature +0 -227
  66. data/features/built_in_matchers/predicates.feature +0 -137
  67. data/features/built_in_matchers/respond_to.feature +0 -84
  68. data/features/built_in_matchers/satisfy.feature +0 -33
  69. data/features/built_in_matchers/start_with.feature +0 -48
  70. data/features/built_in_matchers/throw_symbol.feature +0 -91
  71. data/features/built_in_matchers/types.feature +0 -116
  72. data/features/built_in_matchers/yield.feature +0 -146
  73. data/features/custom_matchers/access_running_example.feature +0 -53
  74. data/features/custom_matchers/define_diffable_matcher.feature +0 -27
  75. data/features/custom_matchers/define_matcher.feature +0 -368
  76. data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
  77. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
  78. data/features/customized_message.feature +0 -22
  79. data/features/diffing.feature +0 -85
  80. data/features/implicit_docstrings.feature +0 -52
  81. data/features/step_definitions/additional_cli_steps.rb +0 -22
  82. data/features/support/env.rb +0 -5
  83. data/features/syntax_configuration.feature +0 -68
  84. data/features/test_frameworks/test_unit.feature +0 -46
  85. data/lib/rspec/expectations/deprecation.rb +0 -38
  86. data/lib/rspec/expectations/differ.rb +0 -115
  87. data/lib/rspec/expectations/errors.rb +0 -9
  88. data/lib/rspec/expectations/extensions/array.rb +0 -9
  89. data/lib/rspec/expectations/extensions/object.rb +0 -39
  90. data/lib/rspec/expectations/extensions.rb +0 -2
  91. data/lib/rspec/matchers/be_close.rb +0 -9
  92. data/lib/rspec/matchers/built_in/have.rb +0 -108
  93. data/lib/rspec/matchers/built_in/match_array.rb +0 -51
  94. data/lib/rspec/matchers/compatibility.rb +0 -14
  95. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  96. data/lib/rspec/matchers/matcher.rb +0 -300
  97. data/lib/rspec/matchers/method_missing.rb +0 -12
  98. data/lib/rspec/matchers/operator_matcher.rb +0 -109
  99. data/lib/rspec-expectations.rb +0 -1
  100. data/spec/rspec/expectations/differ_spec.rb +0 -170
  101. data/spec/rspec/expectations/expectation_target_spec.rb +0 -75
  102. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  103. data/spec/rspec/expectations/fail_with_spec.rb +0 -95
  104. data/spec/rspec/expectations/handler_spec.rb +0 -206
  105. data/spec/rspec/expectations/syntax_spec.rb +0 -97
  106. data/spec/rspec/matchers/base_matcher_spec.rb +0 -62
  107. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  108. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -63
  109. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -41
  110. data/spec/rspec/matchers/be_spec.rb +0 -516
  111. data/spec/rspec/matchers/be_within_spec.rb +0 -129
  112. data/spec/rspec/matchers/change_spec.rb +0 -542
  113. data/spec/rspec/matchers/configuration_spec.rb +0 -238
  114. data/spec/rspec/matchers/cover_spec.rb +0 -69
  115. data/spec/rspec/matchers/description_generation_spec.rb +0 -176
  116. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  117. data/spec/rspec/matchers/eq_spec.rb +0 -60
  118. data/spec/rspec/matchers/eql_spec.rb +0 -41
  119. data/spec/rspec/matchers/equal_spec.rb +0 -78
  120. data/spec/rspec/matchers/exist_spec.rb +0 -124
  121. data/spec/rspec/matchers/has_spec.rb +0 -122
  122. data/spec/rspec/matchers/have_spec.rb +0 -445
  123. data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
  124. data/spec/rspec/matchers/include_spec.rb +0 -531
  125. data/spec/rspec/matchers/match_array_spec.rb +0 -194
  126. data/spec/rspec/matchers/match_spec.rb +0 -61
  127. data/spec/rspec/matchers/matcher_spec.rb +0 -471
  128. data/spec/rspec/matchers/matchers_spec.rb +0 -37
  129. data/spec/rspec/matchers/method_missing_spec.rb +0 -28
  130. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -223
  131. data/spec/rspec/matchers/raise_error_spec.rb +0 -395
  132. data/spec/rspec/matchers/respond_to_spec.rb +0 -292
  133. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  134. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -186
  135. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  136. data/spec/rspec/matchers/yield_spec.rb +0 -437
  137. data/spec/spec_helper.rb +0 -54
  138. data/spec/support/classes.rb +0 -56
  139. data/spec/support/in_sub_process.rb +0 -38
  140. data/spec/support/matchers.rb +0 -22
  141. data/spec/support/ruby_version.rb +0 -10
  142. data/spec/support/shared_examples.rb +0 -13
@@ -1,131 +1,332 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `change`.
6
+ # Not intended to be instantiated directly.
4
7
  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
8
+ include Composable
9
+
10
+ # @api public
11
+ # Specifies the delta of the expected change.
12
+ def by(expected_delta)
13
+ ChangeRelatively.new(@change_details, expected_delta, :by) do |actual_delta|
14
+ values_match?(expected_delta, actual_delta)
15
+ end
16
+ end
17
+
18
+ # @api public
19
+ # Specifies a minimum delta of the expected change.
20
+ def by_at_least(minimum)
21
+ ChangeRelatively.new(@change_details, minimum, :by_at_least) do |actual_delta|
22
+ actual_delta >= minimum
23
+ end
24
+ end
25
+
26
+ # @api public
27
+ # Specifies a maximum delta of the expected change.
28
+ def by_at_most(maximum)
29
+ ChangeRelatively.new(@change_details, maximum, :by_at_most) do |actual_delta|
30
+ actual_delta <= maximum
31
+ end
32
+ end
33
+
34
+ # @api public
35
+ # Specifies the new value you expect.
36
+ def to(value)
37
+ ChangeToValue.new(@change_details, value)
38
+ end
39
+
40
+ # @api public
41
+ # Specifies the original value.
42
+ def from(value)
43
+ ChangeFromValue.new(@change_details, value)
10
44
  end
11
45
 
46
+ # @private
12
47
  def matches?(event_proc)
48
+ @event_proc = event_proc
49
+ return false unless Proc === event_proc
13
50
  raise_block_syntax_error if block_given?
51
+ @change_details.perform_change(event_proc)
52
+ @change_details.changed?
53
+ end
14
54
 
15
- @actual_before = evaluate_value_proc
16
- event_proc.call
17
- @actual_after = evaluate_value_proc
55
+ def does_not_match?(event_proc)
56
+ raise_block_syntax_error if block_given?
57
+ !matches?(event_proc) && Proc === event_proc
58
+ end
18
59
 
19
- (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?
60
+ # @api private
61
+ # @return [String]
62
+ def failure_message
63
+ "expected #{@change_details.message} to have changed, but #{positive_failure_reason}"
20
64
  end
21
- alias == matches?
22
65
 
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
66
+ # @api private
67
+ # @return [String]
68
+ def failure_message_when_negated
69
+ "expected #{@change_details.message} not to have changed, but #{negative_failure_reason}"
27
70
  end
28
71
 
29
- def evaluate_value_proc
30
- case val = @value_proc.call
31
- when Enumerable, String
32
- val.dup
33
- else
34
- val
35
- end
72
+ # @api private
73
+ # @return [String]
74
+ def description
75
+ "change #{@change_details.message}"
36
76
  end
37
77
 
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 #{@expected_after.inspect}, 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
78
+ # @private
79
+ def supports_block_expectations?
80
+ true
52
81
  end
53
82
 
54
- def actual_delta
55
- @actual_after - @actual_before
83
+ private
84
+
85
+ def initialize(receiver=nil, message=nil, &block)
86
+ @change_details = ChangeDetails.new(receiver, message, &block)
56
87
  end
57
88
 
58
- def failure_message_for_should_not
59
- "#{message} should not have changed, but did change from #{@actual_before.inspect} to #{@actual_after.inspect}"
89
+ def raise_block_syntax_error
90
+ raise SyntaxError,
91
+ "The block passed to the `change` matcher must use `{ ... }` instead of do/end"
60
92
  end
61
93
 
62
- def by(expected_delta)
63
- @expected_delta = expected_delta
64
- self
94
+ def positive_failure_reason
95
+ return "was not given a block" unless Proc === @event_proc
96
+ "is still #{description_of @change_details.actual_before}"
65
97
  end
66
98
 
67
- def by_at_least(minimum)
68
- @minimum = minimum
69
- self
99
+ def negative_failure_reason
100
+ return "was not given a block" unless Proc === @event_proc
101
+ "did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
70
102
  end
103
+ end
71
104
 
72
- def by_at_most(maximum)
73
- @maximum = maximum
74
- self
105
+ # Used to specify a relative change.
106
+ # @api private
107
+ class ChangeRelatively
108
+ include Composable
109
+
110
+ def initialize(change_details, expected_delta, relativity, &comparer)
111
+ @change_details = change_details
112
+ @expected_delta = expected_delta
113
+ @relativity = relativity
114
+ @comparer = comparer
75
115
  end
76
116
 
77
- def to(to)
78
- @eval_after = true
79
- @expected_after = to
80
- self
117
+ # @private
118
+ def failure_message
119
+ "expected #{@change_details.message} to have changed #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}, but #{failure_reason}"
81
120
  end
82
121
 
83
- def from (before)
84
- @eval_before = true
85
- @expected_before = before
86
- self
122
+ # @private
123
+ def matches?(event_proc)
124
+ @event_proc = event_proc
125
+ return false unless Proc === event_proc
126
+ @change_details.perform_change(event_proc)
127
+ @comparer.call(@change_details.actual_delta)
128
+ end
129
+
130
+ # @private
131
+ def does_not_match?(event_proc)
132
+ raise NotImplementedError, "`expect { }.not_to change { }.#{@relativity}()` is not supported"
87
133
  end
88
134
 
135
+ # @private
89
136
  def description
90
- "change ##{message}"
137
+ "change #{@change_details.message} #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}"
138
+ end
139
+
140
+ # @private
141
+ def supports_block_expectations?
142
+ true
91
143
  end
92
144
 
93
- private
145
+ private
94
146
 
95
- def message
96
- @message || "result"
147
+ def failure_reason
148
+ return "was not given a block" unless Proc === @event_proc
149
+ "was changed by #{description_of @change_details.actual_delta}"
97
150
  end
151
+ end
98
152
 
99
- def change_expected?
100
- @expected_delta != 0
153
+ # @api private
154
+ # Base class for specifying a change from and/or to specific values.
155
+ class SpecificValuesChange
156
+ include Composable
157
+ # @private
158
+ MATCH_ANYTHING = ::Object.ancestors.last
159
+
160
+ def initialize(change_details, from, to)
161
+ @change_details = change_details
162
+ @expected_before = from
163
+ @expected_after = to
101
164
  end
102
165
 
103
- def changed?
104
- @actual_before != @actual_after
166
+ # @private
167
+ def matches?(event_proc)
168
+ @event_proc = event_proc
169
+ return false unless Proc === event_proc
170
+ @change_details.perform_change(event_proc)
171
+ @change_details.changed? && matches_before? && matches_after?
172
+ end
173
+
174
+ # @private
175
+ def description
176
+ "change #{@change_details.message} #{change_description}"
105
177
  end
106
178
 
179
+ # @private
180
+ def failure_message
181
+ return not_given_a_block_failure unless Proc === @event_proc
182
+ return before_value_failure unless matches_before?
183
+ return did_not_change_failure unless @change_details.changed?
184
+ after_value_failure
185
+ end
186
+
187
+ # @private
188
+ def supports_block_expectations?
189
+ true
190
+ end
191
+
192
+ private
193
+
107
194
  def matches_before?
108
- @eval_before ? expected_matches_actual?(@expected_before, @actual_before) : true
195
+ values_match?(@expected_before, @change_details.actual_before)
109
196
  end
110
197
 
111
198
  def matches_after?
112
- @eval_after ? expected_matches_actual?(@expected_after, @actual_after) : true
199
+ values_match?(@expected_after, @change_details.actual_after)
200
+ end
201
+
202
+ def before_value_failure
203
+ "expected #{@change_details.message} to have initially been #{description_of @expected_before}, but was #{description_of @change_details.actual_before}"
204
+ end
205
+
206
+ def after_value_failure
207
+ "expected #{@change_details.message} to have changed to #{description_of @expected_after}, but is now #{description_of @change_details.actual_after}"
208
+ end
209
+
210
+ def did_not_change_failure
211
+ "expected #{@change_details.message} to have changed #{change_description}, but did not change"
212
+ end
213
+
214
+ def did_change_failure
215
+ "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}"
216
+ end
217
+
218
+ def not_given_a_block_failure
219
+ "expected #{@change_details.message} to have changed #{change_description}, but was not given a block"
220
+ end
221
+ end
222
+
223
+ # @api private
224
+ # Used to specify a change from a specific value
225
+ # (and, optionally, to a specific value).
226
+ class ChangeFromValue < SpecificValuesChange
227
+ def initialize(change_details, expected_before)
228
+ @description_suffix = nil
229
+ super(change_details, expected_before, MATCH_ANYTHING)
230
+ end
231
+
232
+ # @api public
233
+ # Specifies the new value you expect.
234
+ def to(value)
235
+ @expected_after = value
236
+ @description_suffix = " to #{description_of value}"
237
+ self
238
+ end
239
+
240
+ # @private
241
+ def does_not_match?(event_proc)
242
+ if @description_suffix
243
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
244
+ end
245
+
246
+ @event_proc = event_proc
247
+ return false unless Proc === event_proc
248
+ @change_details.perform_change(event_proc)
249
+ !@change_details.changed? && matches_before?
250
+ end
251
+
252
+ # @private
253
+ def failure_message_when_negated
254
+ return not_given_a_block_failure unless Proc === @event_proc
255
+ return before_value_failure unless matches_before?
256
+ did_change_failure
257
+ end
258
+
259
+ private
260
+
261
+ def change_description
262
+ "from #{description_of @expected_before}#{@description_suffix}"
263
+ end
264
+ end
265
+
266
+ # @api private
267
+ # Used to specify a change to a specific value
268
+ # (and, optionally, from a specific value).
269
+ class ChangeToValue < SpecificValuesChange
270
+ def initialize(change_details, expected_after)
271
+ @description_suffix = nil
272
+ super(change_details, MATCH_ANYTHING, expected_after)
273
+ end
274
+
275
+ # @api public
276
+ # Specifies the original value.
277
+ def from(value)
278
+ @expected_before = value
279
+ @description_suffix = " from #{description_of value}"
280
+ self
281
+ end
282
+
283
+ # @private
284
+ def does_not_match?(event_proc)
285
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
113
286
  end
114
287
 
115
- def matches_expected_delta?
116
- @expected_delta ? (@actual_before + @expected_delta == @actual_after) : true
288
+ private
289
+
290
+ def change_description
291
+ "to #{description_of @expected_after}#{@description_suffix}"
117
292
  end
293
+ end
294
+
295
+ # @private
296
+ # Encapsulates the details of the before/after values.
297
+ class ChangeDetails
298
+ attr_reader :message, :actual_before, :actual_after
118
299
 
119
- def matches_min?
120
- @minimum ? (@actual_after - @actual_before >= @minimum) : true
300
+ def initialize(receiver=nil, message=nil, &block)
301
+ @message = message ? "##{message}" : "result"
302
+ @value_proc = block || lambda { receiver.__send__(message) }
121
303
  end
122
304
 
123
- def matches_max?
124
- @maximum ? (@actual_after - @actual_before <= @maximum) : true
305
+ def perform_change(event_proc)
306
+ @actual_before = evaluate_value_proc
307
+ event_proc.call
308
+ @actual_after = evaluate_value_proc
125
309
  end
126
310
 
127
- def expected_matches_actual?(expected, actual)
128
- expected === actual
311
+ def changed?
312
+ @actual_before != @actual_after
313
+ end
314
+
315
+ def actual_delta
316
+ @actual_after - @actual_before
317
+ end
318
+
319
+ private
320
+
321
+ def evaluate_value_proc
322
+ case val = @value_proc.call
323
+ when IO # enumerable, but we don't want to dup it.
324
+ val
325
+ when Enumerable, String
326
+ val.dup
327
+ else
328
+ val
329
+ end
129
330
  end
130
331
  end
131
332
  end
@@ -0,0 +1,122 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Base class for `and` and `or` compound matchers.
6
+ class Compound < BaseMatcher
7
+ # @private
8
+ attr_reader :matcher_1, :matcher_2
9
+
10
+ def initialize(matcher_1, matcher_2)
11
+ @matcher_1 = matcher_1
12
+ @matcher_2 = matcher_2
13
+ end
14
+
15
+ # @private
16
+ def does_not_match?(actual)
17
+ raise NotImplementedError,
18
+ "`expect(...).not_to matcher.#{conjunction} matcher` is not supported"
19
+ end
20
+
21
+ # @api private
22
+ # @return [String]
23
+ def description
24
+ singleline_message(matcher_1.description, matcher_2.description)
25
+ end
26
+
27
+ private
28
+
29
+ def initialize_copy(other)
30
+ @matcher_1 = @matcher_1.clone
31
+ @matcher_2 = @matcher_2.clone
32
+ super
33
+ end
34
+
35
+ def indent_multiline_message(message)
36
+ message.lines.map do |line|
37
+ line =~ /\S/ ? ' ' + line : line
38
+ end.join
39
+ end
40
+
41
+ def compound_failure_message
42
+ message_1 = matcher_1.failure_message
43
+ message_2 = matcher_2.failure_message
44
+
45
+ if multiline?(message_1) || multiline?(message_2)
46
+ multiline_message(message_1, message_2)
47
+ else
48
+ singleline_message(message_1, message_2)
49
+ end
50
+ end
51
+
52
+ def multiline_message(message_1, message_2)
53
+ [
54
+ indent_multiline_message(message_1.sub(/\n+\z/, '')),
55
+ "...#{conjunction}:",
56
+ indent_multiline_message(message_2.sub(/\A\n+/, ''))
57
+ ].join("\n\n")
58
+ end
59
+
60
+ def multiline?(message)
61
+ message.lines.count > 1
62
+ end
63
+
64
+ def singleline_message(message_1, message_2)
65
+ [message_1, conjunction, message_2].join(' ')
66
+ end
67
+
68
+ # @api public
69
+ # Matcher used to represent a compound `and` expectation.
70
+ class And < self
71
+
72
+ # @api private
73
+ # @return [String]
74
+ def failure_message
75
+ if @matcher_1_matches
76
+ matcher_2.failure_message
77
+ elsif @matcher_2_matches
78
+ matcher_1.failure_message
79
+ else
80
+ compound_failure_message
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def match(expected, actual)
87
+ @matcher_1_matches = matcher_1.matches?(actual)
88
+ @matcher_2_matches = matcher_2.matches?(actual)
89
+
90
+ @matcher_1_matches && @matcher_2_matches
91
+ end
92
+
93
+ def conjunction
94
+ "and"
95
+ end
96
+ end
97
+
98
+ # @api public
99
+ # Matcher used to represent a compound `or` expectation.
100
+ class Or < self
101
+
102
+ # @api private
103
+ # @return [String]
104
+ def failure_message
105
+ compound_failure_message
106
+ end
107
+
108
+ private
109
+
110
+ def match(expected, actual)
111
+ matcher_1.matches?(actual) || matcher_2.matches?(actual)
112
+ end
113
+
114
+ def conjunction
115
+ "or"
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+