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.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/app/assets/javascripts/releaf/controllers/releaf/content/nodes.js +88 -0
- data/app/assets/stylesheets/releaf/controllers/releaf/content/nodes.scss +234 -0
- data/app/builders/releaf/content/builders/action_dialog.rb +60 -0
- data/app/builders/releaf/content/builders/dialog.rb +15 -0
- data/app/builders/releaf/content/builders/tree.rb +84 -0
- data/app/builders/releaf/content/content_type_dialog_builder.rb +74 -0
- data/app/builders/releaf/content/copy_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/go_to_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/move_dialog_builder.rb +9 -0
- data/app/builders/releaf/content/nodes/content_form_builder.rb +7 -0
- data/app/builders/releaf/content/nodes/form_builder.rb +108 -0
- data/app/builders/releaf/content/nodes/index_builder.rb +24 -0
- data/app/builders/releaf/content/nodes/toolbox_builder.rb +33 -0
- data/app/controllers/releaf/content/nodes_controller.rb +166 -0
- data/app/middleware/releaf/content/routes_reloader.rb +25 -0
- data/app/validators/releaf/content/node/parent_validator.rb +48 -0
- data/app/validators/releaf/content/node/root_validator.rb +43 -0
- data/app/validators/releaf/content/node/singleness_validator.rb +102 -0
- data/app/views/releaf/content/nodes/content_type_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/copy_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/go_to_dialog.ruby +1 -0
- data/app/views/releaf/content/nodes/move_dialog.ruby +1 -0
- data/lib/releaf-content.rb +6 -0
- data/lib/releaf/content/acts_as_node.rb +73 -0
- data/lib/releaf/content/acts_as_node/action_controller/acts/node.rb +17 -0
- data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +55 -0
- data/lib/releaf/content/builders_autoload.rb +18 -0
- data/lib/releaf/content/engine.rb +40 -0
- data/lib/releaf/content/node.rb +280 -0
- data/lib/releaf/content/node_mapper.rb +9 -0
- data/lib/releaf/content/route.rb +93 -0
- data/lib/releaf/content/router_proxy.rb +23 -0
- data/releaf-content.gemspec +20 -0
- data/spec/builders/content/nodes/content_form_builder_spec.rb +24 -0
- data/spec/builders/content/nodes/form_builder_spec.rb +218 -0
- data/spec/builders/content/nodes/toolbox_builder_spec.rb +108 -0
- data/spec/controllers/releaf/content/nodes_controller_spec.rb +21 -0
- data/spec/features/nodes_spec.rb +239 -0
- data/spec/lib/releaf/content/acts_as_node_spec.rb +118 -0
- data/spec/lib/releaf/content/node_spec.rb +779 -0
- data/spec/lib/releaf/content/route_spec.rb +85 -0
- data/spec/middleware/routes_reloader_spec.rb +48 -0
- data/spec/routing/node_mapper_spec.rb +142 -0
- data/spec/validators/content/node/parent_validator_spec.rb +56 -0
- data/spec/validators/content/node/root_validator_spec.rb +69 -0
- data/spec/validators/content/node/singleness_validator_spec.rb +145 -0
- 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
|