nobrainer-rspec 1.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +37 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +61 -0
- data/Rakefile +8 -0
- data/bin/console +19 -0
- data/bin/setup +7 -0
- data/lib/matchers/associations.rb +287 -0
- data/lib/matchers/be_nobrainer_document.rb +28 -0
- data/lib/matchers/have_field.rb +484 -0
- data/lib/matchers/have_index_for.rb +164 -0
- data/lib/matchers/have_timestamps.rb +38 -0
- data/lib/matchers/validations.rb +80 -0
- data/lib/matchers/validations/acceptance_of.rb +11 -0
- data/lib/matchers/validations/confirmation_of.rb +17 -0
- data/lib/matchers/validations/exclusion_of.rb +51 -0
- data/lib/matchers/validations/format_of.rb +73 -0
- data/lib/matchers/validations/inclusion_of.rb +51 -0
- data/lib/matchers/validations/length_of.rb +127 -0
- data/lib/matchers/validations/numericality_of.rb +92 -0
- data/lib/matchers/validations/presence_of.rb +11 -0
- data/lib/matchers/validations/uniqueness_of.rb +53 -0
- data/lib/nobrainer-rspec.rb +1 -0
- data/lib/nobrainer/rspec.rb +31 -0
- data/lib/nobrainer/rspec/version.rb +7 -0
- data/nobrainer-rspec.gemspec +47 -0
- metadata +158 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NoBrainer
|
4
|
+
module Matchers
|
5
|
+
def be_nobrainer_document
|
6
|
+
BeNoBrainerDocument.new
|
7
|
+
end
|
8
|
+
|
9
|
+
class BeNoBrainerDocument
|
10
|
+
def matches?(actual)
|
11
|
+
@model = actual.is_a?(Class) ? actual : actual.class
|
12
|
+
@model.included_modules.include?(NoBrainer::Document)
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
'include NoBrainer::Document'
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_message
|
20
|
+
"expect #{@model.inspect} class to #{description}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message_when_negated
|
24
|
+
"expect #{@model.inspect} class to not #{description}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,484 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NoBrainer
|
4
|
+
module Matchers
|
5
|
+
# The `have_field` matcher tests that the table that backs your model
|
6
|
+
# has a specific field.
|
7
|
+
#
|
8
|
+
# class User
|
9
|
+
# include NoBrainer::Document
|
10
|
+
#
|
11
|
+
# field :name
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # RSpec
|
15
|
+
# RSpec.describe User, type: :model do
|
16
|
+
# it { is_expected.to have_field(:name) }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# #### Qualifiers
|
20
|
+
#
|
21
|
+
# ##### of_type
|
22
|
+
#
|
23
|
+
# Use `of_type` to assert that a field is defined as a certain type.
|
24
|
+
#
|
25
|
+
# class User
|
26
|
+
# include NoBrainer::Document
|
27
|
+
#
|
28
|
+
# field :name, type: String
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # RSpec
|
32
|
+
# RSpec.describe User, type: :model do
|
33
|
+
# it do
|
34
|
+
# it { is_expected.to have_field(:name).of_type(String) }
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# ##### with_alias
|
39
|
+
#
|
40
|
+
# Use `with_alias` to assert that a field is aliased with a certain name.
|
41
|
+
#
|
42
|
+
# class User
|
43
|
+
# include NoBrainer::Document
|
44
|
+
#
|
45
|
+
# field :name, store_as: :n
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# # RSpec
|
49
|
+
# RSpec.describe User, type: :model do
|
50
|
+
# it do
|
51
|
+
# it { is_expected.to have_field(:name).with_alias(:s) }
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# ##### with_default_value_of
|
56
|
+
#
|
57
|
+
# Use `with_default_value_of` to assert that a field have a certain default
|
58
|
+
# value.
|
59
|
+
#
|
60
|
+
# class User
|
61
|
+
# include NoBrainer::Document
|
62
|
+
#
|
63
|
+
# field :name, default: ''
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# # RSpec
|
67
|
+
# RSpec.describe User, type: :model do
|
68
|
+
# it do
|
69
|
+
# it { is_expected.to have_field(:name).with_default_value_of('') }
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# ##### required
|
74
|
+
#
|
75
|
+
# Use `required` to assert that a field is mandatory.
|
76
|
+
#
|
77
|
+
# class User
|
78
|
+
# include NoBrainer::Document
|
79
|
+
#
|
80
|
+
# field :name, require: true
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# # RSpec
|
84
|
+
# RSpec.describe User, type: :model do
|
85
|
+
# it do
|
86
|
+
# it { is_expected.to have_field(:name).required }
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# ##### to_allow
|
91
|
+
#
|
92
|
+
# Use `to_allow` to assert that a field has a certain list of available
|
93
|
+
# values.
|
94
|
+
#
|
95
|
+
# class User
|
96
|
+
# include NoBrainer::Document
|
97
|
+
#
|
98
|
+
# field :status, type: Enum, in: %i[pending ongoing done]
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# # RSpec
|
102
|
+
# RSpec.describe User, type: :model do
|
103
|
+
# it do
|
104
|
+
# is_expected.to have_field(:status).to_allow(%i[pending ongoing
|
105
|
+
# done])
|
106
|
+
# end
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# ##### readonly
|
110
|
+
#
|
111
|
+
# Use `readonly` to assert that a field cannot be updated.
|
112
|
+
#
|
113
|
+
# class User
|
114
|
+
# include NoBrainer::Document
|
115
|
+
#
|
116
|
+
# field :status, readonly: true
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# # RSpec
|
120
|
+
# RSpec.describe User, type: :model do
|
121
|
+
# it do
|
122
|
+
# is_expected.to have_field(:status).readonly
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# ##### lazy_fetched
|
127
|
+
#
|
128
|
+
# Use `lazy_fetched` to assert that a field should be fetched on
|
129
|
+
# demand.
|
130
|
+
#
|
131
|
+
# class User
|
132
|
+
# include NoBrainer::Document
|
133
|
+
#
|
134
|
+
# field :avatar, type: Binary, lazy_fetch: true
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# # RSpec
|
138
|
+
# RSpec.describe User, type: :model do
|
139
|
+
# it do
|
140
|
+
# is_expected.to have_field(:avatar).lazy_fetched
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# ##### unique
|
145
|
+
#
|
146
|
+
# Use `unique` to assert that a field should be fetched on
|
147
|
+
# demand.
|
148
|
+
#
|
149
|
+
# class User
|
150
|
+
# include NoBrainer::Document
|
151
|
+
#
|
152
|
+
# field :uuid, type: String, unique: true
|
153
|
+
# # or
|
154
|
+
# field :uuid, type: String, uniq: true
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# # RSpec
|
158
|
+
# RSpec.describe User, type: :model do
|
159
|
+
# it do
|
160
|
+
# is_expected.to have_field(:uuid).unique
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# ##### with_primary_key
|
165
|
+
#
|
166
|
+
# Use `with_primary_key` to assert that a field is used as the primary key.
|
167
|
+
#
|
168
|
+
# class User
|
169
|
+
# include NoBrainer::Document
|
170
|
+
#
|
171
|
+
# field :uuid, type: String, primary_key: true
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# # RSpec
|
175
|
+
# RSpec.describe User, type: :model do
|
176
|
+
# it do
|
177
|
+
# is_expected.to have_field(:uuid).with_primary_key
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# ##### of_length
|
182
|
+
#
|
183
|
+
# Use `of_length` to assert that a field has a length included in the given
|
184
|
+
# range.
|
185
|
+
#
|
186
|
+
# class User
|
187
|
+
# include NoBrainer::Document
|
188
|
+
#
|
189
|
+
# field :password, type: String, length: (8..26)
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# # RSpec
|
193
|
+
# RSpec.describe User, type: :model do
|
194
|
+
# it do
|
195
|
+
# is_expected.to have_field(:password).of_length((8..26))
|
196
|
+
# end
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# ##### with_min_length
|
200
|
+
#
|
201
|
+
# Use `with_min_length` to assert that a field has a length of at least the
|
202
|
+
# given one.
|
203
|
+
#
|
204
|
+
# class User
|
205
|
+
# include NoBrainer::Document
|
206
|
+
#
|
207
|
+
# field :username, type: String, min_length: 3
|
208
|
+
# end
|
209
|
+
#
|
210
|
+
# # RSpec
|
211
|
+
# RSpec.describe User, type: :model do
|
212
|
+
# it do
|
213
|
+
# is_expected.to have_field(:username).with_min_length(3)
|
214
|
+
# end
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# ##### with_max_length
|
218
|
+
#
|
219
|
+
# Use `with_max_length` to assert that a field has a length of at most the
|
220
|
+
# given one.
|
221
|
+
#
|
222
|
+
# class User
|
223
|
+
# include NoBrainer::Document
|
224
|
+
#
|
225
|
+
# field :bio, type: Text, max_length: 250
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
# # RSpec
|
229
|
+
# RSpec.describe User, type: :model do
|
230
|
+
# it do
|
231
|
+
# is_expected.to have_field(:bio).with_max_length(250)
|
232
|
+
# end
|
233
|
+
# end
|
234
|
+
#
|
235
|
+
# ##### with_format
|
236
|
+
#
|
237
|
+
# Use `with_format` to assert that a field is validating the given format.
|
238
|
+
#
|
239
|
+
# class User
|
240
|
+
# include NoBrainer::Document
|
241
|
+
#
|
242
|
+
# field :email, format: /@/
|
243
|
+
# end
|
244
|
+
#
|
245
|
+
# # RSpec
|
246
|
+
# RSpec.describe User, type: :model do
|
247
|
+
# it do
|
248
|
+
# is_expected.to have_field(:email).with_format(/@/)
|
249
|
+
# end
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
#
|
253
|
+
# ##### indexed
|
254
|
+
#
|
255
|
+
# Use `indexed` to assert that a field is indexed.
|
256
|
+
#
|
257
|
+
# class User
|
258
|
+
# include NoBrainer::Document
|
259
|
+
#
|
260
|
+
# field :email, index: true
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# # RSpec
|
264
|
+
# RSpec.describe User, type: :model do
|
265
|
+
# it do
|
266
|
+
# is_expected.to have_field(:email).indexed
|
267
|
+
# end
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
class HaveField # :nodoc:
|
271
|
+
def initialize(*attrs)
|
272
|
+
@attributes = attrs.collect(&:to_sym)
|
273
|
+
end
|
274
|
+
|
275
|
+
def localized
|
276
|
+
@localized = true
|
277
|
+
self
|
278
|
+
end
|
279
|
+
|
280
|
+
def of_type(type)
|
281
|
+
@type = type
|
282
|
+
self
|
283
|
+
end
|
284
|
+
|
285
|
+
def with_alias(field_alias)
|
286
|
+
@field_alias = field_alias
|
287
|
+
self
|
288
|
+
end
|
289
|
+
|
290
|
+
def with_default_value_of(default)
|
291
|
+
@default = default
|
292
|
+
self
|
293
|
+
end
|
294
|
+
|
295
|
+
def required(required = true)
|
296
|
+
@required = required
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
def to_allow(allowed_values)
|
301
|
+
@allowed_values = allowed_values
|
302
|
+
self
|
303
|
+
end
|
304
|
+
|
305
|
+
def readonly(readonly = true)
|
306
|
+
@readonly = readonly
|
307
|
+
self
|
308
|
+
end
|
309
|
+
|
310
|
+
def lazy_fetched(lazy_fetched = true)
|
311
|
+
@lazy_fetched = lazy_fetched
|
312
|
+
self
|
313
|
+
end
|
314
|
+
|
315
|
+
def unique(unique = true)
|
316
|
+
@unique = unique
|
317
|
+
self
|
318
|
+
end
|
319
|
+
|
320
|
+
def with_primary_key(primary_key = true)
|
321
|
+
@primary_key = primary_key
|
322
|
+
self
|
323
|
+
end
|
324
|
+
|
325
|
+
def of_length(length)
|
326
|
+
@length = length
|
327
|
+
self
|
328
|
+
end
|
329
|
+
|
330
|
+
def with_min_length(min_length)
|
331
|
+
@min_length = min_length
|
332
|
+
self
|
333
|
+
end
|
334
|
+
|
335
|
+
def with_max_length(max_length)
|
336
|
+
@max_length = max_length
|
337
|
+
self
|
338
|
+
end
|
339
|
+
|
340
|
+
def with_format(format)
|
341
|
+
@format = format
|
342
|
+
self
|
343
|
+
end
|
344
|
+
|
345
|
+
def indexed(index = true)
|
346
|
+
@index = index
|
347
|
+
self
|
348
|
+
end
|
349
|
+
|
350
|
+
def matches?(klass)
|
351
|
+
@klass = klass.is_a?(Class) ? klass : klass.class
|
352
|
+
@errors = []
|
353
|
+
@attributes.each do |attr|
|
354
|
+
if @klass.fields.include?(attr)
|
355
|
+
error = ''
|
356
|
+
|
357
|
+
# Checking field type
|
358
|
+
if @type && (@klass.fields[attr][:type] != @type)
|
359
|
+
error += " of type #{@klass.fields[attr][:type]}"
|
360
|
+
end
|
361
|
+
|
362
|
+
# Checking field default value
|
363
|
+
unless @default.nil?
|
364
|
+
if @klass.fields[attr][:default].nil?
|
365
|
+
error += ' with default not set'
|
366
|
+
elsif @klass.fields[attr][:default] != @default
|
367
|
+
error += " with default value of #{@klass.fields[attr][:default]}"
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Checking field store_as
|
372
|
+
if @field_alias && (@klass.fields[attr][:store_as] != @field_alias)
|
373
|
+
error += " with alias #{@klass.fields[attr][:store_as]}"
|
374
|
+
end
|
375
|
+
|
376
|
+
# Checking field required
|
377
|
+
if @required && (@klass.fields[attr][:required] != true)
|
378
|
+
error += ' being required'
|
379
|
+
end
|
380
|
+
|
381
|
+
# Checking allowed values
|
382
|
+
if @allowed_values && (@klass.fields[attr][:in] != @allowed_values)
|
383
|
+
error += ' allowing all values mentioned'
|
384
|
+
end
|
385
|
+
|
386
|
+
# Checking readonly mode
|
387
|
+
if @readonly && (@klass.fields[attr][:readonly] != @readonly)
|
388
|
+
error += ' in readonly'
|
389
|
+
end
|
390
|
+
|
391
|
+
# Checking lazy fetche
|
392
|
+
if @lazy_fetched && (@klass.fields[attr][:lazy_fetch] != @lazy_fetched)
|
393
|
+
error += ' lazy fetched'
|
394
|
+
end
|
395
|
+
|
396
|
+
# Checking unique
|
397
|
+
if @unique && (@klass.fields[attr][:unique] != @unique)
|
398
|
+
error += ' unique'
|
399
|
+
end
|
400
|
+
|
401
|
+
# Checking primary key
|
402
|
+
if @primary_key && (@klass.fields[attr][:primary_key] != @primary_key)
|
403
|
+
error += ' as primary key'
|
404
|
+
end
|
405
|
+
|
406
|
+
# Checking length range
|
407
|
+
if @length && (@klass.fields[attr][:length] != @length)
|
408
|
+
error += " with a length of #{@klass.fields[attr][:length]}"
|
409
|
+
end
|
410
|
+
|
411
|
+
# Checking min length
|
412
|
+
if @min_length && (@klass.fields[attr][:min_length] != @min_length)
|
413
|
+
error += ' with a minimal length of ' \
|
414
|
+
"#{@klass.fields[attr][:min_length]}"
|
415
|
+
end
|
416
|
+
|
417
|
+
# Checking max length
|
418
|
+
if @max_length && (@klass.fields[attr][:max_length] != @max_length)
|
419
|
+
error += ' with a maximal length of ' \
|
420
|
+
"#{@klass.fields[attr][:max_length]}"
|
421
|
+
end
|
422
|
+
|
423
|
+
# Checking format
|
424
|
+
if @format && (@klass.fields[attr][:format] != @format)
|
425
|
+
error += " in the format #{@klass.fields[attr][:format]}"
|
426
|
+
end
|
427
|
+
|
428
|
+
# Checking index
|
429
|
+
if @index && (@klass.fields[attr][:index] != @index)
|
430
|
+
error += " index #{@klass.fields[attr][:index].inspect}"
|
431
|
+
end
|
432
|
+
|
433
|
+
@errors.push("field #{attr.inspect}" + error) unless error.blank?
|
434
|
+
|
435
|
+
if @localized
|
436
|
+
unless @klass.fields[attr].localized?
|
437
|
+
@errors.push "is not localized #{attr.inspect}"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
else
|
442
|
+
@errors.push "no field named #{attr.inspect}"
|
443
|
+
end
|
444
|
+
end
|
445
|
+
@errors.empty?
|
446
|
+
end
|
447
|
+
|
448
|
+
def failure_message_for_should
|
449
|
+
"Expected #{@klass.inspect} to #{description}, got #{@errors.to_sentence}"
|
450
|
+
end
|
451
|
+
|
452
|
+
def failure_message_for_should_not
|
453
|
+
"Expected #{@klass.inspect} to not #{description}, got #{@klass.inspect} to #{description}"
|
454
|
+
end
|
455
|
+
|
456
|
+
alias failure_message failure_message_for_should
|
457
|
+
alias failure_message_when_negated failure_message_for_should_not
|
458
|
+
|
459
|
+
def description
|
460
|
+
desc = "have #{@attributes.size > 1 ? 'fields' : 'field'} named #{@attributes.collect(&:inspect).to_sentence}"
|
461
|
+
desc += " of type #{@type.inspect}" if @type
|
462
|
+
desc += " with alias #{@field_alias}" if @field_alias
|
463
|
+
desc += " with default value of #{@default.inspect}" unless @default.nil?
|
464
|
+
desc += ' to be required' if @required
|
465
|
+
desc += ' allowing all values mentioned' if @allowed_values
|
466
|
+
desc += ' to be readonly' if @readonly
|
467
|
+
desc += ' to be lazy fetched' if @lazy_fetched
|
468
|
+
desc += ' to be unique' if @unique
|
469
|
+
desc += ' as primary key' if @primary_key
|
470
|
+
desc += " with a length of #{@length}" if @length
|
471
|
+
desc += " with a minimal length of #{@min_length}" if @min_length
|
472
|
+
desc += " with a maximal length of #{@max_length}" if @max_length
|
473
|
+
desc += " in the format #{@format}" if @format
|
474
|
+
desc += ' to be indexed' if @index
|
475
|
+
desc
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def have_field(*args)
|
480
|
+
HaveField.new(*args)
|
481
|
+
end
|
482
|
+
alias have_fields have_field
|
483
|
+
end
|
484
|
+
end
|