nobrainer-rspec 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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