releaf-content 0.2.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +24 -0
  3. data/app/assets/javascripts/releaf/controllers/releaf/content/nodes.js +88 -0
  4. data/app/assets/stylesheets/releaf/controllers/releaf/content/nodes.scss +234 -0
  5. data/app/builders/releaf/content/builders/action_dialog.rb +60 -0
  6. data/app/builders/releaf/content/builders/dialog.rb +15 -0
  7. data/app/builders/releaf/content/builders/tree.rb +84 -0
  8. data/app/builders/releaf/content/content_type_dialog_builder.rb +74 -0
  9. data/app/builders/releaf/content/copy_dialog_builder.rb +9 -0
  10. data/app/builders/releaf/content/go_to_dialog_builder.rb +9 -0
  11. data/app/builders/releaf/content/move_dialog_builder.rb +9 -0
  12. data/app/builders/releaf/content/nodes/content_form_builder.rb +7 -0
  13. data/app/builders/releaf/content/nodes/form_builder.rb +108 -0
  14. data/app/builders/releaf/content/nodes/index_builder.rb +24 -0
  15. data/app/builders/releaf/content/nodes/toolbox_builder.rb +33 -0
  16. data/app/controllers/releaf/content/nodes_controller.rb +166 -0
  17. data/app/middleware/releaf/content/routes_reloader.rb +25 -0
  18. data/app/validators/releaf/content/node/parent_validator.rb +48 -0
  19. data/app/validators/releaf/content/node/root_validator.rb +43 -0
  20. data/app/validators/releaf/content/node/singleness_validator.rb +102 -0
  21. data/app/views/releaf/content/nodes/content_type_dialog.ruby +1 -0
  22. data/app/views/releaf/content/nodes/copy_dialog.ruby +1 -0
  23. data/app/views/releaf/content/nodes/go_to_dialog.ruby +1 -0
  24. data/app/views/releaf/content/nodes/move_dialog.ruby +1 -0
  25. data/lib/releaf-content.rb +6 -0
  26. data/lib/releaf/content/acts_as_node.rb +73 -0
  27. data/lib/releaf/content/acts_as_node/action_controller/acts/node.rb +17 -0
  28. data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +55 -0
  29. data/lib/releaf/content/builders_autoload.rb +18 -0
  30. data/lib/releaf/content/engine.rb +40 -0
  31. data/lib/releaf/content/node.rb +280 -0
  32. data/lib/releaf/content/node_mapper.rb +9 -0
  33. data/lib/releaf/content/route.rb +93 -0
  34. data/lib/releaf/content/router_proxy.rb +23 -0
  35. data/releaf-content.gemspec +20 -0
  36. data/spec/builders/content/nodes/content_form_builder_spec.rb +24 -0
  37. data/spec/builders/content/nodes/form_builder_spec.rb +218 -0
  38. data/spec/builders/content/nodes/toolbox_builder_spec.rb +108 -0
  39. data/spec/controllers/releaf/content/nodes_controller_spec.rb +21 -0
  40. data/spec/features/nodes_spec.rb +239 -0
  41. data/spec/lib/releaf/content/acts_as_node_spec.rb +118 -0
  42. data/spec/lib/releaf/content/node_spec.rb +779 -0
  43. data/spec/lib/releaf/content/route_spec.rb +85 -0
  44. data/spec/middleware/routes_reloader_spec.rb +48 -0
  45. data/spec/routing/node_mapper_spec.rb +142 -0
  46. data/spec/validators/content/node/parent_validator_spec.rb +56 -0
  47. data/spec/validators/content/node/root_validator_spec.rb +69 -0
  48. data/spec/validators/content/node/singleness_validator_spec.rb +145 -0
  49. metadata +145 -0
@@ -0,0 +1,118 @@
1
+ require "rails_helper"
2
+
3
+ class ContactFormController < ActionController::Base
4
+ acts_as_node
5
+ end
6
+
7
+ describe ActsAsNode do
8
+ before do
9
+ Book.acts_as_node
10
+ end
11
+
12
+ describe ".classes" do
13
+ it "returns all registerd classes" do
14
+ expect(ActsAsNode.classes).to include("ContactFormController", "Book")
15
+ end
16
+ end
17
+
18
+ describe ".acts_as_node" do
19
+ it "have configuration options for params and fields available through acts_as_node_configuration class method" do
20
+ expect(Book.acts_as_node_configuration).to eq(params: nil, fields: nil)
21
+
22
+ Book.acts_as_node params: ["x"], fields: ["a"]
23
+ expect(Book.acts_as_node_configuration).to eq(params: ["x"], fields: ["a"])
24
+ end
25
+
26
+ it "has hard typed configuration options" do
27
+ expect{ Book.acts_as_node xxxx: ["x"] }.to raise_error(ArgumentError, "unknown keyword: xxxx")
28
+ end
29
+ end
30
+
31
+ describe ActiveRecord::Acts::Node do
32
+ context "when model acts as node" do
33
+ it "has name included within ActsAsNode.classes" do
34
+ expect(ActsAsNode.classes.include?(Book.to_s)).to be true
35
+ end
36
+ end
37
+
38
+ describe "#node" do
39
+ it "returns corresponding node object" do
40
+ allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
41
+ node = create(:node, content_type: "Book", content_attributes: {title: "xx"})
42
+ expect(Book.last.node).to eq(node)
43
+ end
44
+ end
45
+
46
+ context ".acts_as_node_params" do
47
+ before do
48
+ allow_any_instance_of(Releaf::Core::ResourceParams).to receive(:values).and_return(["a", "b"])
49
+ end
50
+
51
+ context "when `params` configuration is nil" do
52
+ it "returns model params with `id` param" do
53
+ allow(Book).to receive(:acts_as_node_configuration).and_return(params: nil)
54
+ expect(Releaf::Core::ResourceParams).to receive(:new).with(Book).and_call_original
55
+ expect(Book.acts_as_node_params).to eq(["a", "b", :id])
56
+ end
57
+ end
58
+
59
+ context "when `params` configuration is not nil" do
60
+ it "returns configuration values with `id` param" do
61
+ allow(Book).to receive(:acts_as_node_configuration).and_return(params: ["c", "d"])
62
+ expect(Book.acts_as_node_params).to eq(["c", "d", :id])
63
+ end
64
+ end
65
+ end
66
+
67
+ context ".acts_as_node_fields" do
68
+ before do
69
+ allow_any_instance_of(Releaf::Core::ResourceFields).to receive(:values).and_return(["a", "b"])
70
+ end
71
+
72
+ context "when `fields` configuration is nil" do
73
+ it "returns model fields" do
74
+ allow(Book).to receive(:acts_as_node_configuration).and_return(fields: nil)
75
+ expect(Releaf::Core::ResourceFields).to receive(:new).with(Book).and_call_original
76
+ expect(Book.acts_as_node_fields).to eq(["a", "b"])
77
+ end
78
+ end
79
+
80
+ context "when `fields` configuration is not nil" do
81
+ it "returns configuration values" do
82
+ allow(Book).to receive(:acts_as_node_configuration).and_return(fields: ["c", "d"])
83
+ expect(Book.acts_as_node_fields).to eq(["c", "d"])
84
+ end
85
+ end
86
+ end
87
+
88
+ context ".nodes" do
89
+ it "loads tree nodes" do
90
+ expect(Node).to receive(:where).with(content_type: Book.name)
91
+ Book.nodes
92
+ end
93
+
94
+ it "returns relation" do
95
+ expect(Book.nodes.class).to eq(Node::ActiveRecord_Relation)
96
+ end
97
+ end
98
+ end
99
+
100
+ describe ActionController::Acts::Node do
101
+ context "when controller acts as node" do
102
+ it "has name included within ActsAsNode.classes" do
103
+ expect(ActsAsNode.classes.include?(ContactFormController.to_s)).to be true
104
+ end
105
+ end
106
+
107
+ context ".nodes" do
108
+ it "loads tree nodes" do
109
+ expect(Node).to receive(:where).with(content_type: ContactFormController.name)
110
+ ContactFormController.nodes
111
+ end
112
+
113
+ it "returns array" do
114
+ expect(ContactFormController.nodes.class).to eq(Node::ActiveRecord_Relation)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,779 @@
1
+ require "rails_helper"
2
+
3
+ describe Node do
4
+ class PlainNode < ActiveRecord::Base
5
+ include Releaf::Content::Node
6
+ self.table_name = "nodes"
7
+ end
8
+
9
+ let(:plain_subject){ PlainNode.new }
10
+
11
+ it { is_expected.to accept_nested_attributes_for(:content) }
12
+ it { is_expected.to belong_to(:content) }
13
+
14
+ it "includes Releaf::Content::Node module" do
15
+ expect( Node.included_modules ).to include Releaf::Content::Node
16
+ end
17
+
18
+ describe "validations" do
19
+ it { is_expected.to validate_presence_of(:name) }
20
+ it { is_expected.to validate_presence_of(:slug) }
21
+ it { is_expected.to validate_presence_of(:content_type) }
22
+ it { is_expected.to validate_uniqueness_of(:slug).scoped_to(:parent_id) }
23
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
24
+ it { is_expected.to validate_length_of(:slug).is_at_most(255) }
25
+ end
26
+
27
+ describe "after save" do
28
+ it "sets node update to current time" do
29
+ expect( Node ).to receive(:updated).once
30
+ create(:node)
31
+ end
32
+ end
33
+
34
+ describe ".active (scope)" do
35
+ it "returns active nodes" do
36
+ expect( Node ).to receive(:where).with(active: true).and_return('foo')
37
+ expect( Node.active ).to eq 'foo'
38
+ end
39
+ end
40
+
41
+ describe "#content_class" do
42
+ context 'when #content_type is nil' do
43
+ it 'returns nil' do
44
+ subject.content_type = nil
45
+ expect( subject.content_class ).to be_nil
46
+ end
47
+ end
48
+
49
+ context "when #content_type is blank string" do
50
+ it 'returns nil' do
51
+ subject.content_type = ""
52
+ expect( subject.content_class ).to be_nil
53
+ end
54
+ end
55
+
56
+ context "when #content_type is not blank" do
57
+ it "constantizes it" do
58
+ subject.content_type = "Node"
59
+ expect( subject.content_class ).to eq Node
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#to_s" do
65
+ it "returns name" do
66
+ expect(subject.to_s).to eq(subject.name)
67
+ end
68
+ end
69
+
70
+ describe "#locale" do
71
+ before do
72
+ root = create(:node, locale: "lv")
73
+ parent = create(:text_page_node, locale: nil, parent_id: root.id)
74
+ @child1 = create(:text_page_node, locale: nil, parent_id: parent.id)
75
+ @child2 = create(:text_page_node, parent_id: parent.id, locale: "en")
76
+ end
77
+
78
+ context "when node locale is nil" do
79
+ it "uses closest parent locale" do
80
+ expect(@child1.locale).to eq("lv")
81
+ end
82
+ end
83
+
84
+ context "when object node have locale" do
85
+ it "uses closest parent locale" do
86
+ expect(@child2.locale).to eq("en")
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "#destroy" do
92
+ context "when content object class exists" do
93
+ let!(:node) { create(:home_page_node) }
94
+
95
+ it "deletes record" do
96
+ expect { node.destroy }.to change { Node.count }.by(-1)
97
+ end
98
+
99
+ it "deletes associated record" do
100
+ expect { node.destroy }.to change { HomePage.count }.by(-1)
101
+ end
102
+ end
103
+
104
+ context "when content object class doesn't exists" do
105
+ let!(:node) { create(:home_page_node) }
106
+ before do
107
+ node.update_columns(content_type: 'NonExistingTestModel')
108
+ end
109
+
110
+ it "deletes record" do
111
+ expect { node.destroy }.to change { Node.count }.by(-1)
112
+ end
113
+ end
114
+
115
+ it "sets node update to current time" do
116
+ node = create(:node)
117
+ expect( Node ).to receive(:updated).once
118
+ node.destroy
119
+ end
120
+ end
121
+
122
+ describe "#attributes_to_not_copy" do
123
+ it "returns array with attributes" do
124
+ expect( subject.attributes_to_not_copy ).to match_array %w[content_id depth id item_position lft rgt slug created_at updated_at]
125
+ end
126
+ end
127
+
128
+ describe "#attributes_to_copy" do
129
+ it "returns object attributes excluding #attributes_to_not_copy" do
130
+ node = Node.new
131
+ allow( node ).to receive(:attributes_to_not_copy).and_return(%w[lft rgt])
132
+ expect( node.attributes_to_copy ).to eq(Node.column_names - %w[lft rgt])
133
+ end
134
+ end
135
+
136
+ describe "#reasign_slug" do
137
+ it "updates slug" do
138
+ node = create(:node)
139
+ old_slug = node.slug
140
+ node.name = 'woo hoo'
141
+ expect { node.reasign_slug }.to change { node.slug }.from(old_slug).to('woo-hoo')
142
+ end
143
+ end
144
+
145
+ describe "#save_under" do
146
+ let(:node1) { create(:node) }
147
+
148
+ it "saves node nuder node with given node_id" do
149
+ node2 = create(:text_page_node, parent: node1)
150
+
151
+ new_node = FactoryGirl.build(:text_page_node)
152
+ new_node.send(:save_under, node2.id)
153
+ expect( new_node ).to_not be_new_record
154
+ expect( new_node.parent ).to eq node2
155
+ end
156
+
157
+ it "maintains node name, updates slug and then saves record" do
158
+ new_node = FactoryGirl.build(:text_page_node)
159
+ expect( new_node ).to receive(:maintain_name).ordered.and_call_original
160
+ expect( new_node ).to receive(:reasign_slug).ordered.and_call_original
161
+ expect( new_node ).to receive(:save!).ordered.and_call_original
162
+ new_node.save_under(node1.id)
163
+ end
164
+
165
+ context "when #validate_root_locale_uniqueness? returns true" do
166
+ it "sets locale to nil" do
167
+ new_node = FactoryGirl.build(:node)
168
+ allow(new_node).to receive(:validate_root_locale_uniqueness?).and_return(true)
169
+ new_node.locale = 'xx'
170
+ expect { new_node.save_under(nil) }.to change { new_node.locale }.from('xx').to(nil)
171
+ end
172
+ end
173
+
174
+ context "when #validate_root_locale_uniqueness? returns false" do
175
+ it "doesn't set locale to nil" do
176
+ new_node = FactoryGirl.build(:node)
177
+ allow(new_node).to receive(:validate_root_locale_uniqueness?).and_return(false)
178
+ new_node.locale = 'xx'
179
+ expect { new_node.save_under(nil) }.to_not change { new_node.locale }.from('xx')
180
+ end
181
+ end
182
+
183
+ end
184
+
185
+
186
+ describe "#duplicate_content" do
187
+
188
+ context "when #content_id is blank" do
189
+ let(:node) { create(:node) }
190
+
191
+ it "returns nil" do
192
+ expect( node.duplicate_content ).to be_nil
193
+ end
194
+ end
195
+
196
+ context "when #content_id is not blank" do
197
+ let(:node) { create(:home_page_node) }
198
+
199
+ it "returns saved duplicated content" do
200
+ content = double('new content')
201
+ expect( content ).to receive(:save!)
202
+ expect( node ).to receive_message_chain(:content, :dup).and_return(content)
203
+ expect( node.duplicate_content ).to eq content
204
+ end
205
+
206
+ it "doesn't return same as original content" do
207
+ result = node.duplicate_content
208
+ expect( result ).to be_an_instance_of HomePage
209
+ expect( result.id ).to_not eq node.content_id
210
+ end
211
+ end
212
+ end
213
+
214
+ describe "#copy_attributes_from" do
215
+ let(:source_node) { create(:node, active: false) }
216
+
217
+ it "copies #attributes_to_copy attributes" do
218
+
219
+ allow( source_node ).to receive(:attributes_to_copy).and_return(['name'])
220
+
221
+ expect( source_node ).to receive(:name).and_call_original
222
+ expect( source_node ).to_not receive(:parent_id)
223
+ expect( source_node ).to_not receive(:content_type)
224
+ expect( source_node ).to_not receive(:active)
225
+
226
+ new_node = Node.new
227
+
228
+ new_node.copy_attributes_from source_node
229
+
230
+ expect( new_node.parent_id ).to be_nil
231
+ expect( new_node.content_type ).to be_nil
232
+ expect( new_node.active ).to be_truthy
233
+ end
234
+ end
235
+
236
+ describe "#duplicate_under!" do
237
+ let!(:source_node) { create(:node, locale: "lv") }
238
+ let!(:target_node) { create(:node, locale: "en") }
239
+
240
+ before do
241
+ allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
242
+ end
243
+
244
+ it "creates duplicated node under target node" do
245
+ new_node = Node.new
246
+ duplicated_content = double('content', id: 1234)
247
+ expect( Node ).to receive(:new).ordered.and_return(new_node)
248
+ expect( new_node ).to receive(:copy_attributes_from).with(source_node).ordered.and_call_original
249
+ expect( source_node ).to receive(:duplicate_content).ordered.and_return(duplicated_content)
250
+ expect( new_node ).to receive(:content_id=).with(1234).ordered
251
+ expect( new_node ).to receive(:save_under).with(target_node.id).ordered.and_call_original
252
+ source_node.duplicate_under!(target_node.id)
253
+ end
254
+
255
+ it "doesn't update settings timestamp" do
256
+ expect( Node ).to_not receive(:updated)
257
+ source_node.duplicate_under!(target_node.id)
258
+ end
259
+ end
260
+
261
+ describe "#copy", create_nodes: true do
262
+ before create_nodes: true do
263
+ @home_page_node = create(:home_page_node, locale: "lv")
264
+ @home_page_node_2 = create(:home_page_node, locale: "en")
265
+ @text_page_node_3 = create(:text_page_node, parent_id: @home_page_node_2.id)
266
+ @text_page_node_4 = create(:text_page_node, parent_id: @text_page_node_3.id)
267
+ @text_page_node_5 = create(:text_page_node, parent_id: @text_page_node_4.id)
268
+
269
+ # it is important to reload nodes, otherwise associations will return empty set
270
+ @home_page_node.reload
271
+ @home_page_node_2.reload
272
+ @text_page_node_3.reload
273
+ @text_page_node_4.reload
274
+ end
275
+
276
+ context "when one of children becomes invalid" do
277
+ before do
278
+ @text_page_node_4.name = nil
279
+ @text_page_node_4.save(validate: false)
280
+ end
281
+
282
+ it "raises ActiveRecord::RecordInvalid" do
283
+ expect { @text_page_node_3.copy(@home_page_node.id) }.to raise_error ActiveRecord::RecordInvalid
284
+ end
285
+
286
+ it "raises error on node being copied" do
287
+ begin
288
+ @text_page_node_3.copy(@home_page_node.id)
289
+ rescue ActiveRecord::RecordInvalid => e
290
+ expect( e.record ).to eq @text_page_node_3
291
+ end
292
+ expect(@text_page_node_3.errors.messages).to eq(base: ["descendant invalid"])
293
+ end
294
+
295
+ it "doesn't create any new nodes" do
296
+ expect do
297
+ begin
298
+ @text_page_node_3.copy(@home_page_node.id)
299
+ rescue ActiveRecord::RecordInvalid
300
+ end
301
+ end.to_not change { Node.count }
302
+ end
303
+
304
+ it "doesn't update settings timestamp" do
305
+ expect( Node ).to_not receive(:updated)
306
+ begin
307
+ @text_page_node_3.copy(@home_page_node.id)
308
+ rescue ActiveRecord::RecordInvalid
309
+ end
310
+ end
311
+
312
+ end
313
+
314
+
315
+ context "with corect parent_id" do
316
+ it "creates node along with descendant nodes" do
317
+ expect{ @text_page_node_3.copy(@home_page_node.id) }.to change{ Node.count }.by( @text_page_node_3.descendants.size + 1 )
318
+ end
319
+
320
+ it "correctly copies attributes" do
321
+ allow( @text_page_node_3 ).to receive(:children).and_return([@text_page_node_4])
322
+ allow( @text_page_node_4 ).to receive(:children).and_return([@text_page_node_5])
323
+
324
+ @text_page_node_3.update_attribute(:active, false)
325
+ @text_page_node_4.update_attribute(:active, false)
326
+
327
+ allow( @text_page_node_3 ).to receive(:attributes_to_copy).and_return(["name", "parent_id", "content_type"])
328
+
329
+ expect( @text_page_node_3 ).to receive(:duplicate_under!).and_call_original.ordered
330
+ expect( @text_page_node_4 ).to receive(:duplicate_under!).and_call_original.ordered
331
+ expect( @text_page_node_5 ).to receive(:duplicate_under!).and_call_original.ordered
332
+
333
+ @text_page_node_3.copy(@home_page_node.id)
334
+
335
+ @node_2_copy = @home_page_node.children.first
336
+ @node_3_copy = @node_2_copy.children.first
337
+ @node_4_copy = @node_3_copy.children.first
338
+
339
+ # new nodes by default are active, however we stubbed
340
+ # #attributes_to_copy of @test_node_2 to not return active attribute
341
+ # Also we updated @test_node_2#active to be false.
342
+ # However copy is active, because active attribute wasn't copied
343
+ expect( @node_2_copy ).to be_active
344
+ # for copy of @text_page_node_3 active attribute was copied however, as it
345
+ # should have been
346
+ expect( @node_3_copy ).to_not be_active
347
+ expect( @node_4_copy ).to be_active
348
+
349
+ expect( @node_2_copy.name ).to eq @text_page_node_3.name
350
+ expect( @node_3_copy.name ).to eq @text_page_node_4.name
351
+ expect( @node_4_copy.name ).to eq @text_page_node_5.name
352
+ end
353
+
354
+ it "updates settings timestamp only once" do
355
+ expect( Node ).to receive(:updated).once.and_call_original
356
+ @text_page_node_3.copy(@home_page_node.id)
357
+ end
358
+
359
+ context "when parent_id is nil" do
360
+ it "creates new node" do
361
+ allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
362
+ expect{ @text_page_node_3.copy(nil) }.to change{ Node.count }.by(3)
363
+ end
364
+ end
365
+
366
+ context "when copying root nodes", create_nodes: false do
367
+ context "when root locale uniqueness is validated" do
368
+ it "resets locale to nil" do
369
+ @text_page_node = create(:home_page_node, locale: 'en')
370
+ allow_any_instance_of(Node).to receive(:validate_root_locale_uniqueness?).and_return(true)
371
+ @text_page_node.copy(nil)
372
+ expect( Node.last.locale ).to eq nil
373
+ end
374
+ end
375
+
376
+ context "when root locale uniqueness is not validated" do
377
+ it "doesn't reset locale to nil" do
378
+ @text_page_node = create(:home_page_node, locale: 'en')
379
+ allow_any_instance_of(Node).to receive(:validate_root_locale_uniqueness?).and_return(false)
380
+ @text_page_node.copy(nil)
381
+ expect( Node.last.locale ).to eq 'en'
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ context "with nonexistent parent_id" do
388
+ it "raises ActiveRecord::RecordInvalid" do
389
+ expect { @text_page_node_3.copy(99991) }.to raise_error(ActiveRecord::RecordInvalid)
390
+ end
391
+ end
392
+
393
+ context "with same parent_id as node.id" do
394
+ it "raises ActiveRecord::RecordInvalid" do
395
+ expect{ @text_page_node_3.copy(@text_page_node_3.id) }.to raise_error(ActiveRecord::RecordInvalid)
396
+ end
397
+ end
398
+
399
+ context "when copying to child node" do
400
+ it "raises ActiveRecord::RecordInvalid" do
401
+ expect{ @text_page_node_3.copy(@text_page_node_4.id) }.to raise_error(ActiveRecord::RecordInvalid)
402
+ end
403
+ end
404
+ end
405
+
406
+ describe ".children_max_item_position" do
407
+ before do
408
+ @home_page_node = create(:home_page_node, item_position: 1, locale: "lv")
409
+ @home_page_node_2 = create(:home_page_node, item_position: 2, locale: "en")
410
+ @text_page_node_3 = create(:text_page_node, parent_id: @home_page_node_2.id, item_position: 1)
411
+ @text_page_node_4 = create(:text_page_node, parent_id: @home_page_node_2.id, item_position: 2)
412
+
413
+ # it is important to reload nodes, otherwise associations will return empty set
414
+ @home_page_node.reload
415
+ @home_page_node_2.reload
416
+ @text_page_node_3.reload
417
+ @text_page_node_4.reload
418
+ end
419
+
420
+ context "when passing nil" do
421
+ it "returns max item_position of root nodes" do
422
+ expect( Node.children_max_item_position(nil) ).to eq 2
423
+ end
424
+ end
425
+
426
+ context "when passing node with children" do
427
+ it "returns max item_position of node children" do
428
+ expect( Node.children_max_item_position(@home_page_node_2) ).to eq 2
429
+ end
430
+ end
431
+
432
+ context "when passing node without children" do
433
+ it "returns 0" do
434
+ expect( Node.children_max_item_position(@text_page_node_4) ).to eq 0
435
+ end
436
+ end
437
+ end
438
+
439
+ describe "#move" do
440
+ before do
441
+ @home_page_node = create(:home_page_node, locale: "lv")
442
+ @home_page_node_2 = create(:home_page_node, locale: "en")
443
+ @text_page_node_3 = create(:text_page_node, parent_id: @home_page_node_2.id)
444
+ @text_page_node_4 = create(:text_page_node, parent_id: @text_page_node_3.id)
445
+
446
+ # it is important to reload nodes, otherwise associations will return empty set
447
+ @home_page_node.reload
448
+ @home_page_node_2.reload
449
+ @text_page_node_3.reload
450
+ @text_page_node_4.reload
451
+ end
452
+
453
+ context "when one of children becomes invalid" do
454
+ before do
455
+ @text_page_node_4.name = nil
456
+ @text_page_node_4.save(validate: false)
457
+ end
458
+
459
+ it "raises ActiveRecord::RecordInvalid" do
460
+ expect { @text_page_node_3.move(@home_page_node.id) }.to raise_error ActiveRecord::RecordInvalid
461
+ end
462
+
463
+ it "raises error on node being moved, even tought descendant has error" do
464
+ begin
465
+ @text_page_node_3.move(@home_page_node.id)
466
+ rescue ActiveRecord::RecordInvalid => e
467
+ expect( e.record ).to eq @text_page_node_3
468
+ end
469
+
470
+ expect(@text_page_node_3.errors.messages).to eq(name: [], base: ["descendant invalid"])
471
+ end
472
+ end
473
+
474
+ context "when moving existing node to other nodes child's position" do
475
+ it "changes parent_id" do
476
+ expect{ @text_page_node_3.move(@home_page_node.id) }.to change{ Node.find(@text_page_node_3.id).parent_id }.from(@home_page_node_2.id).to(@home_page_node.id)
477
+ end
478
+ end
479
+
480
+ context "when moving to self child's position" do
481
+ it "raises ActiveRecord::RecordInvalid" do
482
+ expect{ @text_page_node_3.move(@text_page_node_3.id) }.to raise_error(ActiveRecord::RecordInvalid)
483
+ end
484
+ end
485
+
486
+ context "when passing nil as target node" do
487
+ it "updates parent_id" do
488
+ allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
489
+ @home_page_node.destroy
490
+ expect{ @text_page_node_3.move(nil) }.to change { Node.find(@text_page_node_3.id).parent_id }.to(nil)
491
+ end
492
+ end
493
+
494
+ context "when passing nonexistent target node's id" do
495
+ it "raises ActiveRecord::RecordInvalid" do
496
+ expect{ @text_page_node_3.move(998123) }.to raise_error(ActiveRecord::RecordInvalid)
497
+ end
498
+ end
499
+
500
+ end
501
+
502
+ describe "#maintain_name" do
503
+ let(:root) { create(:home_page_node) }
504
+ let(:node) { create(:text_page_node, parent_id: root.id, name: "Test node") }
505
+ let(:sibling) { create(:text_page_node, parent_id: root.id, name: "Test node(1)") }
506
+
507
+ context "when node don't have sibling/s with same name" do
508
+ it "does not changes node's name" do
509
+ new_node = Node.new(name: "another name", parent_id: root.id)
510
+ expect{ new_node.maintain_name }.to_not change{new_node.name}
511
+ end
512
+ end
513
+
514
+ context "when node have sibling/s with same name" do
515
+ it "changes node's name" do
516
+ new_node = Node.new(name: node.name, parent_id: root.id)
517
+ expect{ new_node.maintain_name }.to change{new_node.name}.from(node.name).to("#{node.name}(1)")
518
+ end
519
+
520
+ it "increments node's name number" do
521
+ sibling
522
+ new_node = Node.new(name: node.name, parent_id: root.id)
523
+ expect{ new_node.maintain_name }.to change{new_node.name}.from(node.name).to("#{node.name}(2)")
524
+ end
525
+ end
526
+ end
527
+
528
+ describe ".updated_at" do
529
+ it "returns last node update time" do
530
+ expect( Releaf::Settings ).to receive(:[]).with('releaf.content.nodes.updated_at').and_return('test')
531
+ expect( Node.updated_at ).to eq 'test'
532
+ end
533
+ end
534
+
535
+ describe ".updated" do
536
+ it "returns last node update time" do
537
+ allow(Time).to receive(:now).and_return("asd")
538
+ expect( Releaf::Settings ).to receive(:[]=).with('releaf.content.nodes.updated_at', "asd")
539
+ Node.updated
540
+ end
541
+ end
542
+
543
+ describe "#available?" do
544
+ let(:root) { create(:home_page_node, active: true) }
545
+ let(:node_ancestor) { create(:text_page_node, parent_id: root.id, active: true) }
546
+ let(:node) { create(:text_page_node, parent_id: node_ancestor.id, active: true) }
547
+
548
+ context "when object and all its ancestors are active" do
549
+ it "returns true" do
550
+ expect( node ).to be_available
551
+ end
552
+ end
553
+
554
+ context "when object itself is not active" do
555
+ it "returns false" do
556
+ node.update_attribute(:active, false)
557
+ expect( node ).to_not be_available
558
+ end
559
+ end
560
+
561
+ context "when any of object ancestors are not active" do
562
+ it "returns false" do
563
+ node_ancestor.update_attribute(:active, false)
564
+ expect( node ).to_not be_available
565
+ end
566
+ end
567
+ end
568
+
569
+ describe ".valid_node_content_classes" do
570
+ it "returns array of constantized .valid_node_content_class_names" do
571
+ expect( Node ).to receive(:valid_node_content_class_names).with(42).and_return(['TextPage', 'HomePagesController'])
572
+ expect( Node.valid_node_content_classes(42) ).to eq [TextPage, HomePagesController]
573
+ end
574
+ end
575
+
576
+ describe ".valid_node_content_class_names" do
577
+ it "returns class names for Node#content_type that can be used to create valid node" do
578
+ expect( ActsAsNode ).to receive(:classes).and_return(%w[BadNode GoodNode])
579
+
580
+ node_1 = double('BadNode')
581
+ allow(node_1).to receive(:valid?)
582
+ node_1_errors = double("Node 1 Errors object")
583
+ expect( node_1_errors ).to receive(:[]).with(:content_type).and_return(['some error'])
584
+ allow(node_1).to receive(:errors).and_return(node_1_errors)
585
+
586
+ node_2 = double('GoodNode')
587
+ allow(node_2).to receive(:valid?)
588
+ node_2_errors = double("Node 2 Errors object")
589
+ expect( node_2_errors ).to receive(:[]).with(:content_type).and_return(nil)
590
+ allow(node_2).to receive(:errors).and_return(node_2_errors)
591
+
592
+ expect( Node ).to receive(:new).with(hash_including(parent_id: 52, content_type: 'BadNode')).and_return(node_1)
593
+ expect( Node ).to receive(:new).with(hash_including(parent_id: 52, content_type: 'GoodNode')).and_return(node_2)
594
+
595
+ expect( Node.valid_node_content_class_names(52) ).to eq %w[GoodNode]
596
+ end
597
+
598
+ end
599
+
600
+ describe "#locale_selection_enabled?" do
601
+ it "returns false" do
602
+ expect( plain_subject.locale_selection_enabled? ).to be false
603
+ end
604
+ end
605
+
606
+ describe "#build_content" do
607
+ it "builds new content and assigns to #content" do
608
+ subject.content_type = 'TextPage'
609
+ params = {text_html: 'test'}
610
+ expect( TextPage ).to receive(:new).with(params).and_call_original
611
+ subject.build_content(params)
612
+ expect( subject.content ).to be_an_instance_of TextPage
613
+ expect( subject.content.text_html ).to eq 'test'
614
+ end
615
+ end
616
+
617
+ describe "#validate_root_locale_uniqueness?" do
618
+ before do
619
+ allow( subject ).to receive(:root?).and_return(true)
620
+ allow( subject ).to receive(:locale_selection_enabled?).and_return(true)
621
+ end
622
+
623
+ context "when #locale_selection_enabled? and #root? both are true" do
624
+ it "returns true" do
625
+ expect( subject.send(:validate_root_locale_uniqueness?) ).to be_truthy
626
+ end
627
+ end
628
+
629
+ context "when #locale_selection_enabled? is false" do
630
+ it "returns false" do
631
+ allow( subject ).to receive(:locale_selection_enabled?).and_return(false)
632
+ expect( subject.send(:validate_root_locale_uniqueness?) ).to be_falsy
633
+ end
634
+ end
635
+
636
+ context "when #root? is false" do
637
+ it "returns false" do
638
+ allow( subject ).to receive(:root?).and_return(false)
639
+ expect( subject.send(:validate_root_locale_uniqueness?) ).to be_falsy
640
+ end
641
+ end
642
+ end
643
+
644
+ describe "#validate_parent_node_is_not_self" do
645
+ let(:node1) { create(:node, locale: "lv") }
646
+
647
+ it "is called during validations" do
648
+ expect( subject ).to receive(:validate_parent_node_is_not_self)
649
+ subject.valid?
650
+ end
651
+
652
+ context "when #parent_id matches #id" do
653
+ it "adds error on #parent_id" do
654
+ node1.parent_id = node1.id
655
+ node1.send(:validate_parent_node_is_not_self)
656
+ expect( node1.errors[:parent_id] ).to include "can't be parent to itself"
657
+ end
658
+ end
659
+
660
+ context "when parent is nil" do
661
+ it "does nothing" do
662
+ node1.parent_id = nil
663
+ node1.send(:validate_parent_node_is_not_self)
664
+ expect( node1.errors[:parent_id] ).to be_blank
665
+ end
666
+ end
667
+
668
+ context "#id matches #parent_id" do
669
+ it "does nothing" do
670
+ node2 = create(:node, locale: "en")
671
+ node1.parent_id = node2.id
672
+ node1.send(:validate_parent_is_not_descendant)
673
+ expect( node1.errors[:parent_id] ).to be_blank
674
+ end
675
+ end
676
+
677
+
678
+ end
679
+
680
+ describe "#validate_parent_is_not_descendant" do
681
+ before with_tree: true do
682
+ @node1 = create(:home_page_node, locale: "en")
683
+ @node2 = create(:text_page_node, parent: @node1)
684
+ @node3 = create(:text_page_node, parent: @node2)
685
+ @node4 = create(:home_page_node, locale: "lv")
686
+
687
+ @node1.reload
688
+ @node2.reload
689
+ @node3.reload
690
+ end
691
+
692
+ it "is called during validations" do
693
+ expect( subject ).to receive(:validate_parent_is_not_descendant)
694
+ subject.valid?
695
+ end
696
+
697
+ context "when #parent_id matches #id of one of descadents", with_tree: true do
698
+ it "adds error on #parent_id" do
699
+ @node1.parent_id = @node3.id
700
+ @node1.send(:validate_parent_is_not_descendant)
701
+ expect( @node1.errors[:parent_id] ).to include "descendant can't be parent"
702
+ end
703
+ end
704
+
705
+ context "when parent is nil", with_tree: true do
706
+ it "does nothing" do
707
+ @node1.parent_id = nil
708
+ @node1.send(:validate_parent_is_not_descendant)
709
+ expect( @node1.errors[:parent_id] ).to be_blank
710
+ end
711
+ end
712
+
713
+ context "when there are no descadents with #id is #self#parent_id", with_tree: true do
714
+ it "does nothing" do
715
+ @node1.parent_id = @node4.id
716
+ @node1.send(:validate_parent_is_not_descendant)
717
+ expect( @node1.errors[:parent_id] ).to be_blank
718
+ end
719
+ end
720
+ end
721
+
722
+ describe "#add_error_and_raise" do
723
+ it "raises error" do
724
+ expect { subject.add_error_and_raise('test error') }.to raise_error(ActiveRecord::RecordInvalid)
725
+ end
726
+
727
+ it "adds record on base and raise ActiveRecord::RecordInvalid" do
728
+ begin
729
+ subject.add_error_and_raise('test error')
730
+ rescue ActiveRecord::RecordInvalid => e
731
+ expect( e.record.errors[:base] ).to include 'test error'
732
+ end
733
+ end
734
+ end
735
+
736
+ describe "#prevent_auto_update_settings_timestamp" do
737
+ it "sets #prevent_auto_update_settings_timestamp? to true within block" do
738
+ expect do
739
+ subject.prevent_auto_update_settings_timestamp do
740
+ expect( subject.send(:prevent_auto_update_settings_timestamp?) ).to be_truthy
741
+ end
742
+ end.to_not change { subject.send(:prevent_auto_update_settings_timestamp?) }.from(false)
743
+ end
744
+ end
745
+
746
+ describe "#update_settings_timestamp" do
747
+ it "calls .updated" do
748
+ expect( Node ).to receive(:updated).and_call_original
749
+ subject.send(:update_settings_timestamp)
750
+ end
751
+
752
+ context "when #prevent_auto_update_settings_timestamp? is false" do
753
+ it "is called after save" do
754
+ node = FactoryGirl.build(:node)
755
+ allow( node ).to receive(:prevent_auto_update_settings_timestamp?).and_return(false)
756
+ expect( node ).to receive(:update_settings_timestamp).and_call_original
757
+ node.save!
758
+ end
759
+ end
760
+
761
+ context "when #prevent_auto_update_settings_timestamp? is true" do
762
+ it "is not called after save" do
763
+ node = FactoryGirl.build(:node)
764
+ allow( node ).to receive(:prevent_auto_update_settings_timestamp?).and_return(true)
765
+ expect( node ).to_not receive(:update_settings_timestamp)
766
+ node.save!
767
+ end
768
+ end
769
+ end
770
+
771
+ describe "#path" do
772
+ it "returns relative path of node" do
773
+ node = described_class.new(slug: 'foo')
774
+ node2 = described_class.new(slug: 'bar', parent: node)
775
+ expect( node.path ).to eq '/foo'
776
+ expect( node2.path ).to eq '/foo/bar'
777
+ end
778
+ end
779
+ end