locomotive-mongoid-tree 0.6.2

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