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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -56
- data/.github/workflows/ci_jruby.yml +68 -0
- data/.github/workflows/ci_truffleruby.yml +71 -0
- data/.github/workflows/release.yml +17 -0
- data/.gitignore +1 -1
- data/.release-please-manifest.json +1 -0
- data/.tool-versions +1 -0
- data/Appraisals +9 -53
- data/CHANGELOG.md +5 -0
- data/Gemfile +2 -3
- data/README.md +21 -9
- data/Rakefile +11 -16
- data/closure_tree.gemspec +16 -9
- data/lib/closure_tree/active_record_support.rb +3 -14
- data/lib/closure_tree/digraphs.rb +1 -1
- data/lib/closure_tree/finders.rb +1 -1
- data/lib/closure_tree/hash_tree.rb +1 -1
- data/lib/closure_tree/hierarchy_maintenance.rb +3 -6
- data/lib/closure_tree/model.rb +3 -3
- data/lib/closure_tree/numeric_deterministic_ordering.rb +3 -8
- data/lib/closure_tree/support.rb +3 -7
- data/lib/closure_tree/version.rb +1 -1
- data/lib/generators/closure_tree/migration_generator.rb +1 -4
- data/release-please-config.json +4 -0
- data/test/closure_tree/cache_invalidation_test.rb +36 -0
- data/test/closure_tree/cuisine_type_test.rb +42 -0
- data/test/closure_tree/generator_test.rb +49 -0
- data/test/closure_tree/has_closure_tree_root_test.rb +80 -0
- data/test/closure_tree/hierarchy_maintenance_test.rb +56 -0
- data/test/closure_tree/label_test.rb +674 -0
- data/test/closure_tree/metal_test.rb +59 -0
- data/test/closure_tree/model_test.rb +9 -0
- data/test/closure_tree/namespace_type_test.rb +13 -0
- data/test/closure_tree/parallel_test.rb +162 -0
- data/test/closure_tree/pool_test.rb +33 -0
- data/test/closure_tree/support_test.rb +18 -0
- data/test/closure_tree/tag_test.rb +8 -0
- data/test/closure_tree/user_test.rb +175 -0
- data/test/closure_tree/uuid_tag_test.rb +8 -0
- data/test/support/query_counter.rb +25 -0
- data/test/support/tag_examples.rb +923 -0
- data/test/test_helper.rb +99 -0
- metadata +52 -21
- data/_config.yml +0 -1
- data/tests.sh +0 -11
@@ -0,0 +1,923 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TagExamples
|
4
|
+
def self.included(mod)
|
5
|
+
@@described_class = mod.name.safe_constantize
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'TagExamples' do
|
9
|
+
before do
|
10
|
+
@tag_class = @@described_class
|
11
|
+
@tag_hierarchy_class = @@described_class.hierarchy_class
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'class setup' do
|
15
|
+
it 'has correct accessible_attributes' do
|
16
|
+
if @tag_class._ct.use_attr_accessible?
|
17
|
+
assert_equal(%w[parent name title].sort, @tag_class.accessible_attributes.to_a.sort)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should build hierarchy classname correctly' do
|
22
|
+
assert_equal @tag_hierarchy_class, @tag_class.hierarchy_class
|
23
|
+
assert_equal @tag_hierarchy_class.to_s, @tag_class._ct.hierarchy_class_name
|
24
|
+
assert_equal @tag_hierarchy_class.to_s, @tag_class._ct.short_hierarchy_class_name
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should have a correct parent column name' do
|
28
|
+
expected_parent_column_name = @tag_class == UUIDTag ? 'parent_uuid' : 'parent_id'
|
29
|
+
assert_equal expected_parent_column_name, @tag_class._ct.parent_column_name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'from empty db' do
|
34
|
+
describe 'with no tags' do
|
35
|
+
it 'should return no entities' do
|
36
|
+
assert_empty @tag_class.roots
|
37
|
+
assert_empty @tag_class.leaves
|
38
|
+
end
|
39
|
+
|
40
|
+
it '#find_or_create_by_path with strings' do
|
41
|
+
a = @tag_class.create!(name: 'a')
|
42
|
+
assert_equal(%w[a b c], a.find_or_create_by_path(%w[b c]).ancestry_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
it '#find_or_create_by_path with hashes' do
|
46
|
+
a = @tag_class.create!(name: 'a', title: 'A')
|
47
|
+
subject = a.find_or_create_by_path([
|
48
|
+
{ name: 'b', title: 'B' },
|
49
|
+
{ name: 'c', title: 'C' }
|
50
|
+
])
|
51
|
+
assert_equal(%w[a b c], subject.ancestry_path)
|
52
|
+
assert_equal(%w[C B A], subject.self_and_ancestors.map(&:title))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'with 1 tag' do
|
57
|
+
before do
|
58
|
+
@tag = @tag_class.create!(name: 'tag')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should be a leaf' do
|
62
|
+
assert @tag.leaf?
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should be a root' do
|
66
|
+
assert @tag.root?
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'has no parent' do
|
70
|
+
assert_nil @tag.parent
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should return the only entity as a root and leaf' do
|
74
|
+
assert_equal [@tag], @tag_class.all
|
75
|
+
assert_equal [@tag], @tag_class.roots
|
76
|
+
assert_equal [@tag], @tag_class.leaves
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should not be found by passing find_by_path an array of blank strings' do
|
80
|
+
assert_nil @tag_class.find_by_path([''])
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should not be found by passing find_by_path an empty array' do
|
84
|
+
assert_nil @tag_class.find_by_path([])
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should not be found by passing find_by_path nil' do
|
88
|
+
assert_nil @tag_class.find_by_path(nil)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should not be found by passing find_by_path an empty string' do
|
92
|
+
assert_nil @tag_class.find_by_path('')
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should not be found by passing find_by_path an array of nils' do
|
96
|
+
assert_nil @tag_class.find_by_path([nil])
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should not be found by passing find_by_path an array with an additional blank string' do
|
100
|
+
assert_nil @tag_class.find_by_path([@tag.name, ''])
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should not be found by passing find_by_path an array with an additional nil' do
|
104
|
+
assert_nil @tag_class.find_by_path([@tag.name, nil])
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should be found by passing find_by_path an array with its name' do
|
108
|
+
assert_equal @tag, @tag_class.find_by_path([@tag.name])
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should be found by passing find_by_path its name' do
|
112
|
+
assert_equal @tag, @tag_class.find_by_path(@tag.name)
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'with child' do
|
116
|
+
before do
|
117
|
+
@child = @tag_class.create!(name: 'tag 2')
|
118
|
+
end
|
119
|
+
|
120
|
+
def assert_roots_and_leaves
|
121
|
+
assert @tag.root?
|
122
|
+
refute @tag.leaf?
|
123
|
+
|
124
|
+
refute @child.root?
|
125
|
+
assert @child.leaf?
|
126
|
+
end
|
127
|
+
|
128
|
+
def assert_parent_and_children
|
129
|
+
assert_equal @tag, @child.reload.parent
|
130
|
+
assert_equal [@child], @tag.reload.children.to_a
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'adds children through add_child' do
|
134
|
+
@tag.add_child @child
|
135
|
+
assert_roots_and_leaves
|
136
|
+
assert_parent_and_children
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'adds children through collection' do
|
140
|
+
@tag.children << @child
|
141
|
+
assert_roots_and_leaves
|
142
|
+
assert_parent_and_children
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'with 2 tags' do
|
148
|
+
before do
|
149
|
+
@root = @tag_class.create!(name: 'root')
|
150
|
+
@leaf = @root.add_child(@tag_class.create!(name: 'leaf'))
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should return a simple root and leaf' do
|
154
|
+
assert_equal [@root], @tag_class.roots
|
155
|
+
assert_equal [@leaf], @tag_class.leaves
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'should return child_ids for root' do
|
159
|
+
assert_equal [@leaf.id], @root.child_ids
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should return an empty array for leaves' do
|
163
|
+
assert_empty @leaf.child_ids
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe '3 tag collection.create db' do
|
168
|
+
before do
|
169
|
+
@root = @tag_class.create! name: 'root'
|
170
|
+
@mid = @root.children.create! name: 'mid'
|
171
|
+
@leaf = @mid.children.create! name: 'leaf'
|
172
|
+
DestroyedTag.delete_all
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should create all tags' do
|
176
|
+
assert_equal [@root, @mid, @leaf].sort, @tag_class.all.to_a.sort
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should return a root and leaf without middle tag' do
|
180
|
+
assert_equal [@root], @tag_class.roots
|
181
|
+
assert_equal [@leaf], @tag_class.leaves
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should delete leaves' do
|
185
|
+
@tag_class.leaves.destroy_all
|
186
|
+
assert_equal [@root], @tag_class.roots # untouched
|
187
|
+
assert_equal [@mid], @tag_class.leaves
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should delete everything if you delete the roots' do
|
191
|
+
@tag_class.roots.destroy_all
|
192
|
+
assert_empty @tag_class.all
|
193
|
+
assert_empty @tag_class.roots
|
194
|
+
assert_empty @tag_class.leaves
|
195
|
+
assert_equal %w[root mid leaf].sort, DestroyedTag.all.map(&:name).sort
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'fix self_and_ancestors properly on reparenting' do
|
199
|
+
t = @tag_class.create! name: 'moar leaf'
|
200
|
+
assert_equal [t], t.self_and_ancestors.to_a
|
201
|
+
@mid.children << t
|
202
|
+
assert_equal [t, @mid, @root], t.self_and_ancestors.to_a
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'prevents ancestor loops' do
|
206
|
+
@leaf.add_child @root
|
207
|
+
refute @root.valid?
|
208
|
+
assert_includes @root.reload.descendants, @leaf
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'moves non-leaves' do
|
212
|
+
new_root = @tag_class.create! name: 'new_root'
|
213
|
+
new_root.children << @mid
|
214
|
+
assert_empty @root.reload.descendants
|
215
|
+
assert_equal [@mid, @leaf], new_root.descendants
|
216
|
+
assert_equal %w[new_root mid leaf], @leaf.reload.ancestry_path
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'moves leaves' do
|
220
|
+
new_root = @tag_class.create! name: 'new_root'
|
221
|
+
new_root.children << @leaf
|
222
|
+
assert_equal [@leaf], new_root.descendants
|
223
|
+
assert_equal [@mid], @root.reload.descendants
|
224
|
+
assert_equal %w[new_root leaf], @leaf.reload.ancestry_path
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '3 tag explicit_create db' do
|
229
|
+
before do
|
230
|
+
@root = @tag_class.create!(name: 'root')
|
231
|
+
@mid = @root.add_child(@tag_class.create!(name: 'mid'))
|
232
|
+
@leaf = @mid.add_child(@tag_class.create!(name: 'leaf'))
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'should create all tags' do
|
236
|
+
assert_equal [@root, @mid, @leaf].sort, @tag_class.all.to_a.sort
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'should return a root and leaf without middle tag' do
|
240
|
+
assert_equal [@root], @tag_class.roots
|
241
|
+
assert_equal [@leaf], @tag_class.leaves
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'should prevent parental loops from torso' do
|
245
|
+
@mid.children << @root
|
246
|
+
refute @root.valid?
|
247
|
+
assert_equal [@leaf], @mid.reload.children
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'should prevent parental loops from toes' do
|
251
|
+
@leaf.children << @root
|
252
|
+
refute @root.valid?
|
253
|
+
assert_empty @leaf.reload.children
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'should support re-parenting' do
|
257
|
+
@root.children << @leaf
|
258
|
+
assert_equal [@leaf, @mid], @tag_class.leaves
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'cleans up hierarchy references for leaves' do
|
262
|
+
@leaf.destroy
|
263
|
+
assert_empty @tag_hierarchy_class.where(ancestor_id: @leaf.id)
|
264
|
+
assert_empty @tag_hierarchy_class.where(descendant_id: @leaf.id)
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'cleans up hierarchy references' do
|
268
|
+
@mid.destroy
|
269
|
+
assert_empty @tag_hierarchy_class.where(ancestor_id: @mid.id)
|
270
|
+
assert_empty @tag_hierarchy_class.where(descendant_id: @mid.id)
|
271
|
+
assert @root.reload.root?
|
272
|
+
root_hiers = @root.ancestor_hierarchies.to_a
|
273
|
+
assert_equal 1, root_hiers.size
|
274
|
+
assert_equal root_hiers, @tag_hierarchy_class.where(ancestor_id: @root.id)
|
275
|
+
assert_equal root_hiers, @tag_hierarchy_class.where(descendant_id: @root.id)
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should have different hash codes for each hierarchy model' do
|
279
|
+
hashes = @tag_hierarchy_class.all.map(&:hash)
|
280
|
+
assert_equal hashes.uniq.sort, hashes.sort
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'should return the same hash code for equal hierarchy models' do
|
284
|
+
assert_equal @tag_hierarchy_class.first.hash, @tag_hierarchy_class.first.hash
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'performs as the readme says it does' do
|
289
|
+
grandparent = @tag_class.create(name: 'Grandparent')
|
290
|
+
parent = grandparent.children.create(name: 'Parent')
|
291
|
+
child1 = @tag_class.create(name: 'First Child', parent: parent)
|
292
|
+
child2 = @tag_class.new(name: 'Second Child')
|
293
|
+
parent.children << child2
|
294
|
+
child3 = @tag_class.new(name: 'Third Child')
|
295
|
+
parent.add_child child3
|
296
|
+
assert_equal(
|
297
|
+
['Grandparent', 'Parent', 'First Child', 'Second Child', 'Third Child'],
|
298
|
+
grandparent.self_and_descendants.collect(&:name)
|
299
|
+
)
|
300
|
+
assert_equal(['Grandparent', 'Parent', 'First Child'], child1.ancestry_path)
|
301
|
+
assert_equal(['Grandparent', 'Parent', 'Third Child'], child3.ancestry_path)
|
302
|
+
d = @tag_class.find_or_create_by_path %w[a b c d]
|
303
|
+
h = @tag_class.find_or_create_by_path %w[e f g h]
|
304
|
+
e = h.root
|
305
|
+
d.add_child(e) # "d.children << e" would work too, of course
|
306
|
+
assert_equal %w[a b c d e f g h], h.ancestry_path
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'roots sort alphabetically' do
|
310
|
+
expected = ('a'..'z').to_a
|
311
|
+
expected.shuffle.each { |ea| @tag_class.create!(name: ea) }
|
312
|
+
assert_equal expected, @tag_class.roots.collect(&:name)
|
313
|
+
end
|
314
|
+
|
315
|
+
describe 'with simple tree' do
|
316
|
+
before do
|
317
|
+
@tag_class.find_or_create_by_path %w[a1 b1 c1a]
|
318
|
+
@tag_class.find_or_create_by_path %w[a1 b1 c1b]
|
319
|
+
@tag_class.find_or_create_by_path %w[a1 b1 c1c]
|
320
|
+
@tag_class.find_or_create_by_path %w[a1 b1b]
|
321
|
+
@tag_class.find_or_create_by_path %w[a2 b2]
|
322
|
+
@tag_class.find_or_create_by_path %w[a3]
|
323
|
+
|
324
|
+
@a1, @a2, @a3, @b1, @b1b, @b2, @c1a, @c1b, @c1c = @tag_class.all.sort_by(&:name)
|
325
|
+
@expected_roots = [@a1, @a2, @a3]
|
326
|
+
@expected_leaves = [@c1a, @c1b, @c1c, @b1b, @b2, @a3]
|
327
|
+
@expected_siblings = [[@a1, @a2, @a3], [@b1, @b1b], [@c1a, @c1b, @c1c]]
|
328
|
+
@expected_only_children = @tag_class.all - @expected_siblings.flatten
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'should find global roots' do
|
332
|
+
assert_equal @expected_roots.sort, @tag_class.roots.to_a.sort
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'should return root? for roots' do
|
336
|
+
@expected_roots.each { |ea| assert(ea.root?) }
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should not return root? for non-roots' do
|
340
|
+
[@b1, @b2, @c1a, @c1b].each { |ea| refute(ea.root?) }
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'should return the correct root' do
|
344
|
+
{ @a1 => @a1, @a2 => @a2, @a3 => @a3,
|
345
|
+
@b1 => @a1, @b2 => @a2, @c1a => @a1, @c1b => @a1 }.each do |node, root|
|
346
|
+
assert_equal(root, node.root)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'should assemble global leaves' do
|
351
|
+
assert_equal @expected_leaves.sort, @tag_class.leaves.to_a.sort
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'assembles siblings properly' do
|
355
|
+
@expected_siblings.each do |siblings|
|
356
|
+
siblings.each do |ea|
|
357
|
+
assert_equal siblings.sort, ea.self_and_siblings.to_a.sort
|
358
|
+
assert_equal((siblings - [ea]).sort, ea.siblings.to_a.sort)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
@expected_only_children.each do |ea|
|
363
|
+
assert_equal [], ea.siblings
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'assembles before_siblings' do
|
368
|
+
@expected_siblings.each do |siblings|
|
369
|
+
(siblings.size - 1).times do |i|
|
370
|
+
target = siblings[i]
|
371
|
+
expected_before = siblings.first(i)
|
372
|
+
assert_equal expected_before, target.siblings_before.to_a
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'assembles after_siblings' do
|
378
|
+
@expected_siblings.each do |siblings|
|
379
|
+
(siblings.size - 1).times do |i|
|
380
|
+
target = siblings[i]
|
381
|
+
expected_after = siblings.last(siblings.size - 1 - i)
|
382
|
+
assert_equal expected_after, target.siblings_after.to_a
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'should assemble instance leaves' do
|
388
|
+
{ @a1 => [@b1b, @c1a, @c1b, @c1c], @b1 => [@c1a, @c1b, @c1c], @a2 => [@b2] }.each do |node, leaves|
|
389
|
+
assert_equal leaves, node.leaves.to_a
|
390
|
+
end
|
391
|
+
|
392
|
+
@expected_leaves.each { |ea| assert_equal [ea], ea.leaves.to_a }
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'should return leaf? for leaves' do
|
396
|
+
@expected_leaves.each { |ea| assert ea.leaf? }
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'can move roots' do
|
400
|
+
@c1a.children << @a2
|
401
|
+
@b2.reload.children << @a3
|
402
|
+
assert_equal %w[a1 b1 c1a a2 b2 a3], @a3.reload.ancestry_path
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'cascade-deletes from roots' do
|
406
|
+
victim_names = @a1.self_and_descendants.map(&:name)
|
407
|
+
survivor_names = @tag_class.all.map(&:name) - victim_names
|
408
|
+
@a1.destroy
|
409
|
+
assert_equal survivor_names, @tag_class.all.map(&:name)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
describe 'with_ancestor' do
|
414
|
+
it 'works with no rows' do
|
415
|
+
assert_empty @tag_class.with_ancestor.to_a
|
416
|
+
end
|
417
|
+
|
418
|
+
it 'finds only children' do
|
419
|
+
c = @tag_class.find_or_create_by_path %w[A B C]
|
420
|
+
a = c.parent.parent
|
421
|
+
b = c.parent
|
422
|
+
@tag_class.find_or_create_by_path %w[D E]
|
423
|
+
assert_equal [b, c], @tag_class.with_ancestor(a).to_a
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'limits subsequent where clauses' do
|
427
|
+
a1c = @tag_class.find_or_create_by_path %w[A1 B C]
|
428
|
+
a2c = @tag_class.find_or_create_by_path %w[A2 B C]
|
429
|
+
# different paths!
|
430
|
+
refute_equal a2c, a1c
|
431
|
+
assert_equal [a1c, a2c].sort, @tag_class.where(name: 'C').to_a.sort
|
432
|
+
assert_equal [a1c], @tag_class.with_ancestor(a1c.parent.parent).where(name: 'C').to_a.sort
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
describe 'with_descendant' do
|
437
|
+
it 'works with no rows' do
|
438
|
+
assert_empty @tag_class.with_descendant.to_a
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'finds only parents' do
|
442
|
+
c = @tag_class.find_or_create_by_path %w[A B C]
|
443
|
+
a = c.parent.parent
|
444
|
+
b = c.parent
|
445
|
+
_spurious_tags = @tag_class.find_or_create_by_path %w[D E]
|
446
|
+
assert_equal [a, b], @tag_class.with_descendant(c).to_a
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'limits subsequent where clauses' do
|
450
|
+
ac1 = @tag_class.create(name: 'A')
|
451
|
+
ac2 = @tag_class.create(name: 'A')
|
452
|
+
|
453
|
+
c1 = @tag_class.find_or_create_by_path %w[B C1]
|
454
|
+
ac1.children << c1.parent
|
455
|
+
|
456
|
+
c2 = @tag_class.find_or_create_by_path %w[B C2]
|
457
|
+
ac2.children << c2.parent
|
458
|
+
|
459
|
+
# different paths!
|
460
|
+
refute_equal ac2, ac1
|
461
|
+
assert_equal [ac1, ac2].sort, @tag_class.where(name: 'A').to_a.sort
|
462
|
+
assert_equal [ac1], @tag_class.with_descendant(c1).where(name: 'A').to_a
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
describe 'lowest_common_ancestor' do
|
467
|
+
before do
|
468
|
+
@t1 = @tag_class.create!(name: 't1')
|
469
|
+
@t11 = @tag_class.create!(name: 't11', parent: @t1)
|
470
|
+
@t111 = @tag_class.create!(name: 't111', parent: @t11)
|
471
|
+
@t112 = @tag_class.create!(name: 't112', parent: @t11)
|
472
|
+
@t12 = @tag_class.create!(name: 't12', parent: @t1)
|
473
|
+
@t121 = @tag_class.create!(name: 't121', parent: @t12)
|
474
|
+
@t2 = @tag_class.create!(name: 't2')
|
475
|
+
@t21 = @tag_class.create!(name: 't21', parent: @t2)
|
476
|
+
@t21 = @tag_class.create!(name: 't21', parent: @t2)
|
477
|
+
@t211 = @tag_class.create!(name: 't211', parent: @t21)
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'finds the parent for siblings' do
|
481
|
+
assert_equal @t11, @tag_class.lowest_common_ancestor(@t112, @t111)
|
482
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@t12, @t11)
|
483
|
+
|
484
|
+
assert_equal @t11, @tag_class.lowest_common_ancestor([@t112, @t111])
|
485
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor([@t12, @t11])
|
486
|
+
|
487
|
+
assert_equal @t11, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t112 t111]))
|
488
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t12 t11]))
|
489
|
+
end
|
490
|
+
|
491
|
+
it 'finds the grandparent for cousins' do
|
492
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@t112, @t111, @t121)
|
493
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor([@t112, @t111, @t121])
|
494
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t112 t111 t121]))
|
495
|
+
end
|
496
|
+
|
497
|
+
it 'finds the parent/grandparent for aunt-uncle/niece-nephew' do
|
498
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@t12, @t112)
|
499
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor([@t12, @t112])
|
500
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t12 t112]))
|
501
|
+
end
|
502
|
+
|
503
|
+
it 'finds the self/parent for parent/child' do
|
504
|
+
assert_equal @t12, @tag_class.lowest_common_ancestor(@t12, @t121)
|
505
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@t1, @t12)
|
506
|
+
|
507
|
+
assert_equal @t12, @tag_class.lowest_common_ancestor([@t12, @t121])
|
508
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor([@t1, @t12])
|
509
|
+
|
510
|
+
assert_equal @t12, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t12 t121]))
|
511
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t1 t12]))
|
512
|
+
end
|
513
|
+
|
514
|
+
it 'finds the self/grandparent for grandparent/grandchild' do
|
515
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor(@t211, @t2)
|
516
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@t111, @t1)
|
517
|
+
|
518
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor([@t211, @t2])
|
519
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor([@t111, @t1])
|
520
|
+
|
521
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t211 t2]))
|
522
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t111 t1]))
|
523
|
+
end
|
524
|
+
|
525
|
+
it 'finds the grandparent for a whole extended family' do
|
526
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor(@t1, @t11, @t111, @t112, @t12, @t121)
|
527
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor(@t2, @t21, @t211)
|
528
|
+
|
529
|
+
assert_equal @t1, @tag_class.lowest_common_ancestor([@t1, @t11, @t111, @t112, @t12, @t121])
|
530
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor([@t2, @t21, @t211])
|
531
|
+
|
532
|
+
assert_equal @t1,
|
533
|
+
@tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t1 t11 t111 t112 t12 t121]))
|
534
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t2 t21 t211]))
|
535
|
+
end
|
536
|
+
|
537
|
+
it 'is nil for no items' do
|
538
|
+
assert_nil @tag_class.lowest_common_ancestor
|
539
|
+
assert_nil @tag_class.lowest_common_ancestor([])
|
540
|
+
assert_nil @tag_class.lowest_common_ancestor(@tag_class.none)
|
541
|
+
end
|
542
|
+
|
543
|
+
it 'is nil if there are no common ancestors' do
|
544
|
+
assert_nil @tag_class.lowest_common_ancestor(@t111, @t211)
|
545
|
+
assert_nil @tag_class.lowest_common_ancestor([@t111, @t211])
|
546
|
+
assert_nil @tag_class.lowest_common_ancestor(@tag_class.where(name: %w[t111 t211]))
|
547
|
+
end
|
548
|
+
|
549
|
+
it 'is itself for single item' do
|
550
|
+
assert_equal @t111, @tag_class.lowest_common_ancestor(@t111)
|
551
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor(@t2)
|
552
|
+
|
553
|
+
assert_equal @t111, @tag_class.lowest_common_ancestor([@t111])
|
554
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor([@t2])
|
555
|
+
|
556
|
+
assert_equal @t111, @tag_class.lowest_common_ancestor(@tag_class.where(name: 't111'))
|
557
|
+
assert_equal @t2, @tag_class.lowest_common_ancestor(@tag_class.where(name: 't2'))
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
describe 'paths' do
|
562
|
+
describe 'with grandchild ' do
|
563
|
+
before do
|
564
|
+
@child = @tag_class.find_or_create_by_path([
|
565
|
+
{ name: 'grandparent', title: 'Nonnie' },
|
566
|
+
{ name: 'parent', title: 'Mom' },
|
567
|
+
{ name: 'child', title: 'Kid' }
|
568
|
+
])
|
569
|
+
@parent = @child.parent
|
570
|
+
@grandparent = @parent.parent
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'should build ancestry path' do
|
574
|
+
assert_equal %w[grandparent parent child], @child.ancestry_path
|
575
|
+
assert_equal %w[grandparent parent child], @child.ancestry_path(:name)
|
576
|
+
assert_equal %w[Nonnie Mom Kid], @child.ancestry_path(:title)
|
577
|
+
end
|
578
|
+
|
579
|
+
it 'assembles ancestors' do
|
580
|
+
assert_equal [@parent, @grandparent], @child.ancestors
|
581
|
+
assert_equal [@child, @parent, @grandparent], @child.self_and_ancestors
|
582
|
+
end
|
583
|
+
|
584
|
+
it 'should find by path' do
|
585
|
+
# class method:
|
586
|
+
assert_equal @child, @tag_class.find_by_path(%w[grandparent parent child])
|
587
|
+
# instance method:
|
588
|
+
assert_equal @child, @parent.find_by_path(%w[child])
|
589
|
+
assert_equal @child, @grandparent.find_by_path(%w[parent child])
|
590
|
+
assert_nil @parent.find_by_path(%w[child larvae])
|
591
|
+
end
|
592
|
+
|
593
|
+
it 'should respect attribute hashes with both selection and creation' do
|
594
|
+
expected_title = 'something else'
|
595
|
+
attrs = { title: expected_title }
|
596
|
+
existing_title = @grandparent.title
|
597
|
+
new_grandparent = @tag_class.find_or_create_by_path(%w[grandparent], attrs)
|
598
|
+
refute_equal @grandparent, new_grandparent
|
599
|
+
assert_equal expected_title, new_grandparent.title
|
600
|
+
assert_equal existing_title, @grandparent.reload.title
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'should create a hierarchy with a given attribute' do
|
604
|
+
expected_title = 'unicorn rainbows'
|
605
|
+
attrs = { title: expected_title }
|
606
|
+
child = @tag_class.find_or_create_by_path(%w[grandparent parent child], attrs)
|
607
|
+
refute_equal @child, child
|
608
|
+
[child, child.parent, child.parent.parent].each do |ea|
|
609
|
+
assert_equal expected_title, ea.title
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'finds correctly rooted paths' do
|
615
|
+
_decoy = @tag_class.find_or_create_by_path %w[a b c d]
|
616
|
+
b_d = @tag_class.find_or_create_by_path %w[b c d]
|
617
|
+
assert_equal b_d, @tag_class.find_by_path(%w[b c d])
|
618
|
+
assert_nil @tag_class.find_by_path(%w[c d])
|
619
|
+
end
|
620
|
+
|
621
|
+
it 'find_by_path for 1 node' do
|
622
|
+
b = @tag_class.find_or_create_by_path %w[a b]
|
623
|
+
b2 = b.root.find_by_path(%w[b])
|
624
|
+
assert_equal b, b2
|
625
|
+
end
|
626
|
+
|
627
|
+
it 'find_by_path for 2 nodes' do
|
628
|
+
path = %w[a b c]
|
629
|
+
c = @tag_class.find_or_create_by_path path
|
630
|
+
permutations = path.permutation.to_a
|
631
|
+
correct = %w[b c]
|
632
|
+
assert_equal c, c.root.find_by_path(correct)
|
633
|
+
(permutations - correct).each do |bad_path|
|
634
|
+
assert_nil c.root.find_by_path(bad_path)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
it 'find_by_path for 3 nodes' do
|
639
|
+
d = @tag_class.find_or_create_by_path %w[a b c d]
|
640
|
+
assert_equal d, d.root.find_by_path(%w[b c d])
|
641
|
+
assert_equal d, @tag_class.find_by_path(%w[a b c d])
|
642
|
+
assert_nil @tag_class.find_by_path(%w[d])
|
643
|
+
end
|
644
|
+
|
645
|
+
it 'should return nil for missing nodes' do
|
646
|
+
assert_nil @tag_class.find_by_path(%w[missing])
|
647
|
+
assert_nil @tag_class.find_by_path(%w[grandparent missing])
|
648
|
+
assert_nil @tag_class.find_by_path(%w[grandparent parent missing])
|
649
|
+
assert_nil @tag_class.find_by_path(%w[grandparent parent missing child])
|
650
|
+
end
|
651
|
+
|
652
|
+
describe '.find_or_create_by_path' do
|
653
|
+
it 'uses existing records' do
|
654
|
+
grandparent = @tag_class.find_or_create_by_path(%w[grandparent])
|
655
|
+
assert_equal grandparent, grandparent
|
656
|
+
child = @tag_class.find_or_create_by_path(%w[grandparent parent child])
|
657
|
+
assert_equal child, child
|
658
|
+
end
|
659
|
+
|
660
|
+
it 'creates 2-deep trees with strings' do
|
661
|
+
subject = @tag_class.find_or_create_by_path(%w[events anniversary])
|
662
|
+
assert_equal %w[events anniversary], subject.ancestry_path
|
663
|
+
end
|
664
|
+
|
665
|
+
it 'creates 2-deep trees with hashes' do
|
666
|
+
subject = @tag_class.find_or_create_by_path([
|
667
|
+
{ name: 'test1', title: 'TEST1' },
|
668
|
+
{ name: 'test2', title: 'TEST2' }
|
669
|
+
])
|
670
|
+
assert_equal %w[test1 test2], subject.ancestry_path
|
671
|
+
# `self_and_ancestors` and `ancestors` is ordered parent-first. (!!)
|
672
|
+
assert_equal %w[TEST2 TEST1], subject.self_and_ancestors.map(&:title)
|
673
|
+
end
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
describe 'hash_tree' do
|
678
|
+
before do
|
679
|
+
@d1 = @tag_class.find_or_create_by_path %w[a b c1 d1]
|
680
|
+
@c1 = @d1.parent
|
681
|
+
@b = @c1.parent
|
682
|
+
@a = @b.parent
|
683
|
+
@a2 = @tag_class.create(name: 'a2')
|
684
|
+
@b2 = @tag_class.find_or_create_by_path %w[a b2]
|
685
|
+
@c3 = @tag_class.find_or_create_by_path %w[a3 b3 c3]
|
686
|
+
@b3 = @c3.parent
|
687
|
+
@a3 = @b3.parent
|
688
|
+
|
689
|
+
@tree2 = {
|
690
|
+
@a => { @b => {}, @b2 => {} }, @a2 => {}, @a3 => { @b3 => {} }
|
691
|
+
}
|
692
|
+
|
693
|
+
@one_tree = {
|
694
|
+
@a => {},
|
695
|
+
@a2 => {},
|
696
|
+
@a3 => {}
|
697
|
+
}
|
698
|
+
|
699
|
+
@two_tree = {
|
700
|
+
@a => {
|
701
|
+
@b => {},
|
702
|
+
@b2 => {}
|
703
|
+
},
|
704
|
+
@a2 => {},
|
705
|
+
@a3 => {
|
706
|
+
@b3 => {}
|
707
|
+
}
|
708
|
+
}
|
709
|
+
|
710
|
+
@three_tree = {
|
711
|
+
@a => {
|
712
|
+
@b => {
|
713
|
+
@c1 => {}
|
714
|
+
},
|
715
|
+
@b2 => {}
|
716
|
+
},
|
717
|
+
@a2 => {},
|
718
|
+
@a3 => {
|
719
|
+
@b3 => {
|
720
|
+
@c3 => {}
|
721
|
+
}
|
722
|
+
}
|
723
|
+
}
|
724
|
+
|
725
|
+
@full_tree = {
|
726
|
+
@a => {
|
727
|
+
@b => {
|
728
|
+
@c1 => {
|
729
|
+
@d1 => {}
|
730
|
+
}
|
731
|
+
},
|
732
|
+
@b2 => {}
|
733
|
+
},
|
734
|
+
@a2 => {},
|
735
|
+
@a3 => {
|
736
|
+
@b3 => {
|
737
|
+
@c3 => {}
|
738
|
+
}
|
739
|
+
}
|
740
|
+
}
|
741
|
+
end
|
742
|
+
|
743
|
+
describe '#hash_tree' do
|
744
|
+
it 'returns {} for depth 0' do
|
745
|
+
assert_equal({}, @tag_class.hash_tree(limit_depth: 0))
|
746
|
+
end
|
747
|
+
|
748
|
+
it 'limit_depth 1' do
|
749
|
+
assert_equal @one_tree, @tag_class.hash_tree(limit_depth: 1)
|
750
|
+
end
|
751
|
+
|
752
|
+
it 'limit_depth 2' do
|
753
|
+
assert_equal @two_tree, @tag_class.hash_tree(limit_depth: 2)
|
754
|
+
end
|
755
|
+
|
756
|
+
it 'limit_depth 3' do
|
757
|
+
assert_equal @three_tree, @tag_class.hash_tree(limit_depth: 3)
|
758
|
+
end
|
759
|
+
|
760
|
+
it 'limit_depth 4' do
|
761
|
+
assert_equal @full_tree, @tag_class.hash_tree(limit_depth: 4)
|
762
|
+
end
|
763
|
+
|
764
|
+
it 'no limit' do
|
765
|
+
assert_equal @full_tree, @tag_class.hash_tree
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
describe '.hash_tree' do
|
770
|
+
it 'returns {} for depth 0' do
|
771
|
+
assert_equal({}, @b.hash_tree(limit_depth: 0))
|
772
|
+
end
|
773
|
+
|
774
|
+
it 'limit_depth 1' do
|
775
|
+
assert_equal @two_tree[@a].slice(@b), @b.hash_tree(limit_depth: 1)
|
776
|
+
end
|
777
|
+
|
778
|
+
it 'limit_depth 2' do
|
779
|
+
assert_equal @three_tree[@a].slice(@b), @b.hash_tree(limit_depth: 2)
|
780
|
+
end
|
781
|
+
|
782
|
+
it 'limit_depth 3' do
|
783
|
+
assert_equal @full_tree[@a].slice(@b), @b.hash_tree(limit_depth: 3)
|
784
|
+
end
|
785
|
+
|
786
|
+
it 'no limit from subsubroot' do
|
787
|
+
assert_equal @full_tree[@a][@b].slice(@c1), @c1.hash_tree
|
788
|
+
end
|
789
|
+
|
790
|
+
it 'no limit from subroot' do
|
791
|
+
assert_equal @full_tree[@a].slice(@b), @b.hash_tree
|
792
|
+
end
|
793
|
+
|
794
|
+
it 'no limit from root' do
|
795
|
+
assert_equal @full_tree.slice(@a, @a2), @a.hash_tree.merge(@a2.hash_tree)
|
796
|
+
end
|
797
|
+
end
|
798
|
+
|
799
|
+
describe '.hash_tree from relations' do
|
800
|
+
it 'limit_depth 2 from chained activerecord association subroots' do
|
801
|
+
assert_equal @three_tree[@a], @a.children.hash_tree(limit_depth: 2)
|
802
|
+
end
|
803
|
+
|
804
|
+
it 'no limit from chained activerecord association subroots' do
|
805
|
+
assert_equal @full_tree[@a], @a.children.hash_tree
|
806
|
+
end
|
807
|
+
|
808
|
+
it 'limit_depth 3 from b.parent' do
|
809
|
+
assert_equal @three_tree.slice(@a), @b.parent.hash_tree(limit_depth: 3)
|
810
|
+
end
|
811
|
+
|
812
|
+
it 'no limit_depth from b.parent' do
|
813
|
+
assert_equal @full_tree.slice(@a), @b.parent.hash_tree
|
814
|
+
end
|
815
|
+
|
816
|
+
it 'no limit_depth from c.parent' do
|
817
|
+
assert_equal @full_tree[@a].slice(@b), @c1.parent.hash_tree
|
818
|
+
end
|
819
|
+
end
|
820
|
+
end
|
821
|
+
|
822
|
+
it 'finds_by_path for very deep trees' do
|
823
|
+
path = (1..20).to_a.map(&:to_s)
|
824
|
+
subject = @tag_class.find_or_create_by_path(path)
|
825
|
+
assert_equal path, subject.ancestry_path
|
826
|
+
assert_equal subject, @tag_class.find_by_path(path)
|
827
|
+
root = subject.root
|
828
|
+
assert_equal subject, root.find_by_path(path[1..])
|
829
|
+
end
|
830
|
+
|
831
|
+
describe 'DOT rendering' do
|
832
|
+
it 'should render for an empty scope' do
|
833
|
+
assert_equal "digraph G {\n}\n", @tag_class.to_dot_digraph(@tag_class.where('0=1'))
|
834
|
+
end
|
835
|
+
|
836
|
+
it 'should render for an empty scope' do
|
837
|
+
@tag_class.find_or_create_by_path(%w[a b1 c1])
|
838
|
+
@tag_class.find_or_create_by_path(%w[a b2 c2])
|
839
|
+
@tag_class.find_or_create_by_path(%w[a b2 c3])
|
840
|
+
a, b1, b2, c1, c2, c3 = %w[a b1 b2 c1 c2 c3].map { |ea| @tag_class.where(name: ea).first.id }
|
841
|
+
dot = @tag_class.roots.first.to_dot_digraph
|
842
|
+
|
843
|
+
graph = <<~DOT
|
844
|
+
digraph G {
|
845
|
+
"#{a}" [label="a"]
|
846
|
+
"#{a}" -> "#{b1}"
|
847
|
+
"#{b1}" [label="b1"]
|
848
|
+
"#{a}" -> "#{b2}"
|
849
|
+
"#{b2}" [label="b2"]
|
850
|
+
"#{b1}" -> "#{c1}"
|
851
|
+
"#{c1}" [label="c1"]
|
852
|
+
"#{b2}" -> "#{c2}"
|
853
|
+
"#{c2}" [label="c2"]
|
854
|
+
"#{b2}" -> "#{c3}"
|
855
|
+
"#{c3}" [label="c3"]
|
856
|
+
}
|
857
|
+
DOT
|
858
|
+
|
859
|
+
assert_equal(graph, dot)
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
describe '.depth' do
|
864
|
+
it 'should render for an empty scope' do
|
865
|
+
@tag_class.find_or_create_by_path(%w[a b1 c1])
|
866
|
+
@tag_class.find_or_create_by_path(%w[a b2 c2])
|
867
|
+
@tag_class.find_or_create_by_path(%w[a b2 c3])
|
868
|
+
a, b1, b2, c1, c2, c3 = %w[a b1 b2 c1 c2 c3].map { |ea| @tag_class.where(name: ea).first.id }
|
869
|
+
dot = @tag_class.roots.first.to_dot_digraph
|
870
|
+
|
871
|
+
graph = <<~DOT
|
872
|
+
digraph G {
|
873
|
+
"#{a}" [label="a"]
|
874
|
+
"#{a}" -> "#{b1}"
|
875
|
+
"#{b1}" [label="b1"]
|
876
|
+
"#{a}" -> "#{b2}"
|
877
|
+
"#{b2}" [label="b2"]
|
878
|
+
"#{b1}" -> "#{c1}"
|
879
|
+
"#{c1}" [label="c1"]
|
880
|
+
"#{b2}" -> "#{c2}"
|
881
|
+
"#{c2}" [label="c2"]
|
882
|
+
"#{b2}" -> "#{c3}"
|
883
|
+
"#{c3}" [label="c3"]
|
884
|
+
}
|
885
|
+
DOT
|
886
|
+
|
887
|
+
assert_equal(graph, dot)
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
describe '.depth' do
|
892
|
+
before do
|
893
|
+
@d1 = @tag_class.find_or_create_by_path %w[a b c1 d1]
|
894
|
+
@c1 = @d1.parent
|
895
|
+
@b = @c1.parent
|
896
|
+
@a = @b.parent
|
897
|
+
@a2 = @tag_class.create(name: 'a2')
|
898
|
+
@b2 = @tag_class.find_or_create_by_path %w[a b2]
|
899
|
+
@c3 = @tag_class.find_or_create_by_path %w[a3 b3 c3]
|
900
|
+
@b3 = @c3.parent
|
901
|
+
@a3 = @b3.parent
|
902
|
+
|
903
|
+
|
904
|
+
end
|
905
|
+
|
906
|
+
it 'should return 0 for root' do
|
907
|
+
assert_equal 0, @a.depth
|
908
|
+
assert_equal 0, @a2.depth
|
909
|
+
assert_equal 0, @a3.depth
|
910
|
+
end
|
911
|
+
|
912
|
+
it 'should return correct depth for nodes' do
|
913
|
+
assert_equal 1, @b.depth
|
914
|
+
assert_equal 2, @c1.depth
|
915
|
+
assert_equal 3, @d1.depth
|
916
|
+
assert_equal 1, @b2.depth
|
917
|
+
assert_equal 1, @b3.depth
|
918
|
+
assert_equal 2, @c3.depth
|
919
|
+
end
|
920
|
+
end
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|