ordered_tree 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,414 @@
1
+ require 'spec_helper'
2
+
3
+ describe OrderedTree do
4
+ before do
5
+ reload_test_tree
6
+ @people = Person.all
7
+ end
8
+
9
+ describe "when assigning parent" do
10
+ it "should do a bunch of tests on validation" do
11
+ # "should not allow an ancestor of a node to be a child of that node"
12
+ (@people[4].children << @people[0]).should be_false
13
+ @people[0].errors[:base].should include("is an ancestor of the new parent.")
14
+ (@people[7].children << @people[2]).should be_false
15
+ @people[0].errors[:base].should include("is an ancestor of the new parent.")
16
+
17
+ # "should not allow a node to be a parent of itself"
18
+ (@people[3].children << @people[3]).should be_false
19
+ @people[3].errors[:base].should include("cannot be a parent to itself.")
20
+
21
+ # I guess what this means that if there's an operation that fails, it should stay that
22
+ # way until it's reloaded.
23
+ # "should remember that the failed operations leave you with tainted objects"
24
+ @people[2].parent == @people[7]
25
+ @people[2].reload
26
+ @people[0].reload
27
+ @people[2].parent != @people[7]
28
+ end
29
+ end
30
+
31
+ describe "when validate :on => :update is called," do
32
+ it "should reload the descendants" do
33
+ @people[2].descendants # load descendants, so we can begin tests to see if it's reloaded again later
34
+ @people[5].children << @people[7]
35
+ @people[5].children.should include(@people[7])
36
+
37
+ # since people[2].descendants has already been loaded above,
38
+ # it still includes people[7] as a descendant
39
+ @people[2].descendants.should include(@people[7])
40
+
41
+ # so, without the reload on people[2].descendants in validate_on_update,
42
+ # the following would fail
43
+
44
+ # How to test for this?
45
+ #(@people[7].children << @people[2]).should be_false
46
+ # assert people[7].children << people[2], 'Validation Failed: descendants must be reloaded in validate_on_update'
47
+ end
48
+ end
49
+
50
+ describe "on descendants" do
51
+ it "should give the correct count" do
52
+ roots = Person.roots
53
+ roots.should_not be_empty
54
+ count = 0
55
+ roots.each {|root| count = count + root.descendants.size + 1}
56
+ count.should == Person.count
57
+ end
58
+
59
+ it "it should destroy them properly" do
60
+ 7.should == @people[2].descendants.size + 1
61
+ @people[2].destroy
62
+ Person.count.should == @people.size - 7
63
+ end
64
+ end
65
+
66
+ describe "#ancestors" do
67
+ it "should return the ancestors in proper order" do
68
+ @people[7].ancestors.should == [@people[4], @people[2], @people[0]]
69
+ end
70
+ end
71
+
72
+ describe "#root" do
73
+ it "should return the root of the current node" do
74
+ @people[7].root.should == @people[0]
75
+ end
76
+ end
77
+
78
+ describe "class#roots" do
79
+ it "should return all the roots" do
80
+ Person.roots.should == [@people[0], @people[11]]
81
+ end
82
+ end
83
+
84
+ it "should reorder the list when stuff are destroyed" do
85
+ @people[0].descendants.should == [@people[1],@people[2],@people[3],@people[4],@people[7],@people[8],@people[9],@people[10],@people[5],@people[6]]
86
+ @people[5].self_and_siblings.should == [@people[1],@people[2],@people[5],@people[6]]
87
+ @people[5].position_in_list.should == 3
88
+ # taint people[2].parent (since the plugin protects against this)
89
+ @people[10].children << @people[2]
90
+ @people[2].parent.should == @people[10]
91
+ @people[2].destroy.should_not be_nil
92
+ Person.count.should == @people.count - 7
93
+ # Note that I don't need to reload self_and_siblings or children in this case,
94
+ # since the re-ordering action is actually happening against people[0].children
95
+ # (which is what self_and_syblings returns)
96
+ @people[5].self_and_siblings.should == @people[0].children
97
+ @people[5].self_and_siblings.should == [@people[1],@people[5],@people[6]]
98
+ @people[5].reload
99
+ @people[5].position_in_list.should == 2
100
+ # of course, descendants must always be reloaded
101
+ @people[0].descendants.should include(@people[7])
102
+ @people[0].descendants(true).should_not include(@people[7])
103
+ end
104
+
105
+ it "should reorder properly" do
106
+ # when moving a child to another parent
107
+ (@people[13].children << @people[4]).should_not be_false
108
+ @people[4].position_in_list.should == 5
109
+ @people[9].reload
110
+ @people[9].position_in_list.should == 2
111
+ # when moving root to a child of another
112
+ (@people[13].children << @people[0]).should_not be_false
113
+ @people[0].position_in_list.should == 6
114
+ @people[11].reload
115
+ @people[11].position_in_list.should == 1
116
+ end
117
+
118
+ describe "#move_higher" do
119
+ it "should properly update the position" do
120
+ (@people[9].move_higher).should_not be_false
121
+ @people[9].position_in_list.should == 2
122
+ @people[4].reload
123
+ @people[4].position_in_list.should == 3
124
+ end
125
+ end
126
+
127
+ describe "#move_lower" do
128
+ it "should peroperly update the position" do
129
+ (@people[4].move_lower).should_not be_false
130
+ @people[4].position_in_list.should == 3
131
+ @people[9].reload
132
+ @people[9].position_in_list.should == 2
133
+ end
134
+ end
135
+
136
+ describe "#move_to_top" do
137
+ it "should do properly set the position_in_list" do
138
+ (@people[4].move_to_top).should_not be_false
139
+ @people = Person.all
140
+ @people[4].position_in_list.should == 1
141
+ @people[3].position_in_list.should == 2
142
+ @people[9].position_in_list.should == 3
143
+ @people[10].position_in_list.should == 4
144
+ end
145
+ end
146
+
147
+ describe "#move_to_bottom" do
148
+ it "should do properly set the position_in_list" do
149
+ @people = Person.find(:all)
150
+ (@people[4].move_to_bottom).should_not be_false
151
+ @people = Person.find(:all)
152
+ @people[3].position_in_list.should == 1
153
+ @people[9].position_in_list.should == 2
154
+ @people[10].position_in_list.should == 3
155
+ @people[4].position_in_list.should == 4
156
+ end
157
+ end
158
+
159
+ describe "#move_above, moving higher," do
160
+ it "should do properly set the position_in_list" do
161
+ (@people[10].move_above(@people[4])).should_not be_false
162
+ @people = Person.all
163
+ @people[2].children.should == [@people[3],@people[10],@people[4],@people[9]]
164
+ @people[3].position_in_list.should == 1
165
+ @people[10].position_in_list.should == 2
166
+ @people[4].position_in_list.should == 3
167
+ @people[9].position_in_list.should == 4
168
+ end
169
+ end
170
+
171
+ describe "#move_above, moving_lower," do
172
+ it "should do something" do
173
+ (@people[3].move_above(@people[10])).should_not be_false
174
+ @people = Person.all
175
+ @people[2].children.should == [@people[4],@people[9],@people[3],@people[10]]
176
+ @people[4].position_in_list.should == 1
177
+ @people[9].position_in_list.should == 2
178
+ @people[3].position_in_list.should == 3
179
+ @people[10].position_in_list.should == 4
180
+ end
181
+ end
182
+
183
+ describe "#shift_to, with_position," do
184
+ it "should do properly set the position_in_list" do
185
+ (@people[4].shift_to(@people[13], @people[20])).should_not be_false
186
+ @people = Person.all
187
+ @people[2].children.should == [@people[3],@people[9],@people[10]]
188
+ @people[3].position_in_list.should == 1
189
+ @people[9].position_in_list.should == 2
190
+ @people[10].position_in_list.should == 3
191
+ @people[13].children.should == [@people[14],@people[15],@people[4],@people[20],@people[21]]
192
+ @people[14].position_in_list.should == 1
193
+ @people[15].position_in_list.should == 2
194
+ @people[4].position_in_list.should == 3
195
+ @people[20].position_in_list.should == 4
196
+ @people[21].position_in_list.should == 5
197
+ end
198
+ end
199
+
200
+ describe "#shift_to, without_position" do
201
+ before do
202
+ @people[4].shift_to(@people[13])
203
+ @people = Person.all
204
+ end
205
+
206
+ it "should change the old siblings position_in_list accordingly" do
207
+ @people[2].children.should == [@people[3],@people[9],@people[10]]
208
+ @people[3].position_in_list.should == 1
209
+ @people[9].position_in_list.should == 2
210
+ @people[10].position_in_list.should == 3
211
+ end
212
+
213
+ it "should go to the bottom of the new parent's children" do
214
+ @people[13].children.should == [@people[14],@people[15],@people[20],@people[21],@people[4]]
215
+ @people[14].position_in_list.should == 1
216
+ @people[15].position_in_list.should == 2
217
+ @people[20].position_in_list.should == 3
218
+ @people[21].position_in_list.should == 4
219
+ @people[4].position_in_list.should == 5
220
+ end
221
+ end
222
+
223
+ describe "#shift_to roots (without position, ie orphan)" do
224
+ before do
225
+ @people[4].orphan
226
+ @people = Person.all
227
+ end
228
+
229
+ it "should do adjust the old siblings accordingly" do
230
+ @people[2].children.should == [@people[3],@people[9],@people[10]]
231
+ @people[3].position_in_list.should == 1
232
+ @people[9].position_in_list.should == 2
233
+ @people[10].position_in_list.should == 3
234
+ end
235
+
236
+ it "should move it to the root, and add it to the bottom of the root list" do
237
+ Person.roots.should == [@people[0],@people[11],@people[4]]
238
+ @people[0].position_in_list.should == 1
239
+ @people[11].position_in_list.should == 2
240
+ @people[4].position_in_list.should == 3
241
+ end
242
+ end
243
+
244
+ describe "#shift_to roots with position argument" do
245
+ before do
246
+ @people[4].shift_to(nil, @people[11])
247
+ @people = Person.all
248
+ end
249
+
250
+ it "should do properly collapse the position of the old siblings" do
251
+ @people[2].children.should == [@people[3],@people[9],@people[10]]
252
+ @people[3].position_in_list.should == 1
253
+ @people[9].position_in_list.should == 2
254
+ @people[10].position_in_list.should == 3
255
+ end
256
+
257
+ it "should add the node to the root, at the specified location" do
258
+ Person.roots.should == [@people[0],@people[4],@people[11]]
259
+ @people[0].position_in_list.should == 1
260
+ @people[4].position_in_list.should == 2
261
+ @people[11].position_in_list.should == 3
262
+ end
263
+ end
264
+
265
+ describe "#orphan_children" do
266
+ it "should do properly move the children to the root" do
267
+ @people[2].orphan_children
268
+ @people = Person.all
269
+ @people[2].children.should be_empty
270
+ Person.roots.should == [@people[0],@people[11],@people[3],@people[4],@people[9],@people[10]]
271
+ end
272
+ end
273
+
274
+ describe "#parent_adopts_children" do
275
+ it "should do make the node belong to the parent" do
276
+ @people[4].parent_adopts_children
277
+ @people = Person.all
278
+ @people[4].children.should be_empty
279
+ @people[2].children.should == [@people[3],@people[4],@people[9],@people[10],@people[7],@people[8]]
280
+ end
281
+ end
282
+
283
+ describe "#orphan_self_and_children" do
284
+ it "should do move self and children to the roots" do
285
+ @people[2].orphan_self_and_children
286
+ @people = Person.all
287
+ @people[2].children.should be_empty
288
+ Person.roots.should == [@people[0],@people[11],@people[3],@people[4],@people[9],@people[10],@people[2]]
289
+ end
290
+ end
291
+
292
+ describe "#orphan_self_and_parent_adopts_children" do
293
+ before do
294
+ @people[4].orphan_self_and_parent_adopts_children
295
+ @people = Person.all
296
+ end
297
+
298
+ it "should do make itself an orphan" do
299
+ @people[4].children.should be_empty
300
+ Person.roots.should == [@people[0],@people[11],@people[4]]
301
+ end
302
+
303
+ it "should let the parent adopt its children" do
304
+ @people[2].children.should == [@people[3],@people[9],@people[10],@people[7],@people[8]]
305
+ @people[3].position_in_list.should == 1
306
+ @people[9].position_in_list.should == 2
307
+ @people[10].position_in_list.should == 3
308
+ @people[7].position_in_list.should == 4
309
+ @people[8].position_in_list.should == 5
310
+ end
311
+ end
312
+
313
+ describe "#destroy_and_orphan_children" do
314
+ before do
315
+ @people[2].destroy_and_orphan_children
316
+ @people = Person.all
317
+ end
318
+
319
+ it "should do destroy itself, and make children root" do
320
+ # remember, since we deleted @people[2], all below get shifted up
321
+ Person.roots.should == [@people[0],@people[10],@people[2],@people[3],@people[8],@people[9]]
322
+ @people[0].children.should == [@people[1],@people[4],@people[5]]
323
+ @people[1].position_in_list.should == 1
324
+ @people[4].position_in_list.should == 2
325
+ @people[5].position_in_list.should == 3
326
+ end
327
+ end
328
+
329
+ describe "#destroy_and_parent_adopts_children" do
330
+ before do
331
+ @people[4].destroy_and_parent_adopts_children
332
+ @people = Person.all
333
+ end
334
+
335
+ it "should destroy self, and let the parent adopt the children" do
336
+ # remember, since we deleted @people[4], all below get shifted up
337
+ @people[2].children.should == [@people[3],@people[8],@people[9],@people[6],@people[7]]
338
+ @people[3].position_in_list.should == 1
339
+ @people[8].position_in_list.should == 2
340
+ @people[9].position_in_list.should == 3
341
+ @people[6].position_in_list.should == 4
342
+ @people[7].position_in_list.should == 5
343
+ end
344
+ end
345
+
346
+ describe "when inserting a new node with a position already set" do
347
+ before do
348
+ @people[2].children << Person.new(:position => 3, :name => 'Person_23')
349
+ @people = Person.all
350
+ end
351
+
352
+ it "should do insert that node in the position, shifting down the other nodes" do
353
+ @people[2].children.should == [@people[3],@people[4],@people[22],@people[9],@people[10]]
354
+ @people[3].position_in_list.should == 1
355
+ @people[4].position_in_list.should == 2
356
+ @people[22].position_in_list.should == 3
357
+ @people[9].position_in_list.should == 4
358
+ @people[10].position_in_list.should == 5
359
+ end
360
+ end
361
+
362
+ describe "creating a node, passing the parent_id and position already" do
363
+ before do
364
+ Person.create(:parent_id => @people[2].id, :position => 2, :name => 'Person_23')
365
+ @people = Person.all
366
+ end
367
+
368
+ it "should insert the new node in that position, under that parent" do
369
+ @people[2].children.should == [@people[3],@people[22],@people[4],@people[9],@people[10]]
370
+ @people[3].position_in_list.should == 1
371
+ @people[22].position_in_list.should == 2
372
+ @people[4].position_in_list.should == 3
373
+ @people[9].position_in_list.should == 4
374
+ @people[10].position_in_list.should == 5
375
+ end
376
+ end
377
+
378
+ describe "creating a child node with a position already set" do
379
+ before do
380
+ @people[2].children.create(:position => 4, :name => 'Person_23')
381
+ @people = Person.all
382
+ end
383
+
384
+ it "should insert that child in that position (pushing the others down)" do
385
+ @people[2].children.should == [@people[3],@people[4],@people[9],@people[22],@people[10]]
386
+ @people[3].position_in_list.should == 1
387
+ @people[4].position_in_list.should == 2
388
+ @people[9].position_in_list.should == 3
389
+ @people[22].position_in_list.should == 4
390
+ @people[10].position_in_list.should == 5
391
+ end
392
+ end
393
+
394
+ describe "creating a root with a position already set" do
395
+ before do
396
+ Person.create(:position => 2, :name => 'Person_23')
397
+ @people = Person.all
398
+ end
399
+
400
+ it "should do insert it in the root, pushing down the others" do
401
+ Person.roots.should == [@people[0],@people[22],@people[11]]
402
+ @people[0].position_in_list.should == 1
403
+ @people[22].position_in_list.should == 2
404
+ @people[11].position_in_list.should == 3
405
+ end
406
+ end
407
+
408
+ describe "creating with a position that's higher than the bottom" do
409
+ it "should go to the bottom of the list" do
410
+ person_23 = @people[2].children.create(:position => 15, :name => 'Person_23')
411
+ person_23.position_in_list.should == 5
412
+ end
413
+ end
414
+ end
@@ -0,0 +1,114 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rubygems'
4
+ require 'active_record'
5
+ require 'rspec'
6
+ require 'ordered_tree'
7
+ require 'spec/fixtures/person'
8
+
9
+ #Allow to connect to SQLite
10
+ ActiveRecord::Base.establish_connection(
11
+ :adapter => "sqlite3",
12
+ :database => ":memory:"
13
+ )
14
+
15
+ # Requires supporting files with custom matchers and macros, etc,
16
+ # in ./support/ and its subdirectories.
17
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
18
+
19
+ RSpec.configure do |config|
20
+ end
21
+
22
+ def reset_database
23
+ %W(people).each do |table_name|
24
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS '#{table_name}'")
25
+ end
26
+ ActiveRecord::Base.connection.create_table(:people) do |t|
27
+ t.integer :parent_id, :null => false, :default => 0
28
+ t.integer :position
29
+ t.string :name
30
+ #add_index :people, [:parent_id], :name => "index_people_on_parent_id"
31
+ end
32
+ end
33
+
34
+ def ordered_tree(klass, *opts)
35
+ klass.ordered_tree *opts
36
+ yield
37
+ ensure
38
+ klass.ordered_tree
39
+ end
40
+
41
+ # Test Tree
42
+ #
43
+ # We will be working with this tree through out the tests
44
+ #
45
+ # people[0]
46
+ # \_ people[1]
47
+ # \_ people[2]
48
+ # | \_ people[3]
49
+ # | \_ people[4]
50
+ # | | \_ people[7]
51
+ # | | \_ people[8]
52
+ # | \_ people[9]
53
+ # | \_ people[10]
54
+ # \_ people[5]
55
+ # \_ people[6]
56
+ # |
57
+ # |
58
+ # people[11]
59
+ # \_ people[12]
60
+ # \_ people[13]
61
+ # | \_ people[14]
62
+ # | \_ people[15]
63
+ # | | \_ people[18]
64
+ # | | \_ people[19]
65
+ # | \_ people[20]
66
+ # | \_ people[21]
67
+ # \_ people[16]
68
+ # \_ people[17]
69
+ #
70
+ #
71
+ # +----+-----------+----------+-----------+
72
+ # | id | parent_id | position | name |
73
+ # +----+-----------+----------+-----------+
74
+ # | 1 | 0 | 1 | Person_1 |
75
+ # | 2 | 1 | 1 | Person_2 |
76
+ # | 3 | 1 | 2 | Person_3 |
77
+ # | 4 | 3 | 1 | Person_4 |
78
+ # | 5 | 3 | 2 | Person_5 |
79
+ # | 6 | 1 | 3 | Person_6 |
80
+ # | 7 | 1 | 4 | Person_7 |
81
+ # | 8 | 5 | 1 | Person_8 |
82
+ # | 9 | 5 | 2 | Person_9 |
83
+ # | 10 | 3 | 3 | Person_10 |
84
+ # | 11 | 3 | 4 | Person_11 |
85
+ # | 12 | 0 | 2 | Person_12 |
86
+ # | 13 | 12 | 1 | Person_13 |
87
+ # | 14 | 12 | 2 | Person_14 |
88
+ # | 15 | 14 | 1 | Person_15 |
89
+ # | 16 | 14 | 2 | Person_16 |
90
+ # | 17 | 12 | 3 | Person_17 |
91
+ # | 18 | 12 | 4 | Person_18 |
92
+ # | 19 | 16 | 1 | Person_19 |
93
+ # | 20 | 16 | 2 | Person_20 |
94
+ # | 21 | 14 | 3 | Person_21 |
95
+ # | 22 | 14 | 4 | Person_22 |
96
+ # +----+-----------+----------+-----------+
97
+ #
98
+ def reload_test_tree
99
+ reset_database
100
+ people = []
101
+ i = 1
102
+ people << Person.create(:name => "Person_#{i}")
103
+ [0,2,0,4,2,-1,11,13,11,15,13].each do |n|
104
+ if n == -1
105
+ i = i.next
106
+ people << Person.create(:name => "Person_#{i}")
107
+ else
108
+ 2.times do
109
+ i = i.next
110
+ people << people[n].children.create(:name => "Person_#{i}")
111
+ end
112
+ end
113
+ end
114
+ end