iknow_view_models 3.4.2 → 3.5.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,599 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/unit'
5
+ require 'minitest/hooks'
6
+
7
+ require 'view_model'
8
+ require 'view_model/active_record'
9
+
10
+ require_relative '../../../helpers/controller_test_helpers'
11
+ require_relative '../../../helpers/callback_tracer'
12
+
13
+ class ViewModel::ActiveRecord::ControllerNestedTest < ActiveSupport::TestCase
14
+ include ARVMTestUtilities
15
+ include ControllerTestModels
16
+ include ControllerTestControllers
17
+
18
+ def before_all
19
+ super
20
+
21
+ build_controller_test_models(externalize: [:label, :child, :target])
22
+ end
23
+
24
+ def setup
25
+ super
26
+ @parent = make_parent
27
+ @parent_view = ParentView.new(@parent)
28
+
29
+ enable_logging!
30
+ end
31
+
32
+ #### Controller for nested model
33
+
34
+ def test_nested_collection_index_associated
35
+ _distractor = Parent.create(name: 'p2', children: [Child.new(name: 'c3', position: 1)])
36
+
37
+ childcontroller = ChildController.new(params: {
38
+ owner_viewmodel: 'parent',
39
+ association_name: 'children',
40
+ parent_id: @parent.id
41
+ })
42
+ childcontroller.invoke(:index_associated)
43
+
44
+ assert_equal(200, childcontroller.status)
45
+
46
+ expected_children = @parent.children
47
+ assert_equal({ 'data' => expected_children.map { |c| ChildView.new(c).to_hash } },
48
+ childcontroller.hash_response)
49
+
50
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
51
+ end
52
+
53
+ def test_nested_collection_index
54
+ distractor = Parent.create(name: 'p2', children: [Child.new(name: 'c3', position: 1)])
55
+ childcontroller = ChildController.new
56
+
57
+ childcontroller.invoke(:index)
58
+
59
+ assert_equal(200, childcontroller.status)
60
+
61
+ expected_children = @parent.children + distractor.children
62
+ assert_equal({ 'data' => expected_children.map { |c| ChildView.new(c).to_hash } },
63
+ childcontroller.hash_response)
64
+ end
65
+
66
+ def test_nested_collection_append_one
67
+ data = { '_type' => 'Child', 'name' => 'c3' }
68
+ childcontroller = ChildController.new(params: {
69
+ owner_viewmodel: 'parent',
70
+ association_name: 'children',
71
+ parent_id: @parent.id,
72
+ data: data,
73
+ })
74
+
75
+ childcontroller.invoke(:append)
76
+
77
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
78
+
79
+ @parent.reload
80
+
81
+ assert_equal(%w[c1 c2 c3], @parent.children.order(:position).pluck(:name))
82
+ assert_equal({ 'data' => ChildView.new(@parent.children.last).to_hash },
83
+ childcontroller.hash_response)
84
+
85
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
86
+ end
87
+
88
+ def test_nested_collection_append_many
89
+ data = [{ '_type' => 'Child', 'name' => 'c3' },
90
+ { '_type' => 'Child', 'name' => 'c4' },]
91
+
92
+ childcontroller = ChildController.new(params: {
93
+ owner_viewmodel: 'parent',
94
+ association_name: 'children',
95
+ parent_id: @parent.id,
96
+ data: data,
97
+ })
98
+ childcontroller.invoke(:append)
99
+
100
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
101
+
102
+ @parent.reload
103
+
104
+ assert_equal(%w[c1 c2 c3 c4], @parent.children.order(:position).pluck(:name))
105
+ new_children_hashes = @parent.children.last(2).map { |c| ChildView.new(c).to_hash }
106
+ assert_equal({ 'data' => new_children_hashes },
107
+ childcontroller.hash_response)
108
+
109
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
110
+ end
111
+
112
+ # FIXME: nested controllers really need to be to other roots; children aren't roots.
113
+ def test_nested_collection_replace
114
+ # Parent.children
115
+ old_children = @parent.children
116
+
117
+ data = [{ '_type' => 'Child', 'name' => 'newc1' },
118
+ { '_type' => 'Child', 'name' => 'newc2' },]
119
+
120
+ childcontroller = ChildController.new(params: {
121
+ owner_viewmodel: 'parent',
122
+ association_name: 'children',
123
+ parent_id: @parent.id,
124
+ data: data,
125
+ })
126
+ childcontroller.invoke(:replace)
127
+
128
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
129
+
130
+ @parent.reload
131
+
132
+ assert_equal(%w[newc1 newc2], @parent.children.order(:position).pluck(:name))
133
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
134
+
135
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
136
+ end
137
+
138
+ def test_nested_collection_replace_bad_data
139
+ data = [{ 'name' => 'nc' }]
140
+ childcontroller = ChildController.new(params: {
141
+ owner_viewmodel: 'parent',
142
+ association_name: 'children',
143
+ parent_id: @parent.id,
144
+ data: data,
145
+ })
146
+
147
+ childcontroller.invoke(:replace)
148
+
149
+ assert_equal(400, childcontroller.status)
150
+
151
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
152
+ end
153
+
154
+ def test_nested_collection_replace_bulk
155
+ other_parent = make_parent(name: 'p_other', child_names: ['other_c1', 'other_c2'])
156
+
157
+ old_children = other_parent.children + @parent.children
158
+
159
+ data = {
160
+ '_type' => '_bulk_update',
161
+ 'updates' => [
162
+ {
163
+ 'id' => @parent.id,
164
+ 'update' => [
165
+ { '_type' => 'Child', 'name' => 'newc1' },
166
+ { '_type' => 'Child', 'name' => 'newc2' },],
167
+ },
168
+ {
169
+ 'id' => other_parent.id,
170
+ 'update' => [
171
+ { '_type' => 'Child', 'name' => 'other_newc1' },
172
+ { '_type' => 'Child', 'name' => 'other_newc2' },],
173
+ }
174
+ ],
175
+ }
176
+
177
+ childcontroller = ChildController.new(params: {
178
+ owner_viewmodel: 'parent',
179
+ association_name: 'children',
180
+ data: data,
181
+ })
182
+
183
+ childcontroller.invoke(:replace_bulk)
184
+
185
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
186
+
187
+ @parent.reload
188
+ other_parent.reload
189
+
190
+ assert_equal(%w[newc1 newc2], @parent.children.order(:position).pluck(:name))
191
+ assert_equal(%w[other_newc1 other_newc2], other_parent.children.order(:position).pluck(:name))
192
+
193
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
194
+
195
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
196
+ end
197
+
198
+
199
+ def test_nested_collection_disassociate_one
200
+ old_child = @parent.children.first
201
+ childcontroller = ChildController.new(params: {
202
+ owner_viewmodel: 'parent',
203
+ association_name: 'children',
204
+ parent_id: @parent.id,
205
+ id: old_child.id,
206
+ })
207
+ childcontroller.invoke(:disassociate)
208
+
209
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
210
+
211
+ @parent.reload
212
+
213
+ assert_equal(%w[c2], @parent.children.order(:position).pluck(:name))
214
+ assert_predicate(Child.where(id: old_child.id), :empty?)
215
+
216
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
217
+ end
218
+
219
+ def test_nested_collection_disassociate_many
220
+ old_children = @parent.children
221
+
222
+ childcontroller = ChildController.new(params: {
223
+ owner_viewmodel: 'parent',
224
+ association_name: 'children',
225
+ parent_id: @parent.id,
226
+ })
227
+ childcontroller.invoke(:disassociate_all)
228
+
229
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
230
+
231
+ @parent.reload
232
+
233
+ assert_predicate(@parent.children, :empty?)
234
+ assert_predicate(Child.where(id: old_children.map(&:id)), :empty?)
235
+
236
+ assert_all_hooks_nested_inside_parent_hook(childcontroller.hook_trace)
237
+ end
238
+
239
+ # direct methods on nested controller
240
+ def test_nested_collection_destroy
241
+ old_child = @parent.children.first
242
+ childcontroller = ChildController.new(params: { id: old_child.id })
243
+ childcontroller.invoke(:destroy)
244
+
245
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
246
+
247
+ @parent.reload
248
+
249
+ assert_equal(%w[c2], @parent.children.order(:position).pluck(:name))
250
+ assert_predicate(Child.where(id: old_child.id), :empty?)
251
+ end
252
+
253
+ def test_nested_collection_update
254
+ old_child = @parent.children.first
255
+
256
+ data = { 'id' => old_child.id,
257
+ '_type' => 'Child',
258
+ 'name' => 'new_name' }
259
+
260
+ childcontroller = ChildController.new(params: { data: data })
261
+ childcontroller.invoke(:create)
262
+
263
+ assert_equal(200, childcontroller.status, childcontroller.hash_response)
264
+
265
+ old_child.reload
266
+
267
+ assert_equal('new_name', old_child.name)
268
+ assert_equal({ 'data' => ChildView.new(old_child).to_hash },
269
+ childcontroller.hash_response)
270
+ end
271
+
272
+ def test_nested_collection_show
273
+ old_child = @parent.children.first
274
+
275
+ childcontroller = ChildController.new(params: { id: old_child.id })
276
+ childcontroller.invoke(:show)
277
+
278
+ assert_equal({ 'data' => ChildView.new(old_child).to_hash },
279
+ childcontroller.hash_response)
280
+
281
+ assert_equal(200, childcontroller.status)
282
+ end
283
+
284
+ ## Single association
285
+
286
+ def test_nested_singular_replace_from_parent
287
+ old_label = @parent.label
288
+
289
+ data = { '_type' => 'Label', 'text' => 'new label' }
290
+ labelcontroller = LabelController.new(params: {
291
+ owner_viewmodel: 'parent',
292
+ association_name: 'label',
293
+ parent_id: @parent.id,
294
+ data: data,
295
+ })
296
+ labelcontroller.invoke(:create_associated)
297
+
298
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
299
+
300
+ @parent.reload
301
+
302
+ assert_equal({ 'data' => { '_type' => 'Label',
303
+ '_version' => 1,
304
+ 'id' => @parent.label.id,
305
+ 'text' => 'new label' } },
306
+ labelcontroller.hash_response)
307
+
308
+ refute_equal(old_label, @parent.label)
309
+ assert_equal('new label', @parent.label.text)
310
+
311
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
312
+ end
313
+
314
+ def test_nested_singular_show_from_parent
315
+ old_label = @parent.label
316
+
317
+ labelcontroller = LabelController.new(params: {
318
+ owner_viewmodel: 'parent',
319
+ association_name: 'label',
320
+ parent_id: @parent.id,
321
+ })
322
+ labelcontroller.invoke(:show_associated)
323
+
324
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
325
+
326
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
327
+ labelcontroller.hash_response)
328
+
329
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
330
+ end
331
+
332
+ def test_nested_singular_destroy_from_parent
333
+ old_target = @parent.target
334
+
335
+ targetcontroller = TargetController.new(params: {
336
+ owner_viewmodel: 'parent',
337
+ association_name: 'target',
338
+ parent_id: @parent.id,
339
+ })
340
+ targetcontroller.invoke(:destroy_associated)
341
+
342
+ @parent.reload
343
+
344
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
345
+ assert_equal({ 'data' => nil }, targetcontroller.hash_response)
346
+
347
+ assert_nil(@parent.target)
348
+ assert_predicate(Target.where(id: old_target.id), :empty?)
349
+
350
+ assert_all_hooks_nested_inside_parent_hook(targetcontroller.hook_trace)
351
+ end
352
+
353
+ def test_nested_singular_update_from_parent
354
+ old_label = @parent.label
355
+
356
+ data = { '_type' => 'Label', 'id' => old_label.id, 'text' => 'new label' }
357
+ labelcontroller = LabelController.new(params: {
358
+ owner_viewmodel: 'parent',
359
+ association_name: 'label',
360
+ parent_id: @parent.id,
361
+ data: data,
362
+ })
363
+ labelcontroller.invoke(:create_associated)
364
+
365
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
366
+
367
+ old_label.reload
368
+
369
+ assert_equal('new label', old_label.text)
370
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
371
+ labelcontroller.hash_response)
372
+
373
+ assert_all_hooks_nested_inside_parent_hook(labelcontroller.hook_trace)
374
+ end
375
+
376
+ def test_nested_singular_show_from_id
377
+ old_label = @parent.label
378
+
379
+ labelcontroller = LabelController.new(params: { id: old_label.id })
380
+ labelcontroller.invoke(:show)
381
+
382
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
383
+
384
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
385
+ labelcontroller.hash_response)
386
+ end
387
+
388
+ def test_nested_singular_destroy_from_id
389
+ # can't directly destroy pointed-to label that's referenced from parent:
390
+ # foreign key violation. Destroy target instead.
391
+ old_target = @parent.target
392
+
393
+ targetcontroller = TargetController.new(params: { id: old_target.id })
394
+ targetcontroller.invoke(:destroy)
395
+
396
+ @parent.reload
397
+
398
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
399
+ assert_equal({ 'data' => nil }, targetcontroller.hash_response)
400
+
401
+ assert_nil(@parent.target)
402
+ assert_predicate(Target.where(id: old_target.id), :empty?)
403
+ end
404
+
405
+ def test_nested_singular_update
406
+ old_label = @parent.label
407
+
408
+ data = { '_type' => 'Label', 'id' => old_label.id, 'text' => 'new label' }
409
+ labelcontroller = LabelController.new(params: { data: data })
410
+ labelcontroller.invoke(:create)
411
+
412
+ assert_equal(200, labelcontroller.status, labelcontroller.hash_response)
413
+
414
+ old_label.reload
415
+
416
+ assert_equal('new label', old_label.text)
417
+ assert_equal({ 'data' => LabelView.new(old_label).to_hash },
418
+ labelcontroller.hash_response)
419
+ end
420
+
421
+ def test_nested_singular_replace_bulk
422
+ other_parent = make_parent(name: 'p_other', child_names: ['other_c1', 'other_c2'])
423
+
424
+ target = @parent.target
425
+ other_target = other_parent.target
426
+
427
+ data = {
428
+ '_type' => '_bulk_update',
429
+ 'updates' => [
430
+ {
431
+ 'id' => @parent.id,
432
+ 'update' => {
433
+ '_type' => 'Target',
434
+ 'id' => @parent.target.id,
435
+ 'text' => 'parent, new target text'
436
+ }
437
+ },
438
+ {
439
+ 'id' => other_parent.id,
440
+ 'update' => {
441
+ '_type' => 'Target',
442
+ 'id' => other_parent.target.id,
443
+ 'text' => 'other parent, new target text'
444
+ }
445
+ }
446
+ ],
447
+ }
448
+
449
+ targetcontroller = TargetController.new(params: {
450
+ owner_viewmodel: 'parent',
451
+ association_name: 'target',
452
+ data: data,
453
+ })
454
+
455
+ targetcontroller.invoke(:create_associated_bulk)
456
+
457
+ assert_equal(200, targetcontroller.status, targetcontroller.hash_response)
458
+
459
+ target.reload
460
+ other_target.reload
461
+
462
+ assert_equal('parent, new target text', target.text)
463
+ assert_equal('other parent, new target text', other_target.text)
464
+
465
+ response = targetcontroller.hash_response
466
+ response['data']['updates'].sort_by! { |x| x.fetch('id') }
467
+
468
+ assert_equal(
469
+ {
470
+ 'data' => {
471
+ '_type' => '_bulk_update',
472
+ 'updates' => [
473
+ {
474
+ 'id' => @parent.id,
475
+ 'update' => TargetView.new(target).to_hash,
476
+ },
477
+ {
478
+ 'id' => other_parent.id,
479
+ 'update' => TargetView.new(other_target).to_hash,
480
+ },
481
+ ].sort_by { |x| x.fetch('id') }
482
+ }
483
+ },
484
+ response,
485
+ )
486
+ end
487
+
488
+ # Singular shared
489
+
490
+ def test_nested_shared_singular_replace_bulk
491
+ data = {
492
+ '_type' => '_bulk_update',
493
+ 'updates' => [
494
+ {
495
+ 'id' => @parent.id,
496
+ 'update' => { '_ref' => 'new_cat' },
497
+ }
498
+ ]
499
+ }
500
+
501
+ references = {
502
+ 'new_cat' => {
503
+ '_type' => 'Category',
504
+ '_new' => true,
505
+ 'name' => 'cat name'
506
+ }
507
+ }
508
+
509
+ category_controller = CategoryController.new(params: {
510
+ owner_viewmodel: 'parent',
511
+ association_name: 'category',
512
+ data: data,
513
+ references: references,
514
+ })
515
+
516
+ category_controller.invoke(:replace_bulk)
517
+
518
+ response = category_controller.hash_response
519
+
520
+ data, references = response.values_at('data', 'references')
521
+ ref_key = references.keys.first
522
+
523
+ assert_equal(
524
+ {
525
+ '_type' => '_bulk_update',
526
+ 'updates' => [{
527
+ 'id' => @parent.id,
528
+ 'update' => { '_ref' => ref_key }
529
+ }],
530
+ },
531
+ data,
532
+ )
533
+
534
+ @parent.reload
535
+
536
+ assert_equal(
537
+ {
538
+ ref_key => CategoryView.new(@parent.category).to_hash,
539
+ },
540
+ references,
541
+ )
542
+ end
543
+
544
+ # Collection shared
545
+
546
+ def test_nested_shared_collection_replace_bulk
547
+ data = {
548
+ '_type' => '_bulk_update',
549
+ 'updates' => [
550
+ {
551
+ 'id' => @parent.id,
552
+ 'update' => [{ '_ref' => 'new_tag' }],
553
+ }
554
+ ]
555
+ }
556
+
557
+ references = {
558
+ 'new_tag' => {
559
+ '_type' => 'Tag',
560
+ '_new' => true,
561
+ 'name' => 'tag name'
562
+ }
563
+ }
564
+
565
+ tags_controller = TagController.new(params: {
566
+ owner_viewmodel: 'parent',
567
+ association_name: 'tags',
568
+ data: data,
569
+ references: references,
570
+ })
571
+
572
+ tags_controller.invoke(:replace_bulk)
573
+
574
+ response = tags_controller.hash_response
575
+
576
+ data, references = response.values_at('data', 'references')
577
+ ref_key = references.keys.first
578
+
579
+ assert_equal(
580
+ {
581
+ '_type' => '_bulk_update',
582
+ 'updates' => [{
583
+ 'id' => @parent.id,
584
+ 'update' => [{ '_ref' => ref_key }]
585
+ }],
586
+ },
587
+ data,
588
+ )
589
+
590
+ @parent.reload
591
+
592
+ assert_equal(
593
+ {
594
+ ref_key => TagView.new(@parent.parent_tags.first.tag).to_hash,
595
+ },
596
+ references,
597
+ )
598
+ end
599
+ end