alchemy_cms 2.6.3 → 2.7.0

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +14 -0
  3. data/.travis.yml +1 -1
  4. data/Gemfile +7 -6
  5. data/README.md +15 -5
  6. data/alchemy_cms.gemspec +3 -2
  7. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +9 -17
  8. data/app/assets/javascripts/alchemy/alchemy.dirty.js.coffee +70 -0
  9. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +80 -0
  10. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +43 -19
  11. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
  12. data/app/assets/javascripts/alchemy/alchemy.js +4 -2
  13. data/app/assets/javascripts/alchemy/alchemy.onload.js.coffee +1 -1
  14. data/app/assets/javascripts/alchemy/alchemy.spinner.js.coffee +14 -0
  15. data/app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee.erb +96 -0
  16. data/app/assets/javascripts/alchemy/alchemy.translations.js.coffee +22 -0
  17. data/app/assets/javascripts/alchemy/alchemy.windows.js.coffee +28 -17
  18. data/app/assets/stylesheets/alchemy/base.scss +6 -0
  19. data/app/assets/stylesheets/alchemy/elements.scss +2 -28
  20. data/app/assets/stylesheets/alchemy/errors.scss +1 -1
  21. data/app/assets/stylesheets/alchemy/menubar.css.scss +2 -0
  22. data/app/assets/stylesheets/alchemy/sitemap.scss +21 -34
  23. data/app/assets/stylesheets/alchemy/tables.scss +13 -3
  24. data/app/controllers/alchemy/admin/attachments_controller.rb +10 -5
  25. data/app/controllers/alchemy/admin/base_controller.rb +19 -0
  26. data/app/controllers/alchemy/admin/contents_controller.rb +1 -4
  27. data/app/controllers/alchemy/admin/dashboard_controller.rb +2 -1
  28. data/app/controllers/alchemy/admin/elements_controller.rb +1 -1
  29. data/app/controllers/alchemy/admin/essence_files_controller.rb +1 -1
  30. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +70 -56
  31. data/app/controllers/alchemy/admin/pages_controller.rb +37 -114
  32. data/app/controllers/alchemy/admin/pictures_controller.rb +5 -12
  33. data/app/controllers/alchemy/admin/resources_controller.rb +3 -1
  34. data/app/controllers/alchemy/admin/trash_controller.rb +1 -1
  35. data/app/controllers/alchemy/attachments_controller.rb +1 -1
  36. data/app/controllers/alchemy/base_controller.rb +3 -15
  37. data/app/controllers/alchemy/messages_controller.rb +4 -10
  38. data/app/controllers/alchemy/pages_controller.rb +6 -6
  39. data/app/controllers/alchemy/passwords_controller.rb +1 -1
  40. data/app/controllers/alchemy/user_sessions_controller.rb +1 -1
  41. data/app/helpers/alchemy/admin/base_helper.rb +49 -230
  42. data/app/helpers/alchemy/admin/contents_helper.rb +5 -1
  43. data/app/helpers/alchemy/admin/elements_helper.rb +19 -47
  44. data/app/helpers/alchemy/admin/essences_helper.rb +59 -17
  45. data/app/helpers/alchemy/admin/navigation_helper.rb +204 -0
  46. data/app/helpers/alchemy/admin/pages_helper.rb +22 -79
  47. data/app/helpers/alchemy/admin/pictures_helper.rb +1 -1
  48. data/app/helpers/alchemy/admin/tags_helper.rb +42 -0
  49. data/app/helpers/alchemy/base_helper.rb +0 -11
  50. data/app/helpers/alchemy/elements_helper.rb +48 -25
  51. data/app/helpers/alchemy/essences_helper.rb +0 -20
  52. data/app/helpers/alchemy/pages_helper.rb +18 -14
  53. data/app/helpers/alchemy/url_helper.rb +1 -0
  54. data/app/mailers/alchemy/messages.rb +4 -6
  55. data/app/models/alchemy/attachment.rb +3 -0
  56. data/app/models/alchemy/cell.rb +33 -35
  57. data/app/models/alchemy/content.rb +20 -111
  58. data/app/models/alchemy/content/factory.rb +188 -0
  59. data/app/models/alchemy/element.rb +51 -200
  60. data/app/models/alchemy/element/definitions.rb +52 -0
  61. data/app/models/alchemy/element/presenters.rb +87 -0
  62. data/app/models/alchemy/essence_date.rb +1 -1
  63. data/app/models/alchemy/essence_file.rb +6 -7
  64. data/app/models/alchemy/essence_picture.rb +19 -4
  65. data/app/models/alchemy/message.rb +18 -14
  66. data/app/models/alchemy/page.rb +120 -214
  67. data/app/models/alchemy/page/elements.rb +145 -36
  68. data/app/models/alchemy/page/natures.rb +90 -0
  69. data/app/models/alchemy/page/scopes.rb +93 -0
  70. data/app/models/alchemy/page/users.rb +25 -0
  71. data/app/models/alchemy/picture.rb +15 -0
  72. data/app/models/alchemy/site.rb +15 -1
  73. data/app/models/alchemy/site/layout.rb +38 -0
  74. data/app/models/alchemy/user.rb +13 -3
  75. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +7 -7
  76. data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +8 -8
  77. data/app/views/alchemy/admin/attachments/_tag_list.html.erb +1 -16
  78. data/app/views/alchemy/admin/attachments/destroy.js.erb +1 -4
  79. data/app/views/alchemy/admin/contents/create.js.erb +1 -1
  80. data/app/views/alchemy/admin/dashboard/index.html.erb +14 -13
  81. data/app/views/alchemy/admin/elements/_element_head.html.erb +7 -7
  82. data/app/views/alchemy/admin/elements/_refresh_editor.js.erb +10 -0
  83. data/app/views/alchemy/admin/elements/create.js.erb +44 -44
  84. data/app/views/alchemy/admin/elements/fold.js.erb +22 -26
  85. data/app/views/alchemy/admin/elements/trash.js.erb +1 -1
  86. data/app/views/alchemy/admin/elements/update.js.erb +22 -25
  87. data/app/views/alchemy/admin/essence_files/assign.js.erb +8 -3
  88. data/app/views/alchemy/admin/essence_pictures/crop.html.erb +14 -12
  89. data/app/views/alchemy/admin/essence_pictures/edit.html.erb +22 -39
  90. data/app/views/alchemy/admin/pages/_page.html.erb +73 -80
  91. data/app/views/alchemy/admin/pages/destroy.js.erb +2 -2
  92. data/app/views/alchemy/admin/pages/edit.html.erb +21 -18
  93. data/app/views/alchemy/admin/pages/fold.js.erb +1 -0
  94. data/app/views/alchemy/admin/pages/info.html.erb +32 -0
  95. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +11 -13
  96. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +20 -20
  97. data/app/views/alchemy/admin/partials/_sub_navigation.html.erb +8 -0
  98. data/app/views/alchemy/admin/partials/_toolbar_button.html.erb +25 -0
  99. data/app/views/alchemy/admin/partials/_upload_form.html.erb +15 -15
  100. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +39 -39
  101. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +10 -10
  102. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -16
  103. data/app/views/alchemy/admin/resources/destroy.js.erb +1 -1
  104. data/app/views/alchemy/base/500.html.erb +1 -1
  105. data/app/views/alchemy/base/permission_denied.js.erb +1 -1
  106. data/app/views/alchemy/base/redirect.js.erb +1 -1
  107. data/app/views/alchemy/essences/_essence_link_editor.html.erb +1 -1
  108. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +1 -1
  109. data/app/views/alchemy/essences/_essence_richtext_editor.html.erb +1 -1
  110. data/app/views/alchemy/essences/_essence_text_editor.html.erb +1 -1
  111. data/app/views/alchemy/essences/{_essence_picture_tools.html.erb → shared/_essence_picture_tools.html.erb} +5 -5
  112. data/app/views/alchemy/essences/{_linkable_essence_tools.html.erb → shared/_linkable_essence_tools.html.erb} +0 -0
  113. data/app/views/alchemy/messages/contact_form_mail.de.text.erb +12 -0
  114. data/app/views/alchemy/messages/contact_form_mail.en.text.erb +12 -0
  115. data/app/views/alchemy/notifications/reset_password_instructions.de.text.erb +1 -1
  116. data/app/views/alchemy/notifications/reset_password_instructions.en.text.erb +2 -2
  117. data/app/views/alchemy/pages/sitemap.xml.erb +3 -5
  118. data/app/views/alchemy/user_sessions/leave.html.erb +1 -1
  119. data/app/views/layouts/alchemy/admin.html.erb +4 -2
  120. data/app/views/layouts/alchemy/sitemap.xml.erb +1 -1
  121. data/bin/alchemy +7 -13
  122. data/config/alchemy/config.yml +1 -0
  123. data/config/authorization_rules.rb +2 -3
  124. data/config/initializers/dragonfly.rb +2 -0
  125. data/config/locales/alchemy.de.yml +8 -9
  126. data/config/locales/alchemy.en.yml +7 -4
  127. data/config/routes.rb +3 -0
  128. data/db/migrate/{20130214233001_alchemy_two_point_five.rb → 20130827094554_alchemy_two_point_six.rb} +29 -6
  129. data/lib/alchemy/auth/engine.rb +9 -0
  130. data/lib/alchemy/capistrano.rb +37 -12
  131. data/lib/alchemy/config.rb +48 -35
  132. data/lib/alchemy/engine.rb +35 -6
  133. data/lib/alchemy/essence.rb +25 -29
  134. data/lib/alchemy/ferret/search.rb +86 -0
  135. data/lib/alchemy/{scoped_pagination_url_helper.rb → kaminari/scoped_pagination_url_helper.rb} +0 -0
  136. data/lib/alchemy/logger.rb +3 -4
  137. data/lib/alchemy/page_layout.rb +124 -55
  138. data/lib/alchemy/resource.rb +0 -10
  139. data/lib/alchemy/resources_helper.rb +0 -5
  140. data/lib/alchemy/seeder.rb +1 -32
  141. data/lib/alchemy/shell.rb +6 -1
  142. data/lib/alchemy/tinymce.rb +41 -32
  143. data/lib/alchemy/upgrader.rb +3 -1
  144. data/lib/alchemy/upgrader/two_point_five.rb +15 -8
  145. data/lib/alchemy/upgrader/two_point_one.rb +10 -10
  146. data/lib/alchemy/upgrader/two_point_two.rb +96 -51
  147. data/lib/alchemy/version.rb +1 -1
  148. data/lib/alchemy_cms.rb +5 -46
  149. data/lib/rails/generators/alchemy/deploy_script/templates/deploy.rb.tt +1 -1
  150. data/lib/rails/generators/alchemy/devise/devise_generator.rb +9 -4
  151. data/lib/rails/generators/alchemy/essence/essence_generator.rb +7 -6
  152. data/lib/rails/generators/alchemy/essence/templates/editor.html.erb +1 -1
  153. data/lib/rails/generators/alchemy/scaffold/files/_standard.html.erb +1 -0
  154. data/lib/rails/generators/alchemy/scaffold/scaffold_generator.rb +1 -0
  155. data/lib/rails/generators/alchemy/site_layouts/site_layouts_generator.rb +23 -0
  156. data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.erb +1 -0
  157. data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.haml +1 -0
  158. data/lib/rails/generators/alchemy/site_layouts/templates/layout.html.slim +1 -0
  159. data/lib/rails/templates/alchemy.rb +2 -2
  160. data/lib/tasks/alchemy/db.rake +3 -1
  161. data/lib/tasks/alchemy/tidy.rake +82 -0
  162. data/lib/tasks/alchemy/upgrade.rake +2 -1
  163. data/spec/controllers/admin/attachments_controller_spec.rb +124 -0
  164. data/spec/controllers/admin/base_controller_spec.rb +35 -0
  165. data/spec/controllers/admin/clipboard_controller_spec.rb +1 -1
  166. data/spec/controllers/admin/contents_controller_spec.rb +17 -26
  167. data/spec/controllers/admin/dashboard_controller_spec.rb +121 -0
  168. data/spec/controllers/admin/elements_controller_spec.rb +1 -1
  169. data/spec/controllers/admin/essence_files_controller_spec.rb +67 -0
  170. data/spec/controllers/admin/essence_pictures_controller_spec.rb +161 -0
  171. data/spec/controllers/admin/languages_controller_spec.rb +1 -1
  172. data/spec/controllers/admin/layoutpages_controller_spec.rb +28 -0
  173. data/spec/controllers/admin/pages_controller_spec.rb +164 -118
  174. data/spec/controllers/admin/pictures_controller_spec.rb +89 -0
  175. data/spec/controllers/admin/trash_controller_spec.rb +21 -31
  176. data/spec/controllers/admin/users_controller_spec.rb +114 -85
  177. data/spec/controllers/attachments_controller_spec.rb +6 -2
  178. data/spec/controllers/base_controller_spec.rb +22 -0
  179. data/spec/controllers/elements_controller_spec.rb +1 -1
  180. data/spec/controllers/messages_controller_spec.rb +200 -0
  181. data/spec/controllers/pictures_controller_spec.rb +1 -1
  182. data/spec/controllers/user_sessions_controller_spec.rb +7 -6
  183. data/spec/controllers/users_controller_spec.rb +2 -2
  184. data/spec/dummy/config/alchemy/cells.yml +2 -0
  185. data/spec/dummy/config/application.rb +19 -8
  186. data/spec/dummy/db/migrate/{20130214233001_alchemy_two_point_five.rb → 20130827094554_alchemy_two_point_six.rb} +29 -6
  187. data/spec/dummy/db/schema.rb +1 -1
  188. data/spec/fast_specs.rb +15 -0
  189. data/spec/helpers/admin/base_helper_spec.rb +53 -34
  190. data/spec/helpers/admin/contents_helper_spec.rb +15 -7
  191. data/spec/helpers/admin/elements_helper_spec.rb +79 -34
  192. data/spec/helpers/admin/essences_helper_spec.rb +45 -31
  193. data/spec/helpers/admin/navigation_helper_spec.rb +204 -0
  194. data/spec/helpers/admin/pages_helper_spec.rb +25 -15
  195. data/spec/helpers/admin/tags_helper_spec.rb +62 -2
  196. data/spec/helpers/elements_helper_spec.rb +202 -138
  197. data/spec/helpers/pages_helper_spec.rb +48 -0
  198. data/spec/helpers/url_helper_spec.rb +7 -0
  199. data/spec/libraries/config_spec.rb +110 -3
  200. data/spec/libraries/essence_spec.rb +29 -9
  201. data/spec/libraries/page_layout_spec.rb +134 -0
  202. data/spec/libraries/resource_spec.rb +3 -16
  203. data/spec/libraries/resources_helper_spec.rb +4 -8
  204. data/spec/libraries/shell_spec.rb +1 -0
  205. data/spec/libraries/tinymce_spec.rb +61 -0
  206. data/spec/mailers/messages_spec.rb +23 -0
  207. data/spec/models/attachment_spec.rb +45 -0
  208. data/spec/models/cell_spec.rb +62 -9
  209. data/spec/models/content_spec.rb +110 -28
  210. data/spec/models/element_spec.rb +275 -253
  211. data/spec/models/essence_date_spec.rb +25 -0
  212. data/spec/models/essence_file_spec.rb +23 -0
  213. data/spec/models/essence_html_spec.rb +13 -0
  214. data/spec/models/essence_picture_spec.rb +16 -0
  215. data/spec/models/essence_text_spec.rb +29 -0
  216. data/spec/models/language_spec.rb +34 -0
  217. data/spec/models/message_spec.rb +43 -0
  218. data/spec/models/page_spec.rb +726 -567
  219. data/spec/models/picture_spec.rb +98 -0
  220. data/spec/models/site_spec.rb +60 -2
  221. data/spec/models/tag_spec.rb +31 -0
  222. data/spec/models/user_spec.rb +4 -4
  223. data/spec/spec_helper.rb +49 -58
  224. data/spec/support/alchemy/controller_helpers.rb +35 -0
  225. data/spec/support/alchemy/{specs_helpers.rb → integration_helpers.rb} +4 -8
  226. data/spec/{factories.rb → support/factories.rb} +11 -1
  227. data/vendor/assets/javascripts/jquery_plugins/jquery.ui.nestedSortable.js +2 -8
  228. metadata +166 -106
  229. data/Guardfile +0 -16
  230. data/app/assets/javascripts/alchemy/alchemy.dirty.js +0 -93
  231. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js +0 -122
  232. data/app/models/alchemy/tree_node.rb +0 -4
  233. data/app/views/alchemy/admin/pages/_page_infos.html.erb +0 -3
  234. data/app/views/alchemy/admin/partials/_sub_navigation_tab.html.erb +0 -8
  235. data/app/views/alchemy/messages/contact_form_mail.text.erb +0 -12
  236. data/config/initializers/kaminari_config.rb +0 -9
  237. data/db/migrate/20130221200514_migrate_attachments_to_dragonfly.rb +0 -21
  238. data/db/migrate/20130312205327_change_alchemy_users_role_to_roles.rb +0 -11
  239. data/lib/alchemy/auth_engine.rb +0 -7
  240. data/lib/alchemy/authentication_helpers.rb +0 -9
  241. data/lib/alchemy/ferret_search.rb +0 -84
  242. data/lib/extensions/array.rb +0 -25
  243. data/lib/extensions/hash.rb +0 -34
  244. data/spec/dummy/db/migrate/20130221200514_migrate_attachments_to_dragonfly.rb +0 -21
  245. data/spec/dummy/db/migrate/20130312205327_change_alchemy_users_role_to_roles.rb +0 -11
  246. data/spec/models/page_layout_spec.rb +0 -60
@@ -1,305 +1,331 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
3
 
3
4
  module Alchemy
4
5
  describe Element do
6
+ # to prevent memoization
7
+ before { Element.instance_variable_set("@definitions", nil) }
5
8
 
6
- describe '#new_from_scratch' do
9
+ # ClassMethods
7
10
 
8
- it "should initialize an element by name from scratch" do
9
- el = Element.new_from_scratch({:name => 'article'})
10
- el.should be_valid
11
+ describe '.copy' do
12
+ let(:element) { FactoryGirl.create(:element, :create_contents_after_create => true, :tag_list => 'red, yellow') }
13
+
14
+ it "should not create contents from scratch" do
15
+ copy = Element.copy(element)
16
+ copy.contents.count.should == element.contents.count
11
17
  end
12
18
 
13
- it "should raise an error if the given name is not defined in the elements.yml" do
14
- expect { Element.new_from_scratch({:name => 'foobar'}) }.to raise_error
19
+ it "should create a new record with all attributes of source except given differences" do
20
+ copy = Element.copy(element, {:name => 'foobar'})
21
+ copy.name.should == 'foobar'
15
22
  end
16
23
 
17
- it "should take the first part of an given name containing a hash (#)" do
18
- el = Element.new_from_scratch({:name => 'article#header'})
19
- el.name.should == "article"
24
+ it "should make copies of all contents of source" do
25
+ copy = Element.copy(element)
26
+ copy.contents.collect(&:id).should_not == element.contents.collect(&:id)
20
27
  end
21
28
 
22
- it "should merge given attributes into defined ones" do
23
- el = Element.new_from_scratch({:name => 'article', :page_id => 1})
24
- el.page_id.should == 1
29
+ it "the copy should include source element tags" do
30
+ copy = Element.copy(element)
31
+ copy.tag_list.should == element.tag_list
25
32
  end
33
+ end
26
34
 
27
- it "should not have forbidden attributes from definition" do
28
- el = Element.new_from_scratch({:name => 'article'})
29
- el.contents.should == []
35
+ describe '.definitions' do
36
+ context "without existing yml files" do
37
+ before { File.stub!(:exists?).and_return(false) }
38
+
39
+ it "should raise an error" do
40
+ expect { Element.definitions }.to raise_error(LoadError)
41
+ end
30
42
  end
31
43
 
32
- end
44
+ context "without any definitions in elements.yml" do
45
+ before { YAML.stub!(:load_file).and_return(false) } # Yes, YAML.load_file returns false if an empty file exists.
33
46
 
34
- context "scoped" do
47
+ it "should return an empty array" do
48
+ Element.definitions.should == []
49
+ end
50
+ end
51
+ end
35
52
 
36
- it "should return all public elements" do
37
- element_1 = FactoryGirl.create(:element, :public => true)
38
- element_2 = FactoryGirl.create(:element, :public => true)
39
- elements = Element.published.all
40
- elements.should include(element_1)
41
- elements.should include(element_2)
53
+ describe '.display_name_for' do
54
+ it "should return the translation for the given name" do
55
+ I18n.should_receive(:t).with('subheadline', scope: "element_names", default: 'Subheadline').and_return('Überschrift')
56
+ expect(Element.display_name_for('subheadline')).to eq('Überschrift')
42
57
  end
43
58
 
44
- it "should return all elements by name" do
45
- element_1 = FactoryGirl.create(:element, :name => 'article')
46
- element_2 = FactoryGirl.create(:element, :name => 'article')
47
- elements = Element.named(['article']).all
48
- elements.should include(element_1)
49
- elements.should include(element_2)
59
+ it "should return the humanized name if no translation found" do
60
+ expect(Element.display_name_for('not_existing_one')).to eq('Not existing one')
50
61
  end
62
+ end
51
63
 
64
+ describe '.excluded' do
52
65
  it "should return all elements but excluded ones" do
53
66
  FactoryGirl.create(:element, :name => 'article')
54
67
  FactoryGirl.create(:element, :name => 'article')
55
68
  excluded = FactoryGirl.create(:element, :name => 'claim')
56
69
  Element.excluded(['claim']).all.should_not include(excluded)
57
70
  end
71
+ end
58
72
 
59
- context "not_in_cell" do
60
-
61
- it "should return all elements that are not in a cell" do
62
- Element.delete_all
63
- FactoryGirl.create(:element, :cell_id => 6)
64
- FactoryGirl.create(:element, :cell_id => nil)
65
- Element.not_in_cell.should have(1).element
66
- end
67
-
73
+ describe '.named' do
74
+ it "should return all elements by name" do
75
+ element_1 = FactoryGirl.create(:element, :name => 'article')
76
+ element_2 = FactoryGirl.create(:element, :name => 'article')
77
+ elements = Element.named(['article']).all
78
+ elements.should include(element_1)
79
+ elements.should include(element_2)
68
80
  end
69
-
70
81
  end
71
82
 
72
- describe '.all_definitions_for' do
73
-
74
- it "should return a list of element definitions for a list of element names" do
75
- element_names = ["article"]
76
- definitions = Element.all_definitions_for(element_names)
77
- definitions.first.fetch("name").should == 'article'
83
+ describe '.not_in_cell' do
84
+ it "should return all elements that are not in a cell" do
85
+ Element.delete_all
86
+ FactoryGirl.create(:element, :cell_id => 6)
87
+ FactoryGirl.create(:element, :cell_id => nil)
88
+ Element.not_in_cell.should have(1).element
78
89
  end
90
+ end
79
91
 
80
- context "given 'all' as element name" do
92
+ describe '.published' do
93
+ it "should return all public elements" do
94
+ element_1 = FactoryGirl.create(:element, :public => true)
95
+ element_2 = FactoryGirl.create(:element, :public => true)
96
+ elements = Element.published.all
97
+ elements.should include(element_1)
98
+ elements.should include(element_2)
99
+ end
100
+ end
81
101
 
82
- before do
83
- @element_definition = [
84
- {'name' => 'article'},
85
- {'name' => 'headline'}
86
- ]
87
- Element.stub!(:definitions).and_return @element_definition
102
+ context 'trash' do
103
+ describe '.not_trashed' do
104
+ let(:element) do
105
+ FactoryGirl.create(:element, :page_id => 1)
88
106
  end
89
107
 
90
- it "should return all element definitions" do
91
- Element.all_definitions_for('all').should == @element_definition
108
+ it "should return a collection of not trashed elements" do
109
+ Element.not_trashed.should include(element)
92
110
  end
93
-
94
111
  end
95
112
 
96
- it "should always return an array" do
97
- definitions = Element.all_definitions_for(nil)
98
- definitions.should == []
99
- end
113
+ describe ".trashed" do
114
+ let(:element) do
115
+ FactoryGirl.create(:element, :page_id => 1)
116
+ end
100
117
 
118
+ it "should return a collection of trashed elements" do
119
+ not_trashed_element = FactoryGirl.create(:element)
120
+ element.trash
121
+ Element.trashed.should include(element)
122
+ end
123
+ end
101
124
  end
102
125
 
103
- context "no description files are found" do
126
+ describe '.all_from_clipboard_for_page' do
127
+ let(:element_1) { FactoryGirl.build_stubbed(:element) }
128
+ let(:element_2) { FactoryGirl.build_stubbed(:element, name: 'news') }
129
+ let(:page) { FactoryGirl.build_stubbed(:public_page) }
130
+ let(:clipboard) { [{id: element_1.id}, {id: element_2.id}] }
131
+ before {
132
+ Element.stub(:find_all_by_id).and_return([element_1, element_2])
133
+ }
104
134
 
105
- before do
106
- FileUtils.mv(File.join(File.dirname(__FILE__), '../dummy/config/alchemy/elements.yml'), File.join(File.dirname(__FILE__), '../dummy/config/alchemy/elements.yml.bak'))
135
+ it "return all elements from clipboard that could be placed on page" do
136
+ elements = Element.all_from_clipboard_for_page(clipboard, page)
137
+ elements.should == [element_1]
138
+ elements.should_not == [element_2]
107
139
  end
108
140
 
109
- it "should raise an error" do
110
- expect { Element.descriptions }.to raise_error(LoadError)
111
- end
112
-
113
- after do
114
- FileUtils.mv(File.join(File.dirname(__FILE__), '../dummy/config/alchemy/elements.yml.bak'), File.join(File.dirname(__FILE__), '../dummy/config/alchemy/elements.yml'))
141
+ context "page nil" do
142
+ it "returns empty array" do
143
+ Element.all_from_clipboard_for_page(clipboard, nil).should == []
144
+ end
115
145
  end
116
146
 
117
- end
118
-
119
- context "without any descriptions in elements.yml file" do
120
-
121
- it "should return an empty array" do
122
- YAML.stub(:load_file).and_return(false) # Yes, YAML.load_file returns false if an empty file exists.
123
- Element.descriptions.should == []
147
+ context "clipboard nil" do
148
+ it "returns empty array" do
149
+ Element.all_from_clipboard_for_page(nil, page).should == []
150
+ end
124
151
  end
125
-
126
152
  end
127
153
 
128
- context "retrieving contents, essences and ingredients" do
129
-
130
- let(:element) { FactoryGirl.create(:element, :name => 'news', :create_contents_after_create => true) }
154
+ # InstanceMethods
131
155
 
132
- it "should return an ingredient by name" do
133
- element.ingredient('news_headline').should == EssenceText.first.ingredient
134
- end
156
+ describe '#all_contents_by_type' do
157
+ let(:element) { FactoryGirl.create(:element, create_contents_after_create: true) }
158
+ let(:expected_contents) { element.contents.essence_texts }
135
159
 
136
- it "should return the content for rss title" do
137
- element.content_for_rss_title.should == element.contents.find_by_name('news_headline')
160
+ context "with namespaced essence type" do
161
+ subject { element.all_contents_by_type('Alchemy::EssenceText') }
162
+ it { should_not be_empty }
163
+ it('should return the correct list of essences') { should == expected_contents }
138
164
  end
139
165
 
140
- it "should return the content for rss description" do
141
- element.content_for_rss_description.should == element.contents.find_by_name('body')
166
+ context "without namespaced essence type" do
167
+ subject { element.all_contents_by_type('EssenceText') }
168
+ it { should_not be_empty }
169
+ it('should return the correct list of essences') { should == expected_contents }
142
170
  end
143
-
144
171
  end
145
172
 
146
- context "limited amount" do
147
-
173
+ describe '#belonging_cellnames' do
148
174
  before do
149
- defs = [
150
- {
151
- 'name' => 'column_headline',
152
- 'amount' => 3,
153
- 'contents' => [{'name' => 'headline', 'type' => 'EssenceText'}]
154
- },
155
- {
156
- 'name' => 'unique_headline',
157
- 'unique' => true,
158
- 'amount' => 3,
159
- 'contents' => [{'name' => 'headline', 'type' => 'EssenceText'}]
160
- }
161
- ]
162
- # F&%#ing alias methods
163
- Element.stub!(:definitions).and_return(defs)
164
- Element.stub!(:descriptions).and_return(defs)
165
- PageLayout.stub!(:get).and_return({
166
- 'name' => 'columns',
167
- 'elements' => ['column_headline', 'unique_headline'],
168
- 'autogenerate' => ['unique_headline', 'column_headline', 'column_headline', 'column_headline']
169
- })
170
- @page = FactoryGirl.create(:page, :page_layout => 'columns', :do_not_autogenerate => false)
171
- end
172
-
173
- it "should be readable" do
174
- element = Element.all_definitions_for(['column_headline']).first
175
- element['amount'].should be 3
176
- end
177
-
178
- it "should limit elements" do
179
- Element.all_for_page(@page).each { |e| e['name'].should_not == 'column_headline' }
180
- end
181
-
182
- it "should be ignored if unique" do
183
- Element.all_for_page(@page).each { |e| e['name'].should_not == 'unique_headline' }
175
+ @page = FactoryGirl.create(:public_page)
176
+ @element = FactoryGirl.create(:element, :page => @page)
184
177
  end
185
178
 
186
- end
187
-
188
- context "collections" do
189
-
190
- context "for trashed elements" do
191
-
192
- let(:element) do
193
- FactoryGirl.create(:element, :page_id => 1)
179
+ context "with page having cells defining the correct elements" do
180
+ before do
181
+ Cell.stub!(:definitions).and_return([
182
+ {'name' => 'header', 'elements' => ['article', 'headline']},
183
+ {'name' => 'footer', 'elements' => ['article', 'text']},
184
+ {'name' => 'sidebar', 'elements' => ['teaser']}
185
+ ])
194
186
  end
195
187
 
196
- it "should return a collection of trashed elements" do
197
- not_trashed_element = FactoryGirl.create(:element)
198
- element.trash
199
- Element.trashed.should include(element)
188
+ it "should return a list of all cells from given page this element could be placed in" do
189
+ @header_cell = FactoryGirl.create(:cell, :name => 'header', :page => @page)
190
+ @footer_cell = FactoryGirl.create(:cell, :name => 'footer', :page => @page)
191
+ @sidebar_cell = FactoryGirl.create(:cell, :name => 'sidebar', :page => @page)
192
+ @element.belonging_cellnames(@page).should include('header')
193
+ @element.belonging_cellnames(@page).should include('footer')
200
194
  end
201
195
 
202
- it "should return a collection of not trashed elements" do
203
- Element.not_trashed.should include(element)
196
+ context "but without any cells" do
197
+ it "should return the 'nil cell'" do
198
+ @element.belonging_cellnames(@page).should == ['for_other_elements']
199
+ end
204
200
  end
205
201
 
206
202
  end
207
203
 
208
- end
209
-
210
- describe "#trash" do
211
- let(:element) { FactoryGirl.create(:element, :page_id => 1, :cell_id => 1) }
212
- let(:trashed_element) { element.trash ; element }
213
- subject { trashed_element }
214
-
215
- it { should_not be_public }
216
- its(:position) { should be_nil }
217
- specify { expect { element.trash }.to_not change(element, :page_id) }
218
- specify { expect { element.trash }.to_not change(element, :cell_id) }
204
+ context "with page having cells defining the wrong elements" do
205
+ before do
206
+ Cell.stub!(:definitions).and_return([
207
+ {'name' => 'header', 'elements' => ['download', 'headline']},
208
+ {'name' => 'footer', 'elements' => ['contactform', 'text']},
209
+ {'name' => 'sidebar', 'elements' => ['teaser']}
210
+ ])
211
+ end
219
212
 
220
- it "it should be possible to trash more than one element from the same page" do
221
- trashed_element_2 = FactoryGirl.create(:element, :page_id => 1)
222
- trashed_element_2.trash
223
- Element.trashed.should include(trashed_element, trashed_element_2)
213
+ it "should return the 'nil cell'" do
214
+ @header_cell = FactoryGirl.create(:cell, :name => 'header', :page => @page)
215
+ @footer_cell = FactoryGirl.create(:cell, :name => 'footer', :page => @page)
216
+ @sidebar_cell = FactoryGirl.create(:cell, :name => 'sidebar', :page => @page)
217
+ @element.belonging_cellnames(@page).should == ['for_other_elements']
218
+ end
224
219
  end
225
-
226
- end
227
-
228
- it "should raise error if all_for_page method has no page" do
229
- expect { Element.all_for_page(nil) }.to raise_error(TypeError)
230
220
  end
231
221
 
232
- describe "#content_by_type" do
233
-
222
+ describe '#content_by_type' do
234
223
  before(:each) do
235
224
  @element = FactoryGirl.create(:element, :name => 'headline')
236
225
  @content = @element.contents.first
237
226
  end
238
227
 
239
228
  context "with namespaced essence type" do
240
-
241
229
  it "should return content by passing a essence type" do
242
230
  @element.content_by_type('Alchemy::EssenceText').should == @content
243
231
  end
244
-
245
232
  end
246
233
 
247
234
  context "without namespaced essence type" do
248
-
249
235
  it "should return content by passing a essence type" do
250
236
  @element.content_by_type('EssenceText').should == @content
251
237
  end
252
-
253
238
  end
254
-
255
239
  end
256
240
 
257
- describe "#all_contents_by_type" do
258
- let(:element) { FactoryGirl.create(:element, create_contents_after_create: true) }
259
- let(:expected_contents) { element.contents.essence_texts }
241
+ describe '#display_name' do
242
+ let(:element) { Element.new(name: 'article') }
260
243
 
261
- context "with namespaced essence type" do
262
- subject { element.all_contents_by_type('Alchemy::EssenceText') }
263
- it { should_not be_empty }
264
- it('should return the correct list of essences') { should == expected_contents }
244
+ it "should call .display_name_for" do
245
+ Element.should_receive(:display_name_for).with(element.name)
246
+ element.display_name
265
247
  end
248
+ end
266
249
 
267
- context "without namespaced essence type" do
268
- subject { element.all_contents_by_type('EssenceText') }
269
- it { should_not be_empty }
270
- it('should return the correct list of essences') { should == expected_contents }
271
- end
250
+ describe '#display_name_with_preview_text' do
251
+ let(:element) { FactoryGirl.build_stubbed(:element, name: 'Foo') }
272
252
 
253
+ it "returns a string with display name and preview text" do
254
+ element.stub(:preview_text).and_return('Fula')
255
+ element.display_name_with_preview_text.should == "Foo: Fula"
256
+ end
273
257
  end
274
258
 
275
- describe '.copy' do
259
+ describe '#dom_id' do
260
+ let(:element) { FactoryGirl.build_stubbed(:element) }
276
261
 
277
- let(:element) { FactoryGirl.create(:element, :create_contents_after_create => true, :tag_list => 'red, yellow') }
262
+ it "returns an string from element name and id" do
263
+ element.dom_id.should == "#{element.name}_#{element.id}"
264
+ end
265
+ end
278
266
 
279
- it "should not create contents from scratch" do
280
- copy = Element.copy(element)
281
- copy.contents.count.should == element.contents.count
267
+ describe '#new_from_scratch' do
268
+ it "should initialize an element by name from scratch" do
269
+ el = Element.new_from_scratch({:name => 'article'})
270
+ el.should be_valid
282
271
  end
283
272
 
284
- it "should create a new record with all attributes of source except given differences" do
285
- copy = Element.copy(element, {:name => 'foobar'})
286
- copy.name.should == 'foobar'
273
+ it "should raise an error if the given name is not defined in the elements.yml" do
274
+ expect { Element.new_from_scratch({:name => 'foobar'}) }.to raise_error
287
275
  end
288
276
 
289
- it "should make copies of all contents of source" do
290
- copy = Element.copy(element)
291
- copy.contents.collect(&:id).should_not == element.contents.collect(&:id)
277
+ it "should take the first part of an given name containing a hash (#)" do
278
+ el = Element.new_from_scratch({:name => 'article#header'})
279
+ el.name.should == "article"
292
280
  end
293
281
 
294
- it "the copy should include source element tags" do
295
- copy = Element.copy(element)
296
- copy.tag_list.should == element.tag_list
282
+ it "should merge given attributes into defined ones" do
283
+ el = Element.new_from_scratch({:name => 'article', :page_id => 1})
284
+ el.page_id.should == 1
297
285
  end
298
286
 
287
+ it "should not have forbidden attributes from definition" do
288
+ el = Element.new_from_scratch({:name => 'article'})
289
+ el.contents.should == []
290
+ end
299
291
  end
300
292
 
301
- describe "Finding previous or next element." do
293
+ describe '#preview_text' do
294
+ let(:element) { FactoryGirl.build_stubbed(:element) }
295
+ let(:content) { mock_model(Content, preview_text: 'Lorem', preview_content?: false) }
296
+ let(:content_2) { mock_model(Content, preview_text: 'Lorem', preview_content?: false) }
297
+ let(:preview_content) { mock_model(Content, preview_text: 'Lorem', preview_content?: true) }
298
+
299
+ context "without a content marked as preview" do
300
+ let(:contents) { [content, content_2] }
301
+ before { element.stub!(:contents).and_return(contents) }
302
+
303
+ it "returns the preview text of first content found" do
304
+ content.should_receive(:preview_text).with(30)
305
+ element.preview_text
306
+ end
307
+ end
308
+
309
+ context "with a content marked as preview" do
310
+ let(:contents) { [content, preview_content] }
311
+ before { element.stub!(:contents).and_return(contents) }
312
+
313
+ it "should return the preview_text of this content" do
314
+ preview_content.should_receive(:preview_text).with(30)
315
+ element.preview_text
316
+ end
317
+ end
318
+
319
+ context "without any contents present" do
320
+ before { element.stub!(:contents).and_return([]) }
302
321
 
322
+ it "should return nil" do
323
+ element.preview_text.should be_nil
324
+ end
325
+ end
326
+ end
327
+
328
+ context 'previous and next elements.' do
303
329
  let(:page) { FactoryGirl.create(:language_root_page) }
304
330
 
305
331
  before(:each) do
@@ -309,7 +335,6 @@ module Alchemy
309
335
  end
310
336
 
311
337
  describe '#prev' do
312
-
313
338
  it "should return previous element on same page" do
314
339
  @element3.prev.should == @element2
315
340
  end
@@ -319,11 +344,9 @@ module Alchemy
319
344
  @element3.prev('headline').should == @element1
320
345
  end
321
346
  end
322
-
323
347
  end
324
348
 
325
349
  describe '#next' do
326
-
327
350
  it "should return next element on same page" do
328
351
  @element2.next.should == @element3
329
352
  end
@@ -333,99 +356,98 @@ module Alchemy
333
356
  @element1.next('text').should == @element3
334
357
  end
335
358
  end
336
-
337
359
  end
338
-
339
360
  end
340
361
 
341
- describe '#belonging_cellnames' do
362
+ context 'retrieving contents, essences and ingredients' do
363
+ let(:element) { FactoryGirl.create(:element, :name => 'news', :create_contents_after_create => true) }
342
364
 
343
- before do
344
- @page = FactoryGirl.create(:public_page)
345
- @element = FactoryGirl.create(:element, :page => @page)
365
+ it "should return an ingredient by name" do
366
+ element.ingredient('news_headline').should == EssenceText.first.ingredient
346
367
  end
347
368
 
348
- context "with page having cells defining the correct elements" do
349
-
350
- before do
351
- Cell.stub!(:definitions).and_return([
352
- {'name' => 'header', 'elements' => ['article', 'headline']},
353
- {'name' => 'footer', 'elements' => ['article', 'text']},
354
- {'name' => 'sidebar', 'elements' => ['teaser']}
355
- ])
356
- end
357
-
358
- it "should return a list of all cells from given page this element could be placed in" do
359
- @header_cell = FactoryGirl.create(:cell, :name => 'header', :page => @page)
360
- @footer_cell = FactoryGirl.create(:cell, :name => 'footer', :page => @page)
361
- @sidebar_cell = FactoryGirl.create(:cell, :name => 'sidebar', :page => @page)
362
- @element.belonging_cellnames(@page).should include('header')
363
- @element.belonging_cellnames(@page).should include('footer')
364
- end
365
-
366
- context "but without any cells" do
367
-
368
- it "should return the 'nil cell'" do
369
- @element.belonging_cellnames(@page).should == ['for_other_elements']
370
- end
371
-
372
- end
369
+ it "should return the content for rss title" do
370
+ element.content_for_rss_title.should == element.contents.find_by_name('news_headline')
371
+ end
373
372
 
373
+ it "should return the content for rss descdefinitionription" do
374
+ element.content_for_rss_description.should == element.contents.find_by_name('body')
374
375
  end
376
+ end
375
377
 
376
- context "with page having cells defining the wrong elements" do
378
+ describe '#save' do
379
+ let(:time) { Time.now }
380
+ let(:locker) { FactoryGirl.build_stubbed(:user) }
381
+ let(:page) { FactoryGirl.create(:page, updated_at: time) }
382
+ let(:element) { FactoryGirl.create(:element, page: page) }
377
383
 
378
- before do
379
- Cell.stub!(:definitions).and_return([
380
- {'name' => 'header', 'elements' => ['download', 'headline']},
381
- {'name' => 'footer', 'elements' => ['contactform', 'text']},
382
- {'name' => 'sidebar', 'elements' => ['teaser']}
383
- ])
384
- end
384
+ before { User.stub(:stamper).and_return(locker.id) }
385
385
 
386
- it "should return the 'nil cell'" do
387
- @header_cell = FactoryGirl.create(:cell, :name => 'header', :page => @page)
388
- @footer_cell = FactoryGirl.create(:cell, :name => 'footer', :page => @page)
389
- @sidebar_cell = FactoryGirl.create(:cell, :name => 'sidebar', :page => @page)
390
- @element.belonging_cellnames(@page).should == ['for_other_elements']
391
- end
386
+ it "updates page timestamps" do
387
+ element.save
388
+ page.reload
389
+ page.updated_at.should_not eq(time)
390
+ end
392
391
 
392
+ it "updates page userstamps" do
393
+ element.save
394
+ page.reload
395
+ page.updater_id.should eq(locker.id)
393
396
  end
397
+ end
394
398
 
399
+ describe '#save_contents' do
400
+ it "should return true if attributes hash is nil" do
401
+ element = FactoryGirl.create(:element, create_contents_after_create: true)
402
+ element.save_contents(nil).should be_true
403
+ end
395
404
  end
396
405
 
397
406
  describe '#taggable?' do
407
+ let(:element) { FactoryGirl.build(:element) }
398
408
 
399
- context "description has 'taggable' key with true value" do
409
+ context "definition has 'taggable' key with true value" do
400
410
  it "should return true" do
401
- element = FactoryGirl.build(:element)
402
- element.stub(:description).and_return({'name' => 'article', 'taggable' => true})
411
+ element.stub(:definition).and_return({'name' => 'article', 'taggable' => true})
403
412
  element.taggable?.should be_true
404
413
  end
405
414
  end
406
415
 
407
- context "description has 'taggable' key with foo value" do
416
+ context "definition has 'taggable' key with foo value" do
408
417
  it "should return false" do
409
- element = FactoryGirl.build(:element)
410
- element.stub(:description).and_return({'name' => 'article', 'taggable' => 'foo'})
418
+ element.stub(:definition).and_return({'name' => 'article', 'taggable' => 'foo'})
411
419
  element.taggable?.should be_false
412
420
  end
413
421
  end
414
422
 
415
- context "description has no 'taggable' key" do
423
+ context "definition has no 'taggable' key" do
416
424
  it "should return false" do
417
- element = FactoryGirl.build(:element)
418
- element.stub(:description).and_return({'name' => 'article'})
425
+ element.stub(:definition).and_return({'name' => 'article'})
419
426
  element.taggable?.should be_false
420
427
  end
421
428
  end
429
+ end
430
+
431
+ describe '#trash' do
432
+ let(:element) { FactoryGirl.create(:element, :page_id => 1, :cell_id => 1) }
433
+ let(:trashed_element) { element.trash ; element }
434
+ subject { trashed_element }
435
+
436
+ it { should_not be_public }
437
+ its(:position) { should be_nil }
438
+ specify { expect { element.trash }.to_not change(element, :page_id) }
439
+ specify { expect { element.trash }.to_not change(element, :cell_id) }
422
440
 
441
+ it "it should be possible to trash more than one element from the same page" do
442
+ trashed_element_2 = FactoryGirl.create(:element, :page_id => 1)
443
+ trashed_element_2.trash
444
+ Element.trashed.should include(trashed_element, trashed_element_2)
445
+ end
423
446
  end
424
447
 
425
- describe '#save_contents' do
426
- it "should return true if attributes hash is nil" do
427
- element = FactoryGirl.create(:element, create_contents_after_create: true)
428
- element.save_contents(nil).should be_true
448
+ describe "#to_partial_path" do
449
+ it "should return a String in the format of 'alchemy/elements/#{name}_view'" do
450
+ expect(Element.new(name: 'mock').to_partial_path).to eq('alchemy/elements/mock_view')
429
451
  end
430
452
  end
431
453