locomotive-mongoid-tree 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,175 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Tree::Traversal do
4
+
5
+ subject { Node }
6
+
7
+ describe '#traverse' do
8
+
9
+ subject { Node.new }
10
+
11
+ it "should require a block" do
12
+ expect { subject.traverse }.to raise_error(/No block given/)
13
+ end
14
+
15
+ [:depth_first, :breadth_first].each do |method|
16
+ it "should support #{method} traversal" do
17
+ expect { subject.traverse(method) {} }.to_not raise_error
18
+ end
19
+ end
20
+
21
+ it "should complain about unsupported traversal methods" do
22
+ expect { subject.traverse('non_existing') {} }.to raise_error
23
+ end
24
+
25
+ it "should default to depth_first traversal" do
26
+ subject.should_receive(:depth_first_traversal)
27
+ subject.traverse {}
28
+ end
29
+
30
+ end
31
+
32
+ describe 'depth first traversal' do
33
+
34
+ it "should traverse correctly" do
35
+ setup_tree <<-ENDTREE
36
+ node1:
37
+ - node2:
38
+ - node3
39
+ - node4:
40
+ - node5
41
+ - node6
42
+ - node7
43
+ ENDTREE
44
+
45
+ result = []
46
+ node(:node1).traverse(:depth_first) { |node| result << node }
47
+ result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
48
+ end
49
+
50
+ it "should traverse correctly on merged trees" do
51
+
52
+ setup_tree <<-ENDTREE
53
+ - node4:
54
+ - node5
55
+ - node6:
56
+ - node7
57
+
58
+ - node1:
59
+ - node2:
60
+ - node3
61
+ ENDTREE
62
+
63
+
64
+ node(:node1).children << node(:node4)
65
+
66
+
67
+ result = []
68
+ node(:node1).traverse(:depth_first) { |node| result << node }
69
+ result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
70
+ end
71
+
72
+ describe 'with reordered nodes' do
73
+
74
+ subject { OrderedNode }
75
+
76
+ before do
77
+ setup_tree <<-ENDTREE
78
+ node1:
79
+ - node2:
80
+ - node3
81
+ - node4:
82
+ - node6
83
+ - node5
84
+ - node7
85
+ ENDTREE
86
+ node(:node5).move_above(node(:node6))
87
+ end
88
+
89
+ it 'should return the nodes in the correct order' do
90
+ result = []
91
+ node(:node1).traverse(:depth_first) { |node| result << node }
92
+ result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ describe 'breadth first traversal' do
100
+
101
+ it "should traverse correctly" do
102
+ tree = setup_tree <<-ENDTREE
103
+ node1:
104
+ - node2:
105
+ - node5
106
+ - node3:
107
+ - node6
108
+ - node7
109
+ - node4
110
+ ENDTREE
111
+
112
+ result = []
113
+ node(:node1).traverse(:breadth_first) { |n| result << n }
114
+ result.collect { |n| n.name.to_sym }.should == [:node1, :node2, :node3, :node4, :node5, :node6, :node7]
115
+ end
116
+
117
+ end
118
+
119
+ describe '.traverse' do
120
+
121
+ describe 'when not given a block' do
122
+
123
+ it 'raises an error' do
124
+ expect {Node.traverse}.to raise_error ArgumentError, 'No block given'
125
+ end
126
+ end
127
+
128
+ before :each do
129
+ setup_tree <<-ENDTREE
130
+ - root1
131
+ - root2
132
+ ENDTREE
133
+
134
+ @block = Proc.new {}
135
+ @root1 = node(:root1)
136
+ @root2 = node(:root2)
137
+
138
+ Node.stub(:roots).and_return [@root1, @root2]
139
+ end
140
+
141
+ it 'grabs each root' do
142
+ Node.should_receive(:roots).and_return []
143
+
144
+ Node.traverse &@block
145
+ end
146
+
147
+ it 'defaults the "type" arg to :depth_first' do
148
+ @root1.should_receive(:traverse).with(:depth_first)
149
+ @root2.should_receive(:traverse).with(:depth_first)
150
+
151
+ Node.traverse &@block
152
+ end
153
+
154
+ it 'traverses each root' do
155
+ @root1.should_receive(:traverse)
156
+ @root2.should_receive(:traverse)
157
+
158
+ Node.traverse &@block
159
+ end
160
+
161
+ describe 'when the "type" arg is :breadth_first' do
162
+
163
+ it 'traverses breadth-first' do
164
+ @root1.should_receive(:traverse).with(:breadth_first)
165
+ @root2.should_receive(:traverse).with(:breadth_first)
166
+
167
+ Node.traverse :breadth_first, &@block
168
+ end
169
+ end
170
+
171
+ it 'returns nil' do
172
+ Node.traverse(&@block).should be nil
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,382 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Tree do
4
+
5
+ subject { Node }
6
+
7
+ it "should reference many children as inverse of parent with index" do
8
+ a = Node.reflect_on_association(:children)
9
+ a.should be
10
+ a.macro.should eql(:references_many)
11
+ a.class_name.should eql('Node')
12
+ a.foreign_key.should eql('parent_id')
13
+ Node.index_options.should have_key('parent_id')
14
+ end
15
+
16
+ it "should be referenced in one parent as inverse of children" do
17
+ a = Node.reflect_on_association(:parent)
18
+ a.should be
19
+ a.macro.should eql(:referenced_in)
20
+ a.class_name.should eql('Node')
21
+ a.inverse_of.should eql(:children)
22
+ end
23
+
24
+ it "should store parent_ids as Array with [] as default with index" do
25
+ f = Node.fields['parent_ids']
26
+ f.should be
27
+ f.options[:type].should eql(Array)
28
+ f.options[:default].should eql([])
29
+ Node.index_options.should have_key(:parent_ids)
30
+ end
31
+
32
+ describe 'when new' do
33
+ it "should not require a saved parent when adding children" do
34
+ root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
35
+ expect { root.children << child; root.save! }.to_not raise_error(Mongoid::Errors::DocumentNotFound)
36
+ child.should be_persisted
37
+ end
38
+
39
+ it "should not be saved when parent is not saved" do
40
+ root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
41
+ child.should_not_receive(:save)
42
+ root.children << child
43
+ end
44
+
45
+ it "should save its unsaved children" do
46
+ root = Node.new(:name => 'root'); child = Node.new(:name => 'child')
47
+ root.children << child
48
+ child.should_receive(:save).at_most(2).times
49
+ root.save
50
+ end
51
+ end
52
+
53
+ describe 'when saved' do
54
+
55
+ before(:each) do
56
+ setup_tree <<-ENDTREE
57
+ - root:
58
+ - child:
59
+ - subchild:
60
+ - subsubchild
61
+ - other_root:
62
+ - other_child
63
+ ENDTREE
64
+ end
65
+
66
+ it "should set the child's parent_id when added to parent's children" do
67
+ root = Node.create; child = Node.create
68
+ root.children << child
69
+ child.parent.should == root
70
+ child.parent_id.should == root.id
71
+ end
72
+
73
+ it "should set the child's parent_id parent is set on child" do
74
+ root = Node.create; child = Node.create
75
+ child.parent = root
76
+ child.parent.should == root
77
+ child.parent_id.should == root.id
78
+ end
79
+
80
+ it "should rebuild its parent_ids" do
81
+ root = Node.create; child = Node.create
82
+ root.children << child
83
+ child.parent_ids.should == [root.id]
84
+ end
85
+
86
+ it "should rebuild its children's parent_ids when its own parent_ids changed" do
87
+ other_root = node(:other_root); child = node(:child); subchild = node(:subchild);
88
+ other_root.children << child
89
+ subchild.reload # To get the updated version
90
+ subchild.parent_ids.should == [other_root.id, child.id]
91
+ end
92
+
93
+ it "should correctly rebuild its descendants' parent_ids when moved into an other subtree" do
94
+ subchild = node(:subchild); subsubchild = node(:subsubchild); other_child = node(:other_child)
95
+ other_child.children << subchild
96
+ subsubchild.reload
97
+ subsubchild.parent_ids.should == [node(:other_root).id, other_child.id, subchild.id]
98
+ end
99
+
100
+ it "should rebuild its children's parent_ids when its own parent_id is removed" do
101
+ c = node(:child)
102
+ c.parent_id = nil
103
+ c.save
104
+ node(:subchild).parent_ids.should == [node(:child).id]
105
+ end
106
+
107
+ it "should not rebuild its children's parent_ids when it's not required" do
108
+ root = node(:root)
109
+ root.should_not_receive(:rearrange_children)
110
+ root.save
111
+ end
112
+
113
+ it "should prevent cycles" do
114
+ child = node(:child)
115
+ child.parent = node(:subchild)
116
+ child.should_not be_valid
117
+ child.errors[:parent_id].should_not be_nil
118
+ end
119
+
120
+ it "should save its children when added" do
121
+ new_child = Node.new(:name => 'new_child')
122
+ node(:root).children << new_child
123
+ new_child.should be_persisted
124
+ end
125
+ end
126
+
127
+ describe 'when subclassed' do
128
+
129
+ before(:each) do
130
+ setup_tree <<-ENDTREE
131
+ - root:
132
+ - child:
133
+ - subchild
134
+ - other_child
135
+ - other_root
136
+ ENDTREE
137
+ end
138
+
139
+ it "should allow to store any subclass within the tree" do
140
+ subclassed = SubclassedNode.create!(:name => 'subclassed_subchild')
141
+ node(:child).children << subclassed
142
+ subclassed.root.should == node(:root)
143
+ end
144
+
145
+ end
146
+
147
+ describe 'destroy strategies' do
148
+
149
+ before(:each) do
150
+ setup_tree <<-ENDTREE
151
+ - root:
152
+ - child:
153
+ - subchild
154
+ - other_child
155
+ - other_root
156
+ ENDTREE
157
+ end
158
+
159
+ describe ':nullify_children' do
160
+ it "should set its children's parent_id to null" do
161
+ node(:root).nullify_children
162
+ node(:child).should be_root
163
+ node(:subchild).reload.should_not be_descendant_of node(:root)
164
+ end
165
+ end
166
+
167
+ describe ':move_children_to_parent' do
168
+ it "should set its childen's parent_id to the documents parent_id" do
169
+ node(:child).move_children_to_parent
170
+ node(:child).should be_leaf
171
+ node(:root).children.to_a.should =~ [node(:child), node(:other_child), node(:subchild)]
172
+ end
173
+ end
174
+
175
+ describe ':destroy_children' do
176
+ it "should destroy all children" do
177
+ root = node(:root)
178
+ root.children.should_receive(:destroy_all)
179
+ root.destroy_children
180
+ end
181
+ end
182
+
183
+ describe ':delete_descendants' do
184
+ it "should delete all descendants" do
185
+ root = node(:root)
186
+ Node.should_receive(:delete_all).with(:conditions => { :parent_ids => root.id })
187
+ root.delete_descendants
188
+ end
189
+ end
190
+
191
+ end
192
+
193
+ describe 'utility methods' do
194
+
195
+ before(:each) do
196
+ setup_tree <<-ENDTREE
197
+ - root:
198
+ - child:
199
+ - subchild
200
+ - other_child
201
+ - other_root
202
+ ENDTREE
203
+ end
204
+
205
+ describe '.root' do
206
+ it "should return the first root document" do
207
+ Node.root.should == node(:root)
208
+ end
209
+ end
210
+
211
+ describe '.roots' do
212
+ it "should return all root documents" do
213
+ Node.roots.to_a.should == [node(:root), node(:other_root)]
214
+ end
215
+ end
216
+
217
+ describe '.leaves' do
218
+ it "should return all leaf documents" do
219
+ Node.leaves.to_a.should =~ [node(:subchild), node(:other_child), node(:other_root)]
220
+ end
221
+ end
222
+
223
+ describe '#root?' do
224
+ it "should return true for root documents" do
225
+ node(:root).should be_root
226
+ end
227
+
228
+ it "should return false for non-root documents" do
229
+ node(:child).should_not be_root
230
+ end
231
+ end
232
+
233
+ describe '#leaf?' do
234
+ it "should return true for leaf documents" do
235
+ node(:subchild).should be_leaf
236
+ node(:other_child).should be_leaf
237
+ Node.new.should be_leaf
238
+ end
239
+
240
+ it "should return false for non-leaf documents" do
241
+ node(:child).should_not be_leaf
242
+ node(:root).should_not be_leaf
243
+ end
244
+ end
245
+
246
+ describe '#depth' do
247
+ it "should return the depth of this document" do
248
+ node(:root).depth.should == 0
249
+ node(:child).depth.should == 1
250
+ node(:subchild).depth.should == 2
251
+ end
252
+ end
253
+
254
+ describe '#root' do
255
+ it "should return the root for this document" do
256
+ node(:subchild).root.should == node(:root)
257
+ end
258
+
259
+ it "should return itself when there is no root" do
260
+ new_node = Node.new
261
+ new_node.root.should be(new_node)
262
+ end
263
+
264
+ it "should return it root when it's not saved yet" do
265
+ root = Node.new(:name => 'root')
266
+ new_node = Node.new(:name => 'child')
267
+ new_node.parent = root
268
+ new_node.root.should be(root)
269
+ end
270
+ end
271
+
272
+ describe 'ancestors' do
273
+ it "#ancestors should return the documents ancestors" do
274
+ node(:subchild).ancestors.to_a.should == [node(:root), node(:child)]
275
+ end
276
+
277
+ it "#ancestors_and_self should return the documents ancestors and itself" do
278
+ node(:subchild).ancestors_and_self.to_a.should == [node(:root), node(:child), node(:subchild)]
279
+ end
280
+
281
+ describe '#ancestor_of?' do
282
+ it "should return true for ancestors" do
283
+ node(:child).should be_ancestor_of(node(:subchild))
284
+ end
285
+
286
+ it "should return false for non-ancestors" do
287
+ node(:other_child).should_not be_ancestor_of(node(:subchild))
288
+ end
289
+ end
290
+ end
291
+
292
+ describe 'descendants' do
293
+ it "#descendants should return the documents descendants" do
294
+ node(:root).descendants.to_a.should =~ [node(:child), node(:other_child), node(:subchild)]
295
+ end
296
+
297
+ it "#descendants_and_self should return the documents descendants and itself" do
298
+ node(:root).descendants_and_self.to_a.should =~ [node(:root), node(:child), node(:other_child), node(:subchild)]
299
+ end
300
+
301
+ describe '#descendant_of?' do
302
+ it "should return true for descendants" do
303
+ subchild = node(:subchild)
304
+ subchild.should be_descendant_of(node(:child))
305
+ subchild.should be_descendant_of(node(:root))
306
+ end
307
+
308
+ it "should return false for non-descendants" do
309
+ node(:subchild).should_not be_descendant_of(node(:other_child))
310
+ end
311
+ end
312
+ end
313
+
314
+ describe 'siblings' do
315
+ it "#siblings should return the documents siblings" do
316
+ node(:child).siblings.to_a.should == [node(:other_child)]
317
+ end
318
+
319
+ it "#siblings_and_self should return the documents siblings and itself" do
320
+ node(:child).siblings_and_self.is_a?(Mongoid::Criteria).should == true
321
+ node(:child).siblings_and_self.to_a.should == [node(:child), node(:other_child)]
322
+ end
323
+
324
+ describe '#sibling_of?' do
325
+ it "should return true for siblings" do
326
+ node(:child).should be_sibling_of(node(:other_child))
327
+ end
328
+
329
+ it "should return false for non-siblings" do
330
+ node(:root).should_not be_sibling_of(node(:other_child))
331
+ end
332
+ end
333
+ end
334
+
335
+ describe '#leaves' do
336
+ it "should return this documents leaves" do
337
+ node(:root).leaves.to_a.should =~ [node(:other_child), node(:subchild)]
338
+ end
339
+ end
340
+
341
+ end
342
+
343
+ describe 'callbacks' do
344
+
345
+ after(:each) do
346
+ Node.reset_callbacks(:rearrange)
347
+ end
348
+
349
+ it "should provide a before_rearrange callback" do
350
+ Node.should respond_to :before_rearrange
351
+ end
352
+
353
+ it "should provida an after_rearrange callback" do
354
+ Node.should respond_to :after_rearrange
355
+ end
356
+
357
+ describe 'before rearrange callback' do
358
+
359
+ it "should be called before the document is rearranged" do
360
+ Node.before_rearrange :callback
361
+ node = Node.new
362
+ node.should_receive(:callback).ordered
363
+ node.should_receive(:rearrange).ordered
364
+ node.save
365
+ end
366
+
367
+ end
368
+
369
+ describe 'after rearrange callback' do
370
+
371
+ it "should be called after the document is rearranged" do
372
+ Node.after_rearrange :callback
373
+ node = Node.new
374
+ node.should_receive(:rearrange).ordered
375
+ node.should_receive(:callback).ordered
376
+ node.save
377
+ end
378
+
379
+ end
380
+
381
+ end
382
+ end