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,26 +1,136 @@
1
1
  require 'rails_helper'
2
2
  describe "Nodes", js: true, with_tree: true, with_root: true do
3
+
3
4
  before do
4
5
  Rails.cache.clear
5
6
  # preload ActsAsNode classes
6
7
  Rails.application.eager_load!
8
+ end
9
+
10
+
11
+ before with_releaf_node_controller: true do
12
+ # stub node config and admin menu to use default releaf node controller
13
+
14
+ allow( Releaf.application.config).to receive(:content).and_return(Releaf::Content::Configuration.new(
15
+ resources: { 'Node' => { controller: 'Releaf::Content::NodesController' } }
16
+ ))
17
+
18
+ # preserve default config because it will be needed in after block
19
+ @default_menu_config = Releaf.application.config.menu.dup
20
+ stubbed_menu_config = @default_menu_config.map do |item|
21
+ if item.is_a?(Releaf::ControllerDefinition) && item.controller_name == 'admin/nodes'
22
+ Releaf::ControllerDefinition.new("releaf/content/nodes")
23
+ else
24
+ item.dup
25
+ end
26
+ end
27
+ allow( Releaf.application.config ).to receive(:menu).and_return( Releaf::Configuration.normalize_controllers(stubbed_menu_config) )
28
+ # reset cached values
29
+ Releaf.application.config.instance_variable_set(:@controllers, nil)
30
+ Releaf.application.config.instance_variable_set(:@available_controllers, nil)
31
+
32
+ Dummy::Application.reload_routes!
33
+ end
34
+
35
+
36
+ before with_multiple_node_classes: true do
37
+ @default_port_inclusion_state = Capybara.always_include_port
38
+ Capybara.always_include_port = true
39
+
40
+ # stub node config and admin menu to use two node classes with separate controllers
41
+
42
+ allow( Releaf.application.config).to receive(:content).and_return(Releaf::Content::Configuration.new(
43
+ resources: {
44
+ 'Node' => {
45
+ controller: 'Releaf::Content::NodesController',
46
+ routing: { site: "main_site", constraints: { host: Regexp.new( "^" + Regexp.escape("releaf.127.0.0.1.nip.io") + "$" ) } }
47
+ },
48
+ 'OtherSite::OtherNode' => {
49
+ controller: 'Admin::OtherSite::OtherNodesController',
50
+ routing: { site: "other_site", constraints: { host: Regexp.new( "^" + Regexp.escape("other.releaf.127.0.0.1.nip.io") + "$" ) } }
51
+ }
52
+ }
53
+ ))
54
+
55
+ # preserve default config because it will be needed in after block
56
+ @default_menu_config = Releaf.application.config.menu.dup
57
+ node_controller_item = Releaf::ControllerDefinition.new("releaf/content/nodes")
58
+ stubbed_menu_config = @default_menu_config.map do |item|
59
+ if item.is_a?(Releaf::ControllerDefinition) && item.controller_name == 'admin/nodes'
60
+ node_controller_item
61
+ else
62
+ item.dup
63
+ end
64
+ end
65
+ content_index = stubbed_menu_config.index( node_controller_item )
66
+ stubbed_menu_config.insert( content_index + 1, Releaf::ControllerDefinition.new("admin/other_site/other_nodes"))
67
+
68
+ allow( Releaf.application.config ).to receive(:menu).and_return( Releaf::Configuration.normalize_controllers(stubbed_menu_config) )
69
+ # reset cached values
70
+ Releaf.application.config.instance_variable_set(:@controllers, nil)
71
+ Releaf.application.config.instance_variable_set(:@available_controllers, nil)
72
+
73
+ Dummy::Application.reload_routes!
74
+ end
75
+
76
+
77
+ before do
7
78
  @user = auth_as_user
8
79
  end
9
80
 
81
+
10
82
  before with_root: true do
11
- @lv_root = create(:home_page_node, name: "lv", locale: "lv")
83
+ @lv_root = create(:home_page_node, name: "lv", locale: "lv", slug: "lv")
12
84
  end
13
85
 
86
+
14
87
  before with_tree: true do
15
88
  @how_to = create(:text_page_node, parent_id: @lv_root.id)
16
- @about_us = create(:text_page_node, parent_id: @lv_root.id, name: "about us")
89
+ @about_us = create(:text_page_node, parent_id: @lv_root.id, name: "about us", slug: "about-us")
17
90
  @history_node = create(:text_page_node, parent_id: @about_us.id, name: "history")
18
91
 
19
- @en_root = create(:home_page_node, name: "en", locale: "en")
92
+ @en_root = create(:home_page_node, name: "en", locale: "en", slug: "en")
93
+ end
94
+
95
+
96
+ before with_tree: true do |example|
97
+ if example.metadata[:with_releaf_node_controller].blank? && example.metadata[:with_multiple_node_classes].blank?
98
+ visit admin_nodes_path
99
+ end
100
+ end
101
+
102
+
103
+ before with_other_tree: true do
104
+ @other_lv_root = create(:other_home_page_node, name: "Other lv", locale: "lv", slug: "lv")
105
+ @other_about_us = create(:other_text_page_node, parent_id: @other_lv_root.id, name: "Other about us", slug: "about-us")
106
+ @other_history = create(:other_text_page_node, parent_id: @other_lv_root.id, name: "Other history", slug: "other-history")
107
+ end
108
+
20
109
 
21
- visit releaf_content_nodes_path
110
+ after with_multiple_node_classes: true do
111
+ Capybara.always_include_port = @default_port_inclusion_state
22
112
  end
23
113
 
114
+
115
+ after do |example|
116
+
117
+ if example.metadata[:with_releaf_node_controller].present? || example.metadata[:with_multiple_node_classes].present?
118
+ allow( Releaf.application.config ).to receive(:content).and_call_original
119
+ allow( Releaf.application.config ).to receive(:menu).and_return(@default_menu_config)
120
+
121
+ # reset cached values
122
+ Releaf.application.config.instance_variable_set(:@controllers, nil)
123
+ Releaf.application.config.instance_variable_set(:@available_controllers, nil)
124
+ Dummy::Application.reload_routes!
125
+ end
126
+
127
+ end
128
+
129
+ after(:all) do
130
+ Dummy::Application.reload_routes!
131
+ end
132
+
133
+
24
134
  describe "new node" do
25
135
 
26
136
  context "when creating node under root" do
@@ -35,7 +145,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
35
145
  select "en", from: "Locale"
36
146
  end
37
147
 
38
- expect(page).to have_breadcrumbs("Releaf/content/nodes", "en")
148
+ expect(page).to have_breadcrumbs("Admin/nodes", "en")
39
149
  end
40
150
  end
41
151
 
@@ -50,7 +160,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
50
160
  fill_in "resource_name", with: "Contacts"
51
161
  end
52
162
 
53
- expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "Contacts")
163
+ expect(page).to have_breadcrumbs("Admin/nodes", "lv", "Contacts")
54
164
  end
55
165
  end
56
166
  end
@@ -72,7 +182,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
72
182
  it "keeps opened node children visibility permanent" do
73
183
  find('li[data-id="' + @lv_root.id.to_s + '"] > .collapser-cell button').click
74
184
  wait_for_settings_update("content.tree.expanded.#{@lv_root.id}")
75
- visit releaf_content_nodes_path
185
+ visit admin_nodes_path
76
186
 
77
187
  expect(page).to have_css('li[data-id="' + @lv_root.id.to_s + '"]:not(.collapsed)')
78
188
  end
@@ -81,29 +191,13 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
81
191
  find('li[data-id="' + @lv_root.id.to_s + '"] > .collapser-cell button').click
82
192
  find('li[data-id="' + @lv_root.id.to_s + '"] > .collapser-cell button').click
83
193
  wait_for_settings_update("content.tree.expanded.#{@lv_root.id}", false)
84
- visit releaf_content_nodes_path
194
+ visit admin_nodes_path
85
195
 
86
196
  expect(page).to have_css('li[data-id="' + @lv_root.id.to_s + '"].collapsed')
87
197
  end
88
198
  end
89
199
  end
90
200
 
91
- describe "go_to node" do
92
- before do
93
- visit edit_releaf_content_node_path @en_root
94
- end
95
-
96
- context "when going to node from toolbox list" do
97
- it "navigates to targeted node's edit view" do
98
- expect(page).to have_no_header(text: 'lv')
99
- open_toolbox_dialog "Go to"
100
-
101
- click_link "lv"
102
- expect(page).to have_header(text: 'lv')
103
- end
104
- end
105
- end
106
-
107
201
  describe "copy node to" do
108
202
  context "when copying node" do
109
203
  it "shows copied node in tree" do
@@ -134,7 +228,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
134
228
  click_button "Copy"
135
229
  end
136
230
 
137
- error_text = "Node with id #{@about_us.id} has error \"source or descendant node can't be parent of new node\""
231
+ error_text = "source or descendant node can't be parent of new node"
138
232
  expect(page).to have_css('.dialog .form-error-box', text: error_text)
139
233
  end
140
234
  end
@@ -170,7 +264,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
170
264
  click_button "Move"
171
265
  end
172
266
 
173
- error_text = "Node with id #{@about_us.id} has error \"can't be parent to itself\" on attribute \"parent_id\""
267
+ error_text = "can't be parent to itself"
174
268
  expect(page).to have_css('.dialog .form-error-box', text: error_text)
175
269
  end
176
270
  end
@@ -179,8 +273,8 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
179
273
 
180
274
  describe "node order", with_tree: false do
181
275
  def create_child parent, child_text, position=nil
276
+ visit admin_nodes_path
182
277
 
183
- visit releaf_content_nodes_path
184
278
  open_toolbox_dialog 'Add child', parent, ".view-index .collection li"
185
279
  within_dialog do
186
280
  click_link("Text page")
@@ -204,7 +298,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
204
298
  create_child @lv_root, 'd', 'After b'
205
299
  create_child @lv_root, 'e', 'First'
206
300
 
207
- visit releaf_content_nodes_path
301
+ visit admin_nodes_path
208
302
  find('li[data-id="' + @lv_root.id.to_s + '"] > .collapser-cell button').click
209
303
 
210
304
  within(".collection li[data-level='1'][data-id='#{@lv_root.id}'] ul") do
@@ -217,7 +311,7 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
217
311
  create_child @lv_root, 'b'
218
312
  create_child @lv_root, 'c'
219
313
 
220
- visit releaf_content_nodes_path
314
+ visit admin_nodes_path
221
315
  find('li[data-id="' + @lv_root.id.to_s + '"] > .collapser-cell button').click
222
316
 
223
317
  within(".collection li[data-level='1'][data-id='#{@lv_root.id}'] ul") do
@@ -229,11 +323,215 @@ describe "Nodes", js: true, with_tree: true, with_root: true do
229
323
  describe "creating node for placeholder model", with_tree: false, with_root: false, js: false do
230
324
  it "create record in association table" do
231
325
  allow_any_instance_of(Releaf::Content::Node::RootValidator).to receive(:validate)
232
- visit new_releaf_content_node_path(content_type: 'Bundle')
326
+ visit new_admin_node_path(content_type: 'Bundle')
233
327
  fill_in("resource_name", with: "placeholder model node")
234
328
  expect do
235
329
  click_button 'Save'
236
330
  end.to change { [Node.count, Bundle.count] }.from([0, 0]).to([1, 1])
237
331
  end
238
332
  end
333
+
334
+
335
+ feature "using default releaf content controller", with_releaf_node_controller: true do
336
+
337
+ # for this case do not re-test all node features from above
338
+ # just click around a bit to ensure that the controllers, builders and assets work
339
+ # and make sure the public page of the created node works
340
+
341
+ scenario "basic node operations" do
342
+ visit "/admin"
343
+
344
+ expect(page).to have_no_content 'Admin/nodes'
345
+ within "aside nav" do
346
+ click_link "Releaf/content/nodes"
347
+ end
348
+ expect(current_path).to eq releaf_content_nodes_path
349
+
350
+ find('li[data-id="' + @lv_root.id.to_s + '"] > .toolbox-cell button').click
351
+ click_link "Add child"
352
+ within '.ajaxbox-inner .dialog.content-type' do
353
+ click_link "Contacts controller"
354
+ end
355
+ create_resource do
356
+ fill_in "resource_name", with: "Contacts"
357
+ end
358
+
359
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "Contacts")
360
+
361
+ visit lv_contacts_page_path
362
+ expect(page).to have_content "Node class: Node"
363
+ expect(page).to have_content "Releaf github repository"
364
+ end
365
+ end
366
+
367
+ feature "breadcrumbs ordering by depth", with_releaf_node_controller: true do
368
+ # create some nodes and then rearrange them to get some with oldest under newest
369
+
370
+ scenario "create and reorder node depth" do
371
+ visit releaf_content_nodes_path
372
+ find('li[data-id="' + @lv_root.id.to_s + '"] > .toolbox-cell button').click
373
+ click_link "Add child"
374
+ within '.ajaxbox-inner .dialog.content-type' do
375
+ click_link "Text page"
376
+ end
377
+ create_resource do
378
+ fill_in "resource_name", with: "TextContent_1"
379
+ fill_in_richtext 'Text', with: "asdasd"
380
+ end
381
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "TextContent_1")
382
+
383
+ visit releaf_content_nodes_path
384
+ find('li[data-id="' + @lv_root.id.to_s + '"] > .toolbox-cell button').click
385
+ click_link "Add child"
386
+ within '.ajaxbox-inner .dialog.content-type' do
387
+ click_link "Text page"
388
+ end
389
+ create_resource do
390
+ fill_in "resource_name", with: "TextContent_2"
391
+ fill_in_richtext 'Text', with: "asdasd"
392
+ end
393
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "TextContent_2")
394
+
395
+ visit releaf_content_nodes_path
396
+ find('li[data-id="' + @lv_root.id.to_s + '"] > .toolbox-cell button').click
397
+ click_link "Add child"
398
+ within '.ajaxbox-inner .dialog.content-type' do
399
+ click_link "Text page"
400
+ end
401
+ create_resource do
402
+ fill_in "resource_name", with: "TextContent_3"
403
+ fill_in_richtext 'Text', with: "asdasd"
404
+ end
405
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "TextContent_3")
406
+
407
+
408
+ text_page_1_node_id = Node.find_by(name: "TextContent_1").id
409
+ text_page_2_node_id = Node.find_by(name: "TextContent_2").id
410
+ text_page_3_node_id = Node.find_by(name: "TextContent_3").id
411
+
412
+ visit edit_releaf_content_node_path(text_page_1_node_id)
413
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "TextContent_1")
414
+ open_toolbox_dialog "Move"
415
+ within ".dialog" do
416
+ find('li[data-id="' + @lv_root.id.to_s + '"] > .collapser-cell button').click
417
+ find("label[for=new_parent_id_#{text_page_2_node_id}]").click
418
+ click_button "Move"
419
+ end
420
+ expect(page).to have_notification("Move succeeded")
421
+
422
+ visit edit_releaf_content_node_path(text_page_2_node_id)
423
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "TextContent_2")
424
+ open_toolbox_dialog "Move"
425
+ within ".dialog" do
426
+ find("label[for=new_parent_id_#{text_page_3_node_id}]").click
427
+ click_button "Move"
428
+ end
429
+ expect(page).to have_notification("Move succeeded")
430
+
431
+ visit edit_releaf_content_node_path(text_page_1_node_id)
432
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "TextContent_3", "TextContent_2", "TextContent_1")
433
+ end
434
+ end
435
+
436
+ feature "using multiple independent node trees", with_multiple_node_classes: true, with_other_tree: true do
437
+
438
+ # here also do not re-test all node features
439
+ # but ensure that controllers, builders and assets work in both admin node controllers
440
+ # and that the per-site configuration and hostname restrictions defined in the node config work on public routes
441
+
442
+ scenario "multiple site node operations" do
443
+
444
+ visit "/admin"
445
+
446
+ # make sure that the admin/nodes controller is not available in menu
447
+ expect(page).to have_no_content 'Admin/nodes'
448
+
449
+ # test node creation in releaf/content/nodes controller
450
+ within "aside nav" do
451
+ click_link "Releaf/content/nodes"
452
+ end
453
+ expect(current_path).to eq releaf_content_nodes_path
454
+
455
+ find('li[data-id="' + @lv_root.id.to_s + '"] > .toolbox-cell button').click
456
+ click_link "Add child"
457
+ within '.ajaxbox-inner .dialog.content-type' do
458
+ click_link "Contacts controller"
459
+ end
460
+ create_resource do
461
+ fill_in "resource_name", with: "Main contacts"
462
+ fill_in "resource_slug", with: "contacts"
463
+ end
464
+ expect(page).to have_breadcrumbs("Releaf/content/nodes", "lv", "Main contacts")
465
+
466
+
467
+ # test node creation in the other site nodes controller
468
+ within "aside nav" do
469
+ click_link "Admin/other site/other nodes"
470
+ end
471
+ expect(current_path).to eq admin_other_site_other_nodes_path
472
+ find('li[data-id="' + @other_lv_root.id.to_s + '"] > .toolbox-cell button').click
473
+ click_link "Add child"
474
+ within '.ajaxbox-inner .dialog.content-type' do
475
+ click_link "Contacts controller"
476
+ end
477
+ create_resource do
478
+ fill_in "resource_name", with: "Other contacts"
479
+ fill_in "resource_slug", with: "contacts"
480
+ end
481
+ expect(page).to have_breadcrumbs("Admin/other site/other nodes", "lv", "Other contacts")
482
+
483
+
484
+ # test public websites for correct url helpers, node types, site settings and host name constraints
485
+
486
+ allow(Capybara).to receive(:app_host).and_return "http://releaf.127.0.0.1.nip.io"
487
+
488
+ visit main_site_lv_home_page_path
489
+ expect(page).to have_content "Site: main_site"
490
+ expect(page).to have_content "Node class: Node"
491
+ expect(page).to have_content "Node name: lv"
492
+
493
+ visit "/lv/about-us"
494
+ expect(URI.parse(current_url).host).to eq "releaf.127.0.0.1.nip.io"
495
+ expect(page).to have_content "Site: main_site"
496
+ expect(page).to have_content "Node class: Node"
497
+ expect(page).to have_content "Node name: about us"
498
+
499
+ visit main_site_lv_contacts_page_path
500
+ expect(page).to have_content "Site: main_site"
501
+ expect(page).to have_content "Node class: Node"
502
+ expect(page).to have_content "Node name: Main contacts"
503
+
504
+ # make sure the nodes from other site are not reachable via this hostname
505
+ visit "/lv/other-history"
506
+ expect( page ).to have_content "The page you were looking for doesn't exist."
507
+
508
+
509
+ allow(Capybara).to receive(:app_host).and_return "http://other.releaf.127.0.0.1.nip.io"
510
+ visit other_site_lv_home_page_path
511
+ expect(page).to have_content "Site: other_site"
512
+ expect(page).to have_content "Node class: OtherSite::OtherNode"
513
+ expect(page).to have_content "Node name: Other lv"
514
+
515
+ visit "/lv/about-us"
516
+ expect(URI.parse(current_url).host).to eq "other.releaf.127.0.0.1.nip.io"
517
+ expect(page).to have_content "Site: other_site"
518
+ expect(page).to have_content "Node class: OtherSite::OtherNode"
519
+ expect(page).to have_content "Node name: Other about us"
520
+
521
+ visit "/lv/other-history"
522
+ expect(page).to have_content "Site: other_site"
523
+ expect(page).to have_content "Node class: OtherSite::OtherNode"
524
+ expect(page).to have_content "Node name: Other history"
525
+
526
+
527
+ # other site contacts node should not have a route
528
+ # because the route is constrained to main site in dummy application's routes.rb
529
+ visit "/lv/contacts"
530
+ expect( page ).to have_content "The page you were looking for doesn't exist."
531
+
532
+ end
533
+
534
+ end
535
+
536
+
239
537
  end