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.
- data.tar.gz.sig +2 -2
- data/.yardopts +1 -0
- data/Changelog.md +138 -0
- data/README.md +75 -8
- data/features/README.md +2 -2
- data/features/built_in_matchers/README.md +12 -9
- data/features/built_in_matchers/comparisons.feature +2 -2
- data/features/built_in_matchers/contain_exactly.feature +46 -0
- data/features/built_in_matchers/expect_change.feature +2 -2
- data/features/built_in_matchers/include.feature +0 -48
- data/features/built_in_matchers/output.feature +70 -0
- data/features/composing_matchers.feature +250 -0
- data/features/compound_expectations.feature +45 -0
- data/features/custom_matchers/access_running_example.feature +1 -1
- data/features/custom_matchers/define_matcher.feature +6 -6
- data/features/custom_matchers/define_matcher_outside_rspec.feature +4 -8
- data/features/test_frameworks/{test_unit.feature → minitest.feature} +11 -11
- data/lib/rspec/expectations.rb +31 -42
- data/lib/rspec/expectations/diff_presenter.rb +141 -0
- data/lib/rspec/expectations/differ.rb +22 -132
- data/lib/rspec/expectations/encoded_string.rb +56 -0
- data/lib/rspec/expectations/expectation_target.rb +0 -30
- data/lib/rspec/expectations/fail_with.rb +2 -2
- data/lib/rspec/expectations/handler.rb +128 -31
- data/lib/rspec/expectations/minitest_integration.rb +16 -0
- data/lib/rspec/expectations/syntax.rb +4 -58
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers.rb +298 -60
- data/lib/rspec/matchers/aliased_matcher.rb +35 -0
- data/lib/rspec/matchers/built_in.rb +37 -33
- data/lib/rspec/matchers/built_in/base_matcher.rb +25 -15
- data/lib/rspec/matchers/built_in/be.rb +23 -31
- data/lib/rspec/matchers/built_in/be_between.rb +55 -0
- data/lib/rspec/matchers/built_in/be_within.rb +15 -11
- data/lib/rspec/matchers/built_in/change.rb +198 -81
- data/lib/rspec/matchers/built_in/compound.rb +106 -0
- data/lib/rspec/matchers/built_in/contain_exactly.rb +245 -0
- data/lib/rspec/matchers/built_in/eq.rb +43 -4
- data/lib/rspec/matchers/built_in/eql.rb +2 -2
- data/lib/rspec/matchers/built_in/equal.rb +35 -18
- data/lib/rspec/matchers/built_in/has.rb +16 -15
- data/lib/rspec/matchers/built_in/include.rb +45 -23
- data/lib/rspec/matchers/built_in/match.rb +6 -3
- data/lib/rspec/matchers/built_in/operators.rb +103 -0
- data/lib/rspec/matchers/built_in/output.rb +108 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +9 -15
- data/lib/rspec/matchers/built_in/respond_to.rb +5 -4
- data/lib/rspec/matchers/built_in/satisfy.rb +4 -3
- data/lib/rspec/matchers/built_in/start_and_end_with.rb +37 -16
- data/lib/rspec/matchers/built_in/throw_symbol.rb +6 -5
- data/lib/rspec/matchers/built_in/yield.rb +31 -29
- data/lib/rspec/matchers/composable.rb +138 -0
- data/lib/rspec/matchers/dsl.rb +330 -0
- data/lib/rspec/matchers/generated_descriptions.rb +6 -6
- data/lib/rspec/matchers/matcher_delegator.rb +33 -0
- data/lib/rspec/matchers/pretty.rb +13 -2
- data/spec/rspec/expectations/{differ_spec.rb → diff_presenter_spec.rb} +56 -36
- data/spec/rspec/expectations/encoded_string_spec.rb +74 -0
- data/spec/rspec/expectations/extensions/kernel_spec.rb +11 -11
- data/spec/rspec/expectations/fail_with_spec.rb +8 -8
- data/spec/rspec/expectations/handler_spec.rb +27 -49
- data/spec/rspec/expectations/minitest_integration_spec.rb +27 -0
- data/spec/rspec/expectations/syntax_spec.rb +17 -67
- data/spec/rspec/expectations_spec.rb +7 -52
- data/spec/rspec/matchers/aliased_matcher_spec.rb +48 -0
- data/spec/rspec/matchers/aliases_spec.rb +449 -0
- data/spec/rspec/matchers/{base_matcher_spec.rb → built_in/base_matcher_spec.rb} +24 -3
- data/spec/rspec/matchers/built_in/be_between_spec.rb +159 -0
- data/spec/rspec/matchers/{be_instance_of_spec.rb → built_in/be_instance_of_spec.rb} +0 -0
- data/spec/rspec/matchers/{be_kind_of_spec.rb → built_in/be_kind_of_spec.rb} +0 -0
- data/spec/rspec/matchers/{be_spec.rb → built_in/be_spec.rb} +76 -32
- data/spec/rspec/matchers/{be_within_spec.rb → built_in/be_within_spec.rb} +6 -2
- data/spec/rspec/matchers/{change_spec.rb → built_in/change_spec.rb} +310 -69
- data/spec/rspec/matchers/built_in/compound_spec.rb +292 -0
- data/spec/rspec/matchers/built_in/contain_exactly_spec.rb +441 -0
- data/spec/rspec/matchers/{cover_spec.rb → built_in/cover_spec.rb} +0 -0
- data/spec/rspec/matchers/built_in/eq_spec.rb +156 -0
- data/spec/rspec/matchers/{eql_spec.rb → built_in/eql_spec.rb} +2 -2
- data/spec/rspec/matchers/built_in/equal_spec.rb +106 -0
- data/spec/rspec/matchers/{exist_spec.rb → built_in/exist_spec.rb} +1 -1
- data/spec/rspec/matchers/{has_spec.rb → built_in/has_spec.rb} +39 -0
- data/spec/rspec/matchers/{include_spec.rb → built_in/include_spec.rb} +118 -109
- data/spec/rspec/matchers/{match_spec.rb → built_in/match_spec.rb} +30 -2
- data/spec/rspec/matchers/{operator_matcher_spec.rb → built_in/operators_spec.rb} +26 -26
- data/spec/rspec/matchers/built_in/output_spec.rb +165 -0
- data/spec/rspec/matchers/{raise_error_spec.rb → built_in/raise_error_spec.rb} +81 -11
- data/spec/rspec/matchers/{respond_to_spec.rb → built_in/respond_to_spec.rb} +0 -0
- data/spec/rspec/matchers/{satisfy_spec.rb → built_in/satisfy_spec.rb} +0 -0
- data/spec/rspec/matchers/{start_with_end_with_spec.rb → built_in/start_and_end_with_spec.rb} +82 -15
- data/spec/rspec/matchers/{throw_symbol_spec.rb → built_in/throw_symbol_spec.rb} +29 -10
- data/spec/rspec/matchers/{yield_spec.rb → built_in/yield_spec.rb} +90 -0
- data/spec/rspec/matchers/configuration_spec.rb +7 -39
- data/spec/rspec/matchers/description_generation_spec.rb +22 -6
- data/spec/rspec/matchers/dsl_spec.rb +838 -0
- data/spec/rspec/matchers/legacy_spec.rb +101 -0
- data/spec/rspec/matchers_spec.rb +74 -0
- data/spec/spec_helper.rb +35 -21
- data/spec/support/shared_examples.rb +26 -4
- metadata +172 -116
- metadata.gz.sig +3 -4
- checksums.yaml +0 -15
- checksums.yaml.gz.sig +0 -0
- data/features/built_in_matchers/match_array.feature +0 -37
- data/lib/rspec/expectations/errors.rb +0 -9
- data/lib/rspec/expectations/extensions.rb +0 -1
- data/lib/rspec/expectations/extensions/object.rb +0 -29
- data/lib/rspec/matchers/built_in/match_array.rb +0 -51
- data/lib/rspec/matchers/compatibility.rb +0 -14
- data/lib/rspec/matchers/matcher.rb +0 -301
- data/lib/rspec/matchers/method_missing.rb +0 -12
- data/lib/rspec/matchers/operator_matcher.rb +0 -99
- data/lib/rspec/matchers/test_unit_integration.rb +0 -11
- data/spec/rspec/matchers/eq_spec.rb +0 -60
- data/spec/rspec/matchers/equal_spec.rb +0 -78
- data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
- data/spec/rspec/matchers/match_array_spec.rb +0 -194
- data/spec/rspec/matchers/matcher_spec.rb +0 -706
- data/spec/rspec/matchers/matchers_spec.rb +0 -36
- data/spec/rspec/matchers/method_missing_spec.rb +0 -28
- data/spec/support/classes.rb +0 -56
- data/spec/support/in_sub_process.rb +0 -37
- data/spec/support/ruby_version.rb +0 -10
@@ -0,0 +1,292 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RSpec::Matchers::BuiltIn
|
4
|
+
describe Compound do
|
5
|
+
context "when used as a composable matcher" do
|
6
|
+
it 'can pass' do
|
7
|
+
expect(["food", "barn"]).to include(
|
8
|
+
a_string_starting_with("f").and(ending_with("d")),
|
9
|
+
a_string_starting_with("b").and(ending_with("n"))
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can fail' do
|
14
|
+
expect {
|
15
|
+
expect(["foo", "bar"]).to include(
|
16
|
+
a_string_starting_with("f").and(ending_with("d")),
|
17
|
+
a_string_starting_with("b").and(ending_with("n"))
|
18
|
+
)
|
19
|
+
}.to fail_matching('expected ["foo", "bar"] to include (a string starting with "f" and ending with "d") and (a string starting with "b" and ending with "n")')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'provides a description' do
|
23
|
+
matcher = include(
|
24
|
+
a_string_starting_with("f").and(ending_with("d")),
|
25
|
+
a_string_starting_with("b").and(ending_with("n"))
|
26
|
+
)
|
27
|
+
|
28
|
+
expect(matcher.description).to eq('include (a string starting with "f" and ending with "d") and (a string starting with "b" and ending with "n")')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "expect(...).to matcher.and(other_matcher)" do
|
33
|
+
|
34
|
+
it_behaves_like "an RSpec matcher", :valid_value => 3, :invalid_value => 4 do
|
35
|
+
let(:matcher) { eq(3).and be <= 3 }
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when both matchers pass' do
|
39
|
+
it 'passes' do
|
40
|
+
expect(3).to eq(3).and be >= 2
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'has a description composed of both matcher descriptions' do
|
45
|
+
matcher = eq(3).and be >= 2
|
46
|
+
expect(3).to matcher
|
47
|
+
expect(matcher.description).to eq("eq 3 and be >= 2")
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'when only the first matcher fails' do
|
51
|
+
it "fails with the first matcher's failure message" do
|
52
|
+
expect {
|
53
|
+
expect(3).to eq(4).and be >= 2
|
54
|
+
}.to fail_with(dedent <<-EOS)
|
55
|
+
|
|
56
|
+
|expected: 4
|
57
|
+
| got: 3
|
58
|
+
|
|
59
|
+
|(compared using ==)
|
60
|
+
|
|
61
|
+
EOS
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when only the second matcher fails' do
|
66
|
+
it "fails with the second matcher's failure message" do
|
67
|
+
expect {
|
68
|
+
expect(3).to be_kind_of(Integer).and eq(4)
|
69
|
+
}.to fail_with(dedent <<-EOS)
|
70
|
+
|
|
71
|
+
|expected: 4
|
72
|
+
| got: 3
|
73
|
+
|
|
74
|
+
|(compared using ==)
|
75
|
+
|
|
76
|
+
EOS
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when both mathers fail" do
|
81
|
+
context "when both matchers have multi-line failure messages" do
|
82
|
+
it 'fails with a well formatted message containing both sub-messages' do
|
83
|
+
expect {
|
84
|
+
expect(3).to eq(4).and be >= 8
|
85
|
+
}.to fail_with(dedent <<-EOS)
|
86
|
+
|
|
87
|
+
| expected: 4
|
88
|
+
| got: 3
|
89
|
+
|
|
90
|
+
| (compared using ==)
|
91
|
+
|
|
92
|
+
|...and:
|
93
|
+
|
|
94
|
+
| expected: >= 8
|
95
|
+
| got: 3
|
96
|
+
EOS
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "when both matchers have single-line failure messages" do
|
101
|
+
it 'fails with a single-line failure message' do
|
102
|
+
expect {
|
103
|
+
expect("foo").to start_with("a").and end_with("z")
|
104
|
+
}.to fail_with('expected "foo" to start with "a" and expected "foo" to end with "z"')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "when the first matcher has a multi-line failure message" do
|
109
|
+
it 'fails with a well formatted message containing both sub-messages' do
|
110
|
+
expect {
|
111
|
+
expect("foo").to eq(4).and end_with("z")
|
112
|
+
}.to fail_with(dedent <<-EOS)
|
113
|
+
|
|
114
|
+
| expected: 4
|
115
|
+
| got: "foo"
|
116
|
+
|
|
117
|
+
| (compared using ==)
|
118
|
+
|
|
119
|
+
|...and:
|
120
|
+
|
|
121
|
+
| expected "foo" to end with "z"
|
122
|
+
EOS
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "when the second matcher has a multi-line failure message" do
|
127
|
+
it 'fails with a well formatted message containing both sub-messages' do
|
128
|
+
expect {
|
129
|
+
expect("foo").to end_with("z").and eq(4)
|
130
|
+
}.to fail_with(dedent <<-EOS)
|
131
|
+
| expected "foo" to end with "z"
|
132
|
+
|
|
133
|
+
|...and:
|
134
|
+
|
|
135
|
+
| expected: 4
|
136
|
+
| got: "foo"
|
137
|
+
|
|
138
|
+
| (compared using ==)
|
139
|
+
|
|
140
|
+
EOS
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "expect(...).not_to matcher.and(other_matcher)" do
|
147
|
+
it "is not supported" do
|
148
|
+
expect {
|
149
|
+
expect(3).not_to eq(2).and be > 2
|
150
|
+
}.to raise_error(NotImplementedError, /matcher.and matcher` is not supported/)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "expect(...).to matcher.or(other_matcher)" do
|
155
|
+
it_behaves_like "an RSpec matcher", :valid_value => 3, :invalid_value => 5 do
|
156
|
+
let(:matcher) { eq(3).or eq(4) }
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'has a description composed of both matcher descriptions' do
|
160
|
+
matcher = eq(3).or eq(4)
|
161
|
+
expect(3).to matcher
|
162
|
+
expect(matcher.description).to eq("eq 3 or eq 4")
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'when both matchers pass' do
|
166
|
+
it 'passes' do
|
167
|
+
expect("foo").to start_with("f").or end_with("o")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when only the first matcher passes' do
|
172
|
+
it 'passes' do
|
173
|
+
expect("foo").to start_with("f").or end_with("z")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'when only the last matcher passes' do
|
178
|
+
it 'passes' do
|
179
|
+
expect("foo").to start_with("a").or end_with("o")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context 'when both matchers fail' do
|
184
|
+
context "when both matchers have multi-line failure messages" do
|
185
|
+
it 'fails with a well formatted message containing both sub-messages' do
|
186
|
+
expect {
|
187
|
+
expect(3).to eq(4).or be >= 8
|
188
|
+
}.to fail_with(dedent <<-EOS)
|
189
|
+
|
|
190
|
+
| expected: 4
|
191
|
+
| got: 3
|
192
|
+
|
|
193
|
+
| (compared using ==)
|
194
|
+
|
|
195
|
+
|...or:
|
196
|
+
|
|
197
|
+
| expected: >= 8
|
198
|
+
| got: 3
|
199
|
+
EOS
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "when both matchers have single-line failure messages" do
|
204
|
+
it 'fails with a single-line failure message' do
|
205
|
+
expect {
|
206
|
+
expect("foo").to start_with("a").or end_with("z")
|
207
|
+
}.to fail_with('expected "foo" to start with "a" or expected "foo" to end with "z"')
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "when the first matcher has a multi-line failure message" do
|
212
|
+
it 'fails with a well formatted message containing both sub-messages' do
|
213
|
+
expect {
|
214
|
+
expect("foo").to eq(4).or end_with("z")
|
215
|
+
}.to fail_with(dedent <<-EOS)
|
216
|
+
|
|
217
|
+
| expected: 4
|
218
|
+
| got: "foo"
|
219
|
+
|
|
220
|
+
| (compared using ==)
|
221
|
+
|
|
222
|
+
|...or:
|
223
|
+
|
|
224
|
+
| expected "foo" to end with "z"
|
225
|
+
EOS
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context "when the second matcher has a multi-line failure message" do
|
230
|
+
it 'fails with a well formatted message containing both sub-messages' do
|
231
|
+
expect {
|
232
|
+
expect("foo").to end_with("z").or eq(4)
|
233
|
+
}.to fail_with(dedent <<-EOS)
|
234
|
+
| expected "foo" to end with "z"
|
235
|
+
|
|
236
|
+
|...or:
|
237
|
+
|
|
238
|
+
| expected: 4
|
239
|
+
| got: "foo"
|
240
|
+
|
|
241
|
+
| (compared using ==)
|
242
|
+
|
|
243
|
+
EOS
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context "when chaining many matchers together" do
|
250
|
+
it 'can pass appropriately' do
|
251
|
+
matcher = start_with("f").and end_with("z").or end_with("o")
|
252
|
+
expect("foo").to matcher
|
253
|
+
expect(matcher.description).to eq('start with "f" and end with "z" or end with "o"')
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'fails with a complete message' do
|
257
|
+
expect {
|
258
|
+
expect(3).to eq(1).and eq(2).and eq(3).and eq(4)
|
259
|
+
}.to fail_with(dedent <<-EOS)
|
260
|
+
|
|
261
|
+
| expected: 1
|
262
|
+
| got: 3
|
263
|
+
|
|
264
|
+
| (compared using ==)
|
265
|
+
|
|
266
|
+
|...and:
|
267
|
+
|
|
268
|
+
| expected: 2
|
269
|
+
| got: 3
|
270
|
+
|
|
271
|
+
| (compared using ==)
|
272
|
+
|
|
273
|
+
| ...and:
|
274
|
+
|
|
275
|
+
| expected: 4
|
276
|
+
| got: 3
|
277
|
+
|
|
278
|
+
| (compared using ==)
|
279
|
+
|
|
280
|
+
EOS
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
describe "expect(...).not_to matcher.or(other_matcher)" do
|
285
|
+
it "is not supported" do
|
286
|
+
expect {
|
287
|
+
expect(3).not_to eq(2).or be > 2
|
288
|
+
}.to raise_error(NotImplementedError, /matcher.or matcher` is not supported/)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
@@ -0,0 +1,441 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
class UnsortableObject
|
5
|
+
def initialize(id)
|
6
|
+
@id = id
|
7
|
+
end
|
8
|
+
|
9
|
+
def inspect
|
10
|
+
@id.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# AR::Relation is meant to act like a collection, but does
|
19
|
+
# not include `Enumerable`. It does implement `to_ary`.
|
20
|
+
class FakeActiveRecordRelation
|
21
|
+
def initialize(records)
|
22
|
+
@records = records
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_ary
|
26
|
+
@records
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "should =~ array", :uses_should do
|
31
|
+
it "passes a valid positive expectation" do
|
32
|
+
[1, 2].should =~ [2, 1]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "fails an invalid positive expectation" do
|
36
|
+
expect {
|
37
|
+
[1, 2, 3].should =~ [2, 1]
|
38
|
+
}.to fail_with(/expected collection contained/)
|
39
|
+
end
|
40
|
+
|
41
|
+
context "when the array defines a `=~` method" do
|
42
|
+
it 'delegates to that method rather than using the contain_exactly matcher' do
|
43
|
+
array = []
|
44
|
+
def array.=~(other)
|
45
|
+
other == :foo
|
46
|
+
end
|
47
|
+
|
48
|
+
array.should =~ :foo
|
49
|
+
expect {
|
50
|
+
array.should =~ :bar
|
51
|
+
}.to fail_with(/expected: :bar/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when the array defines a `send` method' do
|
56
|
+
it 'still works' do
|
57
|
+
array = [1, 2]
|
58
|
+
def array.send; :sent; end
|
59
|
+
|
60
|
+
array.should =~ array
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "should_not =~ [:with, :multiple, :args]", :uses_should do
|
66
|
+
it "is not supported" do
|
67
|
+
expect {
|
68
|
+
[1,2,3].should_not =~ [1,2,3]
|
69
|
+
}.to fail_with(/`contain_exactly` does not support negation/)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "using contain_exactly with expect" do
|
74
|
+
it "passes a valid positive expectation" do
|
75
|
+
expect([1, 2]).to contain_exactly(2, 1)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "fails an invalid positive expectation" do
|
79
|
+
expect {
|
80
|
+
expect([1, 2, 3]).to contain_exactly(2, 1)
|
81
|
+
}.to fail_with(/expected collection contained/)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "expect(array).to contain_exactly(*other_array)" do
|
86
|
+
it_behaves_like "an RSpec matcher", :valid_value => [1, 2], :invalid_value => [1] do
|
87
|
+
let(:matcher) { contain_exactly(2, 1) }
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'is also exposed as `match_array` (with unsplatted args)' do
|
91
|
+
expect([1, 2, 3]).to match_array([3, 2, 1])
|
92
|
+
end
|
93
|
+
|
94
|
+
it "passes if target contains all items" do
|
95
|
+
expect([1,2,3]).to contain_exactly(1,2,3)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "passes if target contains all items out of order" do
|
99
|
+
expect([1,3,2]).to contain_exactly(1,2,3)
|
100
|
+
end
|
101
|
+
|
102
|
+
def timeout_if_not_debugging(time)
|
103
|
+
return yield if defined?(::Debugger)
|
104
|
+
Timeout.timeout(time) { yield }
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'fails a match of 11 items with duplicates in a reasonable amount of time' do
|
108
|
+
timeout_if_not_debugging(0.1) do
|
109
|
+
expected = [0, 1, 1, 3, 3, 3, 4, 4, 8, 8, 9 ]
|
110
|
+
actual = [ 1, 2, 3, 3, 3, 3, 7, 8, 8, 9, 9]
|
111
|
+
|
112
|
+
expect {
|
113
|
+
expect(actual).to contain_exactly(*expected)
|
114
|
+
}.to fail_matching("the missing elements were: [0, 1, 4, 4]")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it "fails if target includes extra items" do
|
119
|
+
expect {
|
120
|
+
expect([1,2,3,4]).to contain_exactly(1,2,3)
|
121
|
+
}.to fail_with(<<-MESSAGE)
|
122
|
+
expected collection contained: [1, 2, 3]
|
123
|
+
actual collection contained: [1, 2, 3, 4]
|
124
|
+
the extra elements were: [4]
|
125
|
+
MESSAGE
|
126
|
+
end
|
127
|
+
|
128
|
+
it "fails if target is missing items" do
|
129
|
+
expect {
|
130
|
+
expect([1,2]).to contain_exactly(1,2,3)
|
131
|
+
}.to fail_with(<<-MESSAGE)
|
132
|
+
expected collection contained: [1, 2, 3]
|
133
|
+
actual collection contained: [1, 2]
|
134
|
+
the missing elements were: [3]
|
135
|
+
MESSAGE
|
136
|
+
end
|
137
|
+
|
138
|
+
it "fails if target is missing items and has extra items" do
|
139
|
+
expect {
|
140
|
+
expect([1,2,4]).to contain_exactly(1,2,3)
|
141
|
+
}.to fail_with(<<-MESSAGE)
|
142
|
+
expected collection contained: [1, 2, 3]
|
143
|
+
actual collection contained: [1, 2, 4]
|
144
|
+
the missing elements were: [3]
|
145
|
+
the extra elements were: [4]
|
146
|
+
MESSAGE
|
147
|
+
end
|
148
|
+
|
149
|
+
it "sorts items in the error message if they all respond to <=>" do
|
150
|
+
expect {
|
151
|
+
expect([6,2,1,5]).to contain_exactly(4,1,2,3)
|
152
|
+
}.to fail_with(<<-MESSAGE)
|
153
|
+
expected collection contained: [1, 2, 3, 4]
|
154
|
+
actual collection contained: [1, 2, 5, 6]
|
155
|
+
the missing elements were: [3, 4]
|
156
|
+
the extra elements were: [5, 6]
|
157
|
+
MESSAGE
|
158
|
+
end
|
159
|
+
|
160
|
+
it "does not sort items in the error message if they don't all respond to <=>" do
|
161
|
+
expect {
|
162
|
+
expect([UnsortableObject.new(2), UnsortableObject.new(1)]).to contain_exactly(UnsortableObject.new(4), UnsortableObject.new(3))
|
163
|
+
}.to fail_with(<<-MESSAGE)
|
164
|
+
expected collection contained: [4, 3]
|
165
|
+
actual collection contained: [2, 1]
|
166
|
+
the missing elements were: [4, 3]
|
167
|
+
the extra elements were: [2, 1]
|
168
|
+
MESSAGE
|
169
|
+
end
|
170
|
+
|
171
|
+
it "accurately reports extra elements when there are duplicates" do
|
172
|
+
expect {
|
173
|
+
expect([1,1,1,5]).to contain_exactly(1,5)
|
174
|
+
}.to fail_with(<<-MESSAGE)
|
175
|
+
expected collection contained: [1, 5]
|
176
|
+
actual collection contained: [1, 1, 1, 5]
|
177
|
+
the extra elements were: [1, 1]
|
178
|
+
MESSAGE
|
179
|
+
end
|
180
|
+
|
181
|
+
it "accurately reports missing elements when there are duplicates" do
|
182
|
+
expect {
|
183
|
+
expect([1,5]).to contain_exactly(1,1,5)
|
184
|
+
}.to fail_with(<<-MESSAGE)
|
185
|
+
expected collection contained: [1, 1, 5]
|
186
|
+
actual collection contained: [1, 5]
|
187
|
+
the missing elements were: [1]
|
188
|
+
MESSAGE
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "expect(...).not_to contain_exactly(:with, :multiple, :args]" do
|
193
|
+
it "is not supported" do
|
194
|
+
expect {
|
195
|
+
expect([1,2,3]).not_to contain_exactly(1,2,3)
|
196
|
+
}.to fail_with(/`contain_exactly` does not support negation/)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "matching against things that aren't arrays" do
|
201
|
+
it "fails with nil and the expected error message is given" do
|
202
|
+
expect {
|
203
|
+
expect(nil).to contain_exactly(1, 2, 3)
|
204
|
+
}.to fail_with(/expected a collection/)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "fails with a float and the expected error message is given" do
|
208
|
+
expect {
|
209
|
+
expect(3.7).to contain_exactly(1, 2, 3)
|
210
|
+
}.to fail_with(/expected a collection/)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "fails with a string and the expected error message is given" do
|
214
|
+
expect {
|
215
|
+
expect("I like turtles").to contain_exactly(1, 2, 3)
|
216
|
+
}.to fail_with(/expected a collection/)
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'works with other collection types' do
|
220
|
+
expect(Set.new([3, 2, 1])).to contain_exactly(1, 2, 3)
|
221
|
+
expect {
|
222
|
+
expect(Set.new([3, 2, 1])).to contain_exactly(1, 2)
|
223
|
+
}.to fail_matching("expected collection contained: [1, 2]")
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'works with non-enumerables that implement `to_ary`' do
|
227
|
+
relation = FakeActiveRecordRelation.new([1, 2, 3])
|
228
|
+
expect(relation).to contain_exactly(2, 1, 3)
|
229
|
+
expect {
|
230
|
+
expect(relation).to contain_exactly(1, 2)
|
231
|
+
}.to fail_matching("expected collection contained: [1, 2]")
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "Composing `contain_exactly` with other matchers" do
|
236
|
+
describe "expect(...).to contain_exactly(matcher, matcher])" do
|
237
|
+
it 'passes when the array matches the matchers in the same order' do
|
238
|
+
expect(["food", "barn"]).to contain_exactly(
|
239
|
+
a_string_matching(/foo/),
|
240
|
+
a_string_matching(/bar/)
|
241
|
+
)
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'passes when the array matches the matchers in a different order' do
|
245
|
+
expect(["food", "barn"]).to contain_exactly(
|
246
|
+
a_string_matching(/bar/),
|
247
|
+
a_string_matching(/foo/)
|
248
|
+
)
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'fails with a useful message when there is an extra element' do
|
252
|
+
expect {
|
253
|
+
expect(["food", "barn", "goo"]).to contain_exactly(
|
254
|
+
a_string_matching(/bar/),
|
255
|
+
a_string_matching(/foo/)
|
256
|
+
)
|
257
|
+
}.to fail_with(dedent <<-EOS)
|
258
|
+
|expected collection contained: [(a string matching /bar/), (a string matching /foo/)]
|
259
|
+
|actual collection contained: ["barn", "food", "goo"]
|
260
|
+
|the extra elements were: ["goo"]
|
261
|
+
|
|
262
|
+
EOS
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'fails with a useful message when there is a missing element' do
|
266
|
+
expect {
|
267
|
+
expect(["food", "barn"]).to contain_exactly(
|
268
|
+
a_string_matching(/bar/),
|
269
|
+
a_string_matching(/foo/),
|
270
|
+
a_string_matching(/goo/)
|
271
|
+
)
|
272
|
+
}.to fail_with(dedent <<-EOS)
|
273
|
+
|expected collection contained: [(a string matching /bar/), (a string matching /foo/), (a string matching /goo/)]
|
274
|
+
|actual collection contained: ["barn", "food"]
|
275
|
+
|the missing elements were: [(a string matching /goo/)]
|
276
|
+
|
|
277
|
+
EOS
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'pairs up the items in order to minimize the number of unpaired items' do
|
281
|
+
expect {
|
282
|
+
expect(["fool", "food", "good"]).to contain_exactly(/foo/, /fool/, /poo/)
|
283
|
+
}.to fail_with(dedent <<-EOS)
|
284
|
+
|expected collection contained: [/foo/, /fool/, /poo/]
|
285
|
+
|actual collection contained: ["food", "fool", "good"]
|
286
|
+
|the missing elements were: [/poo/]
|
287
|
+
|the extra elements were: ["good"]
|
288
|
+
|
|
289
|
+
EOS
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'provides a description' do
|
293
|
+
description = contain_exactly(a_string_matching(/bar/), a_string_matching(/foo/)).description
|
294
|
+
expect(description).to eq("contain exactly (a string matching /bar/) and (a string matching /foo/)")
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'when an earlier matcher matches more strictly than a later matcher' do
|
298
|
+
it 'works when the actual items match in the same order' do
|
299
|
+
expect(["food", "fool"]).to contain_exactly(a_string_matching(/foo/), a_string_matching(/fool/))
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'works when the actual items match in reverse order' do
|
303
|
+
expect(["fool", "food"]).to contain_exactly(a_string_matching(/foo/), a_string_matching(/fool/))
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'can handle multiple sets of overlapping matches' do
|
307
|
+
expect(["fool", "barn", "bare", "food"]).to contain_exactly(
|
308
|
+
a_string_matching(/bar/),
|
309
|
+
a_string_matching(/barn/),
|
310
|
+
a_string_matching(/foo/),
|
311
|
+
a_string_matching(/fool/)
|
312
|
+
)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
it "can use `a_value_within` and `a_string_starting_with` against multiple types of values" do
|
317
|
+
expect(["barn", 2.45]).to contain_exactly(
|
318
|
+
a_value_within(0.1).of(2.5),
|
319
|
+
a_string_starting_with("bar")
|
320
|
+
)
|
321
|
+
end
|
322
|
+
|
323
|
+
context 'when a later matcher matches more strictly than an earlier matcher' do
|
324
|
+
it 'works when the actual items match in the same order' do
|
325
|
+
expect(["fool", "food"]).to contain_exactly(a_string_matching(/fool/), a_string_matching(/foo/))
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'works when the actual items match in reverse order' do
|
329
|
+
expect(["food", "fool"]).to contain_exactly(a_string_matching(/fool/), a_string_matching(/foo/))
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
module RSpec
|
336
|
+
module Matchers
|
337
|
+
module BuiltIn
|
338
|
+
class ContainExactly
|
339
|
+
describe PairingsMaximizer do
|
340
|
+
it 'finds unmatched expected indexes' do
|
341
|
+
maximizer = PairingsMaximizer.new({ 0 => [], 1 => [0] }, { 0 => [1] })
|
342
|
+
expect(maximizer.solution.unmatched_expected_indexes).to eq([0])
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'finds unmatched actual indexes' do
|
346
|
+
maximizer = PairingsMaximizer.new({ 0 => [0] }, { 0 => [0], 1 => [] })
|
347
|
+
expect(maximizer.solution.unmatched_actual_indexes).to eq([1])
|
348
|
+
end
|
349
|
+
|
350
|
+
describe "finding indeterminate indexes" do
|
351
|
+
it 'does not include unmatched indexes' do
|
352
|
+
maximizer = PairingsMaximizer.new({ 0 => [], 1 => [0] }, { 0 => [1], 1 => [] })
|
353
|
+
|
354
|
+
expect(maximizer.solution.indeterminate_expected_indexes).not_to include(0)
|
355
|
+
expect(maximizer.solution.indeterminate_actual_indexes).not_to include(1)
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'does not include indexes that are reciprocally to exactly one index' do
|
359
|
+
maximizer = PairingsMaximizer.new({ 0 => [], 1 => [0] }, { 0 => [1], 1 => [0] })
|
360
|
+
|
361
|
+
expect(maximizer.solution.indeterminate_expected_indexes).not_to include(1)
|
362
|
+
expect(maximizer.solution.indeterminate_actual_indexes).not_to include(0)
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'includes indexes that have multiple matches' do
|
366
|
+
maximizer = PairingsMaximizer.new({ 0 => [0, 2], 1 => [0, 2], 2 => [] },
|
367
|
+
{ 0 => [0, 1], 1 => [], 2 => [0, 1] })
|
368
|
+
|
369
|
+
|
370
|
+
expect(maximizer.solution.indeterminate_expected_indexes).to include(0, 1)
|
371
|
+
expect(maximizer.solution.indeterminate_actual_indexes).to include(0, 2)
|
372
|
+
end
|
373
|
+
|
374
|
+
it 'includes indexes that have one match which has multiple matches' do
|
375
|
+
maximizer = PairingsMaximizer.new({ 0 => [0], 1 => [0], 2 => [1, 2] },
|
376
|
+
{ 0 => [0, 1], 1 => [2], 2 => [2] })
|
377
|
+
|
378
|
+
expect(maximizer.solution.indeterminate_expected_indexes).to include(0, 1)
|
379
|
+
expect(maximizer.solution.indeterminate_actual_indexes).to include(1, 2)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
describe "#unmatched_item_count" do
|
384
|
+
it 'returns the count of unmatched items' do
|
385
|
+
maximizer = PairingsMaximizer.new({ 0 => [1], 1 => [0] },
|
386
|
+
{ 0 => [1], 1 => [0] })
|
387
|
+
expect(maximizer.solution.unmatched_item_count).to eq(0)
|
388
|
+
|
389
|
+
maximizer = PairingsMaximizer.new({ 0 => [1], 1 => [0] },
|
390
|
+
{ 0 => [1], 1 => [0], 2 => [] })
|
391
|
+
expect(maximizer.solution.unmatched_item_count).to eq(1)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "#find_best_solution" do
|
396
|
+
matcher :produce_result do |unmatched_expected, unmatched_actual|
|
397
|
+
match do |result|
|
398
|
+
result.candidate? &&
|
399
|
+
result.unmatched_expected_indexes == unmatched_expected &&
|
400
|
+
result.unmatched_actual_indexes == unmatched_actual
|
401
|
+
end
|
402
|
+
|
403
|
+
failure_message do |result|
|
404
|
+
if result.candidate_result?
|
405
|
+
"expected a complete solution, but still had indeterminate indexes: " +
|
406
|
+
"expected: #{result.indeterminate_expected_indexes.inspect}; " +
|
407
|
+
"actual: #{result.indeterminate_actual_indexes.inspect}"
|
408
|
+
elsif result.unmatched_expected_indexes != unmatched_expected
|
409
|
+
"expected unmatched_expected_indexes: #{unmatched_expected.inspect} " +
|
410
|
+
"but got: #{result.unmatched_expected_indexes.inspect}"
|
411
|
+
elsif result.unmatched_actual_indexes != unmatched_actual
|
412
|
+
"expected unmatched_actual_indexes: #{unmatched_actual.inspect} " +
|
413
|
+
"but got: #{result.unmatched_actual_indexes.inspect}"
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
it 'returns no unmatched indexes when everything reciprocally matches one item' do
|
419
|
+
maximizer = PairingsMaximizer.new({ 0 => [1], 1 => [0] },
|
420
|
+
{ 0 => [1], 1 => [0] })
|
421
|
+
expect(maximizer.find_best_solution).to produce_result([], [])
|
422
|
+
end
|
423
|
+
|
424
|
+
it 'returns unmatched indexes for everything that has no matches' do
|
425
|
+
maximizer = PairingsMaximizer.new({ 0 => [], 1 => [0] },
|
426
|
+
{ 0 => [1], 1 => [] })
|
427
|
+
expect(maximizer.find_best_solution).to produce_result([0], [1])
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'searches the solution space for a perfectly matching solution' do
|
431
|
+
maximizer = PairingsMaximizer.new({ 0 => [0, 1], 1 => [0] },
|
432
|
+
{ 0 => [0, 1], 1 => [0] })
|
433
|
+
expect(maximizer.find_best_solution).to produce_result([], [])
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|