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