acts_as_ordered_tree 0.0.7 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ module ActsAsOrderedTree
2
+ module Validators #:nodoc:all:
3
+ class CyclicReferenceValidator < ActiveModel::Validator
4
+ def validate(record)
5
+ record.errors.add(:parent, :invalid) if record.is_or_is_ancestor_of?(record.parent)
6
+ end
7
+ end
8
+
9
+ class ScopeValidator < ActiveModel::Validator
10
+ def validate(record)
11
+ record.errors.add(:parent, :scope) unless record.same_scope?(record.parent)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsAsOrderedTree
2
- VERSION = "0.0.7"
2
+ VERSION = "1.0.3"
3
3
  end
@@ -1,314 +1,819 @@
1
- require File.expand_path('../test_helper', __FILE__)
1
+ require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
3
  describe ActsAsOrderedTree do
4
- before :all do
5
- root = Node.create(:name => "Root")
6
- child1 = Node.create(:parent_id => root.id, :name => "Child 1")
7
- child2 = Node.create(:parent_id => root.id, :name => "Child 2")
8
-
9
- Node.create(:parent_id => child1.id, :name => "Subchild 1")
10
- Node.create(:parent_id => child1.id, :name => "Subchild 2")
11
- Node.create(:parent_id => child2.id, :name => "Subchild 3")
4
+ describe "defaults" do
5
+ subject { Default }
6
+
7
+ its(:parent_column) { should eq :parent_id }
8
+ its(:position_column) { should eq :position }
9
+ its(:depth_column) { should eq :depth }
10
+ its(:children_counter_cache_column) { be_nil }
11
+
12
+ context "instance" do
13
+ subject { Default.new }
14
+
15
+ it { should_not allow_mass_assignment_of(:position) }
16
+ it { should_not allow_mass_assignment_of(:depth) }
17
+ end
18
+ end
19
+
20
+ describe "default with counter cache" do
21
+ subject { DefaultWithCounterCache }
22
+
23
+ its(:children_counter_cache_column) { should eq :categories_count }
12
24
  end
13
25
 
14
- let(:root) { Node.where(:parent_id => nil).first }
15
- let(:branch) { Node.where(:parent_id => root.id).first }
16
- let(:second_branch) { Node.where(:parent_id => root.id).last }
17
- let(:leaf) { Node.where(:parent_id => branch.id).first }
18
- let(:last) { Node.last }
19
- let(:blank) { Node.new(:parent_id => branch.id) }
26
+ describe "renamed columns" do
27
+ subject { RenamedColumns }
28
+
29
+ its(:parent_column) { should eq :mother_id }
30
+ its(:position_column) { should eq :red }
31
+ its(:depth_column) { should eq :pitch }
32
+
33
+ context "instance" do
34
+ subject { RenamedColumns.new }
20
35
 
21
- describe "class" do
22
- it "should be properly configured" do
23
- Node.position_column.should eq(:position)
24
- Node.parent_column.should eq(:parent_id)
36
+ it { should_not allow_mass_assignment_of(:red) }
37
+ it { should_not allow_mass_assignment_of(:pitch) }
25
38
  end
39
+ end
40
+
41
+ it "creation_with_altered_column_names" do
42
+ lambda {
43
+ RenamedColumns.create!()
44
+ }.should_not raise_exception
45
+ end
26
46
 
27
- it "should have roots" do
28
- Node.roots.count.should eq(1)
29
- Node.roots.first.should eq(root)
30
- Node.root.should eq(root)
47
+ describe ".roots" do
48
+ # create fixture
49
+ before { FactoryGirl.create_list(:default, 3) }
50
+
51
+ subject { Default.roots }
52
+
53
+ its(:entries) { should eq Default.where(:parent_id => nil).order(:position).to_a }
54
+ end
55
+
56
+ describe ".leaves" do
57
+ # create fixture
58
+ let(:root) { create :default_with_counter_cache }
59
+ before { create_list :default_with_counter_cache, 2, :parent => root }
60
+
61
+ subject { DefaultWithCounterCache }
62
+
63
+ it { should respond_to(:leaves) }
64
+ its(:leaves) { should have(2).items }
65
+ end
66
+
67
+ describe ".root" do
68
+ # create fixture
69
+ let!(:root) { create :default }
70
+
71
+ context "given a single root node" do
72
+ subject { root }
73
+
74
+ its(:position) { should eq 1 }
75
+ end
76
+
77
+ context "given multiple root nodes" do
78
+ before { create_list :default, 3 }
79
+
80
+ subject { Default }
81
+
82
+ its(:root) { should eq root }
31
83
  end
32
84
  end
33
85
 
34
- describe "tree" do
35
- it "should have roots" do
36
- root.root.should eq(root)
37
- branch.root.should eq(root)
38
- leaf.root.should eq(root)
86
+ describe "#root?, #child?, #leaf?, #branch? and #root" do
87
+ shared_examples "tree with predicates" do |factory_name|
88
+ # create fixture
89
+ let!(:root) { create factory_name }
90
+ let!(:child) { create factory_name, :parent => root }
91
+ let!(:grandchild) { create factory_name, :parent => child }
92
+
93
+ before { root.reload }
94
+ before { child.reload }
95
+ before { grandchild.reload }
96
+
97
+ context "given root node" do
98
+ subject { root }
99
+
100
+ it { should be_root }
101
+ it { should_not be_child }
102
+ it { should_not be_leaf }
103
+ it { should be_branch }
104
+ its(:root) { should eq root }
105
+ its(:level) { should eq 0 }
106
+ end
107
+
108
+ context "given a branch node with children" do
109
+ subject { child }
110
+
111
+ it { should_not be_root }
112
+ it { should be_child }
113
+ it { should_not be_leaf }
114
+ it { should be_branch }
115
+ its(:root) { should eq root }
116
+ its(:level) { should eq 1 }
117
+ end
118
+
119
+ context "given a leaf node" do
120
+ subject { grandchild }
121
+
122
+ it { should_not be_root }
123
+ it { should be_child }
124
+ it { should be_leaf }
125
+ it { should_not be_branch }
126
+ its(:root) { should eq root }
127
+ its(:level) { should eq 2 }
128
+ end
129
+
130
+ context "given a new record" do
131
+ subject { build factory_name }
132
+
133
+ it { should_not be_leaf }
134
+ it { should be_branch }
135
+ end
39
136
  end
40
137
 
41
- it "should have children" do
42
- root.children.count.should eq(2)
43
- branch.children.count.should eq(2)
44
- leaf.children.count.should eq(0)
138
+ it_behaves_like "tree with predicates", :default
139
+ it_behaves_like "tree with predicates", :default_with_counter_cache
140
+ end
141
+
142
+ describe "#first?, #last?" do
143
+ let!(:root) { create :default }
144
+ let!(:child_1) { create :default, :parent => root }
145
+ let!(:child_2) { create :default, :parent => root }
146
+ let!(:child_3) { create :default, :parent => root }
147
+
148
+ context "given a node without siblings" do
149
+ subject { root }
150
+
151
+ it { should be_first }
152
+ it { should be_last }
45
153
  end
46
154
 
47
- it "should have parents" do
48
- root.parent.should be(nil)
49
- branch.parent.should eq(root)
50
- leaf.parent.should eq(branch)
155
+ context "given a node, first in the list" do
156
+ subject { child_1 }
157
+
158
+ it { should be_first }
159
+ it { should_not be_last }
51
160
  end
52
161
 
53
- it "should return true if root" do
54
- root.root?.should be(true)
55
- branch.root?.should be(false)
56
- leaf.root?.should be(false)
162
+ context "given a node, nor first neither last" do
163
+ subject { child_2 }
164
+
165
+ it { should_not be_first }
166
+ it { should_not be_last }
57
167
  end
58
168
 
59
- it "should return true if leaf" do
60
- root.leaf?.should be(false)
61
- branch.leaf?.should be(false)
62
- leaf.leaf?.should be(true)
169
+ context "given a node, last in the list" do
170
+ subject { child_3 }
171
+
172
+ it { should_not be_first }
173
+ it { should be_last }
63
174
  end
175
+ end
176
+
177
+ describe "#level" do
178
+ context "given a persistent root node" do
179
+ subject { create :default }
64
180
 
65
- it "should tell about node's depth" do
66
- root.depth.should eq(0)
67
- branch.depth.should eq(1)
68
- leaf.depth.should eq(2)
181
+ its(:level) { should eq 0 }
69
182
  end
183
+ context "given a new root record" do
184
+ subject { build :default }
70
185
 
71
- it "should iterate over ancestors" do
72
- leaf.self_and_ancestors.should have(3).items
73
- leaf.ancestors.should have(2).items
74
- branch.ancestors.should have(1).items
75
- root.ancestors.should have(0).items
186
+ its(:level) { should eq 0 }
187
+ end
188
+ context "given a persistent node with parent" do
189
+ let(:root) { create :default }
190
+ subject { create :default, :parent => root }
191
+ its(:level) { should eq 1 }
192
+ end
193
+ context "given a new node with parent" do
194
+ let(:root) { create :default }
195
+ subject { build :default, :parent => root }
196
+ its(:level) { should eq 1 }
76
197
  end
198
+ end
199
+
200
+ describe "#self_and_ancestors" do
201
+ # create fixture
202
+ let!(:root) { create :default }
203
+ let!(:child) { create :default, :parent => root }
204
+ let!(:grandchild) { create :default, :parent => child }
205
+
206
+ context "leaf" do
207
+ subject { grandchild.self_and_ancestors }
208
+
209
+ it { should be_a ActiveRecord::Relation }
210
+ it { should be_loaded }
211
+ it { should have(3).items }
212
+ its(:first) { should eq root }
213
+ its(:last) { should eq subject }
214
+ end
215
+
216
+ context "child" do
217
+ subject { child.self_and_ancestors }
218
+
219
+ it { should be_a ActiveRecord::Relation }
220
+ it { should be_loaded }
221
+ it { should have(2).items }
222
+ its(:first) { should eq root }
223
+ its(:last) { should eq subject }
224
+ end
225
+
226
+ context "root" do
227
+ subject { root.self_and_ancestors }
228
+
229
+ it { should be_a ActiveRecord::Relation }
230
+ it { should be_loaded }
231
+ it { should have(1).item }
232
+ its(:first) { should eq root }
233
+ end
234
+ end
235
+
236
+ describe "#ancestors" do
237
+ # create fixture
238
+ let!(:root) { create :default }
239
+ let!(:child) { create :default, :parent => root }
240
+ let!(:grandchild) { create :default, :parent => child }
241
+
242
+ context "leaf" do
243
+ subject { grandchild.ancestors }
244
+
245
+ it { should be_a ActiveRecord::Relation }
246
+ it { should be_loaded }
247
+ it { should have(2).items }
248
+ its(:first) { should eq root }
249
+ its(:last) { should eq child }
250
+ end
251
+
252
+ context "child" do
253
+ subject { child.ancestors }
254
+
255
+ it { should be_a ActiveRecord::Relation }
256
+ it { should be_loaded }
257
+ it { should have(1).item }
258
+ its(:first) { should eq root }
259
+ end
260
+
261
+ context "root" do
262
+ subject { root.ancestors }
263
+
264
+ it { should be_a ActiveRecord::Relation }
265
+ it { should be_loaded }
266
+ it { should be_empty }
267
+ end
268
+ end
77
269
 
78
- it "should iterate over descendants" do
79
- root.self_and_descendants.should have(6).items
270
+ describe "#self_and_descendants" do
271
+ # create fixture
272
+ let!(:root) { create :default }
273
+ let!(:child) { create :default, :parent => root }
274
+ let!(:grandchild) { create :default, :parent => child }
80
275
 
81
- root.descendants.should have(5).items
82
- root.descendants.first.should eq(branch)
83
- root.descendants.last.should eq(last)
276
+ context "leaf" do
277
+ subject { grandchild.self_and_descendants }
84
278
 
85
- branch.descendants.should have(2).items
86
- branch.descendants.first.should eq(leaf)
279
+ it { should be_a ActiveRecord::Relation }
280
+ it { should be_loaded }
281
+ it { should have(1).item }
282
+ its(:first) { should eq grandchild }
283
+ end
284
+
285
+ context "child" do
286
+ subject { child.self_and_descendants }
87
287
 
88
- leaf.descendants.should have(0).items
288
+ it { should be_a ActiveRecord::Relation }
289
+ it { should be_loaded }
290
+ it { should have(2).items }
291
+ its(:first) { should eq child }
292
+ its(:last) { should eq grandchild }
89
293
  end
90
294
 
91
- it "should have siblings" do
92
- branch.self_and_siblings.should have(2).items
93
- branch.self_and_siblings.should include(branch)
295
+ context "root" do
296
+ subject { root.self_and_descendants }
94
297
 
95
- branch.siblings.should have(1).item
96
- branch.siblings.should_not include(branch)
298
+ it { should be_a ActiveRecord::Relation }
299
+ it { should be_loaded }
300
+ it { should have(3).items }
301
+ its(:first) { should eq root }
302
+ its(:last) { should eq grandchild }
97
303
  end
98
304
  end
99
305
 
100
- describe "list" do
101
- it "should be ordered" do
102
- root.position.should eq(1)
103
- root.children.first.position.should eq(1)
104
- root.children.last.position.should eq(2)
306
+ describe "#is_descendant_of?, #is_or_is_descendant_of?, #is_ancestor_of?, #is_or_is_ancestor_of?" do
307
+ # create fixture
308
+ let!(:root) { create :default }
309
+ let!(:child) { create :default, :parent => root }
310
+ let!(:grandchild) { create :default, :parent => child }
311
+
312
+ context "grandchild" do
313
+ subject { grandchild }
314
+
315
+ it { should be_is_descendant_of(root) }
316
+ it { should be_is_or_is_descendant_of(root) }
317
+ it { should_not be_is_ancestor_of(root) }
318
+ it { should_not be_is_or_is_ancestor_of(root) }
319
+
320
+ it { should be_is_descendant_of(child) }
321
+ it { should be_is_or_is_descendant_of(child) }
322
+ it { should_not be_is_ancestor_of(child) }
323
+ it { should_not be_is_or_is_ancestor_of(child) }
324
+
325
+ it { should_not be_is_descendant_of(grandchild) }
326
+ it { should be_is_or_is_descendant_of(grandchild) }
327
+ it { should_not be_is_ancestor_of(grandchild) }
328
+ it { should be_is_or_is_ancestor_of(grandchild) }
329
+ end
330
+
331
+ context "child" do
332
+ subject { child }
333
+
334
+ it { should be_is_descendant_of(root) }
335
+ it { should be_is_or_is_descendant_of(root) }
336
+ it { should_not be_is_ancestor_of(root) }
337
+ it { should_not be_is_or_is_ancestor_of(root) }
338
+
339
+ it { should_not be_is_descendant_of(child) }
340
+ it { should be_is_or_is_descendant_of(child) }
341
+ it { should_not be_is_ancestor_of(child) }
342
+ it { should be_is_or_is_ancestor_of(child) }
343
+
344
+ it { should_not be_is_descendant_of(grandchild) }
345
+ it { should_not be_is_or_is_descendant_of(grandchild) }
346
+ it { should be_is_ancestor_of(grandchild) }
347
+ it { should be_is_or_is_ancestor_of(grandchild) }
105
348
  end
106
349
 
107
- it "should be sortable through scope" do
108
- Node.where(:parent_id => root.id).ordered.first.should eq(branch)
350
+ context "root" do
351
+ subject { root }
352
+
353
+ it { should_not be_is_descendant_of(root) }
354
+ it { should be_is_or_is_descendant_of(root) }
355
+ it { should_not be_is_ancestor_of(root) }
356
+ it { should be_is_or_is_ancestor_of(root) }
357
+
358
+ it { should_not be_is_descendant_of(child) }
359
+ it { should_not be_is_or_is_descendant_of(child) }
360
+ it { should be_is_ancestor_of(child) }
361
+ it { should be_is_or_is_ancestor_of(child) }
362
+
363
+ it { should_not be_is_descendant_of(grandchild) }
364
+ it { should_not be_is_or_is_descendant_of(grandchild) }
365
+ it { should be_is_ancestor_of(grandchild) }
366
+ it { should be_is_or_is_ancestor_of(grandchild) }
109
367
  end
110
368
  end
111
369
 
112
- describe "mutations" do
113
- around(:each) do |example|
114
- Node.transaction do
115
- example.run
370
+ describe "#left_sibling" do
371
+ shared_examples "tree with siblings" do
372
+ subject { items }
373
+
374
+ its('first.left_sibling') { should be_nil }
375
+ its('first.right_sibling') { should eq items.second }
376
+
377
+ its('second.left_sibling') { should eq items.first }
378
+ its('second.right_sibling') { should eq items.last }
116
379
 
117
- raise ActiveRecord::Rollback
380
+ its('third.left_sibling') { should eq items.second }
381
+ its('third.right_sibling') { should be_nil }
382
+ end
383
+
384
+ context "given unscoped tree" do
385
+ it_should_behave_like "tree with siblings" do
386
+ let(:items) { create_list :default, 3 }
118
387
  end
119
388
  end
120
389
 
121
- it "should be placed to the bottom of the list" do
122
- blank.save
123
- branch.children.last.should eq(blank)
390
+ context "given scoped tree" do
391
+ let!(:items_1) { create_list :scoped, 3, :scope_type => "s1" }
392
+ let!(:items_2) { create_list :scoped, 3, :scope_type => "s2" }
393
+
394
+ it_should_behave_like "tree with siblings" do
395
+ let(:items) { items_1 }
396
+ end
397
+ it_should_behave_like "tree with siblings" do
398
+ let(:items) { items_2 }
399
+ end
124
400
  end
401
+ end
125
402
 
126
- it "should be placed to the middle of the list" do
127
- blank.position = 2
128
- blank.save
403
+ describe "#reload_node" do
404
+ let!(:node) { create :default }
129
405
 
130
- blank.position.should eq(2)
131
- blank.siblings.should have(2).items
132
- blank.siblings.last.position.should eq(3)
406
+ before do
407
+ node.name = 'changed'
408
+ node.parent_id = 200
409
+ node.position = 1000
133
410
  end
134
411
 
135
- it "should be movable inside parent" do
136
- last_child = branch.children.last
412
+ subject { node.send :reload_node }
137
413
 
138
- blank.save
139
- blank.move_higher
414
+ its(:name) { should eq 'changed' }
415
+ its(:parent_id) { should be_nil }
416
+ its(:position) { should eq 1 }
417
+ end
418
+
419
+ describe "move actions" do
420
+ let!(:root) { create :default_with_counter_cache, :name => 'root' }
421
+ let!(:child_1) { create :default_with_counter_cache, :parent => root, :name => 'child_1' }
422
+ let!(:child_2) { create :default_with_counter_cache, :parent => root, :name => 'child_2' }
423
+ let!(:child_3) { create :default_with_counter_cache, :parent => root, :name => 'child_3' }
140
424
 
141
- blank.position.should eq(2)
142
- last_child.reload.position.should eq(3)
425
+ context "initial" do
426
+ specify { expect([child_1, child_2, child_3]).to be_sorted }
143
427
 
144
- blank.move_lower
145
- blank.position.should eq(3)
428
+ subject { root.reload }
429
+ its(:parent_id) { should be_nil }
430
+ its(:level) { should be_zero }
431
+ its(:position) { should eq 1 }
432
+ its(:categories_count) { should eq 3}
146
433
  end
147
434
 
148
- it "should be movable to bottom of its parent" do
149
- first_child = branch.children.first
435
+ context "on_save_when_parent_changed" do
436
+ example "move_1_to_root" do
437
+ child_1.parent = nil
438
+ child_1.save
439
+ expect(child_1.position).to eq 2
440
+ expect([root, child_1]).to be_sorted
441
+ end
150
442
 
151
- first_child.move_to_bottom
152
- first_child.position.should eq(2)
153
- first_child.reload.position.should eq(2)
443
+ example "move_3_to_root" do
444
+ child_3.parent = nil
445
+ child_3.save
446
+ expect(child_3.position).to eq 2
447
+ expect([root, child_3]).to be_sorted
448
+ end
154
449
  end
155
450
 
156
- it "should be movable to top of its parent" do
157
- first_child = branch.children.first
158
- last_child = branch.children.last
451
+ describe "#move_left" do
452
+ example "move_1_left" do
453
+ expect{ child_1.move_left }.to raise_exception ActiveRecord::ActiveRecordError
454
+ expect([child_1, child_2, child_3]).to be_sorted
455
+ end
159
456
 
160
- last_child.move_to_top
457
+ example "move_2_left" do
458
+ child_2.move_left
459
+ expect([child_2, child_1, child_3]).to be_sorted
460
+ end
461
+
462
+ example "move_3_left" do
463
+ child_3.move_left
464
+ expect([child_1, child_3, child_2]).to be_sorted
465
+ end
466
+ end
467
+
468
+ describe "#move_right" do
469
+ example "move_3_right" do
470
+ expect{ child_3.move_right }.to raise_exception ActiveRecord::ActiveRecordError
471
+ expect([child_1, child_2, child_3]).to be_sorted
472
+ end
161
473
 
162
- last_child.position.should eq(1)
163
- last_child.reload.position.should eq(1)
474
+ example "move_2_right" do
475
+ child_2.move_right
476
+ expect([child_1, child_3, child_2]).to be_sorted
477
+ end
164
478
 
165
- first_child.reload.position.should eq(2)
479
+ example "move_1_right" do
480
+ child_1.move_right
481
+ expect([child_2, child_1, child_3]).to be_sorted
482
+ end
166
483
  end
167
484
 
168
- it "should shift up lower items when parent is changed" do
169
- first_child = branch.children.first
170
- last_child = branch.children.last
171
-
172
- # move to other parent
173
- first_child.parent = second_branch
174
- first_child.should be_parent_changed
485
+ describe "#move_to_left_of" do
486
+ example "move_3_to_left_of_1" do
487
+ child_3.move_to_left_of child_1
488
+ expect([child_3, child_1, child_2]).to be_sorted
489
+ end
490
+
491
+ example "move_3_to_left_of_2" do
492
+ child_3.move_to_left_of child_2
493
+ expect([child_1, child_3, child_2]).to be_sorted
494
+ end
175
495
 
176
- first_child.save
496
+ example "move_1_to_left_of_3" do
497
+ child_1.move_to_left_of child_3
498
+ expect([child_2, child_1, child_3]).to be_sorted
499
+ end
177
500
 
178
- # old sibling should shift up
179
- last_child.reload.position.should eq(1)
501
+ example "move_1_to_left_of_3_id" do
502
+ child_1.move_to_left_of child_3.id
503
+ expect([child_2, child_1, child_3]).to be_sorted
504
+ end
505
+
506
+ example "move_root_to_left_of_child_2" do
507
+ expect{ root.move_to_left_of child_2 }.to raise_exception ActiveRecord::ActiveRecordError
508
+ end
180
509
  end
181
510
 
182
- it "should save its previous position when parent is changed" do
183
- first_child = branch.children.first
511
+ describe "#move_to_right_of" do
512
+ example "move_1_to_right_of_2" do
513
+ child_1.move_to_right_of child_2
514
+ expect([child_2, child_1, child_3]).to be_sorted
515
+ end
184
516
 
185
- first_child.parent = second_branch
186
- first_child.save
517
+ example "move_1_to_right_of_3" do
518
+ child_1.move_to_right_of child_3
519
+ expect([child_2, child_3, child_1]).to be_sorted
520
+ end
187
521
 
188
- first_child.position.should eq(1)
189
- last.position.should eq(2)
522
+ example "move_1_to_right_of_3_id" do
523
+ child_1.move_to_right_of child_3.id
524
+ expect([child_2, child_3, child_1]).to be_sorted
525
+ end
526
+
527
+ example "move_3_to_right_of_1" do
528
+ child_3.move_to_right_of child_1
529
+ expect([child_1, child_3, child_2]).to be_sorted
530
+ end
531
+
532
+ example "move_root_to_right_of_child_2" do
533
+ expect{ root.move_to_right_of child_2 }.to raise_exception ActiveRecord::ActiveRecordError
534
+ end
190
535
  end
191
536
 
192
- it "should be movable to last position of new parent" do
193
- first_child = branch.children.first
537
+ describe "#move_to_root" do
538
+ before { child_2.move_to_root }
539
+
540
+ context "child_2" do
541
+ subject { child_2 }
194
542
 
195
- first_child.move_to_child_of(second_branch)
196
- first_child.parent.should eq(second_branch)
197
- first_child.should be_last
543
+ its(:level) { should be_zero }
544
+ its(:parent_id) { should be_nil }
545
+ its(:position) { should eq 2 }
546
+
547
+ it "should not become new root" do
548
+ DefaultWithCounterCache.root.should eq root
549
+ end
550
+ end
551
+
552
+ context "other_nodes" do
553
+ specify { child_1.reload.position.should eq 1 }
554
+ specify { child_3.reload.position.should eq 2 }
555
+ specify { root.reload.categories_count.should eq 2 }
556
+ end
557
+
558
+
559
+ context "given a root node" do
560
+ before { root.move_to_root }
561
+ subject { root }
562
+
563
+ its(:position) { should eq 1 }
564
+
565
+ it "positions should not change" do
566
+ expect([root, child_3]).to be_sorted
567
+ end
568
+ end
198
569
  end
199
570
 
200
- it "should be movable to above of some node" do
201
- first_child = branch.children.first
202
- above_of = second_branch.children.first
571
+ describe "#move_to_child_of" do
572
+ let(:moved_child) { create :default_with_counter_cache, :name => 'moved_child' }
203
573
 
204
- first_child.move_to_above_of(above_of)
205
- first_child.parent.should eq(second_branch)
206
-
207
- first_child.position.should eq(1)
208
- above_of.position.should eq(2)
574
+ before { moved_child.move_to_child_of root }
575
+ context "moved_child" do
576
+ subject { moved_child }
577
+ its(:level) { should eq 1 }
578
+ its(:position) { should eq 4 }
579
+ end
580
+
581
+ context "root" do
582
+ subject { root.reload }
583
+ its(:right_sibling) { should be_nil }
584
+ its(:categories_count) { should eq 4 }
585
+ end
586
+
587
+ context "given a node which already is children of target" do
588
+ subject { child_2 }
589
+ before { child_2.move_to_child_of root }
590
+
591
+ its(:position) { should eq 2 }
592
+
593
+ it "positions_should_not_change" do
594
+ expect([child_1, child_2, child_3, moved_child]).to be_sorted
595
+ end
596
+ end
597
+
598
+ it { expect([child_1, child_2, child_3, moved_child]).to be_sorted }
599
+ it { expect{ root.move_to_child_of root }.to raise_exception ActiveRecord::ActiveRecordError }
600
+ it { expect{ root.move_to_child_of child_1 }.to raise_exception ActiveRecord::ActiveRecordError }
209
601
  end
210
602
 
211
- it "should be movable to bottom of some node" do
212
- second = second_branch
603
+ describe "#move_to_child_with_index" do
604
+ let(:moved_child) { create :default, :name => 'moved_child' }
605
+
606
+ example "move_to_child_as_first" do
607
+ moved_child.move_to_child_with_index root, 0
608
+ expect([moved_child, child_1, child_2, child_3]).to be_sorted
609
+ moved_child.position.should eq 1
610
+ end
611
+
612
+ example "move_to_child_as_second" do
613
+ moved_child.move_to_child_with_index root, 1
614
+ expect([child_1, moved_child, child_2, child_3]).to be_sorted
615
+ moved_child.position.should eq 2
616
+ end
617
+
618
+ example "move_to_child_as_third" do
619
+ moved_child.move_to_child_with_index root, 2
620
+ expect([child_1, child_2, moved_child, child_3]).to be_sorted
621
+ moved_child.position.should eq 3
622
+ end
623
+
624
+ example "move_to_child_as_last" do
625
+ moved_child.move_to_child_with_index root, 3
626
+ expect([child_1, child_2, child_3, moved_child]).to be_sorted
627
+ moved_child.position.should eq 4
628
+ end
629
+
630
+ example "move_child_to_root_as_first" do
631
+ child_3.move_to_child_with_index nil, 0
632
+ child_3.level.should be_zero
633
+ expect([child_3, root, moved_child]).to be_sorted
634
+ expect([child_1, child_2]).to be_sorted
635
+ child_2.right_sibling.should be_nil
636
+ end
637
+
638
+ example "move_to_child_with_large_index" do
639
+ moved_child.move_to_child_with_index root, 100
640
+ expect([child_1, child_2, child_3, moved_child]).to be_sorted
641
+ moved_child.position.should eq 4
642
+ end
643
+
644
+ example "move_to_child_with_negative_index" do
645
+ moved_child.move_to_child_with_index root, -2
646
+ expect([child_1, child_2, moved_child, child_3]).to be_sorted
647
+ moved_child.position.should eq 3
648
+ end
649
+
650
+ example "move_to_child_with_large_negative_index" do
651
+ expect{ moved_child.move_to_child_with_index root, -100 }.to raise_exception ActiveRecord::ActiveRecordError
652
+ end
213
653
 
214
- first_child = branch.children.first
654
+ example "move_to_child_with_nil_index" do
655
+ expect{ moved_child.move_to_child_with_index root, nil }.to raise_exception ActiveRecord::ActiveRecordError
656
+ end
215
657
 
216
- first_child.move_to_bottom_of(branch)
217
- first_child.parent.should eq(branch.parent)
658
+ example "move_to_child_with_float_index" do
659
+ moved_child.move_to_child_with_index root, 1.7
660
+ expect([child_1, moved_child, child_2, child_3]).to be_sorted
661
+ end
662
+
663
+ example "move_root_to_child_of_self" do
664
+ expect{ root.move_to_child_with_index child_1, 1 }.to raise_exception ActiveRecord::ActiveRecordError
665
+ end
218
666
 
219
- first_child.position.should eq(2)
220
- second.reload.position.should eq(3)
221
667
  end
222
668
 
223
- it "should shift up lower items on destroy" do
224
- branch.children.first.destroy
669
+ describe "#insert_at" do
670
+ before { child_3.insert_at(1) }
671
+ before { child_3.reload }
225
672
 
226
- branch.children.should have(1).items
227
- branch.children.first.position.should eq(1)
673
+ specify { expect([child_3, child_1, child_2]).to be_sorted }
228
674
  end
229
675
 
230
676
  describe "callbacks" do
231
- it "should fire *_reorder callbacks when position (but not parent) changes" do
232
- examples_count = 6
677
+ subject { child_3 }
678
+
679
+ it { should fire_callback(:before_move).when_calling(:move_to_root).once }
680
+ it { should fire_callback(:after_move).when_calling(:move_to_root).once }
681
+ it { should fire_callback(:around_move).when_calling(:move_to_root).once }
682
+
683
+ it { should_not fire_callback(:before_move).when_calling(:move_left) }
684
+ it { should_not fire_callback(:after_move).when_calling(:move_left) }
685
+ it { should_not fire_callback(:around_move).when_calling(:move_left) }
686
+
687
+ it { should fire_callback(:before_reorder).when_calling(:move_higher).once }
688
+ it { should fire_callback(:after_reorder).when_calling(:move_higher).once }
689
+
690
+ it { should_not fire_callback(:before_reorder).when_calling(:move_to_root) }
233
691
 
234
- second_branch.should_receive(:on_before_reorder).exactly(examples_count)
235
- second_branch.should_receive(:on_around_reorder).exactly(examples_count)
236
- second_branch.should_receive(:on_after_reorder).exactly(examples_count)
692
+ it "should cache depth on save" do
693
+ record = build :default_with_counter_cache
237
694
 
238
- second_branch.move_higher
239
- second_branch.move_lower
240
- second_branch.move_to_top
241
- second_branch.move_to_bottom
242
- second_branch.decrement_position
243
- second_branch.increment_position
695
+ record.depth.should be_nil
696
+ record.save
697
+
698
+ record.depth.should eq 0
699
+
700
+ record.move_to_left_of child_3
701
+ record.depth.should eq child_3.level
244
702
  end
245
703
 
246
- it "should not fire *_reorder callbacks when parent_changes" do
247
- leaf.should_not_receive(:on_before_reorder)
248
- leaf.should_not_receive(:on_around_reorder)
249
- leaf.should_not_receive(:on_after_reorder)
704
+ it "should recalculate depth of descendants" do
705
+ record = create :default_with_counter_cache, :parent => child_3
706
+ record.depth.should eq 2
250
707
 
251
- p1 = leaf.parent
252
- p2 = second_branch
708
+ child_3.move_to_root
709
+ record.reload.depth.should eq 1
253
710
 
254
- leaf.move_to_child_of(p2)
255
- leaf.move_to_above_of(p1.children.first)
256
- leaf.move_to_child_of(p2)
257
- leaf.move_to_bottom_of(p1.children.first)
711
+ child_3.move_to_child_of child_1
712
+ record.reload.depth.should eq 3
258
713
  end
714
+ end
715
+
716
+ end
259
717
 
260
- it "should not fire *_reorder callbacks when position is not changed" do
261
- leaf.should_not_receive(:on_before_reorder)
262
- leaf.should_not_receive(:on_around_reorder)
263
- leaf.should_not_receive(:on_after_reorder)
718
+ describe "scoped trees" do
719
+ let!(:root1) { create :scoped, :scope_type => "t1" }
720
+ let!(:child1) { create :scoped, :parent => root1 }
721
+ let!(:orphan) do
722
+ record = create :scoped, :parent => root1
723
+ record.class.update_all({:scope_type => "t0", :position => 1}, {:id => record.id})
724
+ record
725
+ end
264
726
 
265
- last.should_not_receive(:on_before_reorder)
266
- last.should_not_receive(:on_around_reorder)
267
- last.should_not_receive(:on_after_reorder)
727
+ let!(:root2) { create :scoped, :scope_type => "t2" }
728
+ let!(:child2) { create :scoped, :scope_type => "t2", :parent => root2 }
268
729
 
269
- leaf.move_higher
270
- last.move_lower
730
+ it "should not stick positions together for different scopes" do
731
+ root1.position.should eq root2.position
732
+ end
733
+ it "should automatically set scope for new records with parent" do
734
+ child1.should be_same_scope(root1)
735
+ end
736
+ it "should not include orphans" do
737
+ root1.children.reload.should_not include orphan
738
+ root1.descendants.reload.should_not include orphan
739
+ end
740
+ it "should not allow to move records between scopes" do
741
+ expect { child2.move_to_child_of root1 }.to raise_error(ActiveRecord::ActiveRecordError)
742
+ end
743
+ it "should not allow to change scope" do
744
+ child2.parent = root1
745
+ child2.should have_at_least(1).error_on(:parent)
746
+ end
747
+ it "should not allow to add scoped record to children collection" do
748
+ root1.children << child2
749
+ root1.children.reload.should_not include child2
750
+ end
751
+ end
271
752
 
272
- leaf.save
273
- last.save
274
- end
753
+ describe "#destroy behavior" do
754
+ let!(:root) { create :default_with_counter_cache, :name => 'root' }
755
+ let!(:child_1) { create :default_with_counter_cache, :parent => root, :name => 'child_1' }
756
+ let!(:child_2) { create :default_with_counter_cache, :parent => root, :name => 'child_2' }
757
+ let!(:child_3) { create :default_with_counter_cache, :parent => root, :name => 'child_3' }
275
758
 
276
- it "should fire *_move callbacks when parent is changed" do
277
- examples_count = 3
278
- leaf.should_receive(:on_before_move).exactly(examples_count)
279
- leaf.should_receive(:on_after_move).exactly(examples_count)
280
- leaf.should_receive(:on_around_move).exactly(examples_count)
759
+ describe "it should destroy descendants" do
760
+ subject { root }
761
+ before { subject.destroy }
281
762
 
282
- p1 = leaf.parent
283
- p2 = second_branch
763
+ it { should be_destroyed }
764
+ its('descendants.reload') { should be_empty }
284
765
 
285
- leaf.move_to_child_of(p2)
286
- leaf.move_to_above_of(p1)
287
- leaf.move_to_bottom_of(p1.children.first)
766
+ specify "ensure the loneliness" do
767
+ root.class.all.should be_empty
288
768
  end
769
+ end
770
+
771
+ describe "it should stick positions together" do
772
+ before { child_2.destroy }
773
+ before { child_3.reload }
774
+
775
+ subject { child_3 }
289
776
 
290
- it "should not fire *_move callbacks when parent is not changed" do
291
- leaf.should_not_receive(:on_before_move)
292
- leaf.should_not_receive(:on_after_move)
293
- leaf.should_not_receive(:on_around_move)
777
+ its(:left_sibling) { should eq child_1 }
778
+ its(:position) { should eq 2 }
294
779
 
295
- leaf.move_to_child_of(leaf.parent)
296
- leaf.move_to_above_of(leaf.siblings.first)
297
- leaf.move_to_bottom_of(leaf.siblings.first)
298
- leaf.reload.save
780
+ specify "root categories_count should decrease" do
781
+ root.reload.categories_count.should eq 2
299
782
  end
300
783
  end
301
784
  end
302
785
 
303
- describe "validations" do
304
- it "should not allow to link parent to itself" do
305
- branch.parent = branch
306
- branch.should_not be_valid
786
+ describe "potential vulnerabilities" do
787
+ describe "attempt to link parent to one of descendants" do
788
+ let(:root) { create :default }
789
+ let(:child) { create :default, :parent => root }
790
+ let(:grandchild) { create :default, :parent => child }
791
+
792
+ subject { root }
793
+
794
+ context "given self as parent" do
795
+ before { root.parent = root }
796
+
797
+ it { should have_at_least(1).error_on(:parent) }
798
+ end
799
+
800
+ context "given child as parent" do
801
+ before { root.parent = child }
802
+
803
+ it { should have_at_least(1).error_on(:parent) }
804
+ end
805
+
806
+ context "given grandchild as parent" do
807
+ before { root.parent = grandchild }
808
+
809
+ it { should have_at_least(1).error_on(:parent) }
810
+ end
307
811
  end
308
812
 
309
- it "should not allow to link to one of its descendants" do
310
- branch.parent = leaf
311
- branch.should_not be_valid
813
+ describe "attempt to create node with wrong position" do
814
+ it "should not throw error" do
815
+ expect{ create :default, :position => 22 }.not_to raise_error
816
+ end
312
817
  end
313
818
  end
314
819
  end