alchemy_cms 3.0.0.rc5 → 3.0.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -5
  3. data/README.md +32 -5
  4. data/alchemy_cms.gemspec +1 -1
  5. data/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +3 -3
  6. data/app/assets/javascripts/alchemy/alchemy.char_counter.js.coffee +19 -0
  7. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +5 -0
  8. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +3 -2
  9. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +2 -0
  10. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
  11. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -26
  12. data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +1 -1
  13. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +2 -0
  14. data/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee +12 -7
  15. data/app/assets/javascripts/alchemy/alchemy.js +1 -1
  16. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +33 -4
  17. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +1 -1
  18. data/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee +2 -13
  19. data/app/assets/javascripts/alchemy/{alchemy.tinymce.js.coffee.erb → alchemy.tinymce.js.coffee} +1 -25
  20. data/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +63 -105
  21. data/app/assets/javascripts/alchemy/alchemy.uploader.js.coffee +3 -3
  22. data/app/assets/stylesheets/alchemy/_extends.scss +2 -8
  23. data/app/assets/stylesheets/alchemy/_mixins.scss +4 -9
  24. data/app/assets/stylesheets/alchemy/base.scss +6 -6
  25. data/app/assets/stylesheets/alchemy/buttons.scss +56 -29
  26. data/app/assets/stylesheets/alchemy/elements.scss +66 -14
  27. data/app/assets/stylesheets/alchemy/form_fields.scss +39 -6
  28. data/app/assets/stylesheets/alchemy/forms.scss +32 -0
  29. data/app/assets/stylesheets/alchemy/frame.scss +44 -44
  30. data/app/assets/stylesheets/alchemy/icons.scss +2 -2
  31. data/app/assets/stylesheets/alchemy/jquery-ui.scss +2 -0
  32. data/app/assets/stylesheets/alchemy/notices.scss +6 -0
  33. data/app/assets/stylesheets/alchemy/selects.scss +10 -0
  34. data/app/assets/stylesheets/alchemy/sitemap.scss +8 -10
  35. data/app/assets/stylesheets/alchemy/toolbar.scss +40 -31
  36. data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +11 -22
  37. data/app/controllers/alchemy/admin/base_controller.rb +14 -1
  38. data/app/controllers/alchemy/admin/elements_controller.rb +4 -2
  39. data/app/controllers/alchemy/admin/layoutpages_controller.rb +5 -0
  40. data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +39 -0
  41. data/app/controllers/alchemy/admin/pages_controller.rb +0 -3
  42. data/app/controllers/alchemy/base_controller.rb +7 -4
  43. data/app/helpers/alchemy/admin/base_helper.rb +33 -3
  44. data/app/helpers/alchemy/pages_helper.rb +1 -1
  45. data/app/models/alchemy/element.rb +6 -4
  46. data/app/models/alchemy/legacy_page_url.rb +5 -1
  47. data/app/models/alchemy/message.rb +2 -2
  48. data/app/models/alchemy/page.rb +8 -9
  49. data/app/models/alchemy/page/{cells.rb → page_cells.rb} +1 -1
  50. data/app/models/alchemy/page/{elements.rb → page_elements.rb} +1 -1
  51. data/app/models/alchemy/page/{naming.rb → page_naming.rb} +33 -18
  52. data/app/models/alchemy/page/{natures.rb → page_natures.rb} +1 -1
  53. data/app/models/alchemy/page/{scopes.rb → page_scopes.rb} +1 -1
  54. data/app/models/alchemy/page/{users.rb → page_users.rb} +13 -1
  55. data/app/views/alchemy/admin/elements/_add_element_button.html.erb +18 -0
  56. data/app/views/alchemy/admin/elements/_add_picture.html.erb +1 -1
  57. data/app/views/alchemy/admin/elements/_element.html.erb +12 -11
  58. data/app/views/alchemy/admin/elements/_new_element_form.html.erb +3 -0
  59. data/app/views/alchemy/admin/elements/create.js.erb +3 -3
  60. data/app/views/alchemy/admin/elements/index.html.erb +19 -4
  61. data/app/views/alchemy/admin/elements/new.html.erb +3 -0
  62. data/app/views/alchemy/admin/elements/trash.js.erb +16 -12
  63. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +1 -1
  64. data/app/views/alchemy/admin/layoutpages/edit.html.erb +11 -0
  65. data/app/views/alchemy/admin/layoutpages/index.html.erb +4 -16
  66. data/app/views/alchemy/admin/legacy_page_urls/_form.html.erb +5 -0
  67. data/app/views/alchemy/admin/legacy_page_urls/_label.html.erb +1 -0
  68. data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +13 -0
  69. data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +20 -0
  70. data/app/views/alchemy/admin/legacy_page_urls/create.js.erb +10 -0
  71. data/app/views/alchemy/admin/legacy_page_urls/destroy.js.erb +6 -0
  72. data/app/views/alchemy/admin/legacy_page_urls/update.js.erb +2 -0
  73. data/app/views/alchemy/admin/pages/_form.html.erb +58 -0
  74. data/app/views/alchemy/admin/pages/_internal_link.html.erb +9 -4
  75. data/app/views/alchemy/admin/pages/_legacy_urls.html.erb +23 -0
  76. data/app/views/alchemy/admin/pages/_locked_page.html.erb +21 -0
  77. data/app/views/alchemy/admin/pages/_page.html.erb +2 -2
  78. data/app/views/alchemy/admin/pages/_page_status.html.erb +11 -9
  79. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +13 -0
  80. data/app/views/alchemy/admin/pages/configure.html.erb +16 -57
  81. data/app/views/alchemy/admin/pages/edit.html.erb +64 -66
  82. data/app/views/alchemy/admin/pages/index.html.erb +9 -19
  83. data/app/views/alchemy/admin/pages/update.js.erb +1 -1
  84. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +7 -12
  85. data/app/views/alchemy/admin/partials/_routes.html.erb +25 -0
  86. data/app/views/alchemy/admin/partials/_search_form.html.erb +10 -12
  87. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +53 -47
  88. data/app/views/alchemy/admin/pictures/index.html.erb +34 -29
  89. data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +1 -1
  90. data/app/views/alchemy/navigation/_link.html.erb +9 -9
  91. data/app/views/alchemy/pages/show.rss.builder +2 -2
  92. data/app/views/layouts/alchemy/admin.html.erb +20 -9
  93. data/bin/alchemy +1 -1
  94. data/config/alchemy/config.yml +1 -0
  95. data/config/locales/alchemy.de.yml +15 -1
  96. data/config/locales/alchemy.en.yml +29 -19
  97. data/config/locales/alchemy.nl.yml +11 -1
  98. data/config/routes.rb +2 -1
  99. data/lib/alchemy/errors.rb +2 -2
  100. data/lib/alchemy/permissions.rb +2 -0
  101. data/lib/alchemy/resource.rb +22 -9
  102. data/lib/alchemy/tinymce.rb +13 -7
  103. data/lib/alchemy/version.rb +1 -1
  104. data/spec/controllers/admin/base_controller_spec.rb +39 -0
  105. data/spec/controllers/admin/elements_controller_spec.rb +17 -14
  106. data/spec/controllers/admin/pages_controller_spec.rb +1 -2
  107. data/spec/controllers/pages_controller_spec.rb +7 -3
  108. data/spec/dummy/app/models/dummy_user.rb +12 -2
  109. data/spec/features/admin/dashboard_spec.rb +45 -0
  110. data/spec/features/admin/legacy_page_url_management_spec.rb +62 -0
  111. data/spec/features/admin/page_editing_feature_spec.rb +66 -6
  112. data/spec/features/page_feature_spec.rb +13 -0
  113. data/spec/helpers/admin/base_helper_spec.rb +36 -0
  114. data/spec/libraries/resource_spec.rb +168 -84
  115. data/spec/libraries/tinymce_spec.rb +10 -0
  116. data/spec/models/element_spec.rb +16 -0
  117. data/spec/models/legacy_page_url_spec.rb +21 -0
  118. data/spec/models/message_spec.rb +23 -7
  119. data/spec/models/page_spec.rb +89 -12
  120. data/vendor/assets/javascripts/tinymce/plugins/anchor/plugin.min.js +1 -0
  121. data/vendor/assets/javascripts/tinymce/plugins/hr/plugin.min.js +1 -0
  122. metadata +96 -75
  123. data/app/assets/javascripts/alchemy/alchemy.routes.js.erb +0 -38
  124. data/spec/models/resource_spec.rb +0 -159
@@ -96,25 +96,28 @@ module Alchemy
96
96
  describe 'insertion position' do
97
97
  before { element }
98
98
 
99
- it "should insert the element at bottom of list" do
100
- xhr :post, :create, {element: {name: 'news', page_id: alchemy_page.id}}
101
- alchemy_page.elements.count.should == 2
102
- alchemy_page.elements.last.name.should == 'news'
99
+ context 'with no insert_after passed' do
100
+ it "should insert the element at top of list" do
101
+ xhr :post, :create, {element: {name: 'news', page_id: alchemy_page.id}}
102
+ alchemy_page.elements.count.should == 2
103
+ alchemy_page.elements.first.name.should == 'news'
104
+ end
103
105
  end
104
106
 
105
- context "on a page with a setting for insert_elements_at of top" do
107
+ context "with insert_after passed in the params" do
108
+ let(:element_before) { create(:element, name: 'headline', page: alchemy_page) }
109
+
106
110
  before do
107
- PageLayout.stub(:get).and_return({
108
- 'name' => 'news',
109
- 'elements' => ['news'],
110
- 'insert_elements_at' => 'top'
111
- })
111
+ alchemy_page.elements.delete_all
112
+ element_before
112
113
  end
113
114
 
114
- it "should insert the element at top of list" do
115
- xhr :post, :create, {element: {name: 'news', page_id: alchemy_page.id}}
115
+ it "should insert the new element after that element" do
116
+ xhr :post, :create, {element: {name: 'news', page_id: alchemy_page.id}, insert_after: element_before.id}
116
117
  alchemy_page.elements.count.should == 2
117
- alchemy_page.elements.first.name.should == 'news'
118
+ alchemy_page.elements.reload
119
+ alchemy_page.elements.first.name.should == 'headline'
120
+ alchemy_page.elements.last.name.should == 'news'
118
121
  end
119
122
  end
120
123
  end
@@ -223,7 +226,7 @@ module Alchemy
223
226
  it "should create an element from clipboard" do
224
227
  xhr :post, :create, {paste_from_clipboard: element_in_clipboard.id, element: {page_id: alchemy_page.id}}
225
228
  response.status.should == 200
226
- response.body.should match(/Succesfully added new element/)
229
+ response.body.should match(/Successfully added new element/)
227
230
  end
228
231
 
229
232
  context "and with cut as action parameter" do
@@ -100,10 +100,9 @@ module Alchemy
100
100
  render_views
101
101
 
102
102
  context "with page having nested urlname" do
103
- let(:page) { mock_model(Page, {name: 'Foobar', slug: 'foobar', urlname: 'root/parent/foobar', redirects_to_external?: false, layoutpage?: false, taggable?: false}) }
103
+ let(:page) { create(:page, name: 'Foobar') }
104
104
 
105
105
  it "should always show the slug" do
106
- Page.stub(:find).and_return(page)
107
106
  xhr :get, :configure, {id: page.id}
108
107
  response.body.should match /value="foobar"/
109
108
  end
@@ -131,8 +131,9 @@ module Alchemy
131
131
  let(:page) { FactoryGirl.create(:public_page, name: 'New page name') }
132
132
  let(:second_page) { FactoryGirl.create(:public_page, name: 'Second Page') }
133
133
  let(:legacy_page) { FactoryGirl.create(:public_page, name: 'Legacy Url') }
134
- let(:legacy_url) { LegacyPageUrl.create(urlname: 'legacy-url', page: page) }
134
+ let!(:legacy_url) { LegacyPageUrl.create(urlname: 'legacy-url', page: page) }
135
135
  let(:legacy_url2) { LegacyPageUrl.create(urlname: 'legacy-url', page: second_page) }
136
+ let(:legacy_url3) { LegacyPageUrl.create(urlname: 'index.php?id=2', page: second_page) }
136
137
 
137
138
  it "should redirect permanently to page that belongs to legacy page url." do
138
139
  get :show, urlname: legacy_url.urlname
@@ -141,17 +142,20 @@ module Alchemy
141
142
  end
142
143
 
143
144
  it "should only redirect to legacy url if no page was found for urlname" do
144
- legacy_url
145
145
  get :show, urlname: legacy_page.urlname
146
146
  response.status.should == 200
147
147
  response.should_not redirect_to("/#{page.urlname}")
148
148
  end
149
149
 
150
150
  it "should redirect to last page that has that legacy url" do
151
- legacy_url
152
151
  get :show, urlname: legacy_url2.urlname
153
152
  response.should redirect_to("/#{second_page.urlname}")
154
153
  end
154
+
155
+ it "should redirect even if the url has get parameters" do
156
+ get :show, urlname: legacy_url3.urlname
157
+ response.should redirect_to("/#{second_page.urlname}")
158
+ end
155
159
  end
156
160
  end
157
161
 
@@ -1,7 +1,17 @@
1
- class DummyUser < ActiveRecord::Base
2
- attr_accessor :alchemy_roles
1
+ class DummyUser
2
+ include ActiveModel::Validations
3
+
4
+ attr_accessor :alchemy_roles, :language, :cache_key, :email, :password, :name, :id
3
5
 
4
6
  def self.logged_in
5
7
  []
6
8
  end
9
+
10
+ def self.stamper_class_name
11
+ :DummyUser
12
+ end
13
+
14
+ def update_attributes(attributes)
15
+ attributes.each { |key,value| send("#{key}=".to_sym, value) }
16
+ end
7
17
  end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dashboard feature' do
4
+ let(:a_page) { FactoryGirl.create(:public_page, visible: true) }
5
+
6
+ before do
7
+ @user = DummyUser.new
8
+ @user.update_attributes(alchemy_roles: %w(admin), name: "Joe User", id: 1)
9
+ authorize_as_admin(@user)
10
+ end
11
+
12
+ describe 'Locked pages summary' do
13
+ it "should initially show no pages are locked" do
14
+ visit admin_dashboard_path
15
+ locked_pages_widget = all('div[@class="widget"]').first
16
+ expect(locked_pages_widget).to have_content "Currently locked pages:"
17
+ expect(locked_pages_widget).to have_content "no pages"
18
+ end
19
+
20
+ context 'When locked by current user' do
21
+ it "should show locked by me" do
22
+ a_page.lock_to!(@user)
23
+ visit admin_dashboard_path
24
+ locked_pages_widget = all('div[@class="widget"]').first
25
+ expect(locked_pages_widget).to have_content "Currently locked pages:"
26
+ expect(locked_pages_widget).to have_content a_page.name
27
+ expect(locked_pages_widget).to have_content "Me"
28
+ end
29
+ end
30
+
31
+ context 'When locked by another user' do
32
+ it "should show locked by user's name" do
33
+ user = DummyUser.new
34
+ user.update_attributes(alchemy_roles: %w(admin), name: "Sue Smith", id: 2)
35
+ a_page.lock_to!(user)
36
+ DummyUser.stub(:find).and_return(user)
37
+ visit admin_dashboard_path
38
+ locked_pages_widget = all('div[@class="widget"]').first
39
+ expect(locked_pages_widget).to have_content "Currently locked pages:"
40
+ expect(locked_pages_widget).to have_content a_page.name
41
+ expect(locked_pages_widget).to have_content "Sue Smith"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'Legacy page url management', js: true do
5
+ before do
6
+ authorize_as_admin
7
+ end
8
+
9
+ let!(:a_page) { create(:page) }
10
+
11
+ def open_page_properties
12
+ visit admin_pages_path
13
+ within "#page_#{a_page.id}" do
14
+ click_link Alchemy::I18n.t(:edit_page_properties)
15
+ end
16
+ end
17
+
18
+ it "lets a user add a page link" do
19
+ open_page_properties
20
+ click_link 'Links'
21
+ fill_in 'legacy_page_url_urlname', with: 'new-urlname'
22
+ click_button 'Add'
23
+ within '#legacy_page_urls' do
24
+ expect(page).to have_content('new-urlname')
25
+ end
26
+ within '#legacy_urls_label' do
27
+ expect(page).to have_content('(1) Link')
28
+ end
29
+ end
30
+
31
+ context 'with wrong url format' do
32
+ it "displays error message" do
33
+ open_page_properties
34
+ click_link 'Links'
35
+ fill_in 'legacy_page_url_urlname', with: 'invalid url name'
36
+ click_button 'Add'
37
+ within '#new_legacy_page_url' do
38
+ expect(page).to have_content('URL path is invalid')
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'with legacy page url present' do
44
+ before do
45
+ a_page.legacy_urls.create!(urlname: 'a-page-link')
46
+ open_page_properties
47
+ click_link '(1) Link'
48
+ end
49
+
50
+ it "lets a user remove a page link" do
51
+ click_link 'Remove'
52
+ click_button 'Yes'
53
+ within '#legacy_page_urls' do
54
+ expect(page).to_not have_content('a-page-link')
55
+ expect(page).to have_content(Alchemy::I18n.t('No page links for this page found'))
56
+ end
57
+ within '#legacy_urls_label' do
58
+ expect(page).to have_content('(0) Links')
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,15 +1,75 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'Page editing feature', js: true do
4
- let(:a_page) { FactoryGirl.create(:page) }
3
+ describe 'Page editing feature' do
4
+ let(:a_page) { FactoryGirl.create(:public_page, visible: true) }
5
5
 
6
6
  before { authorize_as_admin }
7
7
 
8
- context "in preview mode" do
8
+ context "in configure overlay" do
9
+
10
+ context "when editing a normal page" do
11
+ it "should show all relevant input fields" do
12
+ visit alchemy.configure_admin_page_path(a_page)
13
+ expect(page).to have_selector('input#page_urlname')
14
+ expect(page).to have_selector('input#page_title')
15
+ expect(page).to have_selector('input#page_robot_index')
16
+ expect(page).to have_selector('input#page_robot_follow')
17
+ end
18
+
19
+ context "with sitemaps show_flag config option set to true" do
20
+ before do
21
+ Alchemy::Config.stub(:get) { |arg| arg == :sitemap ? {'show_flag' => true} : Alchemy::Config.show[arg.to_s] }
22
+ end
23
+
24
+ it "should show sitemap checkbox" do
25
+ visit alchemy.configure_admin_page_path(a_page)
26
+ expect(page).to have_selector('input[type="checkbox"]#page_sitemap')
27
+ end
28
+ end
29
+
30
+ context "with sitemaps show_flag config option set to false" do
31
+ before do
32
+ Alchemy::Config.stub(:get) { |arg| arg == :sitemap ? {'show_flag' => false} : Alchemy::Config.show[arg.to_s] }
33
+ end
34
+
35
+ it "should show sitemap checkbox" do
36
+ visit alchemy.configure_admin_page_path(a_page)
37
+ expect(page).to_not have_selector('input[type="checkbox"]#page_sitemap')
38
+ end
39
+ end
40
+ end
41
+
42
+ context "when editing a global page" do
43
+ let(:layout_page) { FactoryGirl.create(:page, layoutpage: true) }
44
+
45
+ it "should not show the input fields for normal pages" do
46
+ visit alchemy.edit_admin_layoutpage_path(layout_page)
47
+ expect(page).to_not have_selector('input#page_urlname')
48
+ expect(page).to_not have_selector('input#page_title')
49
+ expect(page).to_not have_selector('input#page_robot_index')
50
+ expect(page).to_not have_selector('input#page_robot_follow')
51
+ end
52
+ end
53
+
54
+ context "when page is taggable" do
55
+ before { Alchemy::Page.any_instance.stub(:taggable?).and_return(true) }
56
+ it "should show the tag_list input field" do
57
+ visit alchemy.configure_admin_page_path(a_page)
58
+ expect(page).to have_selector('input#page_tag_list')
59
+ end
60
+ end
61
+ end
62
+
63
+ context "in preview frame" do
9
64
  it "the menubar does not render on the page" do
10
- visit alchemy.edit_admin_page_path(a_page)
11
- within_frame('alchemy_preview_window') do
12
- a_page.should_not have_selector('#alchemy_menubar')
65
+ visit alchemy.admin_page_path(a_page)
66
+ page.should_not have_selector('#alchemy_menubar')
67
+ end
68
+
69
+ it "navigation links are not clickable" do
70
+ visit alchemy.admin_page_path(a_page)
71
+ within('#navigation') do
72
+ page.should have_selector('a[href="javascript: void(0)"]')
13
73
  end
14
74
  end
15
75
  end
@@ -221,5 +221,18 @@ module Alchemy
221
221
  end
222
222
  end
223
223
 
224
+ describe 'navigation rendering' do
225
+ context 'with page having an external url without protocol' do
226
+ let!(:external_page) { create(:page, urlname: 'google.com', page_layout: 'external', visible: true) }
227
+
228
+ it "adds an prefix to url" do
229
+ visit "/#{public_page_1.urlname}"
230
+ within '#navigation' do
231
+ expect(page.body).to match('http://google.com')
232
+ end
233
+ end
234
+ end
235
+ end
236
+
224
237
  end
225
238
  end
@@ -228,5 +228,41 @@ module Alchemy
228
228
  end
229
229
  end
230
230
 
231
+ describe '#current_alchemy_user_name' do
232
+ subject { helper.current_alchemy_user_name }
233
+
234
+ before { helper.stub(current_alchemy_user: user) }
235
+
236
+ context 'with a user having a `alchemy_display_name` method' do
237
+ let(:user) { double('User', alchemy_display_name: 'Peter Schroeder') }
238
+
239
+ it "Returns a span showing the name of the currently logged in user." do
240
+ should have_content("#{Alchemy::I18n.t('Logged in as')} Peter Schroeder")
241
+ should have_selector("span.current-user-name")
242
+ end
243
+ end
244
+
245
+ context 'with a user not having a `alchemy_display_name` method' do
246
+ let(:user) { double('User', name: 'Peter Schroeder') }
247
+
248
+ it { should be_nil }
249
+ end
250
+ end
251
+
252
+ describe '#link_url_regexp' do
253
+ subject { helper.link_url_regexp }
254
+
255
+ it "returns the regular expression for external link urls" do
256
+ expect(subject).to be_a(Regexp)
257
+ end
258
+
259
+ context 'if the expression from config is nil' do
260
+ before { Alchemy::Config.stub(get: {link_url: nil}) }
261
+
262
+ it "returns the default expression" do
263
+ expect(subject).to_not be_nil
264
+ end
265
+ end
266
+ end
231
267
  end
232
268
  end
@@ -1,5 +1,4 @@
1
- require File.dirname(__FILE__) + '/../../lib/alchemy/resource'
2
- require File.dirname(__FILE__) + '/../../lib/alchemy/errors'
1
+ require 'spec_helper'
3
2
 
4
3
  class Party
5
4
  end
@@ -23,47 +22,54 @@ module PartyEngine
23
22
  end
24
23
  end
25
24
 
26
- # Alchemy's standard module definition. Only engine_name is relevant here
27
- def module_definition
28
- {
29
- "name" => "party_list",
30
- "engine_name" => "party_engine",
31
- "navigation" => {
32
- "name" => "modules.party_list",
33
- "controller" => "/admin/parties",
34
- "action" => "index",
35
- "image" => "/assets/party_list_module.png"
36
- }
37
- }
38
- end
39
-
40
25
  module Alchemy
41
26
  describe Resource do
27
+ # Alchemy's standard module definition. Only engine_name is relevant here
28
+ let(:module_definition) do
29
+ {
30
+ "name" => "party_list",
31
+ "engine_name" => "party_engine",
32
+ "navigation" => {
33
+ "name" => "modules.party_list",
34
+ "controller" => "/admin/parties",
35
+ "action" => "index",
36
+ "image" => "/assets/party_list_module.png"
37
+ }
38
+ }
39
+ end
40
+
41
+ let(:columns) do
42
+ [
43
+ double(:column, {name: 'name', type: :string, array: false}),
44
+ double(:column, {name: 'hidden_value', type: :string, array: false}),
45
+ double(:column, {name: 'description', type: :string, array: false}),
46
+ double(:column, {name: 'id', type: :integer, array: false}),
47
+ double(:column, {name: 'starts_at', type: :datetime, array: false}),
48
+ double(:column, {name: 'location_id', type: :integer, array: false}),
49
+ double(:column, {name: 'organizer_id', type: :integer, array: false}),
50
+ ]
51
+ end
52
+
53
+ let(:resource) { Resource.new("admin/parties", module_definition) }
42
54
 
43
55
  before :each do
44
56
  # stubbing an ActiveRecord::ModelSchema...
45
- columns = [
46
- double(:column, {:name => 'name', :type => :string}),
47
- double(:column, {:name => 'hidden_value', :type => :string}),
48
- double(:column, {:name => 'description', :type => :string}),
49
- double(:column, {:name => 'id', :type => :integer}),
50
- double(:column, {:name => 'starts_at', :type => :datetime}),
51
- double(:column, {:name => 'location_id', :type => :integer}),
52
- double(:column, {:name => 'organizer_id', :type => :integer}),
53
- ]
54
57
  Party.stub(:columns).and_return columns
55
58
  end
56
59
 
57
60
  describe "#initialize" do
61
+ it "sets the standard database attributes (rails defaults) to be skipped" do
62
+ resource = Resource.new("admin/parties")
63
+ resource.skipped_attributes.should == %w(id updated_at created_at creator_id updater_id)
64
+ end
58
65
 
59
- it "should set an instance variable that holds the controller path" do
66
+ it "sets an instance variable that holds the controller path" do
60
67
  resource = Resource.new("admin/parties")
61
68
  resource.instance_variable_get(:@controller_path).should == "admin/parties"
62
69
  end
63
70
 
64
71
  context "when initialized with a module definition" do
65
72
  it "sets an instance variable that holds the module definition" do
66
- resource = Resource.new("admin/parties", module_definition)
67
73
  resource.instance_variable_get(:@module_definition).should == module_definition
68
74
  end
69
75
  end
@@ -90,17 +96,20 @@ module Alchemy
90
96
  {location: {attr_method: 'name', type: 'string'}}
91
97
  end
92
98
  end
93
- Party.stub(:respond_to?).and_return { |arg|
94
- case arg
95
- when :reflect_on_all_associations
96
- then false
97
- when :alchemy_resource_relations
98
- then true
99
- end
100
- }
101
99
  end
102
100
 
103
101
  context ", but not an ActiveRecord association" do
102
+ before do
103
+ Party.stub(:respond_to?).and_return do |arg|
104
+ case arg
105
+ when :reflect_on_all_associations
106
+ then false
107
+ when :alchemy_resource_relations
108
+ then true
109
+ end
110
+ end
111
+ end
112
+
104
113
  it "should raise error." do
105
114
  expect { Resource.new("admin/parties") }.to raise_error(MissingActiveRecordAssociation)
106
115
  end
@@ -114,46 +123,38 @@ module Alchemy
114
123
  end
115
124
  end
116
125
  end
117
-
118
126
  end
119
127
 
120
128
  describe "#resource_array" do
121
-
122
129
  it "splits the controller_path and returns it as array." do
123
130
  resource = Resource.new("namespace1/namespace2/parties")
124
- resource.resource_array.should eql(%W[namespace1 namespace2 parties])
131
+ resource.resource_array.should eql(%w(namespace1 namespace2 parties))
125
132
  end
126
133
 
127
134
  it "deletes 'admin' if found hence our model isn't in the admin-namespace by convention" do
128
- resource = Resource.new("admin/parties")
129
- resource.resource_array.should eql(%W[parties])
135
+ resource.resource_array.should eql(%w(parties))
130
136
  end
131
-
132
137
  end
133
138
 
134
139
  describe "#model" do
135
140
  it "returns the @model instance variable" do
136
- resource = Resource.new("admin/parties")
137
141
  resource.model.should == resource.instance_variable_get(:@model)
138
142
  end
139
143
  end
140
144
 
141
145
  describe "#resources_name" do
142
146
  it "returns plural name (like parties for model Party)" do
143
- resource = Resource.new("admin/parties")
144
147
  resource.resources_name.should == "parties"
145
148
  end
146
149
  end
147
150
 
148
151
  describe "#resource_name" do
149
152
  it "returns the resources name as singular" do
150
- resource = Resource.new("admin/parties")
151
153
  resource.resource_name.should == "party"
152
154
  end
153
155
  end
154
156
 
155
157
  describe "#namespaced_resource_name" do
156
-
157
158
  it "returns resource_name with namespace (namespace_party for Namespace::Party), i.e. for use in forms" do
158
159
  namespaced_resource = Resource.new("admin/namespace/parties")
159
160
  namespaced_resource.namespaced_resource_name.should == 'namespace_party'
@@ -168,7 +169,6 @@ module Alchemy
168
169
  namespaced_resource = Resource.new("admin/party_engine/namespace/parties", module_definition)
169
170
  namespaced_resource.namespaced_resource_name.should == 'namespace_party'
170
171
  end
171
-
172
172
  end
173
173
 
174
174
  describe "#engine_name" do
@@ -179,16 +179,13 @@ module Alchemy
179
179
  end
180
180
 
181
181
  describe "#namespace_for_scope" do
182
- it "returns a scope for use in url_for-based path-helpers" do
183
- resource = Resource.new("admin/parties")
184
- resource.namespace_for_scope.should == %W[admin]
182
+ it "returns a scope for use in url_for based path helpers" do
183
+ resource.namespace_for_scope.should == %w(admin)
185
184
  end
186
185
  end
187
186
 
188
187
  describe "#attributes" do
189
- let(:resource) { Resource.new("admin/parties") }
190
-
191
- it "parses and returns the resource-model's attributes from ActiveRecord::ModelSchema" do
188
+ it "parses and returns the resource model's attributes from ActiveRecord::ModelSchema" do
192
189
  resource.attributes.should == [
193
190
  {:name => "name", :type => :string},
194
191
  {:name => "hidden_value", :type => :string},
@@ -199,59 +196,146 @@ module Alchemy
199
196
  ]
200
197
  end
201
198
 
202
- it "skips attributes returned by skip_attributes" do
203
- # attr_accessor, hence skip_attributes= works
204
- resource.skip_attributes = %W[hidden_value]
199
+ it "skips attributes returned by skipped_alchemy_resource_attributes" do
200
+ # attr_accessor, hence skipped_alchemy_resource_attributes= works
201
+ resource.skipped_attributes = %w(hidden_value)
205
202
  resource.attributes.should include({:name => "id", :type => :integer})
206
203
  resource.attributes.should_not include({:name => "hidden_value", :type => :string})
207
204
  end
205
+
206
+ context "when resource_relations are not defined" do
207
+ it "includes the attribute" do
208
+ resource.attributes.detect { |a| a[:name] == "location_id" }.should == {:name => "location_id", :type => :integer}
209
+ end
210
+ end
208
211
  end
209
212
 
213
+ context "when `skipped_alchemy_resource_attributes` is defined as class method in the model" do
214
+ before do
215
+ Party.class_eval do
216
+ def self.skipped_alchemy_resource_attributes
217
+ %w(hidden_name)
218
+ end
219
+ end
220
+ end
210
221
 
211
- describe "#skip_attributes" do
212
- let(:resource) { Resource.new("admin/parties") }
222
+ describe '#attributes' do
223
+ it "does not return the attributes returned by that method" do
224
+ resource.attributes.detect { |a| a[:name] == 'hidden_name' }.should be_nil
225
+ resource.attributes.detect { |a| a[:name] == 'name' }.should_not be_nil
226
+ end
227
+ end
213
228
 
214
- it "returns a set of default attributes (Rails' default database attributes)" do
215
- # read from Resource::DEFAULT_SKIPPED_ATTRIBUTES
216
- default_skipped_attributes = %W[id updated_at created_at creator_id updater_id]
217
- resource.skip_attributes = default_skipped_attributes
229
+ describe '#skipped_attributes' do
230
+ it "returns the result of Model.skipped_alchemy_resource_attributes" do
231
+ custom_skipped_attributes = %w(hidden_name)
232
+ resource.skipped_attributes = custom_skipped_attributes
233
+ end
218
234
  end
219
235
 
220
- context "when skip_attributes is defined as class-method in the model" do
221
- before do
222
- Party.class_eval do
223
- def self.skip_attributes
224
- %W[hidden_name]
225
- end
236
+ after do
237
+ Party.class_eval do
238
+ class << self
239
+ undef skipped_alchemy_resource_attributes
226
240
  end
227
241
  end
228
- after do
229
- Party.class_eval do
230
- class << self
231
- undef skip_attributes
232
- end
242
+ end
243
+ end
244
+
245
+ describe "#searchable_attributes" do
246
+ subject { resource.searchable_attributes }
247
+
248
+ before { resource.skipped_attributes = [] }
249
+
250
+ it "returns all attributes of type string" do
251
+ should == [
252
+ {:name => "name", :type => :string},
253
+ {:name => "hidden_value", :type => :string},
254
+ {:name => "description", :type => :string}
255
+ ]
256
+ end
257
+
258
+ context "with an array attribute" do
259
+ let(:columns) do
260
+ [
261
+ double(:column, {name: 'name', type: :string, array: false}),
262
+ double(:column, {name: 'languages', type: :string, array: true})
263
+ ]
264
+ end
265
+
266
+ it "does not include this column" do
267
+ should == [{name: "name", type: :string}]
268
+ end
269
+ end
270
+ end
271
+
272
+ context "when alchemy_resource_relations defined as class method in the model" do
273
+ let(:resource) { Resource.new("admin/events") }
274
+
275
+ before do
276
+ Event.class_eval do
277
+ def self.alchemy_resource_relations
278
+ {
279
+ :location => {:attr_method => "name", :attr_type => :string}
280
+ }
233
281
  end
234
282
  end
283
+ end
284
+
285
+ describe '#resource_relations' do
286
+ it "should contain model_association from ActiveRecord::Reflections" do
287
+ relation = resource.resource_relations[:location_id]
288
+ relation.keys.should include(:model_association)
289
+ relation[:model_association].class.should be(ActiveRecord::Reflection::AssociationReflection)
290
+ end
235
291
 
236
- it "returns the result of Model.skip_attributes" do
237
- custom_skipped_attributes = %W[hidden_name]
238
- resource.skip_attributes = custom_skipped_attributes
292
+ it "adds '_id' to relation key" do
293
+ resource.resource_relations[:location_id].should_not be_nil
239
294
  end
240
295
 
296
+ it "stores the relation name" do
297
+ relation = resource.resource_relations[:location_id]
298
+ relation.keys.should include(:name)
299
+ relation[:name].should == 'location'
300
+ end
241
301
  end
242
302
 
243
- describe "#searchable_attributes" do
244
- it "returns all attributes of type string" do
245
- resource = Resource.new("admin/parties")
246
- resource.skip_attributes = []
247
- resource.searchable_attributes.should == [
248
- {:name => "name", :type => :string},
249
- {:name => "hidden_value", :type => :string},
250
- {:name => "description", :type => :string}
251
- ]
303
+ describe '#model_associations' do
304
+ it "skip default alchemy model associations" do
305
+ resource.model_associations.collect(&:name).should_not include(*resource.class.const_get(:DEFAULT_SKIPPED_ASSOCIATIONS).map(&:to_sym))
306
+ end
307
+ end
308
+
309
+ describe '#attributes' do
310
+ it "contains the attribute of the related model" do
311
+ resource.attributes.detect { |a| a[:name] == 'location_id' }.keys.should include(:relation)
312
+ end
313
+
314
+ it "contains the related model's column type as type" do
315
+ resource.attributes.detect { |a| a[:name] == "location_id" }[:type].should == :string
316
+ end
317
+ end
318
+
319
+ after do
320
+ Event.class_eval do
321
+ class << self
322
+ undef :alchemy_resource_relations
323
+ end
252
324
  end
253
325
  end
326
+ end
327
+
328
+ describe "#engine_name" do
329
+ it "should return the engine name of the module" do
330
+ resource.engine_name.should == "party_engine"
331
+ end
332
+ end
254
333
 
334
+ describe "#in_engine?" do
335
+ it "should return true if the module is shipped within an engine" do
336
+ resource.in_engine?.should == true
337
+ end
255
338
  end
339
+
256
340
  end
257
341
  end