releaf-content 0.2.1 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +19 -21
- data/app/assets/javascripts/{releaf/controllers → controllers}/releaf/content/nodes.js +0 -0
- data/app/assets/stylesheets/{releaf/controllers → controllers}/releaf/content/nodes.scss +0 -0
- data/app/builders/releaf/content/builders/action_dialog.rb +9 -1
- data/app/builders/releaf/content/builders/tree.rb +1 -1
- data/app/builders/releaf/content/content_type_dialog_builder.rb +2 -2
- data/app/builders/releaf/content/nodes/form_builder.rb +3 -3
- data/app/builders/releaf/content/nodes/index_builder.rb +1 -1
- data/app/builders/releaf/content/nodes/toolbox_builder.rb +0 -5
- data/app/controllers/releaf/content/nodes_controller.rb +109 -129
- data/app/middleware/releaf/content/routes_reloader.rb +12 -4
- data/app/services/releaf/content/node/copy.rb +90 -0
- data/app/services/releaf/content/node/move.rb +21 -0
- data/app/services/releaf/content/node/save_under_parent.rb +22 -0
- data/app/services/releaf/content/node/service.rb +17 -0
- data/app/validators/releaf/content/node/singleness_validator.rb +1 -24
- data/lib/releaf-content.rb +85 -6
- data/lib/releaf/content/acts_as_node.rb +0 -5
- data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +2 -8
- data/lib/releaf/content/configuration.rb +61 -0
- data/lib/releaf/content/engine.rb +1 -34
- data/lib/releaf/content/node.rb +30 -101
- data/lib/releaf/content/node_mapper.rb +28 -2
- data/lib/releaf/content/route.rb +37 -25
- data/spec/builders/content/nodes/action_dialog_spec.rb +39 -0
- data/spec/builders/content/nodes/form_builder_spec.rb +47 -3
- data/spec/builders/content/nodes/toolbox_builder_spec.rb +1 -32
- data/spec/controllers/releaf/content/nodes_controller_spec.rb +42 -11
- data/spec/features/nodes_services_spec.rb +207 -0
- data/spec/features/nodes_spec.rb +328 -30
- data/spec/lib/releaf/content/acts_as_node_spec.rb +4 -32
- data/spec/lib/releaf/content/configuration_spec.rb +159 -0
- data/spec/lib/releaf/content/engine_spec.rb +149 -0
- data/spec/lib/releaf/content/node_spec.rb +66 -324
- data/spec/lib/releaf/content/route_spec.rb +223 -34
- data/spec/middleware/routes_reloader_spec.rb +62 -14
- data/spec/routing/node_mapper_spec.rb +223 -55
- data/spec/services/releaf/content/node/copy_spec.rb +115 -0
- data/spec/services/releaf/content/node/move_spec.rb +20 -0
- data/spec/services/releaf/content/node/save_under_parent_spec.rb +49 -0
- data/spec/services/releaf/content/node/service_spec.rb +19 -0
- metadata +38 -21
- data/app/builders/releaf/content/go_to_dialog_builder.rb +0 -9
- data/app/views/releaf/content/nodes/go_to_dialog.ruby +0 -1
- data/lib/releaf/content/builders_autoload.rb +0 -18
- data/releaf-content.gemspec +0 -20
@@ -1,9 +1,35 @@
|
|
1
1
|
module Releaf::Content
|
2
2
|
module NodeMapper
|
3
|
-
|
4
|
-
|
3
|
+
attr_accessor :default_node_class
|
4
|
+
|
5
|
+
def node_routes_for(
|
6
|
+
node_content_class,
|
7
|
+
controller: Releaf::Content::Route.default_controller(node_content_class),
|
8
|
+
node_class: default_node_class || Releaf::Content.default_model,
|
9
|
+
&block
|
10
|
+
)
|
11
|
+
Releaf::Content::Route.for(node_class, node_content_class, controller).each do |route|
|
5
12
|
Releaf::Content::RouterProxy.new(self, route).draw(&block)
|
6
13
|
end
|
7
14
|
end
|
15
|
+
|
16
|
+
def for_node_class( node_class )
|
17
|
+
previous_node_class = self.default_node_class
|
18
|
+
self.default_node_class = node_class
|
19
|
+
yield if block_given?
|
20
|
+
self.default_node_class = previous_node_class
|
21
|
+
end
|
22
|
+
|
23
|
+
# expects Releaf::Content.routing hash or a subset of it as an argument
|
24
|
+
def node_routing( routing )
|
25
|
+
routing.each_pair do | node_class_name, node_class_routing |
|
26
|
+
constraints node_class_routing[:constraints] do
|
27
|
+
for_node_class(node_class_name.constantize) do
|
28
|
+
yield
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
8
34
|
end
|
9
35
|
end
|
data/lib/releaf/content/route.rb
CHANGED
@@ -1,17 +1,12 @@
|
|
1
1
|
module Releaf::Content
|
2
2
|
class Route
|
3
|
-
attr_accessor :path, :node, :locale, :node_id, :default_controller
|
3
|
+
attr_accessor :path, :node, :locale, :node_class, :node_id, :default_controller, :site
|
4
4
|
|
5
|
-
def self.
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.node_class_default_controller(node_class)
|
11
|
-
if node_class <= ActionController::Base
|
12
|
-
node_class.name.underscore.sub(/_controller$/, '')
|
5
|
+
def self.default_controller(node_content_class)
|
6
|
+
if node_content_class <= ActionController::Base
|
7
|
+
node_content_class.name.underscore.sub(/_controller$/, '')
|
13
8
|
else
|
14
|
-
|
9
|
+
node_content_class.name.pluralize.underscore
|
15
10
|
end
|
16
11
|
end
|
17
12
|
|
@@ -22,29 +17,47 @@ module Releaf::Content
|
|
22
17
|
# @return [Hash] route options. Will return at least node "node_id" and "locale" keys.
|
23
18
|
def params(method_or_path, options = {})
|
24
19
|
method_or_path = method_or_path.to_s
|
25
|
-
|
26
|
-
|
20
|
+
[
|
21
|
+
path_for(method_or_path, options),
|
22
|
+
options_for(method_or_path, options)
|
23
|
+
]
|
24
|
+
end
|
27
25
|
|
26
|
+
def options_for( method_or_path, options )
|
28
27
|
route_options = options.merge({
|
29
|
-
|
30
|
-
|
28
|
+
to: controller_and_action_for(method_or_path, options),
|
29
|
+
node_class: node_class.name,
|
30
|
+
node_id: node_id.to_s,
|
31
|
+
locale: locale
|
31
32
|
})
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
route_options[:as] = "#{locale}_#{route_options[:as]}"
|
36
|
-
end
|
34
|
+
route_options[:site] = site if site.present?
|
35
|
+
route_options[:as] = name( route_options )
|
37
36
|
|
38
|
-
|
37
|
+
route_options
|
38
|
+
end
|
39
|
+
|
40
|
+
def name( route_options )
|
41
|
+
return nil unless route_options[:as].present?
|
42
|
+
|
43
|
+
# prepend :as with locale and site to prevent duplicate route names
|
44
|
+
name_parts = [ route_options[:as] ]
|
45
|
+
|
46
|
+
name_parts.unshift( route_options[:locale] ) if route_options[:locale].present?
|
47
|
+
name_parts.unshift( route_options[:site] ) if route_options[:site].present?
|
48
|
+
|
49
|
+
name_parts.join('_')
|
39
50
|
end
|
40
51
|
|
41
52
|
# Return routes for given class that implement ActsAsNode
|
42
53
|
#
|
43
|
-
# @param
|
54
|
+
# @param node_class [Class] class name to load related nodes
|
55
|
+
# @param node_content_class [Class] class name to load related nodes
|
44
56
|
# @param default_controller [String]
|
45
57
|
# @return [Array] array of Content::Route objects
|
46
|
-
def self.for(
|
47
|
-
node_class
|
58
|
+
def self.for(node_class, node_content_class, default_controller)
|
59
|
+
node_class = node_class.constantize if node_class.is_a? String
|
60
|
+
node_class.where(content_type: node_content_class).each.inject([]) do |routes, node|
|
48
61
|
routes << build_route_object(node, default_controller) if node.available?
|
49
62
|
routes
|
50
63
|
end
|
@@ -55,16 +68,15 @@ module Releaf::Content
|
|
55
68
|
# Build Content::Route from Node object
|
56
69
|
def self.build_route_object(node, default_controller)
|
57
70
|
route = new
|
71
|
+
route.node_class = node.class
|
58
72
|
route.node_id = node.id.to_s
|
59
73
|
route.path = node.path
|
60
74
|
route.locale = node.root.locale
|
61
75
|
route.default_controller = default_controller
|
62
|
-
|
76
|
+
route.site = Releaf::Content.routing[node.class.name][:site]
|
63
77
|
route
|
64
78
|
end
|
65
79
|
|
66
|
-
private
|
67
|
-
|
68
80
|
def path_for(method_or_path, options)
|
69
81
|
if method_or_path.include?('#')
|
70
82
|
path
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "rails_helper"
|
2
|
+
|
3
|
+
describe Releaf::Content::Builders::ActionDialog, type: :class do
|
4
|
+
class ConfirmDestroyDialogTestHelper < ActionView::Base
|
5
|
+
include Releaf::ApplicationHelper
|
6
|
+
end
|
7
|
+
|
8
|
+
class ActionDialogIncluder
|
9
|
+
include Releaf::Content::Builders::ActionDialog
|
10
|
+
def action; end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:template){ ConfirmDestroyDialogTestHelper.new }
|
14
|
+
let(:object){ Book.new }
|
15
|
+
let(:subject){ ActionDialogIncluder.new(template) }
|
16
|
+
|
17
|
+
describe "#confirm_button_text" do
|
18
|
+
it "returns translation for humanized builder action" do
|
19
|
+
allow(subject).to receive(:action).and_return(:move_to_the_right)
|
20
|
+
allow(subject).to receive(:t).with("Move to the right").and_return("to the left")
|
21
|
+
expect(subject.confirm_button_text).to eq("to the left")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#confirm_button_attributes" do
|
26
|
+
it "returns hash with confirm button attributes" do
|
27
|
+
expect(subject.confirm_button_attributes).to be_instance_of Hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#confirm_button" do
|
32
|
+
it "returns confirm button" do
|
33
|
+
allow(subject).to receive(:confirm_button_text).and_return("Yess")
|
34
|
+
allow(subject).to receive(:confirm_button_attributes).and_return(a: "b")
|
35
|
+
allow(subject).to receive(:button).with("Yess", "check", a: "b").and_return("x")
|
36
|
+
expect(subject.confirm_button).to eq("x")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -159,12 +159,56 @@ describe Releaf::Content::Nodes::FormBuilder, type: :class do
|
|
159
159
|
end
|
160
160
|
end
|
161
161
|
|
162
|
+
describe "#slug_base_url" do
|
163
|
+
before do
|
164
|
+
request = double(:request, protocol: "http:://", host_with_port: "somehost:8080")
|
165
|
+
allow(subject).to receive(:request).and_return(request)
|
166
|
+
allow(object).to receive(:parent).and_return(Node.new)
|
167
|
+
end
|
168
|
+
|
169
|
+
context "when trailing slash for path enabled" do
|
170
|
+
it "returns absolute url without extra slash added" do
|
171
|
+
allow(object).to receive(:trailing_slash_for_path?).and_return(true)
|
172
|
+
allow(object.parent).to receive(:path).and_return("/parent/path/")
|
173
|
+
expect(subject.slug_base_url).to eq("http:://somehost:8080/parent/path/")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "when trailing slash for path disabled" do
|
178
|
+
it "returns absolute url with extra slash added" do
|
179
|
+
allow(object).to receive(:trailing_slash_for_path?).and_return(false)
|
180
|
+
allow(object.parent).to receive(:path).and_return("/parent/path")
|
181
|
+
expect(subject.slug_base_url).to eq("http:://somehost:8080/parent/path/")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "#slug_link" do
|
187
|
+
before do
|
188
|
+
allow(subject).to receive(:slug_base_url).and_return("http://some.host/parent/path/")
|
189
|
+
end
|
190
|
+
|
191
|
+
context "when trailing slash for path enabled" do
|
192
|
+
it "returns absolute url without extra slash added" do
|
193
|
+
allow(object).to receive(:trailing_slash_for_path?).and_return(true)
|
194
|
+
expect(subject.slug_link).to eq('<a href="/a/b/">http://some.host/parent/path/<span>b</span>/</a>')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context "when trailing slash for path disabled" do
|
199
|
+
it "returns absolute url with extra slash added" do
|
200
|
+
allow(object).to receive(:trailing_slash_for_path?).and_return(false)
|
201
|
+
expect(subject.slug_link).to eq('<a href="/a/b">http://some.host/parent/path/<span>b</span></a>')
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
162
206
|
describe "#render_slug" do
|
163
207
|
it "renders customized field" do
|
164
|
-
controller =
|
208
|
+
controller = Admin::NodesController.new
|
165
209
|
allow(subject).to receive(:controller).and_return(controller)
|
166
210
|
allow(subject).to receive(:slug_base_url).and_return("http://localhost/parent")
|
167
|
-
allow(subject).to receive(:url_for).with(controller: "/
|
211
|
+
allow(subject).to receive(:url_for).with(controller: "admin/nodes", action: "generate_url", parent_id: 1, id: 2)
|
168
212
|
.and_return("http://localhost/slug-generation-url")
|
169
213
|
|
170
214
|
content = '
|
@@ -178,7 +222,7 @@ describe Releaf::Content::Nodes::FormBuilder, type: :class do
|
|
178
222
|
<i class="fa fa-keyboard-o"></i>
|
179
223
|
</button>
|
180
224
|
<div class="link">
|
181
|
-
<a href="/a/b">http://localhost/parent<span>b</span
|
225
|
+
<a href="/a/b">http://localhost/parent<span>b</span></a>
|
182
226
|
</div>
|
183
227
|
</div>
|
184
228
|
</div>
|
@@ -27,7 +27,6 @@ describe Releaf::Content::Nodes::ToolboxBuilder, type: :class do
|
|
27
27
|
before do
|
28
28
|
allow(subject).to receive(:params).and_return({})
|
29
29
|
allow(subject).to receive(:add_child_button).and_return( :add_child_item )
|
30
|
-
allow(subject).to receive(:go_to_button).and_return( :go_to_item )
|
31
30
|
allow(subject).to receive(:copy_button).and_return( :copy_item )
|
32
31
|
allow(subject).to receive(:move_button).and_return( :move_item )
|
33
32
|
end
|
@@ -41,26 +40,6 @@ describe Releaf::Content::Nodes::ToolboxBuilder, type: :class do
|
|
41
40
|
|
42
41
|
end
|
43
42
|
|
44
|
-
context "when applied to an existing record" do
|
45
|
-
|
46
|
-
before do
|
47
|
-
allow(node).to receive(:new_record?).and_return false
|
48
|
-
end
|
49
|
-
|
50
|
-
it "prepends add child, go to, copy and move items to the list returned by parent class" do
|
51
|
-
expect(subject.items).to eq([ :add_child_item, :go_to_item, :copy_item, :move_item, :super_item ])
|
52
|
-
end
|
53
|
-
|
54
|
-
context "when in index context" do
|
55
|
-
|
56
|
-
it "does not include go_to_item" do
|
57
|
-
allow(subject).to receive(:params).and_return({ context: "index" })
|
58
|
-
expect(subject.items).to eq([ :add_child_item, :copy_item, :move_item, :super_item ])
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
43
|
end
|
65
44
|
|
66
45
|
describe "item methods" do
|
@@ -74,16 +53,6 @@ describe Releaf::Content::Nodes::ToolboxBuilder, type: :class do
|
|
74
53
|
end
|
75
54
|
end
|
76
55
|
|
77
|
-
describe "#go_to_button" do
|
78
|
-
it "returns an ajaxbox link to go to dialog" do
|
79
|
-
allow(subject).to receive(:t).with('Go to').and_return('gotoxx')
|
80
|
-
allow(subject).to receive(:url_for).with(action: 'go_to_dialog').and_return('dialogurl')
|
81
|
-
html = '<a class="button ajaxbox" title="gotoxx" href="dialogurl">gotoxx</a>'
|
82
|
-
expect(subject.go_to_button).to eq(html)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
56
|
describe "#copy_button" do
|
88
57
|
it "returns an ajaxbox link to copy dialog" do
|
89
58
|
allow(subject).to receive(:t).with('Copy').and_return('copyxx')
|
@@ -105,4 +74,4 @@ describe Releaf::Content::Nodes::ToolboxBuilder, type: :class do
|
|
105
74
|
|
106
75
|
end
|
107
76
|
|
108
|
-
end
|
77
|
+
end
|
@@ -1,21 +1,52 @@
|
|
1
1
|
require 'rails_helper'
|
2
2
|
|
3
3
|
describe Releaf::Content::NodesController do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
expect(subject.builder_scopes).to eq(["xx", "Releaf::Content::Nodes", "Admin::Builders"])
|
4
|
+
|
5
|
+
describe "#features"do
|
6
|
+
it "excludes `create another` and `search` features" do
|
7
|
+
expect(subject.features).to_not include(:create_another, :search)
|
9
8
|
end
|
10
9
|
end
|
11
10
|
|
12
|
-
describe "#
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
describe "#ancestor_nodes" do
|
12
|
+
let(:node){ Node.new }
|
13
|
+
let(:ancestors){ Node.where(id: 1212) }
|
14
|
+
|
15
|
+
before do
|
16
|
+
allow(ancestors).to receive(:reorder).with(:depth).and_return(["depth_ordered_ancestors"])
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when new node" do
|
20
|
+
context "when node has parent" do
|
21
|
+
it "returns parent ancestors ordered by depth alongside parent ancestor" do
|
22
|
+
parent_node = Node.new
|
23
|
+
node.parent = parent_node
|
24
|
+
allow(parent_node).to receive(:ancestors).and_return(ancestors)
|
25
|
+
expect(subject.ancestor_nodes(node)).to eq(["depth_ordered_ancestors", parent_node])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when node has no parent" do
|
30
|
+
it "returns empty array" do
|
31
|
+
expect(subject.ancestor_nodes(node)).to eq([])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when persisted node" do
|
37
|
+
it "returns resource ancestors ordered by depth" do
|
38
|
+
allow(node).to receive(:persisted?).and_return(true)
|
39
|
+
allow(node).to receive(:ancestors).and_return(ancestors)
|
40
|
+
expect(subject.ancestor_nodes(node)).to eq(["depth_ordered_ancestors"])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
16
44
|
|
17
|
-
|
18
|
-
|
45
|
+
describe ".resource_class" do
|
46
|
+
it "looks up node class in releaf content resource configuration" do
|
47
|
+
config = { 'OtherSite::OtherNode' => { controller: 'Releaf::Content::NodesController' } }
|
48
|
+
allow( Releaf::Content ).to receive(:resources).and_return(config)
|
49
|
+
expect( described_class.resource_class ).to be OtherSite::OtherNode
|
19
50
|
end
|
20
51
|
end
|
21
52
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
describe "Nodes services (copy, move)" do
|
3
|
+
|
4
|
+
describe "Moving" do
|
5
|
+
before do
|
6
|
+
@home_page_node = create(:home_page_node, locale: "lv")
|
7
|
+
@home_page_node_2 = create(:home_page_node, locale: "en")
|
8
|
+
@text_page_node_3 = create(:text_page_node, parent_id: @home_page_node_2.id)
|
9
|
+
@text_page_node_4 = create(:text_page_node, parent_id: @text_page_node_3.id)
|
10
|
+
|
11
|
+
# it is important to reload nodes, otherwise associations will return empty set
|
12
|
+
@home_page_node.reload
|
13
|
+
@home_page_node_2.reload
|
14
|
+
@text_page_node_3.reload
|
15
|
+
@text_page_node_4.reload
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when one of children becomes invalid" do
|
19
|
+
before do
|
20
|
+
@text_page_node_4.name = nil
|
21
|
+
@text_page_node_4.save(validate: false)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "raises ActiveRecord::RecordInvalid" do
|
25
|
+
expect { @text_page_node_3.move(@home_page_node.id) }.to raise_error ActiveRecord::RecordInvalid
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises error on node being moved, even tought descendant has error" do
|
29
|
+
begin
|
30
|
+
@text_page_node_3.move(@home_page_node.id)
|
31
|
+
rescue ActiveRecord::RecordInvalid => e
|
32
|
+
expect( e.record ).to eq @text_page_node_3
|
33
|
+
end
|
34
|
+
|
35
|
+
expect(@text_page_node_3.errors.messages).to eq(name: [], base: ["descendant invalid"])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when moving existing node to other nodes child's position" do
|
40
|
+
it "changes parent_id" do
|
41
|
+
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)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when moving to self child's position" do
|
46
|
+
it "raises ActiveRecord::RecordInvalid" do
|
47
|
+
expect{ @text_page_node_3.move(@text_page_node_3.id) }.to raise_error(ActiveRecord::RecordInvalid)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when passing nil as target node" do
|
52
|
+
it "updates parent_id" do
|
53
|
+
allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
|
54
|
+
@home_page_node.destroy
|
55
|
+
expect{ @text_page_node_3.move(nil) }.to change { Node.find(@text_page_node_3.id).parent_id }.to(nil)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when passing nonexistent target node's id" do
|
60
|
+
it "raises ActiveRecord::RecordInvalid" do
|
61
|
+
expect{ @text_page_node_3.move(998123) }.to raise_error(ActiveRecord::RecordInvalid)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "Copying", create_nodes: true do
|
68
|
+
before create_nodes: true do
|
69
|
+
@home_page_node = create(:home_page_node, locale: "lv")
|
70
|
+
@home_page_node_2 = create(:home_page_node, locale: "en")
|
71
|
+
@text_page_node_3 = create(:text_page_node, parent_id: @home_page_node_2.id)
|
72
|
+
@text_page_node_4 = create(:text_page_node, parent_id: @text_page_node_3.id)
|
73
|
+
@text_page_node_5 = create(:text_page_node, parent_id: @text_page_node_4.id)
|
74
|
+
|
75
|
+
# it is important to reload nodes, otherwise associations will return empty set
|
76
|
+
@home_page_node.reload
|
77
|
+
@home_page_node_2.reload
|
78
|
+
@text_page_node_3.reload
|
79
|
+
@text_page_node_4.reload
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when one of children becomes invalid" do
|
83
|
+
before do
|
84
|
+
@text_page_node_4.name = nil
|
85
|
+
@text_page_node_4.save(validate: false)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "raises ActiveRecord::RecordInvalid" do
|
89
|
+
expect { @text_page_node_3.copy(@home_page_node.id) }.to raise_error ActiveRecord::RecordInvalid
|
90
|
+
end
|
91
|
+
|
92
|
+
it "raises error on node being copied" do
|
93
|
+
begin
|
94
|
+
@text_page_node_3.copy(@home_page_node.id)
|
95
|
+
rescue ActiveRecord::RecordInvalid => e
|
96
|
+
expect( e.record ).to eq @text_page_node_3
|
97
|
+
end
|
98
|
+
expect(@text_page_node_3.errors.messages).to eq(base: ["descendant invalid"])
|
99
|
+
end
|
100
|
+
|
101
|
+
it "doesn't create any new nodes" do
|
102
|
+
expect do
|
103
|
+
begin
|
104
|
+
@text_page_node_3.copy(@home_page_node.id)
|
105
|
+
rescue ActiveRecord::RecordInvalid
|
106
|
+
end
|
107
|
+
end.to_not change { Node.count }
|
108
|
+
end
|
109
|
+
|
110
|
+
it "doesn't update settings timestamp" do
|
111
|
+
expect( Node ).to_not receive(:updated)
|
112
|
+
begin
|
113
|
+
@text_page_node_3.copy(@home_page_node.id)
|
114
|
+
rescue ActiveRecord::RecordInvalid
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
context "with corect parent_id" do
|
122
|
+
it "creates node along with descendant nodes" do
|
123
|
+
expect{ @text_page_node_3.copy(@home_page_node.id) }.to change{ Node.count }.by( @text_page_node_3.descendants.size + 1 )
|
124
|
+
end
|
125
|
+
|
126
|
+
it "correctly copies attributes" do
|
127
|
+
allow( @text_page_node_3 ).to receive(:children).and_return([@text_page_node_4])
|
128
|
+
allow( @text_page_node_4 ).to receive(:children).and_return([@text_page_node_5])
|
129
|
+
|
130
|
+
@text_page_node_3.update_attribute(:active, false)
|
131
|
+
@text_page_node_4.update_attribute(:active, false)
|
132
|
+
|
133
|
+
allow( @text_page_node_3 ).to receive(:attributes_to_copy).and_return(["name", "parent_id", "content_type"])
|
134
|
+
|
135
|
+
@text_page_node_3.copy(@home_page_node.id)
|
136
|
+
|
137
|
+
@node_2_copy = @home_page_node.children.first
|
138
|
+
@node_3_copy = @node_2_copy.children.first
|
139
|
+
@node_4_copy = @node_3_copy.children.first
|
140
|
+
|
141
|
+
# new nodes by default are active, however we stubbed
|
142
|
+
# #attributes_to_copy of @test_node_2 to not return active attribute
|
143
|
+
# Also we updated @test_node_2#active to be false.
|
144
|
+
# However copy is active, because active attribute wasn't copied
|
145
|
+
expect( @node_2_copy ).to be_active
|
146
|
+
# for copy of @text_page_node_3 active attribute was copied however, as it
|
147
|
+
# should have been
|
148
|
+
expect( @node_3_copy ).to_not be_active
|
149
|
+
expect( @node_4_copy ).to be_active
|
150
|
+
|
151
|
+
expect( @node_2_copy.name ).to eq @text_page_node_3.name
|
152
|
+
expect( @node_3_copy.name ).to eq @text_page_node_4.name
|
153
|
+
expect( @node_4_copy.name ).to eq @text_page_node_5.name
|
154
|
+
end
|
155
|
+
|
156
|
+
it "updates settings timestamp only once" do
|
157
|
+
expect( Node ).to receive(:updated).once.and_call_original
|
158
|
+
@text_page_node_3.copy(@home_page_node.id)
|
159
|
+
end
|
160
|
+
|
161
|
+
context "when parent_id is nil" do
|
162
|
+
it "creates new node" do
|
163
|
+
allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
|
164
|
+
expect{ @text_page_node_3.copy(nil) }.to change{ Node.count }.by(3)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context "when copying root nodes", create_nodes: false do
|
169
|
+
context "when root locale uniqueness is validated" do
|
170
|
+
it "resets locale to nil" do
|
171
|
+
@text_page_node = create(:home_page_node, locale: 'en')
|
172
|
+
allow_any_instance_of(Node).to receive(:validate_root_locale_uniqueness?).and_return(true)
|
173
|
+
@text_page_node.copy(nil)
|
174
|
+
expect( Node.last.locale ).to eq nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "when root locale uniqueness is not validated" do
|
179
|
+
it "doesn't reset locale to nil" do
|
180
|
+
@text_page_node = create(:home_page_node, locale: 'en')
|
181
|
+
allow_any_instance_of(Node).to receive(:validate_root_locale_uniqueness?).and_return(false)
|
182
|
+
@text_page_node.copy(nil)
|
183
|
+
expect( Node.last.locale ).to eq 'en'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "with nonexistent parent_id" do
|
190
|
+
it "raises ActiveRecord::RecordInvalid" do
|
191
|
+
expect { @text_page_node_3.copy(99991) }.to raise_error(ActiveRecord::RecordInvalid)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "with same parent_id as node.id" do
|
196
|
+
it "raises ActiveRecord::RecordInvalid" do
|
197
|
+
expect{ @text_page_node_3.copy(@text_page_node_3.id) }.to raise_error(ActiveRecord::RecordInvalid)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "when copying to child node" do
|
202
|
+
it "raises ActiveRecord::RecordInvalid" do
|
203
|
+
expect{ @text_page_node_3.copy(@text_page_node_4.id) }.to raise_error(ActiveRecord::RecordInvalid)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|