releaf-content 0.2.1 → 1.0.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -21
  3. data/app/assets/javascripts/{releaf/controllers → controllers}/releaf/content/nodes.js +0 -0
  4. data/app/assets/stylesheets/{releaf/controllers → controllers}/releaf/content/nodes.scss +0 -0
  5. data/app/builders/releaf/content/builders/action_dialog.rb +9 -1
  6. data/app/builders/releaf/content/builders/tree.rb +1 -1
  7. data/app/builders/releaf/content/content_type_dialog_builder.rb +2 -2
  8. data/app/builders/releaf/content/nodes/form_builder.rb +3 -3
  9. data/app/builders/releaf/content/nodes/index_builder.rb +1 -1
  10. data/app/builders/releaf/content/nodes/toolbox_builder.rb +0 -5
  11. data/app/controllers/releaf/content/nodes_controller.rb +109 -129
  12. data/app/middleware/releaf/content/routes_reloader.rb +12 -4
  13. data/app/services/releaf/content/node/copy.rb +90 -0
  14. data/app/services/releaf/content/node/move.rb +21 -0
  15. data/app/services/releaf/content/node/save_under_parent.rb +22 -0
  16. data/app/services/releaf/content/node/service.rb +17 -0
  17. data/app/validators/releaf/content/node/singleness_validator.rb +1 -24
  18. data/lib/releaf-content.rb +85 -6
  19. data/lib/releaf/content/acts_as_node.rb +0 -5
  20. data/lib/releaf/content/acts_as_node/active_record/acts/node.rb +2 -8
  21. data/lib/releaf/content/configuration.rb +61 -0
  22. data/lib/releaf/content/engine.rb +1 -34
  23. data/lib/releaf/content/node.rb +30 -101
  24. data/lib/releaf/content/node_mapper.rb +28 -2
  25. data/lib/releaf/content/route.rb +37 -25
  26. data/spec/builders/content/nodes/action_dialog_spec.rb +39 -0
  27. data/spec/builders/content/nodes/form_builder_spec.rb +47 -3
  28. data/spec/builders/content/nodes/toolbox_builder_spec.rb +1 -32
  29. data/spec/controllers/releaf/content/nodes_controller_spec.rb +42 -11
  30. data/spec/features/nodes_services_spec.rb +207 -0
  31. data/spec/features/nodes_spec.rb +328 -30
  32. data/spec/lib/releaf/content/acts_as_node_spec.rb +4 -32
  33. data/spec/lib/releaf/content/configuration_spec.rb +159 -0
  34. data/spec/lib/releaf/content/engine_spec.rb +149 -0
  35. data/spec/lib/releaf/content/node_spec.rb +66 -324
  36. data/spec/lib/releaf/content/route_spec.rb +223 -34
  37. data/spec/middleware/routes_reloader_spec.rb +62 -14
  38. data/spec/routing/node_mapper_spec.rb +223 -55
  39. data/spec/services/releaf/content/node/copy_spec.rb +115 -0
  40. data/spec/services/releaf/content/node/move_spec.rb +20 -0
  41. data/spec/services/releaf/content/node/save_under_parent_spec.rb +49 -0
  42. data/spec/services/releaf/content/node/service_spec.rb +19 -0
  43. metadata +38 -21
  44. data/app/builders/releaf/content/go_to_dialog_builder.rb +0 -9
  45. data/app/views/releaf/content/nodes/go_to_dialog.ruby +0 -1
  46. data/lib/releaf/content/builders_autoload.rb +0 -18
  47. data/releaf-content.gemspec +0 -20
@@ -1,9 +1,35 @@
1
1
  module Releaf::Content
2
2
  module NodeMapper
3
- def releaf_routes_for(node_class, controller: Releaf::Content::Route.node_class_default_controller(node_class), &block)
4
- Releaf::Content::Route.for(node_class, controller).each do |route|
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
@@ -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.node_class
6
- # TODO model should be configurable
7
- ::Node
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
- node_class.name.pluralize.underscore
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
- action_path = path_for(method_or_path, options)
26
- options[:to] = controller_and_action_for(method_or_path, options)
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
- node_id: node_id.to_s,
30
- locale: locale,
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
- # normalize as with locale
34
- if locale.present? && route_options[:as].present?
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
- [action_path, route_options]
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 class_name [Class] class name to load related nodes
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(content_type, default_controller)
47
- node_class.where(content_type: content_type).each.inject([]) do |routes, node|
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 = Releaf::BaseController.new
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: "/releaf/content/nodes", action: "generate_url", parent_id: 1, id: 2)
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>/</a>
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
- describe "#builder_scopes" do
5
- it "adds node builder scope as first scope before default builder scopes" do
6
- allow(subject).to receive(:application_scope).and_return("Admin")
7
- allow(subject).to receive(:node_builder_scope).and_return("xx")
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 "#node_builder_scope" do
13
- it "returns node builder scope within releaf mount location scope" do
14
- allow(subject).to receive(:application_scope).and_return("Admin")
15
- expect(subject.node_builder_scope).to eq("Admin::Nodes")
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
- allow(subject).to receive(:application_scope).and_return(nil)
18
- expect(subject.node_builder_scope).to eq("Nodes")
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