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.
@@ -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