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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -5
- data/README.md +32 -5
- data/alchemy_cms.gemspec +1 -1
- data/app/assets/javascripts/alchemy/alchemy.buttons.js.coffee +3 -3
- data/app/assets/javascripts/alchemy/alchemy.char_counter.js.coffee +19 -0
- data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +5 -0
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +3 -2
- data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +2 -0
- data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -26
- data/app/assets/javascripts/alchemy/alchemy.file_progress.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +2 -0
- data/app/assets/javascripts/alchemy/alchemy.i18n.js.coffee +12 -7
- data/app/assets/javascripts/alchemy/alchemy.js +1 -1
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +33 -4
- data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +1 -1
- data/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee +2 -13
- data/app/assets/javascripts/alchemy/{alchemy.tinymce.js.coffee.erb → alchemy.tinymce.js.coffee} +1 -25
- data/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +63 -105
- data/app/assets/javascripts/alchemy/alchemy.uploader.js.coffee +3 -3
- data/app/assets/stylesheets/alchemy/_extends.scss +2 -8
- data/app/assets/stylesheets/alchemy/_mixins.scss +4 -9
- data/app/assets/stylesheets/alchemy/base.scss +6 -6
- data/app/assets/stylesheets/alchemy/buttons.scss +56 -29
- data/app/assets/stylesheets/alchemy/elements.scss +66 -14
- data/app/assets/stylesheets/alchemy/form_fields.scss +39 -6
- data/app/assets/stylesheets/alchemy/forms.scss +32 -0
- data/app/assets/stylesheets/alchemy/frame.scss +44 -44
- data/app/assets/stylesheets/alchemy/icons.scss +2 -2
- data/app/assets/stylesheets/alchemy/jquery-ui.scss +2 -0
- data/app/assets/stylesheets/alchemy/notices.scss +6 -0
- data/app/assets/stylesheets/alchemy/selects.scss +10 -0
- data/app/assets/stylesheets/alchemy/sitemap.scss +8 -10
- data/app/assets/stylesheets/alchemy/toolbar.scss +40 -31
- data/app/assets/stylesheets/tinymce/skins/alchemy/skin.min.css.scss +11 -22
- data/app/controllers/alchemy/admin/base_controller.rb +14 -1
- data/app/controllers/alchemy/admin/elements_controller.rb +4 -2
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +5 -0
- data/app/controllers/alchemy/admin/legacy_page_urls_controller.rb +39 -0
- data/app/controllers/alchemy/admin/pages_controller.rb +0 -3
- data/app/controllers/alchemy/base_controller.rb +7 -4
- data/app/helpers/alchemy/admin/base_helper.rb +33 -3
- data/app/helpers/alchemy/pages_helper.rb +1 -1
- data/app/models/alchemy/element.rb +6 -4
- data/app/models/alchemy/legacy_page_url.rb +5 -1
- data/app/models/alchemy/message.rb +2 -2
- data/app/models/alchemy/page.rb +8 -9
- data/app/models/alchemy/page/{cells.rb → page_cells.rb} +1 -1
- data/app/models/alchemy/page/{elements.rb → page_elements.rb} +1 -1
- data/app/models/alchemy/page/{naming.rb → page_naming.rb} +33 -18
- data/app/models/alchemy/page/{natures.rb → page_natures.rb} +1 -1
- data/app/models/alchemy/page/{scopes.rb → page_scopes.rb} +1 -1
- data/app/models/alchemy/page/{users.rb → page_users.rb} +13 -1
- data/app/views/alchemy/admin/elements/_add_element_button.html.erb +18 -0
- data/app/views/alchemy/admin/elements/_add_picture.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_element.html.erb +12 -11
- data/app/views/alchemy/admin/elements/_new_element_form.html.erb +3 -0
- data/app/views/alchemy/admin/elements/create.js.erb +3 -3
- data/app/views/alchemy/admin/elements/index.html.erb +19 -4
- data/app/views/alchemy/admin/elements/new.html.erb +3 -0
- data/app/views/alchemy/admin/elements/trash.js.erb +16 -12
- data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +1 -1
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +11 -0
- data/app/views/alchemy/admin/layoutpages/index.html.erb +4 -16
- data/app/views/alchemy/admin/legacy_page_urls/_form.html.erb +5 -0
- data/app/views/alchemy/admin/legacy_page_urls/_label.html.erb +1 -0
- data/app/views/alchemy/admin/legacy_page_urls/_legacy_page_url.html.erb +13 -0
- data/app/views/alchemy/admin/legacy_page_urls/_new.html.erb +20 -0
- data/app/views/alchemy/admin/legacy_page_urls/create.js.erb +10 -0
- data/app/views/alchemy/admin/legacy_page_urls/destroy.js.erb +6 -0
- data/app/views/alchemy/admin/legacy_page_urls/update.js.erb +2 -0
- data/app/views/alchemy/admin/pages/_form.html.erb +58 -0
- data/app/views/alchemy/admin/pages/_internal_link.html.erb +9 -4
- data/app/views/alchemy/admin/pages/_legacy_urls.html.erb +23 -0
- data/app/views/alchemy/admin/pages/_locked_page.html.erb +21 -0
- data/app/views/alchemy/admin/pages/_page.html.erb +2 -2
- data/app/views/alchemy/admin/pages/_page_status.html.erb +11 -9
- data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +13 -0
- data/app/views/alchemy/admin/pages/configure.html.erb +16 -57
- data/app/views/alchemy/admin/pages/edit.html.erb +64 -66
- data/app/views/alchemy/admin/pages/index.html.erb +9 -19
- data/app/views/alchemy/admin/pages/update.js.erb +1 -1
- data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +7 -12
- data/app/views/alchemy/admin/partials/_routes.html.erb +25 -0
- data/app/views/alchemy/admin/partials/_search_form.html.erb +10 -12
- data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +53 -47
- data/app/views/alchemy/admin/pictures/index.html.erb +34 -29
- data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +1 -1
- data/app/views/alchemy/navigation/_link.html.erb +9 -9
- data/app/views/alchemy/pages/show.rss.builder +2 -2
- data/app/views/layouts/alchemy/admin.html.erb +20 -9
- data/bin/alchemy +1 -1
- data/config/alchemy/config.yml +1 -0
- data/config/locales/alchemy.de.yml +15 -1
- data/config/locales/alchemy.en.yml +29 -19
- data/config/locales/alchemy.nl.yml +11 -1
- data/config/routes.rb +2 -1
- data/lib/alchemy/errors.rb +2 -2
- data/lib/alchemy/permissions.rb +2 -0
- data/lib/alchemy/resource.rb +22 -9
- data/lib/alchemy/tinymce.rb +13 -7
- data/lib/alchemy/version.rb +1 -1
- data/spec/controllers/admin/base_controller_spec.rb +39 -0
- data/spec/controllers/admin/elements_controller_spec.rb +17 -14
- data/spec/controllers/admin/pages_controller_spec.rb +1 -2
- data/spec/controllers/pages_controller_spec.rb +7 -3
- data/spec/dummy/app/models/dummy_user.rb +12 -2
- data/spec/features/admin/dashboard_spec.rb +45 -0
- data/spec/features/admin/legacy_page_url_management_spec.rb +62 -0
- data/spec/features/admin/page_editing_feature_spec.rb +66 -6
- data/spec/features/page_feature_spec.rb +13 -0
- data/spec/helpers/admin/base_helper_spec.rb +36 -0
- data/spec/libraries/resource_spec.rb +168 -84
- data/spec/libraries/tinymce_spec.rb +10 -0
- data/spec/models/element_spec.rb +16 -0
- data/spec/models/legacy_page_url_spec.rb +21 -0
- data/spec/models/message_spec.rb +23 -7
- data/spec/models/page_spec.rb +89 -12
- data/vendor/assets/javascripts/tinymce/plugins/anchor/plugin.min.js +1 -0
- data/vendor/assets/javascripts/tinymce/plugins/hr/plugin.min.js +1 -0
- metadata +96 -75
- data/app/assets/javascripts/alchemy/alchemy.routes.js.erb +0 -38
- 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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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 "
|
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
|
-
|
108
|
-
|
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
|
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.
|
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(/
|
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) {
|
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)
|
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
|
2
|
-
|
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'
|
4
|
-
let(:a_page) { FactoryGirl.create(:
|
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
|
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.
|
11
|
-
|
12
|
-
|
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
|
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 "
|
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(%
|
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
|
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
|
183
|
-
resource
|
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
|
-
|
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
|
203
|
-
# attr_accessor, hence
|
204
|
-
resource.
|
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
|
-
|
212
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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 "
|
237
|
-
|
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
|
244
|
-
it "
|
245
|
-
resource
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
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
|