ordered_tree 0.1.1

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,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