closure_tree 7.4.0 → 8.0.0

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +30 -56
  3. data/.github/workflows/ci_jruby.yml +68 -0
  4. data/.github/workflows/ci_truffleruby.yml +71 -0
  5. data/.github/workflows/release.yml +17 -0
  6. data/.gitignore +1 -1
  7. data/.release-please-manifest.json +1 -0
  8. data/.tool-versions +1 -0
  9. data/Appraisals +9 -53
  10. data/CHANGELOG.md +5 -0
  11. data/Gemfile +2 -3
  12. data/README.md +21 -9
  13. data/Rakefile +11 -16
  14. data/closure_tree.gemspec +16 -9
  15. data/lib/closure_tree/active_record_support.rb +3 -14
  16. data/lib/closure_tree/digraphs.rb +1 -1
  17. data/lib/closure_tree/finders.rb +1 -1
  18. data/lib/closure_tree/hash_tree.rb +1 -1
  19. data/lib/closure_tree/hierarchy_maintenance.rb +3 -6
  20. data/lib/closure_tree/model.rb +3 -3
  21. data/lib/closure_tree/numeric_deterministic_ordering.rb +3 -8
  22. data/lib/closure_tree/support.rb +3 -7
  23. data/lib/closure_tree/version.rb +1 -1
  24. data/lib/generators/closure_tree/migration_generator.rb +1 -4
  25. data/release-please-config.json +4 -0
  26. data/test/closure_tree/cache_invalidation_test.rb +36 -0
  27. data/test/closure_tree/cuisine_type_test.rb +42 -0
  28. data/test/closure_tree/generator_test.rb +49 -0
  29. data/test/closure_tree/has_closure_tree_root_test.rb +80 -0
  30. data/test/closure_tree/hierarchy_maintenance_test.rb +56 -0
  31. data/test/closure_tree/label_test.rb +674 -0
  32. data/test/closure_tree/metal_test.rb +59 -0
  33. data/test/closure_tree/model_test.rb +9 -0
  34. data/test/closure_tree/namespace_type_test.rb +13 -0
  35. data/test/closure_tree/parallel_test.rb +162 -0
  36. data/test/closure_tree/pool_test.rb +33 -0
  37. data/test/closure_tree/support_test.rb +18 -0
  38. data/test/closure_tree/tag_test.rb +8 -0
  39. data/test/closure_tree/user_test.rb +175 -0
  40. data/test/closure_tree/uuid_tag_test.rb +8 -0
  41. data/test/support/query_counter.rb +25 -0
  42. data/test/support/tag_examples.rb +923 -0
  43. data/test/test_helper.rb +99 -0
  44. metadata +52 -21
  45. data/_config.yml +0 -1
  46. data/tests.sh +0 -11
@@ -0,0 +1,674 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module CorrectOrderValue
6
+ def self.shared_examples(&block)
7
+ describe "correct order_value" do
8
+ before do
9
+ instance_exec(&block)
10
+ @root = @model.create(name: "root")
11
+ @a, @b, @c = %w[a b c].map { |n| @root.children.create(name: n) }
12
+ end
13
+
14
+ it "should set order_value on roots" do
15
+ if @expected_root_order_value.nil?
16
+ assert_nil @root.order_value
17
+ else
18
+ assert_equal @expected_root_order_value, @root.order_value
19
+ end
20
+ end
21
+
22
+ it "should set order_value with siblings" do
23
+ assert_equal 0, @a.order_value
24
+ assert_equal 1, @b.order_value
25
+ assert_equal 2, @c.order_value
26
+ end
27
+
28
+ it "should reset order_value when a node is moved to another location" do
29
+ root2 = @model.create(name: "root2")
30
+ root2.add_child @b
31
+ assert_equal 0, @a.order_value
32
+ assert_equal 0, @b.order_value
33
+ assert_equal 1, @c.reload.order_value
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def create_label_tree
40
+ @d1 = Label.find_or_create_by_path %w[a1 b1 c1 d1]
41
+ @c1 = @d1.parent
42
+ @b1 = @c1.parent
43
+ @a1 = @b1.parent
44
+ @d2 = Label.find_or_create_by_path %w[a1 b1 c2 d2]
45
+ @c2 = @d2.parent
46
+ @d3 = Label.find_or_create_by_path %w[a2 b2 c3 d3]
47
+ @c3 = @d3.parent
48
+ @b2 = @c3.parent
49
+ @a2 = @b2.parent
50
+ Label.update_all("#{Label._ct.order_column} = id")
51
+ end
52
+
53
+ def create_preorder_tree(suffix = "")
54
+ %w[
55
+ a/l/n/r
56
+ a/l/n/q
57
+ a/l/n/p
58
+ a/l/n/o
59
+ a/l/m
60
+ a/b/h/i/j/k
61
+ a/b/c/d/g
62
+ a/b/c/d/f
63
+ a/b/c/d/e
64
+ ].shuffle.each { |ea| Label.find_or_create_by_path(ea.split("/").collect { |ea| "#{ea}#{suffix}" }) }
65
+
66
+ Label.roots.each_with_index do |root, root_idx|
67
+ root.order_value = root_idx
68
+ yield(root) if block_given?
69
+ root.save!
70
+ root.self_and_descendants.each do |ea|
71
+ ea.children.to_a.sort_by(&:name).each_with_index do |ea, idx|
72
+ ea.order_value = idx
73
+ yield(ea) if block_given?
74
+ ea.save!
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe Label do
81
+ describe "destruction" do
82
+ it "properly destroys descendents created with find_or_create_by_path" do
83
+ c = Label.find_or_create_by_path %w[a b c]
84
+ b = c.parent
85
+ a = c.root
86
+ a.destroy
87
+ refute Label.exists?(id: [a.id, b.id, c.id])
88
+ end
89
+
90
+ it "properly destroys descendents created with add_child" do
91
+ a = Label.create(name: "a")
92
+ b = a.add_child Label.new(name: "b")
93
+ c = b.add_child Label.new(name: "c")
94
+ a.destroy
95
+ refute Label.exists?(a.id)
96
+ refute Label.exists?(b.id)
97
+ refute Label.exists?(c.id)
98
+ end
99
+
100
+ it "properly destroys descendents created with <<" do
101
+ a = Label.create(name: "a")
102
+ b = Label.new(name: "b")
103
+ a.children << b
104
+ c = Label.new(name: "c")
105
+ b.children << c
106
+ a.destroy
107
+ refute Label.exists?(a.id)
108
+ refute Label.exists?(b.id)
109
+ refute Label.exists?(c.id)
110
+ end
111
+ end
112
+
113
+ describe "roots" do
114
+ it "sorts alphabetically" do
115
+ expected = (0..10).to_a
116
+ expected.shuffle.each do |ea|
117
+ Label.create! do |l|
118
+ l.name = "root #{ea}"
119
+ l.order_value = ea
120
+ end
121
+ end
122
+ assert_equal expected, Label.roots.collect { |ea| ea.order_value }
123
+ end
124
+ end
125
+
126
+ describe "Base Label class" do
127
+ it "should find or create by path" do
128
+ # class method:
129
+ c = Label.find_or_create_by_path(%w[grandparent parent child])
130
+ assert_equal %w[grandparent parent child], c.ancestry_path
131
+ assert_equal "child", c.name
132
+ assert_equal "parent", c.parent.name
133
+ end
134
+ end
135
+
136
+ describe "Parent/child inverse relationships" do
137
+ it "should associate both sides of the parent and child relationships" do
138
+ parent = Label.new(name: "parent")
139
+ child = parent.children.build(name: "child")
140
+ assert parent.root?
141
+ refute parent.leaf?
142
+ refute child.root?
143
+ assert child.leaf?
144
+ end
145
+ end
146
+
147
+ describe "DateLabel" do
148
+ it "should find or create by path" do
149
+ date = DateLabel.find_or_create_by_path(%w[2011 November 23])
150
+ assert_equal %w[2011 November 23], date.ancestry_path
151
+ date.self_and_ancestors.each { |ea| assert_equal DateLabel, ea.class }
152
+ assert_equal "23", date.name
153
+ assert_equal "November", date.parent.name
154
+ end
155
+ end
156
+
157
+ describe "DirectoryLabel" do
158
+ it "should find or create by path" do
159
+ dir = DirectoryLabel.find_or_create_by_path(%w[grandparent parent child])
160
+ assert_equal %w[grandparent parent child], dir.ancestry_path
161
+ assert_equal "child", dir.name
162
+ assert_equal "parent", dir.parent.name
163
+ assert_equal "grandparent", dir.parent.parent.name
164
+ assert_equal "grandparent", dir.root.name
165
+ refute_equal Label.find_or_create_by_path(%w[grandparent parent child]), dir.id
166
+ dir.self_and_ancestors.each { |ea| assert_equal DirectoryLabel, ea.class }
167
+ end
168
+ end
169
+
170
+ describe "Mixed class tree" do
171
+ describe "preorder tree" do
172
+ before do
173
+ classes = [Label, DateLabel, DirectoryLabel, EventLabel]
174
+ create_preorder_tree do |ea|
175
+ ea.type = classes[ea.order_value % 4].to_s
176
+ end
177
+ end
178
+
179
+ it "finds roots with specific classes" do
180
+ assert_equal Label.where(name: "a").to_a, Label.roots
181
+ assert DirectoryLabel.roots.empty?
182
+ assert EventLabel.roots.empty?
183
+ end
184
+
185
+ it "all is limited to subclasses" do
186
+ assert_equal %w[f h l n p].sort, DateLabel.all.map(&:name).sort
187
+ assert_equal %w[g q].sort, DirectoryLabel.all.map(&:name).sort
188
+ assert_equal %w[r], EventLabel.all.map(&:name)
189
+ end
190
+
191
+ it "returns descendents regardless of subclass" do
192
+ assert_equal %w[Label DateLabel DirectoryLabel EventLabel].sort, Label.root.descendants.map { |ea|
193
+ ea.class.to_s
194
+ }.uniq.sort
195
+ end
196
+ end
197
+
198
+ it "supports children << and add_child" do
199
+ a = EventLabel.create!(name: "a")
200
+ b = DateLabel.new(name: "b")
201
+ a.children << b
202
+ c = Label.new(name: "c")
203
+ b.add_child(c)
204
+
205
+ assert_equal [EventLabel, DateLabel, Label], a.self_and_descendants.collect { |ea| ea.class }
206
+ assert_equal %w[a b c], a.self_and_descendants.collect { |ea| ea.name }
207
+ end
208
+ end
209
+
210
+ describe "find_all_by_generation" do
211
+ before do
212
+ create_label_tree
213
+ end
214
+
215
+ it "finds roots from the class method" do
216
+ assert_equal [@a1, @a2], Label.find_all_by_generation(0).to_a
217
+ end
218
+
219
+ it "finds roots from themselves" do
220
+ assert_equal [@a1], @a1.find_all_by_generation(0).to_a
221
+ end
222
+
223
+ it "finds itself for non-roots" do
224
+ assert_equal [@b1], @b1.find_all_by_generation(0).to_a
225
+ end
226
+
227
+ it "finds children for roots" do
228
+ assert_equal [@b1, @b2], Label.find_all_by_generation(1).to_a
229
+ end
230
+
231
+ it "finds children" do
232
+ assert_equal [@b1], @a1.find_all_by_generation(1).to_a
233
+ assert_equal [@c1, @c2], @b1.find_all_by_generation(1).to_a
234
+ end
235
+
236
+ it "finds grandchildren for roots" do
237
+ assert_equal [@c1, @c2, @c3], Label.find_all_by_generation(2).to_a
238
+ end
239
+
240
+ it "finds grandchildren" do
241
+ assert_equal [@c1, @c2], @a1.find_all_by_generation(2).to_a
242
+ assert_equal [@d1, @d2], @b1.find_all_by_generation(2).to_a
243
+ end
244
+
245
+ it "finds great-grandchildren for roots" do
246
+ assert_equal [@d1, @d2, @d3], Label.find_all_by_generation(3).to_a
247
+ end
248
+ end
249
+
250
+ describe "loading through self_and_ scopes" do
251
+ before do
252
+ create_label_tree
253
+ end
254
+
255
+ it "self_and_descendants should result in one select" do
256
+ assert_database_queries_count(1) do
257
+ a1_array = @a1.self_and_descendants
258
+ assert_equal(%w[a1 b1 c1 c2 d1 d2], a1_array.collect { |ea| ea.name })
259
+ end
260
+ end
261
+
262
+ it "self_and_ancestors should result in one select" do
263
+ assert_database_queries_count(1) do
264
+ d1_array = @d1.self_and_ancestors
265
+ assert_equal(%w[d1 c1 b1 a1], d1_array.collect { |ea| ea.name })
266
+ end
267
+ end
268
+ end
269
+
270
+ describe "deterministically orders with polymorphic siblings" do
271
+ before do
272
+ @parent = Label.create!(name: "parent")
273
+ @a, @b, @c, @d, @e, @f = ("a".."f").map { |ea| EventLabel.new(name: ea) }
274
+ @parent.children << @a
275
+ @a.append_sibling(@b)
276
+ @b.append_sibling(@c)
277
+ @c.append_sibling(@d)
278
+ @parent.append_sibling(@e)
279
+ @e.append_sibling(@f)
280
+ end
281
+
282
+ def name_and_order(enum)
283
+ enum.map { |ea| [ea.name, ea.order_value] }
284
+ end
285
+
286
+ def children_name_and_order
287
+ name_and_order(@parent.children.reload)
288
+ end
289
+
290
+ def roots_name_and_order
291
+ name_and_order(Label.roots)
292
+ end
293
+
294
+ it "order_values properly" do
295
+ assert_equal [["a", 0], ["b", 1], ["c", 2], ["d", 3]], children_name_and_order
296
+ end
297
+
298
+ it "when inserted before" do
299
+ @b.append_sibling(@a)
300
+ assert_equal [["b", 0], ["a", 1], ["c", 2], ["d", 3]], children_name_and_order
301
+ end
302
+
303
+ it "when inserted after" do
304
+ @a.append_sibling(@c)
305
+ assert_equal [["a", 0], ["c", 1], ["b", 2], ["d", 3]], children_name_and_order
306
+ end
307
+
308
+ it "when inserted before the first" do
309
+ @a.prepend_sibling(@d)
310
+ assert_equal [["d", 0], ["a", 1], ["b", 2], ["c", 3]], children_name_and_order
311
+ end
312
+
313
+ it "when inserted after the last" do
314
+ @d.append_sibling(@b)
315
+ assert_equal [["a", 0], ["c", 1], ["d", 2], ["b", 3]], children_name_and_order
316
+ end
317
+
318
+ it "prepends to root nodes" do
319
+ @parent.prepend_sibling(@f)
320
+ assert_equal [["f", 0], ["parent", 1], ["e", 2]], roots_name_and_order
321
+ end
322
+ end
323
+
324
+ describe "doesn't order roots when requested" do
325
+ before do
326
+ @root1 = LabelWithoutRootOrdering.create!(name: "root1")
327
+ @root2 = LabelWithoutRootOrdering.create!(name: "root2")
328
+ @a, @b, @c, @d, @e = ("a".."e").map { |ea| LabelWithoutRootOrdering.new(name: ea) }
329
+ @root1.children << @a
330
+ @root1.append_child(@c)
331
+ @root1.prepend_child(@d)
332
+
333
+ # Reload is needed here and below because order values may have been adjusted in the DB during
334
+ # prepend_child, append_sibling, etc.
335
+ [@a, @c, @d].each(&:reload)
336
+
337
+ @a.append_sibling(@b)
338
+ [@a, @c, @d, @b].each(&:reload)
339
+ @d.prepend_sibling(@e)
340
+ end
341
+
342
+ it "order_values properly" do
343
+ assert @root1.reload.order_value.nil?
344
+ orders_and_names = @root1.children.reload.map { |ea| [ea.name, ea.order_value] }
345
+ assert_equal [["e", 0], ["d", 1], ["a", 2], ["b", 3], ["c", 4]], orders_and_names
346
+ end
347
+
348
+ it "raises on prepending and appending to root" do
349
+ assert_raises ClosureTree::RootOrderingDisabledError do
350
+ @root1.prepend_sibling(@f)
351
+ end
352
+
353
+ assert_raises ClosureTree::RootOrderingDisabledError do
354
+ @root1.append_sibling(@f)
355
+ end
356
+ end
357
+
358
+ it "returns empty array for siblings_before and after" do
359
+ assert_equal [], @root1.siblings_before
360
+ assert_equal [], @root1.siblings_after
361
+ end
362
+
363
+ unless sqlite?
364
+ it "returns expected result for self_and_descendants_preordered" do
365
+ assert_equal [@root1, @e, @d, @a, @b, @c], @root1.self_and_descendants_preordered.to_a
366
+ end
367
+ end
368
+
369
+ it "raises on roots_and_descendants_preordered" do
370
+ assert_raises ClosureTree::RootOrderingDisabledError do
371
+ LabelWithoutRootOrdering.roots_and_descendants_preordered
372
+ end
373
+ end
374
+ end
375
+
376
+ describe "code in the readme" do
377
+ it "creates STI label hierarchies" do
378
+ child = Label.find_or_create_by_path([
379
+ {type: "DateLabel", name: "2014"},
380
+ {type: "DateLabel", name: "August"},
381
+ {type: "DateLabel", name: "5"},
382
+ {type: "EventLabel", name: "Visit the Getty Center"}
383
+ ])
384
+ assert child.is_a?(EventLabel)
385
+ assert_equal "Visit the Getty Center", child.name
386
+ assert_equal %w[5 August 2014], child.ancestors.map(&:name)
387
+ assert_equal [DateLabel, DateLabel, DateLabel], child.ancestors.map(&:class)
388
+ end
389
+
390
+ it "appends and prepends siblings" do
391
+ root = Label.create(name: "root")
392
+ a = root.append_child(Label.new(name: "a"))
393
+ b = Label.create(name: "b")
394
+ c = Label.create(name: "c")
395
+
396
+ a.append_sibling(b)
397
+ assert_equal %w[a b], a.self_and_siblings.collect(&:name)
398
+ assert_equal %w[a b], root.reload.children.collect(&:name)
399
+ assert_equal [0, 1], root.children.collect(&:order_value)
400
+
401
+ a.prepend_sibling(b)
402
+ assert_equal %w[b a], a.self_and_siblings.collect(&:name)
403
+ assert_equal %w[b a], root.reload.children.collect(&:name)
404
+ assert_equal [0, 1], root.children.collect(&:order_value)
405
+
406
+ a.append_sibling(c)
407
+ assert_equal %w[b a c], a.self_and_siblings.collect(&:name)
408
+ assert_equal %w[b a c], root.reload.children.collect(&:name)
409
+ assert_equal [0, 1, 2], root.children.collect(&:order_value)
410
+
411
+ # We need to reload b because it was updated by a.append_sibling(c)
412
+ b.reload.append_sibling(c)
413
+ assert_equal %w[b c a], root.reload.children.collect(&:name)
414
+ assert_equal [0, 1, 2], root.children.collect(&:order_value)
415
+
416
+ d = a.reload.append_sibling(Label.new(name: "d"))
417
+ assert_equal %w[b c a d], d.self_and_siblings.collect(&:name)
418
+ assert_equal [0, 1, 2, 3], d.self_and_siblings.collect(&:order_value)
419
+ end
420
+ end
421
+
422
+ # https://github.com/mceachen/closure_tree/issues/84
423
+ it "properly appends children with <<" do
424
+ root = Label.create(name: "root")
425
+ a = Label.create(name: "a", parent: root)
426
+ b = Label.create(name: "b", parent: root)
427
+
428
+ # Add a child to root at end of children.
429
+ root.children << b
430
+ assert_equal root, b.parent
431
+ assert_equal %w[a b], a.self_and_siblings.collect(&:name)
432
+ assert_equal %w[a b], root.reload.children.collect(&:name)
433
+ assert_equal [0, 1], root.children.collect(&:order_value)
434
+ end
435
+
436
+ describe "#add_sibling" do
437
+ it "should move a node before another node which has an uninitialized order_value" do
438
+ f = Label.find_or_create_by_path %w[a b c d e fa]
439
+ f0 = f.prepend_sibling(Label.new(name: "fb")) # < not alpha sort, so name shouldn't matter
440
+ assert_equal %w[a b c d e fb], f0.ancestry_path
441
+ assert_equal [f0], f.siblings_before.to_a
442
+ assert f0.siblings_before.empty?
443
+ assert_equal [f], f0.siblings_after
444
+ assert f.siblings_after.empty?
445
+ assert_equal [f0, f], f0.self_and_siblings
446
+ assert_equal [f0, f], f.self_and_siblings
447
+ end
448
+
449
+ before do
450
+ @f1 = Label.find_or_create_by_path %w[a1 b1 c1 d1 e1 f1]
451
+ end
452
+
453
+ it "should move a node to another tree" do
454
+ f2 = Label.find_or_create_by_path %w[a2 b2 c2 d2 e2 f2]
455
+ @f1.add_sibling(f2)
456
+ assert_equal %w[a1 b1 c1 d1 e1 f2], f2.ancestry_path
457
+ assert_equal [@f1, f2], @f1.parent.reload.children
458
+ end
459
+
460
+ it "should reorder old-parent siblings when a node moves to another tree" do
461
+ f2 = Label.find_or_create_by_path %w[a2 b2 c2 d2 e2 f2]
462
+ f3 = f2.prepend_sibling(Label.new(name: "f3"))
463
+ _f4 = f2.append_sibling(Label.new(name: "f4"))
464
+ @f1.add_sibling(f2)
465
+ assert_equal [0, 1], @f1.self_and_siblings.collect(&:order_value)
466
+ assert_equal [0, 1], f3.self_and_siblings.collect(&:order_value)
467
+ assert_equal %w[f1 f2], @f1.self_and_siblings.collect(&:name)
468
+ assert_equal %w[f3 f4], f3.self_and_siblings.collect(&:name)
469
+ end
470
+ end
471
+
472
+ describe "order_value must be set" do
473
+ describe "with normal model" do
474
+ CorrectOrderValue.shared_examples do
475
+ @model = Label
476
+ @expected_root_order_value = 0
477
+ end
478
+ end
479
+
480
+ describe "without root ordering" do
481
+ CorrectOrderValue.shared_examples do
482
+ @model = LabelWithoutRootOrdering
483
+ @expected_root_order_value = nil
484
+ end
485
+ end
486
+ end
487
+
488
+ describe "destructive reordering" do
489
+ before do
490
+ # to make sure order_value isn't affected by additional nodes:
491
+ create_preorder_tree
492
+ @root = Label.create(name: "root")
493
+ @a = @root.children.create!(name: "a")
494
+ @b = @a.append_sibling(Label.new(name: "b"))
495
+ @c = @b.append_sibling(Label.new(name: "c"))
496
+ end
497
+
498
+ describe "doesn't create sort order gaps" do
499
+ it "from head" do
500
+ @a.destroy
501
+ assert_equal [@b, @c], @root.reload.children
502
+ assert_equal([0, 1], @root.children.map { |ea| ea.order_value })
503
+ end
504
+
505
+ it "from mid" do
506
+ @b.destroy
507
+ assert_equal [@a, @c], @root.reload.children
508
+ assert_equal([0, 1], @root.children.map { |ea| ea.order_value })
509
+ end
510
+
511
+ it "from tail" do
512
+ @c.destroy
513
+ assert_equal [@a, @b], @root.reload.children
514
+ assert_equal([0, 1], @root.children.map { |ea| ea.order_value })
515
+ end
516
+ end
517
+
518
+ describe "add_sibling moves descendant nodes" do
519
+ before do
520
+ @roots = (0..10).map { |ea| Label.create(name: ea) }
521
+ @first_root = @roots.first
522
+ @last_root = @roots.last
523
+ end
524
+
525
+ it "should retain sort orders of descendants when moving to a new parent" do
526
+ expected_order = ("a".."z").to_a.shuffle
527
+ expected_order.map { |ea| @first_root.add_child(Label.new(name: ea)) }
528
+ actual_order = @first_root.children.reload.pluck(:name)
529
+ assert_equal expected_order, actual_order
530
+ @last_root.append_child(@first_root)
531
+ assert_equal(%w[10 0] + expected_order, @last_root.self_and_descendants.pluck(:name))
532
+ end
533
+
534
+ it "should retain sort orders of descendants when moving within the same new parent" do
535
+ path = ("a".."z").to_a
536
+ z = @first_root.find_or_create_by_path(path)
537
+ z_children_names = (100..150).to_a.shuffle.map { |ea| ea.to_s }
538
+ z_children_names.reverse_each { |ea| z.prepend_child(Label.new(name: ea)) }
539
+ assert_equal z_children_names, z.children.reload.pluck(:name)
540
+ a = @first_root.find_by_path(["a"])
541
+ # move b up to a's level:
542
+ b = a.children.first
543
+ a.add_sibling(b)
544
+ assert_equal @first_root, b.parent
545
+ assert_equal z_children_names, z.children.reload.pluck(:name)
546
+ end
547
+ end
548
+
549
+ it "shouldn't fail if all children are destroyed" do
550
+ roots = Label.roots.to_a
551
+ roots.each { |ea| ea.children.destroy_all }
552
+ assert_equal roots.sort, Label.all.to_a.sort
553
+ end
554
+ end
555
+
556
+ describe "descendent destruction" do
557
+ it "properly destroys descendents created with add_child" do
558
+ a = Label.create(name: "a")
559
+ b = Label.new(name: "b")
560
+ a.add_child b
561
+ c = Label.new(name: "c")
562
+ b.add_child c
563
+ a.destroy
564
+ refute Label.exists?(id: [a.id, b.id, c.id])
565
+ end
566
+
567
+ it "properly destroys descendents created with <<" do
568
+ a = Label.create(name: "a")
569
+ b = Label.new(name: "b")
570
+ a.children << b
571
+ c = Label.new(name: "c")
572
+ b.children << c
573
+ a.destroy
574
+ refute Label.exists?(id: [a.id, b.id, c.id])
575
+ end
576
+ end
577
+
578
+ unless sqlite?
579
+ describe "preorder" do
580
+ it "returns descendants in proper order" do
581
+ create_preorder_tree
582
+ a = Label.root
583
+ assert_equal "a", a.name
584
+ expected = ("a".."r").to_a
585
+ assert_equal expected, a.self_and_descendants_preordered.collect { |ea| ea.name }
586
+ assert_equal expected, Label.roots_and_descendants_preordered.collect { |ea| ea.name }
587
+ # Let's create the second root by hand so we can explicitly set the sort order
588
+ Label.create! do |l|
589
+ l.name = "a1"
590
+ l.order_value = a.order_value + 1
591
+ end
592
+ create_preorder_tree("1")
593
+ # Should be no change:
594
+ assert_equal expected, a.reload.self_and_descendants_preordered.collect { |ea| ea.name }
595
+ expected += ("a".."r").collect { |ea| "#{ea}1" }
596
+ assert_equal expected, Label.roots_and_descendants_preordered.collect { |ea| ea.name }
597
+ end
598
+ end
599
+ end
600
+
601
+ describe "hash_tree" do
602
+ before do
603
+ @a = EventLabel.create(name: "a")
604
+ @b = DateLabel.create(name: "b")
605
+ @c = DirectoryLabel.create(name: "c")
606
+ (1..3).each { |i| DirectoryLabel.create!(name: "c#{i}", mother_id: @c.id) }
607
+ end
608
+
609
+ it "should return tree with correct scope when called on class" do
610
+ tree = DirectoryLabel.hash_tree
611
+ assert_equal 1, tree.keys.size
612
+ assert_equal @c, tree.keys.first
613
+ assert_equal 3, tree[@c].keys.size
614
+ end
615
+
616
+ it "should return tree with correct scope when called on all" do
617
+ tree = DirectoryLabel.all.hash_tree
618
+ assert_equal 1, tree.keys.size
619
+ assert_equal @c, tree.keys.first
620
+ assert_equal 3, tree[@c].keys.size
621
+ end
622
+
623
+ it "should return tree with correct scope when called on scope chain" do
624
+ tree = Label.where(name: "b").hash_tree
625
+ assert_equal 1, tree.keys.size
626
+ assert_equal @b, tree.keys.first
627
+ assert_equal({}, tree[@b])
628
+ end
629
+ end
630
+
631
+ describe "relationship between nodes" do
632
+ before do
633
+ create_label_tree
634
+ end
635
+
636
+ it "checks parent of node" do
637
+ assert @a1.parent_of?(@b1)
638
+ assert @c2.parent_of?(@d2)
639
+ refute @c1.parent_of?(@b1)
640
+ end
641
+
642
+ it "checks children of node" do
643
+ assert @d1.child_of?(@c1)
644
+ assert @c2.child_of?(@b1)
645
+ refute @c3.child_of?(@b1)
646
+ end
647
+
648
+ it "checks root of node" do
649
+ assert @a1.root_of?(@d1)
650
+ assert @a1.root_of?(@c2)
651
+ refute @a2.root_of?(@c2)
652
+ end
653
+
654
+ it "checks ancestor of node" do
655
+ assert @a1.ancestor_of?(@d1)
656
+ assert @b1.ancestor_of?(@d1)
657
+ refute @b1.ancestor_of?(@c3)
658
+ end
659
+
660
+ it "checks descendant of node" do
661
+ assert @c1.descendant_of?(@a1)
662
+ assert @d2.descendant_of?(@a1)
663
+ refute @b1.descendant_of?(@a2)
664
+ end
665
+
666
+ it "checks descendant of node" do
667
+ assert @b1.family_of?(@b1)
668
+ assert @a1.family_of?(@c1)
669
+ assert @d3.family_of?(@a2)
670
+ assert @c1.family_of?(@d2)
671
+ refute @c3.family_of?(@a1)
672
+ end
673
+ end
674
+ end