acts_as_ordered_tree 0.0.7 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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