shoulda-matchers 2.6.0 → 2.6.1.rc1
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/Gemfile.lock +1 -1
- data/NEWS.md +34 -0
- data/README.md +14 -0
- data/features/activemodel_integration.feature +15 -0
- data/features/step_definitions/activemodel_steps.rb +21 -0
- data/gemfiles/3.0.gemfile.lock +1 -1
- data/gemfiles/3.1.gemfile.lock +1 -1
- data/gemfiles/3.2.gemfile.lock +1 -1
- data/gemfiles/4.0.0.gemfile.lock +1 -1
- data/gemfiles/4.0.1.gemfile.lock +1 -1
- data/gemfiles/4.1.gemfile.lock +1 -1
- data/lib/shoulda/matchers.rb +1 -0
- data/lib/shoulda/matchers/action_controller/callback_matcher.rb +11 -6
- data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +59 -95
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -18
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +10 -0
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +60 -18
- data/lib/shoulda/matchers/active_model/errors.rb +9 -7
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +4 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +24 -5
- data/lib/shoulda/matchers/doublespeak.rb +27 -0
- data/lib/shoulda/matchers/doublespeak/double.rb +74 -0
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +54 -0
- data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +27 -0
- data/lib/shoulda/matchers/doublespeak/object_double.rb +32 -0
- data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +30 -0
- data/lib/shoulda/matchers/doublespeak/structs.rb +8 -0
- data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +34 -0
- data/lib/shoulda/matchers/doublespeak/world.rb +38 -0
- data/lib/shoulda/matchers/independent/delegate_matcher.rb +112 -61
- data/lib/shoulda/matchers/integrations/test_unit.rb +8 -6
- data/lib/shoulda/matchers/rails_shim.rb +16 -0
- data/lib/shoulda/matchers/version.rb +1 -1
- data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +22 -19
- data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +174 -65
- data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +14 -0
- data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +553 -211
- data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +6 -0
- data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +22 -0
- data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +23 -4
- data/spec/shoulda/matchers/doublespeak/double_collection_spec.rb +102 -0
- data/spec/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +21 -0
- data/spec/shoulda/matchers/doublespeak/double_spec.rb +144 -0
- data/spec/shoulda/matchers/doublespeak/object_double_spec.rb +77 -0
- data/spec/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +40 -0
- data/spec/shoulda/matchers/doublespeak/stub_implementation_spec.rb +88 -0
- data/spec/shoulda/matchers/doublespeak/world_spec.rb +88 -0
- data/spec/shoulda/matchers/doublespeak_spec.rb +19 -0
- data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +105 -39
- data/spec/support/controller_builder.rb +18 -9
- data/spec/support/rails_versions.rb +4 -0
- metadata +34 -8
@@ -1,320 +1,662 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
shared_context 'for a generic attribute' do
|
5
|
+
def self.testing_values_of_option(option_name, &block)
|
6
|
+
[nil, true, false].each do |option_value|
|
7
|
+
context_name = "+ #{option_name}"
|
8
|
+
option_args = []
|
9
|
+
matches_or_not = ['matches', 'does not match']
|
10
|
+
to_or_not_to = [:to, :not_to]
|
11
|
+
|
12
|
+
unless option_value == nil
|
13
|
+
context_name << "(#{option_value})"
|
14
|
+
option_args = [option_value]
|
15
|
+
end
|
16
|
+
|
17
|
+
if option_value == false
|
18
|
+
matches_or_not.reverse!
|
19
|
+
to_or_not_to.reverse!
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
8
23
|
end
|
9
|
-
end
|
10
24
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
25
|
+
context 'against an integer attribute' do
|
26
|
+
it_behaves_like 'it supports in_array',
|
27
|
+
possible_values: (1..5).to_a,
|
28
|
+
zero: 0,
|
29
|
+
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_FIXNUM
|
30
|
+
|
31
|
+
it_behaves_like 'it supports in_range',
|
32
|
+
possible_values: 1..5,
|
33
|
+
zero: 0
|
34
|
+
|
35
|
+
context 'when attribute validates a range of values via custom validation' do
|
36
|
+
it 'matches ensuring the correct range and messages' do
|
37
|
+
expect_to_match_ensuring_range_and_messages(2..5, 2, 5)
|
38
|
+
expect_to_match_ensuring_range_and_messages(2...5, 2, 4)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_object(options = {}, &block)
|
43
|
+
build_object_with_generic_attribute(
|
44
|
+
options.merge(column_type: :integer, value: 1),
|
45
|
+
&block
|
46
|
+
)
|
47
|
+
end
|
16
48
|
|
17
|
-
|
49
|
+
def add_outside_value_to(values)
|
50
|
+
values + [values.last + 1]
|
51
|
+
end
|
18
52
|
end
|
19
|
-
end
|
20
53
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
54
|
+
context "against a float attribute" do
|
55
|
+
it_behaves_like 'it supports in_array',
|
56
|
+
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0],
|
57
|
+
zero: 0.0,
|
58
|
+
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_FIXNUM
|
59
|
+
|
60
|
+
it_behaves_like 'it supports in_range',
|
61
|
+
possible_values: 1.0..5.0,
|
62
|
+
zero: 0.0
|
63
|
+
|
64
|
+
def build_object(options = {}, &block)
|
65
|
+
build_object_with_generic_attribute(
|
66
|
+
options.merge(column_type: :float, value: 1.0),
|
67
|
+
&block
|
68
|
+
)
|
69
|
+
end
|
26
70
|
|
27
|
-
|
71
|
+
def add_outside_value_to(values)
|
72
|
+
values + [values.last + 1]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "against a decimal attribute" do
|
77
|
+
it_behaves_like 'it supports in_array',
|
78
|
+
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0].map { |number|
|
79
|
+
BigDecimal.new(number.to_s)
|
80
|
+
},
|
81
|
+
zero: BigDecimal.new('0.0'),
|
82
|
+
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DECIMAL
|
83
|
+
|
84
|
+
it_behaves_like 'it supports in_range',
|
85
|
+
possible_values: BigDecimal.new('1.0') .. BigDecimal.new('5.0'),
|
86
|
+
zero: BigDecimal.new('0.0')
|
87
|
+
|
88
|
+
def build_object(options = {}, &block)
|
89
|
+
build_object_with_generic_attribute(
|
90
|
+
options.merge(column_type: :decimal, value: BigDecimal.new('1.0')),
|
91
|
+
&block
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_outside_value_to(values)
|
96
|
+
values + [values.last + 1]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'against a string attribute' do
|
101
|
+
it_behaves_like 'it supports in_array',
|
102
|
+
possible_values: %w(foo bar baz),
|
103
|
+
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_STRING
|
104
|
+
|
105
|
+
def build_object(options = {}, &block)
|
106
|
+
build_object_with_generic_attribute(
|
107
|
+
options.merge(column_type: :string),
|
108
|
+
&block
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_outside_value_to(values)
|
113
|
+
values + %w(qux)
|
114
|
+
end
|
28
115
|
end
|
29
116
|
end
|
30
117
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
118
|
+
shared_examples_for 'it supports allow_nil' do |args|
|
119
|
+
valid_values = args.fetch(:valid_values)
|
120
|
+
|
121
|
+
testing_values_of_option 'allow_nil' do |option_args, matches_or_not, to_or_not_to|
|
122
|
+
it "#{matches_or_not[0]} when the validation specifies allow_nil" do
|
123
|
+
builder = build_object_allowing(valid_values, allow_nil: true)
|
124
|
+
|
125
|
+
__send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
|
126
|
+
matcher.allow_nil(*option_args)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "#{matches_or_not[1]} when the validation does not specify allow_nil" do
|
131
|
+
builder = build_object_allowing(valid_values)
|
132
|
+
|
133
|
+
__send__("expect_#{to_or_not_to[1]}_match_on_values", builder, valid_values) do |matcher|
|
134
|
+
matcher.allow_nil(*option_args)
|
135
|
+
end
|
136
|
+
end
|
35
137
|
end
|
36
138
|
end
|
37
139
|
|
38
|
-
|
39
|
-
|
40
|
-
|
140
|
+
shared_examples_for 'it supports allow_blank' do |args|
|
141
|
+
valid_values = args.fetch(:valid_values)
|
142
|
+
|
143
|
+
testing_values_of_option 'allow_blank' do |option_args, matches_or_not, to_or_not_to|
|
144
|
+
it "#{matches_or_not[0]} when the validation specifies allow_blank" do
|
145
|
+
builder = build_object_allowing(valid_values, allow_blank: true)
|
146
|
+
|
147
|
+
__send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
|
148
|
+
matcher.allow_blank(*option_args)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it "#{matches_or_not[1]} when the validation does not specify allow_blank" do
|
153
|
+
builder = build_object_allowing(valid_values)
|
41
154
|
|
42
|
-
|
43
|
-
|
155
|
+
__send__("expect_#{to_or_not_to[1]}_match_on_values", builder, valid_values) do |matcher|
|
156
|
+
matcher.allow_blank(*option_args)
|
157
|
+
end
|
158
|
+
end
|
44
159
|
end
|
45
160
|
end
|
46
161
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
162
|
+
shared_examples_for 'it supports with_message' do |args|
|
163
|
+
valid_values = args.fetch(:valid_values)
|
164
|
+
|
165
|
+
context 'given a string' do
|
166
|
+
it 'matches when validation uses given message' do
|
167
|
+
builder = build_object_allowing(valid_values, message: 'a message')
|
168
|
+
|
169
|
+
expect_to_match_on_values(builder, valid_values) do |matcher|
|
170
|
+
matcher.with_message('a message')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'does not match when validation uses the default message instead of given message' do
|
175
|
+
pending 'does not work'
|
176
|
+
|
177
|
+
builder = build_object_allowing(valid_values)
|
178
|
+
|
179
|
+
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
180
|
+
matcher.with_message('a message')
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'does not match when validation uses a message but it is not same as given' do
|
185
|
+
pending 'does not work'
|
186
|
+
|
187
|
+
builder = build_object_allowing(valid_values, message: 'a different message')
|
188
|
+
|
189
|
+
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
190
|
+
matcher.with_message('a message')
|
191
|
+
end
|
192
|
+
end
|
51
193
|
end
|
52
194
|
|
53
|
-
|
54
|
-
|
55
|
-
|
195
|
+
context 'given a regex' do
|
196
|
+
it 'matches when validation uses a message that matches the regex' do
|
197
|
+
builder = build_object_allowing(valid_values, message: 'this is a message')
|
198
|
+
|
199
|
+
expect_to_match_on_values(builder, valid_values) do |matcher|
|
200
|
+
matcher.with_message(/a message/)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'does not match when validation uses the default message instead of given message' do
|
205
|
+
pending 'does not work'
|
206
|
+
|
207
|
+
builder = build_object_allowing(valid_values)
|
208
|
+
|
209
|
+
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
210
|
+
matcher.with_message(/a message/)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'does not match when validation uses a message but it does not match regex' do
|
215
|
+
pending 'does not work'
|
216
|
+
|
217
|
+
builder = build_object_allowing(valid_values, message: 'a different message')
|
218
|
+
|
219
|
+
expect_not_to_match_on_values(builder, valid_values) do |matcher|
|
220
|
+
matcher.with_message(/a message/)
|
221
|
+
end
|
222
|
+
end
|
56
223
|
end
|
57
224
|
|
58
|
-
|
59
|
-
|
60
|
-
|
225
|
+
context 'given nil' do
|
226
|
+
it 'is as if with_message had never been called' do
|
227
|
+
builder = build_object_allowing(valid_values)
|
228
|
+
|
229
|
+
expect_to_match_on_values(builder, valid_values) do |matcher|
|
230
|
+
matcher.with_message(nil)
|
231
|
+
end
|
232
|
+
end
|
61
233
|
end
|
234
|
+
end
|
62
235
|
|
63
|
-
|
64
|
-
|
65
|
-
|
236
|
+
shared_examples_for 'it supports in_array' do |args|
|
237
|
+
possible_values = args.fetch(:possible_values)
|
238
|
+
zero = args[:zero]
|
239
|
+
reserved_outside_value = args.fetch(:reserved_outside_value)
|
240
|
+
|
241
|
+
it 'does not match a record with no validations' do
|
242
|
+
builder = build_object
|
243
|
+
expect_not_to_match_on_values(builder, possible_values)
|
66
244
|
end
|
67
245
|
|
68
|
-
it '
|
69
|
-
|
70
|
-
|
246
|
+
it 'matches given the same array of valid values' do
|
247
|
+
builder = build_object_allowing(possible_values)
|
248
|
+
expect_to_match_on_values(builder, possible_values)
|
71
249
|
end
|
72
250
|
|
73
|
-
it '
|
74
|
-
|
75
|
-
|
251
|
+
it 'matches given a subset of the valid values' do
|
252
|
+
builder = build_object_allowing(possible_values)
|
253
|
+
expect_to_match_on_values(builder, possible_values[1..-1])
|
76
254
|
end
|
77
|
-
end
|
78
255
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
256
|
+
if zero
|
257
|
+
it 'matches when one of the given values is a 0' do
|
258
|
+
valid_values = possible_values + [zero]
|
259
|
+
builder = build_object_allowing(valid_values)
|
260
|
+
expect_to_match_on_values(builder, valid_values)
|
261
|
+
end
|
83
262
|
end
|
84
263
|
|
85
|
-
it '
|
86
|
-
|
87
|
-
|
264
|
+
it 'does not match when one of the given values is invalid' do
|
265
|
+
builder = build_object_allowing(possible_values)
|
266
|
+
expect_not_to_match_on_values(builder, add_outside_value_to(possible_values))
|
88
267
|
end
|
89
|
-
end
|
90
268
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
269
|
+
it 'raises an error when valid and given value is our test outside value' do
|
270
|
+
error_class = Shoulda::Matchers::ActiveModel::CouldNotDetermineValueOutsideOfArray
|
271
|
+
builder = build_object_allowing([reserved_outside_value])
|
272
|
+
|
273
|
+
expect { expect_to_match_on_values(builder, [reserved_outside_value]) }.
|
274
|
+
to raise_error(error_class)
|
95
275
|
end
|
96
|
-
end
|
97
276
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
277
|
+
it_behaves_like 'it supports allow_nil', valid_values: possible_values
|
278
|
+
it_behaves_like 'it supports allow_blank', valid_values: possible_values
|
279
|
+
it_behaves_like 'it supports with_message', valid_values: possible_values
|
280
|
+
|
281
|
+
if active_model_3_2?
|
282
|
+
context '+ strict' do
|
283
|
+
context 'when the validation specifies strict' do
|
284
|
+
it 'matches when the given values match the valid values' do
|
285
|
+
builder = build_object_allowing(possible_values, strict: true)
|
286
|
+
|
287
|
+
expect_to_match_on_values(builder, possible_values) do |matcher|
|
288
|
+
matcher.strict
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'does not match when the given values do not match the valid values' do
|
293
|
+
builder = build_object_allowing(possible_values, strict: true)
|
294
|
+
|
295
|
+
values = add_outside_value_to(possible_values)
|
296
|
+
expect_not_to_match_on_values(builder, values) do |matcher|
|
297
|
+
matcher.strict
|
298
|
+
end
|
299
|
+
end
|
105
300
|
end
|
106
|
-
end
|
107
301
|
|
108
|
-
|
109
|
-
|
302
|
+
context 'when the validation does not specify strict' do
|
303
|
+
it 'does not match' do
|
304
|
+
builder = build_object_allowing(possible_values)
|
110
305
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
errors.add(:attr, 'too high')
|
306
|
+
expect_not_to_match_on_values(builder, possible_values) do |matcher|
|
307
|
+
matcher.strict
|
308
|
+
end
|
309
|
+
end
|
116
310
|
end
|
117
311
|
end
|
118
|
-
|
119
|
-
expect(model).to ensure_inclusion_of(:attr).in_range(2...5).
|
120
|
-
with_low_message(/low/).with_high_message(/high/)
|
121
312
|
end
|
122
|
-
end
|
123
313
|
|
124
|
-
|
125
|
-
|
126
|
-
expect(validating_inclusion(in: %w(one two))).
|
127
|
-
to ensure_inclusion_of(:attr).in_array(%w(one two))
|
314
|
+
def expect_to_match_on_values(builder, values, &block)
|
315
|
+
expect_to_match_in_array(builder, values, &block)
|
128
316
|
end
|
129
317
|
|
130
|
-
|
131
|
-
|
132
|
-
not_to ensure_inclusion_of(:attr).in_array(%w(one wrong_value))
|
318
|
+
def expect_not_to_match_on_values(builder, values, &block)
|
319
|
+
expect_not_to_match_in_array(builder, values, &block)
|
133
320
|
end
|
321
|
+
end
|
134
322
|
|
135
|
-
|
136
|
-
|
137
|
-
not_to ensure_inclusion_of(:attr).in_array(%w(cat dog))
|
138
|
-
end
|
323
|
+
shared_examples_for 'it supports in_range' do |args|
|
324
|
+
possible_values = args[:possible_values]
|
139
325
|
|
140
|
-
it '
|
141
|
-
|
142
|
-
|
326
|
+
it 'does not match a record with no validations' do
|
327
|
+
builder = build_object
|
328
|
+
expect_not_to_match_on_values(builder, possible_values)
|
143
329
|
end
|
144
330
|
|
145
|
-
it '
|
146
|
-
|
147
|
-
|
331
|
+
it 'matches given a range that exactly matches the valid range' do
|
332
|
+
builder = build_object_allowing(possible_values)
|
333
|
+
expect_to_match_on_values(builder, possible_values)
|
148
334
|
end
|
149
335
|
|
150
|
-
it '
|
151
|
-
|
152
|
-
|
336
|
+
it 'does not match given a range whose start value falls outside valid range' do
|
337
|
+
builder = build_object_allowing(possible_values)
|
338
|
+
expect_not_to_match_on_values(builder,
|
339
|
+
Range.new(possible_values.first - 1, possible_values.last)
|
340
|
+
)
|
153
341
|
end
|
154
342
|
|
155
|
-
it '
|
156
|
-
|
157
|
-
|
343
|
+
it 'does not match given a range whose start value falls inside valid range' do
|
344
|
+
builder = build_object_allowing(possible_values)
|
345
|
+
expect_not_to_match_on_values(builder,
|
346
|
+
Range.new(possible_values.first + 1, possible_values.last)
|
347
|
+
)
|
158
348
|
end
|
159
349
|
|
160
|
-
it '
|
161
|
-
|
162
|
-
|
350
|
+
it 'does not match given a range whose end value falls inside valid range' do
|
351
|
+
builder = build_object_allowing(possible_values)
|
352
|
+
expect_not_to_match_on_values(builder,
|
353
|
+
Range.new(possible_values.first, possible_values.last - 1)
|
354
|
+
)
|
163
355
|
end
|
164
|
-
end
|
165
356
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
357
|
+
it 'does not match given a range whose end value falls outside valid range' do
|
358
|
+
builder = build_object_allowing(possible_values)
|
359
|
+
expect_not_to_match_on_values(builder,
|
360
|
+
Range.new(possible_values.first, possible_values.last + 1)
|
361
|
+
)
|
170
362
|
end
|
171
363
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
364
|
+
it_behaves_like 'it supports allow_nil', valid_values: possible_values
|
365
|
+
it_behaves_like 'it supports allow_blank', valid_values: possible_values
|
366
|
+
it_behaves_like 'it supports with_message', valid_values: possible_values
|
367
|
+
|
368
|
+
if active_model_3_2?
|
369
|
+
context '+ strict' do
|
370
|
+
context 'when the validation specifies strict' do
|
371
|
+
it 'matches when the given range matches the range in the validation' do
|
372
|
+
builder = build_object_allowing(possible_values, strict: true)
|
373
|
+
|
374
|
+
expect_to_match_on_values(builder, possible_values) do |matcher|
|
375
|
+
matcher.strict
|
376
|
+
end
|
377
|
+
end
|
176
378
|
|
177
|
-
|
178
|
-
|
179
|
-
|
379
|
+
it 'matches when the given range does not match the range in the validation' do
|
380
|
+
builder = build_object_allowing(possible_values, strict: true)
|
381
|
+
|
382
|
+
range = Range.new(possible_values.first, possible_values.last + 1)
|
383
|
+
expect_not_to_match_on_values(builder, range) do |matcher|
|
384
|
+
matcher.strict
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
context 'when the validation does not specify strict' do
|
390
|
+
it 'does not match' do
|
391
|
+
builder = build_object_allowing(possible_values)
|
392
|
+
|
393
|
+
expect_not_to_match_on_values(builder, possible_values) do |matcher|
|
394
|
+
matcher.strict
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
180
399
|
end
|
181
400
|
|
182
|
-
|
183
|
-
|
184
|
-
not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(false)
|
401
|
+
def expect_to_match_on_values(builder, range, &block)
|
402
|
+
expect_to_match_in_range(builder, range, &block)
|
185
403
|
end
|
186
|
-
end
|
187
404
|
|
188
|
-
|
189
|
-
|
190
|
-
expect(validating_inclusion(in: ['one', 'two', ''])).
|
191
|
-
not_to ensure_inclusion_of(:attr).in_array(['one', 'two', '']).allow_blank(true)
|
405
|
+
def expect_not_to_match_on_values(builder, range, &block)
|
406
|
+
expect_not_to_match_in_range(builder, range, &block)
|
192
407
|
end
|
193
408
|
end
|
194
409
|
|
195
|
-
|
196
|
-
context '
|
197
|
-
it '
|
198
|
-
|
199
|
-
|
410
|
+
shared_context 'against a boolean attribute for true and false' do
|
411
|
+
context 'when ensuring inclusion of true' do
|
412
|
+
it 'matches' do
|
413
|
+
valid_values = [true]
|
414
|
+
builder = build_object_allowing(valid_values)
|
415
|
+
expect_to_match_in_array(builder, valid_values)
|
200
416
|
end
|
417
|
+
end
|
201
418
|
|
202
|
-
|
203
|
-
|
204
|
-
|
419
|
+
context 'when ensuring inclusion of false' do
|
420
|
+
it 'matches' do
|
421
|
+
valid_values = [false]
|
422
|
+
builder = build_object_allowing(valid_values)
|
423
|
+
expect_to_match_in_array(builder, valid_values)
|
205
424
|
end
|
206
425
|
end
|
207
|
-
end
|
208
426
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
427
|
+
context 'when ensuring inclusion of true and false' do
|
428
|
+
it 'matches' do
|
429
|
+
valid_values = [true, false]
|
430
|
+
builder = build_object_allowing(valid_values)
|
431
|
+
capture(:stderr) do
|
432
|
+
expect_to_match_in_array(builder, valid_values)
|
215
433
|
end
|
216
434
|
end
|
217
435
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
436
|
+
it 'prints a warning' do
|
437
|
+
valid_values = [true, false]
|
438
|
+
builder = build_object_allowing(valid_values)
|
439
|
+
message = 'You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones'
|
440
|
+
|
441
|
+
stderr = capture(:stderr) do
|
442
|
+
expect_to_match_in_array(builder, valid_values)
|
222
443
|
end
|
444
|
+
|
445
|
+
expect(stderr.gsub(/\n+/, ' ')).to include(message)
|
223
446
|
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
context 'for a database column' do
|
451
|
+
include_context 'for a generic attribute'
|
452
|
+
|
453
|
+
context 'against a boolean attribute' do
|
454
|
+
context 'which is nullable' do
|
455
|
+
include_context 'against a boolean attribute for true and false'
|
224
456
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
457
|
+
context 'when ensuring inclusion of nil' do
|
458
|
+
it 'matches' do
|
459
|
+
valid_values = [nil]
|
460
|
+
builder = build_object_allowing(valid_values)
|
461
|
+
capture(:stderr) do
|
462
|
+
expect_to_match_in_array(builder, valid_values)
|
463
|
+
end
|
230
464
|
end
|
231
|
-
end
|
232
465
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
466
|
+
it 'prints a warning' do
|
467
|
+
valid_values = [nil]
|
468
|
+
builder = build_object_allowing(valid_values)
|
469
|
+
message = 'You are using `ensure_inclusion_of` to assert that a boolean column allows nil'
|
470
|
+
|
471
|
+
stderr = capture(:stderr) do
|
472
|
+
expect_to_match_in_array(builder, valid_values)
|
473
|
+
end
|
474
|
+
|
475
|
+
expect(stderr.gsub(/\n+/, ' ')).to include(message)
|
237
476
|
end
|
238
|
-
|
239
|
-
|
477
|
+
end
|
478
|
+
|
479
|
+
def build_object(options = {}, &block)
|
480
|
+
super(options.merge(column_options: { null: true }, value: true))
|
240
481
|
end
|
241
482
|
end
|
242
483
|
|
243
|
-
context '
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
484
|
+
context 'which is non-nullable' do
|
485
|
+
include_context 'against a boolean attribute for true and false'
|
486
|
+
|
487
|
+
context 'when ensuring inclusion of nil' do
|
488
|
+
it 'raises a specific error' do
|
489
|
+
valid_values = [nil]
|
490
|
+
builder = build_object_allowing(valid_values)
|
491
|
+
error_class = Shoulda::Matchers::ActiveModel::NonNullableBooleanError
|
492
|
+
|
493
|
+
expect {
|
494
|
+
expect_to_match_in_array(builder, valid_values)
|
495
|
+
}.to raise_error(error_class)
|
248
496
|
end
|
249
497
|
end
|
250
498
|
|
251
|
-
|
252
|
-
|
253
|
-
stderr = capture(:stderr) do
|
254
|
-
expect(record).to ensure_inclusion_of(:attr).in_array([nil])
|
255
|
-
end
|
256
|
-
expect(stderr.gsub(/\n+/, ' ')).
|
257
|
-
to include('You are using `ensure_inclusion_of` to assert that a boolean column allows nil')
|
499
|
+
def build_object(options = {}, &block)
|
500
|
+
super(options.merge(column_options: { null: false }))
|
258
501
|
end
|
259
502
|
end
|
503
|
+
|
504
|
+
def build_object(options = {}, &block)
|
505
|
+
build_object_with_generic_attribute(
|
506
|
+
options.merge(column_type: :boolean),
|
507
|
+
&block
|
508
|
+
)
|
509
|
+
end
|
260
510
|
end
|
261
511
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
512
|
+
|
513
|
+
def build_object_with_generic_attribute(options = {}, &block)
|
514
|
+
attribute_name = :attr
|
515
|
+
column_type = options.fetch(:column_type)
|
516
|
+
column_options = {
|
517
|
+
type: column_type,
|
518
|
+
options: options.fetch(:column_options, {})
|
519
|
+
}
|
520
|
+
validation_options = options[:validation_options]
|
521
|
+
custom_validation = options[:custom_validation]
|
522
|
+
|
523
|
+
model = define_model :example, attribute_name => column_options
|
524
|
+
customize_model_class(
|
525
|
+
model,
|
526
|
+
attribute_name,
|
527
|
+
validation_options,
|
528
|
+
custom_validation
|
529
|
+
)
|
530
|
+
|
531
|
+
object = model.new
|
532
|
+
|
533
|
+
object_builder_class.new(attribute_name, object, validation_options)
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
context 'for a plain Ruby attribute' do
|
538
|
+
include_context 'for a generic attribute'
|
539
|
+
|
540
|
+
context 'against a boolean attribute (designated by true)' do
|
541
|
+
include_context 'against a boolean attribute for true and false'
|
542
|
+
|
543
|
+
def build_object(options = {}, &block)
|
544
|
+
build_object_with_generic_attribute(options.merge(value: true))
|
268
545
|
end
|
546
|
+
end
|
269
547
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
548
|
+
context 'against a boolean attribute (designated by false)' do
|
549
|
+
include_context 'against a boolean attribute for true and false'
|
550
|
+
|
551
|
+
def build_object(options = {}, &block)
|
552
|
+
build_object_with_generic_attribute(options.merge(value: false))
|
275
553
|
end
|
554
|
+
end
|
276
555
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
end
|
283
|
-
end
|
556
|
+
def build_object_with_generic_attribute(options = {}, &block)
|
557
|
+
attribute_name = :attr
|
558
|
+
validation_options = options[:validation_options]
|
559
|
+
custom_validation = options[:custom_validation]
|
560
|
+
value = options[:value]
|
284
561
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
562
|
+
model = define_active_model_class :example, accessors: [attribute_name]
|
563
|
+
customize_model_class(
|
564
|
+
model,
|
565
|
+
attribute_name,
|
566
|
+
validation_options,
|
567
|
+
custom_validation
|
568
|
+
)
|
569
|
+
|
570
|
+
object = model.new
|
571
|
+
object.__send__("#{attribute_name}=", value)
|
572
|
+
|
573
|
+
object_builder_class.new(attribute_name, object, validation_options)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
def object_builder_class
|
578
|
+
@_object_builder_class ||= Struct.new(:attribute, :object, :validation_options)
|
579
|
+
end
|
580
|
+
|
581
|
+
def customize_model_class(klass, attribute_name, validation_options, custom_validation)
|
582
|
+
klass.class_eval do
|
583
|
+
if validation_options
|
584
|
+
validates_inclusion_of attribute_name, validation_options
|
293
585
|
end
|
294
586
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
error_class = Shoulda::Matchers::ActiveModel::NonNullableBooleanError
|
299
|
-
expect {
|
300
|
-
expect(record).to ensure_inclusion_of(:attr).in_array([nil])
|
301
|
-
}.to raise_error(error_class)
|
587
|
+
if custom_validation
|
588
|
+
define_method :custom_validation do
|
589
|
+
instance_exec(attribute_name, &custom_validation)
|
302
590
|
end
|
591
|
+
|
592
|
+
validate :custom_validation
|
303
593
|
end
|
304
594
|
end
|
305
595
|
end
|
306
596
|
|
307
|
-
def
|
308
|
-
|
309
|
-
validates_inclusion_of :attr, options
|
310
|
-
end.new
|
597
|
+
def build_object_allowing(values, options = {})
|
598
|
+
build_object(validation_options: options.merge(in: values))
|
311
599
|
end
|
312
600
|
|
313
|
-
def
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
601
|
+
def expect_to_match(builder)
|
602
|
+
matcher = ensure_inclusion_of(builder.attribute)
|
603
|
+
yield matcher if block_given?
|
604
|
+
expect(builder.object).to(matcher)
|
605
|
+
end
|
606
|
+
|
607
|
+
def expect_not_to_match(builder)
|
608
|
+
matcher = ensure_inclusion_of(builder.attribute)
|
609
|
+
yield matcher if block_given?
|
610
|
+
expect(builder.object).not_to(matcher)
|
611
|
+
end
|
612
|
+
|
613
|
+
def expect_to_match_in_array(builder, array)
|
614
|
+
expect_to_match(builder) do |matcher|
|
615
|
+
matcher.in_array(array)
|
616
|
+
yield matcher if block_given?
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def expect_not_to_match_in_array(builder, array)
|
621
|
+
expect_not_to_match(builder) do |matcher|
|
622
|
+
matcher.in_array(array)
|
623
|
+
yield matcher if block_given?
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
def expect_to_match_in_range(builder, range)
|
628
|
+
expect_to_match(builder) do |matcher|
|
629
|
+
matcher.in_range(range)
|
630
|
+
yield matcher if block_given?
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
def expect_not_to_match_in_range(builder, range)
|
635
|
+
expect_not_to_match(builder) do |matcher|
|
636
|
+
matcher.in_range(range)
|
637
|
+
yield matcher if block_given?
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
def expect_to_match_ensuring_range_and_messages(range, low_value, high_value)
|
642
|
+
low_message = 'too low'
|
643
|
+
high_message = 'too high'
|
644
|
+
|
645
|
+
builder = build_object custom_validation: ->(attribute) {
|
646
|
+
value = __send__(attribute)
|
647
|
+
|
648
|
+
if value < low_value
|
649
|
+
errors.add(attribute, low_message)
|
650
|
+
elsif value > high_value
|
651
|
+
errors.add(attribute, high_message)
|
652
|
+
end
|
653
|
+
}
|
654
|
+
|
655
|
+
expect_to_match(builder) do |matcher|
|
656
|
+
matcher.
|
657
|
+
in_range(range).
|
658
|
+
with_low_message(low_message).
|
659
|
+
with_high_message(high_message)
|
660
|
+
end
|
319
661
|
end
|
320
662
|
end
|