alchemy_cms 5.2.0 → 6.0.0.b3

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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -14
  3. data/.gitignore +0 -1
  4. data/.hound.yml +1 -1
  5. data/.rubocop.yml +46 -4
  6. data/CHANGELOG.md +114 -5
  7. data/Gemfile +8 -1
  8. data/README.md +5 -2
  9. data/alchemy_cms.gemspec +78 -65
  10. data/app/assets/javascripts/alchemy/admin.js +0 -2
  11. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +0 -27
  12. data/app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee +2 -1
  13. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +1 -1
  14. data/app/assets/javascripts/alchemy/alchemy.dragndrop.js.coffee +0 -25
  15. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -1
  16. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +2 -0
  17. data/app/assets/javascripts/alchemy/alchemy.fixed_elements.js +1 -1
  18. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +3 -1
  19. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +1 -1
  20. data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +40 -27
  21. data/app/assets/javascripts/alchemy/templates/node_folder.hbs +1 -1
  22. data/app/assets/stylesheets/alchemy/_extends.scss +15 -2
  23. data/app/assets/stylesheets/alchemy/admin.scss +1 -1
  24. data/app/assets/stylesheets/alchemy/archive.scss +20 -5
  25. data/app/assets/stylesheets/alchemy/buttons.scss +0 -4
  26. data/app/assets/stylesheets/alchemy/elements.scss +73 -61
  27. data/app/assets/stylesheets/alchemy/images.scss +8 -0
  28. data/app/assets/stylesheets/alchemy/node-select.scss +4 -3
  29. data/app/assets/stylesheets/alchemy/page-select.scss +1 -0
  30. data/app/controllers/alchemy/admin/attachments_controller.rb +8 -4
  31. data/app/controllers/alchemy/admin/base_controller.rb +5 -7
  32. data/app/controllers/alchemy/admin/elements_controller.rb +59 -34
  33. data/app/controllers/alchemy/admin/essence_audios_controller.rb +30 -0
  34. data/app/controllers/alchemy/admin/essence_files_controller.rb +0 -14
  35. data/app/controllers/alchemy/admin/essence_pictures_controller.rb +8 -79
  36. data/app/controllers/alchemy/admin/essence_videos_controller.rb +33 -0
  37. data/app/controllers/alchemy/admin/ingredients_controller.rb +30 -0
  38. data/app/controllers/alchemy/admin/layoutpages_controller.rb +0 -1
  39. data/app/controllers/alchemy/admin/pages_controller.rb +7 -22
  40. data/app/controllers/alchemy/admin/pictures_controller.rb +56 -17
  41. data/app/controllers/alchemy/admin/resources_controller.rb +84 -10
  42. data/app/controllers/alchemy/api/elements_controller.rb +13 -4
  43. data/app/controllers/alchemy/api/pages_controller.rb +4 -3
  44. data/app/controllers/concerns/alchemy/admin/archive_overlay.rb +13 -3
  45. data/app/controllers/concerns/alchemy/admin/crop_action.rb +26 -0
  46. data/app/decorators/alchemy/element_editor.rb +26 -1
  47. data/app/decorators/alchemy/ingredient_editor.rb +158 -0
  48. data/app/helpers/alchemy/admin/elements_helper.rb +1 -0
  49. data/app/helpers/alchemy/admin/essences_helper.rb +1 -1
  50. data/app/helpers/alchemy/admin/ingredients_helper.rb +42 -0
  51. data/app/helpers/alchemy/elements_block_helper.rb +23 -6
  52. data/app/helpers/alchemy/elements_helper.rb +12 -5
  53. data/app/helpers/alchemy/pages_helper.rb +3 -11
  54. data/app/jobs/alchemy/base_job.rb +11 -0
  55. data/app/jobs/alchemy/publish_page_job.rb +11 -0
  56. data/app/models/alchemy/attachment.rb +24 -7
  57. data/app/models/alchemy/content.rb +1 -6
  58. data/app/models/alchemy/content/factory.rb +23 -27
  59. data/app/models/alchemy/element.rb +39 -72
  60. data/app/models/alchemy/element/definitions.rb +29 -27
  61. data/app/models/alchemy/element/element_contents.rb +131 -122
  62. data/app/models/alchemy/element/element_essences.rb +111 -98
  63. data/app/models/alchemy/element/element_ingredients.rb +184 -0
  64. data/app/models/alchemy/element/presenters.rb +104 -85
  65. data/app/models/alchemy/elements_repository.rb +126 -0
  66. data/app/models/alchemy/essence_audio.rb +12 -0
  67. data/app/models/alchemy/essence_headline.rb +40 -0
  68. data/app/models/alchemy/essence_picture.rb +4 -116
  69. data/app/models/alchemy/essence_richtext.rb +12 -0
  70. data/app/models/alchemy/essence_video.rb +12 -0
  71. data/app/models/alchemy/image_cropper_settings.rb +87 -0
  72. data/app/models/alchemy/ingredient.rb +183 -0
  73. data/app/models/alchemy/ingredient_validator.rb +97 -0
  74. data/app/models/alchemy/ingredients/audio.rb +29 -0
  75. data/app/models/alchemy/ingredients/boolean.rb +21 -0
  76. data/app/models/alchemy/ingredients/datetime.rb +20 -0
  77. data/app/models/alchemy/ingredients/file.rb +30 -0
  78. data/app/models/alchemy/ingredients/headline.rb +42 -0
  79. data/app/models/alchemy/ingredients/html.rb +19 -0
  80. data/app/models/alchemy/ingredients/link.rb +16 -0
  81. data/app/models/alchemy/ingredients/node.rb +23 -0
  82. data/app/models/alchemy/ingredients/page.rb +23 -0
  83. data/app/models/alchemy/ingredients/picture.rb +41 -0
  84. data/app/models/alchemy/ingredients/richtext.rb +57 -0
  85. data/app/models/alchemy/ingredients/select.rb +10 -0
  86. data/app/models/alchemy/ingredients/text.rb +17 -0
  87. data/app/models/alchemy/ingredients/video.rb +33 -0
  88. data/app/models/alchemy/language.rb +0 -11
  89. data/app/models/alchemy/page.rb +76 -33
  90. data/app/models/alchemy/page/fixed_attributes.rb +53 -51
  91. data/app/models/alchemy/page/page_elements.rb +186 -205
  92. data/app/models/alchemy/page/page_naming.rb +66 -64
  93. data/app/models/alchemy/page/page_natures.rb +139 -142
  94. data/app/models/alchemy/page/page_scopes.rb +117 -102
  95. data/app/models/alchemy/page/publisher.rb +50 -0
  96. data/app/models/alchemy/page/url_path.rb +1 -1
  97. data/app/models/alchemy/page_version.rb +58 -0
  98. data/app/models/alchemy/picture.rb +18 -40
  99. data/app/models/alchemy/picture/calculations.rb +2 -8
  100. data/app/models/alchemy/picture/preprocessor.rb +2 -0
  101. data/app/models/alchemy/picture/transformations.rb +24 -96
  102. data/app/models/concerns/alchemy/picture_thumbnails.rb +181 -0
  103. data/app/models/concerns/alchemy/touch_elements.rb +2 -2
  104. data/app/presenters/alchemy/picture_view.rb +88 -0
  105. data/app/serializers/alchemy/element_serializer.rb +5 -0
  106. data/app/serializers/alchemy/page_tree_serializer.rb +3 -2
  107. data/app/services/alchemy/delete_elements.rb +44 -0
  108. data/app/services/alchemy/duplicate_element.rb +56 -0
  109. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +2 -3
  110. data/app/views/alchemy/admin/attachments/_file_to_assign.html.erb +3 -3
  111. data/app/views/alchemy/admin/attachments/assign.js.erb +11 -0
  112. data/app/views/alchemy/admin/attachments/index.html.erb +2 -3
  113. data/app/views/alchemy/admin/crop.html.erb +36 -0
  114. data/app/views/alchemy/admin/elements/_element.html.erb +14 -10
  115. data/app/views/alchemy/admin/elements/{_element_footer.html.erb → _footer.html.erb} +0 -0
  116. data/app/views/alchemy/admin/elements/{_new_element_form.html.erb → _form.html.erb} +1 -1
  117. data/app/views/alchemy/admin/elements/{_element_header.html.erb → _header.html.erb} +1 -1
  118. data/app/views/alchemy/admin/elements/{_element_toolbar.html.erb → _toolbar.html.erb} +5 -6
  119. data/app/views/alchemy/admin/elements/create.js.erb +1 -1
  120. data/app/views/alchemy/admin/elements/{trash.js.erb → destroy.js.erb} +2 -6
  121. data/app/views/alchemy/admin/elements/fold.js.erb +2 -2
  122. data/app/views/alchemy/admin/elements/new.html.erb +3 -3
  123. data/app/views/alchemy/admin/elements/order.js.erb +0 -17
  124. data/app/views/alchemy/admin/elements/update.js.erb +3 -2
  125. data/app/views/alchemy/admin/essence_audios/edit.html.erb +7 -0
  126. data/app/views/alchemy/admin/essence_pictures/update.js.erb +0 -1
  127. data/app/views/alchemy/admin/essence_videos/edit.html.erb +11 -0
  128. data/app/views/alchemy/admin/ingredients/_audio_fields.html.erb +4 -0
  129. data/app/views/alchemy/admin/ingredients/_file_fields.html.erb +18 -0
  130. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +25 -0
  131. data/app/views/alchemy/admin/ingredients/_video_fields.html.erb +8 -0
  132. data/app/views/alchemy/admin/ingredients/edit.html.erb +4 -0
  133. data/app/views/alchemy/admin/layoutpages/edit.html.erb +0 -5
  134. data/app/views/alchemy/admin/nodes/_node.html.erb +2 -2
  135. data/app/views/alchemy/admin/pages/_anchor_link.html.erb +1 -1
  136. data/app/views/alchemy/admin/pages/_external_link.html.erb +1 -1
  137. data/app/views/alchemy/admin/pages/_file_link.html.erb +1 -1
  138. data/app/views/alchemy/admin/pages/_form.html.erb +0 -6
  139. data/app/views/alchemy/admin/pages/_internal_link.html.erb +1 -1
  140. data/app/views/alchemy/admin/pages/_tinymce_custom_config.html.erb +5 -2
  141. data/app/views/alchemy/admin/pages/_toolbar.html.erb +1 -1
  142. data/app/views/alchemy/admin/pages/edit.html.erb +36 -24
  143. data/app/views/alchemy/admin/pages/index.html.erb +2 -9
  144. data/app/views/alchemy/admin/partials/_remote_search_form.html.erb +2 -4
  145. data/app/views/alchemy/admin/partials/_routes.html.erb +7 -11
  146. data/app/views/alchemy/admin/partials/_search_form.html.erb +9 -0
  147. data/app/views/alchemy/admin/pictures/_archive.html.erb +1 -1
  148. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -1
  149. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +5 -7
  150. data/app/views/alchemy/admin/pictures/_infos.html.erb +0 -1
  151. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +4 -4
  152. data/app/views/alchemy/admin/pictures/assign.js.erb +10 -0
  153. data/app/views/alchemy/admin/pictures/index.html.erb +8 -3
  154. data/app/views/alchemy/admin/resources/_filter.html.erb +12 -0
  155. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +14 -17
  156. data/app/views/alchemy/admin/resources/_form.html.erb +3 -0
  157. data/app/views/alchemy/admin/resources/_table_header.html.erb +15 -0
  158. data/app/views/alchemy/admin/resources/index.html.erb +3 -11
  159. data/app/views/alchemy/essences/_essence_audio_editor.html.erb +4 -0
  160. data/app/views/alchemy/essences/_essence_audio_view.html.erb +15 -0
  161. data/app/views/alchemy/essences/_essence_file_editor.html.erb +15 -6
  162. data/app/views/alchemy/essences/_essence_headline_editor.html.erb +36 -0
  163. data/app/views/alchemy/essences/_essence_headline_view.html.erb +10 -0
  164. data/app/views/alchemy/essences/_essence_link_editor.html.erb +8 -4
  165. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +27 -12
  166. data/app/views/alchemy/essences/_essence_picture_view.html.erb +3 -3
  167. data/app/views/alchemy/essences/_essence_text_editor.html.erb +12 -4
  168. data/app/views/alchemy/essences/_essence_video_editor.html.erb +4 -0
  169. data/app/views/alchemy/essences/_essence_video_view.html.erb +18 -0
  170. data/app/views/alchemy/essences/shared/_essence_picture_tools.html.erb +21 -16
  171. data/app/views/alchemy/essences/shared/_linkable_essence_tools.html.erb +2 -2
  172. data/app/views/alchemy/ingredients/_audio_editor.html.erb +5 -0
  173. data/app/views/alchemy/ingredients/_audio_view.html.erb +14 -0
  174. data/app/views/alchemy/ingredients/_boolean_editor.html.erb +11 -0
  175. data/app/views/alchemy/ingredients/_boolean_view.html.erb +1 -0
  176. data/app/views/alchemy/ingredients/_datetime_editor.html.erb +17 -0
  177. data/app/views/alchemy/ingredients/_datetime_view.html.erb +9 -0
  178. data/app/views/alchemy/ingredients/_file_editor.html.erb +52 -0
  179. data/app/views/alchemy/ingredients/_file_view.html.erb +17 -0
  180. data/app/views/alchemy/ingredients/_headline_editor.html.erb +30 -0
  181. data/app/views/alchemy/ingredients/_headline_view.html.erb +9 -0
  182. data/app/views/alchemy/ingredients/_html_editor.html.erb +8 -0
  183. data/app/views/alchemy/ingredients/_html_view.html.erb +1 -0
  184. data/app/views/alchemy/ingredients/_link_editor.html.erb +24 -0
  185. data/app/views/alchemy/ingredients/_link_view.html.erb +9 -0
  186. data/app/views/alchemy/ingredients/_node_editor.html.erb +26 -0
  187. data/app/views/alchemy/ingredients/_node_view.html.erb +1 -0
  188. data/app/views/alchemy/ingredients/_page_editor.html.erb +25 -0
  189. data/app/views/alchemy/ingredients/_page_view.html.erb +4 -0
  190. data/app/views/alchemy/ingredients/_picture_editor.html.erb +60 -0
  191. data/app/views/alchemy/ingredients/_picture_view.html.erb +5 -0
  192. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +12 -0
  193. data/app/views/alchemy/ingredients/_richtext_view.html.erb +3 -0
  194. data/app/views/alchemy/ingredients/_select_editor.html.erb +30 -0
  195. data/app/views/alchemy/ingredients/_select_view.html.erb +1 -0
  196. data/app/views/alchemy/ingredients/_text_editor.html.erb +20 -0
  197. data/app/views/alchemy/ingredients/_text_view.html.erb +16 -0
  198. data/app/views/alchemy/ingredients/_video_editor.html.erb +5 -0
  199. data/app/views/alchemy/ingredients/_video_view.html.erb +17 -0
  200. data/app/views/alchemy/ingredients/shared/_link_tools.html.erb +20 -0
  201. data/app/views/alchemy/ingredients/shared/_picture_tools.html.erb +57 -0
  202. data/config/brakeman.ignore +66 -159
  203. data/config/initializers/dragonfly.rb +10 -0
  204. data/config/locales/alchemy.en.yml +108 -64
  205. data/config/routes.rb +17 -22
  206. data/db/migrate/20201207131309_create_page_versions.rb +19 -0
  207. data/db/migrate/20201207135820_add_page_version_id_to_alchemy_elements.rb +76 -0
  208. data/db/migrate/20210205143548_rename_public_on_and_public_until_on_alchemy_pages.rb +10 -0
  209. data/db/migrate/20210326105046_add_sanitized_body_to_alchemy_essence_richtexts.rb +7 -0
  210. data/db/migrate/20210406093436_add_alchemy_essence_headlines.rb +12 -0
  211. data/db/migrate/20210506135919_create_essence_audios.rb +19 -0
  212. data/db/migrate/20210506140258_create_essence_videos.rb +23 -0
  213. data/db/migrate/20210508091432_create_alchemy_ingredients.rb +22 -0
  214. data/lib/alchemy/admin/preview_url.rb +2 -0
  215. data/lib/alchemy/deprecation.rb +1 -1
  216. data/lib/alchemy/dragonfly/processors/auto_orient.rb +18 -0
  217. data/lib/alchemy/dragonfly/processors/crop_resize.rb +35 -0
  218. data/lib/alchemy/elements_finder.rb +14 -60
  219. data/lib/alchemy/essence.rb +1 -2
  220. data/lib/alchemy/forms/builder.rb +21 -1
  221. data/lib/alchemy/hints.rb +8 -4
  222. data/lib/alchemy/page_layout.rb +0 -13
  223. data/lib/alchemy/permissions.rb +30 -29
  224. data/lib/alchemy/resource.rb +13 -3
  225. data/lib/alchemy/resource_filter.rb +40 -0
  226. data/lib/alchemy/resources_helper.rb +1 -16
  227. data/lib/alchemy/tasks/tidy.rb +29 -0
  228. data/lib/alchemy/test_support.rb +2 -11
  229. data/lib/alchemy/test_support/essence_shared_examples.rb +0 -1
  230. data/lib/alchemy/test_support/factories/element_factory.rb +8 -8
  231. data/lib/alchemy/test_support/factories/essence_audio_factory.rb +7 -0
  232. data/lib/alchemy/test_support/factories/essence_video_factory.rb +7 -0
  233. data/lib/alchemy/test_support/factories/ingredient_factory.rb +25 -0
  234. data/lib/alchemy/test_support/factories/page_factory.rb +20 -1
  235. data/lib/alchemy/test_support/factories/page_version_factory.rb +23 -0
  236. data/lib/alchemy/test_support/having_crop_action_examples.rb +170 -0
  237. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +646 -0
  238. data/lib/alchemy/test_support/shared_ingredient_editor_examples.rb +21 -0
  239. data/lib/alchemy/test_support/shared_ingredient_examples.rb +75 -0
  240. data/lib/alchemy/tinymce.rb +17 -0
  241. data/lib/alchemy/upgrader/six_point_zero.rb +21 -0
  242. data/lib/alchemy/upgrader/tasks/add_page_versions.rb +33 -0
  243. data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +62 -0
  244. data/lib/alchemy/version.rb +1 -1
  245. data/lib/alchemy_cms.rb +1 -0
  246. data/lib/generators/alchemy/elements/elements_generator.rb +1 -0
  247. data/lib/generators/alchemy/elements/templates/view.html.erb +9 -0
  248. data/lib/generators/alchemy/elements/templates/view.html.haml +9 -0
  249. data/lib/generators/alchemy/elements/templates/view.html.slim +9 -0
  250. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +38 -0
  251. data/lib/generators/alchemy/ingredient/templates/editor.html.erb +14 -0
  252. data/lib/generators/alchemy/ingredient/templates/model.rb.tt +13 -0
  253. data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -0
  254. data/lib/generators/alchemy/install/templates/dragonfly.rb.tt +1 -1
  255. data/lib/generators/alchemy/menus/templates/node.html.erb +1 -1
  256. data/lib/generators/alchemy/menus/templates/node.html.haml +1 -1
  257. data/lib/generators/alchemy/menus/templates/node.html.slim +1 -1
  258. data/lib/generators/alchemy/menus/templates/wrapper.html.erb +1 -1
  259. data/lib/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
  260. data/lib/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
  261. data/lib/tasks/alchemy/thumbnails.rake +4 -2
  262. data/lib/tasks/alchemy/tidy.rake +12 -0
  263. data/lib/tasks/alchemy/upgrade.rake +26 -0
  264. data/package.json +3 -2
  265. data/package/admin.js +11 -1
  266. data/package/src/__tests__/i18n.spec.js +23 -0
  267. data/package/src/file_editors.js +28 -0
  268. data/package/src/i18n.js +1 -3
  269. data/package/src/image_cropper.js +103 -0
  270. data/package/src/image_loader.js +58 -0
  271. data/package/src/node_tree.js +5 -5
  272. data/package/src/picture_editors.js +169 -0
  273. data/package/src/utils/__tests__/ajax.spec.js +20 -12
  274. data/package/src/utils/ajax.js +8 -3
  275. data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +3 -18
  276. data/vendor/assets/stylesheets/jquery.Jcrop.min.scss +2 -28
  277. metadata +292 -55
  278. data/app/assets/javascripts/alchemy/alchemy.image_cropper.js.coffee +0 -44
  279. data/app/assets/javascripts/alchemy/alchemy.trash_window.js.coffee +0 -30
  280. data/app/assets/stylesheets/alchemy/trash.scss +0 -8
  281. data/app/controllers/alchemy/admin/trash_controller.rb +0 -44
  282. data/app/views/alchemy/admin/attachments/_filter_bar.html.erb +0 -29
  283. data/app/views/alchemy/admin/essence_files/assign.js.erb +0 -3
  284. data/app/views/alchemy/admin/essence_pictures/assign.js.erb +0 -4
  285. data/app/views/alchemy/admin/essence_pictures/crop.html.erb +0 -48
  286. data/app/views/alchemy/admin/pictures/_filter_bar.html.erb +0 -30
  287. data/app/views/alchemy/admin/trash/clear.js.erb +0 -4
  288. data/app/views/alchemy/admin/trash/index.html.erb +0 -31
  289. data/lib/alchemy/test_support/factories.rb +0 -16
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples_for "having crop action" do |args|
4
+ describe "#crop" do
5
+ let(:picture) { Alchemy::Picture.new }
6
+
7
+ before do
8
+ expect(args[:model_class]).to receive(:find).and_return(croppable_resource)
9
+ end
10
+
11
+ context "with no picture assigned" do
12
+ it "renders error message" do
13
+ get :crop, params: { id: 1 }
14
+ expect(assigns(:no_image_notice)).to eq(Alchemy.t(:no_image_for_cropper_found))
15
+ end
16
+ end
17
+
18
+ context "with picture assigned" do
19
+ subject { get :crop, params: { id: 1, picture_id: picture.id } }
20
+
21
+ let(:default_mask) do
22
+ [
23
+ 0,
24
+ 0,
25
+ 300,
26
+ 250,
27
+ ]
28
+ end
29
+
30
+ let(:settings) { {} }
31
+
32
+ before do
33
+ picture.image_file_width = 300
34
+ picture.image_file_height = 250
35
+ allow(croppable_resource).to receive(:settings) { settings }
36
+ expect(Alchemy::Picture).to receive(:find_by) { picture }
37
+ end
38
+
39
+ context "with no render_size present in croppable_resource" do
40
+ before do
41
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return(nil)
42
+ end
43
+
44
+ context "with sizes in settings" do
45
+ let(:settings) do
46
+ { size: "300x250" }
47
+ end
48
+
49
+ it "sets sizes to given values" do
50
+ subject
51
+ expect(assigns(:settings)[:min_size]).to eq([300, 250])
52
+ end
53
+ end
54
+
55
+ context "with no sizes in settings" do
56
+ it "sets sizes to zero" do
57
+ subject
58
+ expect(assigns(:settings)[:min_size]).to eq([0, 0])
59
+ end
60
+ end
61
+ end
62
+
63
+ context "with render_size present in croppable_resource" do
64
+ it "sets sizes from these values" do
65
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("30x25")
66
+
67
+ subject
68
+ expect(assigns(:settings)[:min_size]).to eq([30, 25])
69
+ end
70
+
71
+ context "when width or height is not fixed" do
72
+ it "infers the height from the image file preserving the aspect ratio" do
73
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("30x")
74
+
75
+ subject
76
+ expect(assigns(:settings)[:min_size]).to eq([30, 25])
77
+ end
78
+
79
+ context "and aspect ratio set on the settings" do
80
+ let(:settings) do
81
+ { fixed_ratio: "2" }
82
+ end
83
+
84
+ it "does not infer the height from the image file preserving the aspect ratio" do
85
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("x25")
86
+
87
+ subject
88
+ expect(assigns(:settings)[:min_size]).to eq([50, 25])
89
+ end
90
+ end
91
+ end
92
+
93
+ context "when width or height is not fixed and an aspect ratio is given" do
94
+ context "and aspect ratio set on the settings" do
95
+ let(:settings) do
96
+ { fixed_ratio: "0.5" }
97
+ end
98
+
99
+ it "width is given, it infers the height from width and ratio" do
100
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("30x")
101
+
102
+ subject
103
+ expect(assigns(:settings)[:min_size]).to eq([30, 60])
104
+ end
105
+ end
106
+
107
+ it "infers the height from the image file preserving the aspect ratio" do
108
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("x25")
109
+
110
+ subject
111
+ expect(assigns(:settings)[:min_size]).to eq([30, 25])
112
+ end
113
+ end
114
+ end
115
+
116
+ context "no crop sizes present in croppable_resource" do
117
+ it "assigns default mask boxes" do
118
+ subject
119
+ expect(assigns(:settings)[:default_box]).to eq(default_mask)
120
+ end
121
+ end
122
+
123
+ context "crop sizes present in croppable_resource" do
124
+ let(:mask) { [0, 0, 120, 160] }
125
+
126
+ before do
127
+ allow(croppable_resource).to receive(:crop_from).and_return("0x0")
128
+ allow(croppable_resource).to receive(:crop_size).and_return("120x160")
129
+ end
130
+
131
+ it "assigns cropping boxes" do
132
+ subject
133
+ expect(assigns(:settings)[:default_box]).to eq(default_mask)
134
+ end
135
+ end
136
+
137
+ context "with fixed_ratio set to false" do
138
+ let(:settings) do
139
+ { fixed_ratio: false }
140
+ end
141
+
142
+ it "sets ratio to false" do
143
+ subject
144
+ expect(assigns(:settings)[:ratio]).to eq(false)
145
+ end
146
+ end
147
+
148
+ context "with fixed_ratio set to a non float string" do
149
+ let(:settings) do
150
+ { fixed_ratio: "123,45" }
151
+ end
152
+
153
+ it "raises error" do
154
+ expect { subject }.to raise_error(ArgumentError)
155
+ end
156
+ end
157
+
158
+ context "with no fixed_ratio set" do
159
+ let(:settings) do
160
+ { size: "80x60" }
161
+ end
162
+
163
+ it "sets a fixed ratio from sizes" do
164
+ subject
165
+ expect(assigns(:settings)[:ratio]).to eq(80.0 / 60.0)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,646 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples_for "having picture thumbnails" do
4
+ it "should not store negative values for crop values" do
5
+ record.crop_from = "-1x100"
6
+ record.crop_size = "-20x30"
7
+ record.save
8
+ expect(record.crop_from).to eq("0x100")
9
+ expect(record.crop_size).to eq("0x30")
10
+ end
11
+
12
+ it "should not store float values for crop values" do
13
+ record.crop_from = "0.05x104.5"
14
+ record.crop_size = "99.5x203.4"
15
+ record.save
16
+ expect(record.crop_from).to eq("0x105")
17
+ expect(record.crop_size).to eq("100x203")
18
+ end
19
+
20
+ it "should not store empty strings for nil crop values" do
21
+ record.crop_from = nil
22
+ record.crop_size = nil
23
+ record.save
24
+ expect(record.crop_from).to eq(nil)
25
+ expect(record.crop_size).to eq(nil)
26
+ end
27
+
28
+ describe "#picture_url" do
29
+ subject(:picture_url) { record.picture_url(options) }
30
+
31
+ let(:options) { {} }
32
+ let(:picture) { create(:alchemy_picture) }
33
+
34
+ context "with no format in the options" do
35
+ it "includes the image's default render format." do
36
+ expect(picture_url).to match(/\.png/)
37
+ end
38
+ end
39
+
40
+ context "with format in the options" do
41
+ let(:options) { { format: "gif" } }
42
+
43
+ it "takes this as format." do
44
+ expect(picture_url).to match(/\.gif/)
45
+ end
46
+ end
47
+
48
+ context "when crop values are present" do
49
+ before do
50
+ allow(record).to receive(:crop_from) { "10x10" }
51
+ allow(record).to receive(:crop_size) { "200x200" }
52
+ end
53
+
54
+ context "if cropping is enabled" do
55
+ before do
56
+ allow(record).to receive(:settings) { { crop: true } }
57
+ end
58
+
59
+ it "passes these crop values to the picture's url method." do
60
+ expect(picture).to receive(:url).with(
61
+ hash_including(crop_from: "10x10", crop_size: "200x200"),
62
+ )
63
+ picture_url
64
+ end
65
+
66
+ context "but with crop values in the options" do
67
+ let(:options) do
68
+ { crop_from: "30x30", crop_size: "75x75" }
69
+ end
70
+
71
+ it "passes these crop values instead." do
72
+ expect(picture).to receive(:url).with(
73
+ hash_including(crop_from: "30x30", crop_size: "75x75"),
74
+ )
75
+ picture_url
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ context "with other options" do
82
+ let(:options) { { foo: "baz" } }
83
+
84
+ context "and the image does not need to be processed" do
85
+ before do
86
+ allow(record).to receive(:settings) { {} }
87
+ end
88
+
89
+ it "adds them to the url" do
90
+ expect(picture_url).to match(/\?foo=baz/)
91
+ end
92
+ end
93
+ end
94
+
95
+ context "without picture assigned" do
96
+ let(:picture) { nil }
97
+
98
+ it { is_expected.to be_nil }
99
+ end
100
+
101
+ context "if picture.url returns nil" do
102
+ before do
103
+ expect(picture).to receive(:url) { nil }
104
+ end
105
+
106
+ it "returns missing image url" do
107
+ is_expected.to eq "missing-image.png"
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "#picture_url_options" do
113
+ subject(:picture_url_options) { record.picture_url_options }
114
+
115
+ let(:picture) { build_stubbed(:alchemy_picture) }
116
+
117
+ it { is_expected.to be_a(HashWithIndifferentAccess) }
118
+
119
+ it "includes the pictures default render format." do
120
+ expect(picture).to receive(:default_render_format) { "img" }
121
+ expect(picture_url_options[:format]).to eq("img")
122
+ end
123
+
124
+ context "with crop values present" do
125
+ before do
126
+ allow(record).to receive(:crop_from) { "10x10" }
127
+ allow(record).to receive(:crop_size) { "200x200" }
128
+ end
129
+
130
+ context "with cropping enabled" do
131
+ before do
132
+ allow(record).to receive(:settings) { { crop: true } }
133
+ end
134
+
135
+ it "includes these crop values.", :aggregate_failures do
136
+ expect(picture_url_options[:crop_from]).to eq "10x10"
137
+ expect(picture_url_options[:crop_size]).to eq "200x200"
138
+ end
139
+ end
140
+
141
+ context "with cropping disabled" do
142
+ before do
143
+ allow(record).to receive(:settings) { { crop: nil } }
144
+ end
145
+
146
+ it "does not include these crop values.", :aggregate_failures do
147
+ expect(picture_url_options[:crop_from]).to be_nil
148
+ expect(picture_url_options[:crop_size]).to be_nil
149
+ end
150
+ end
151
+
152
+ # Regression spec for issue #1279
153
+ context "with crop values being empty strings" do
154
+ before do
155
+ allow(record).to receive(:crop_from) { "" }
156
+ allow(record).to receive(:crop_size) { "" }
157
+ end
158
+
159
+ it "does not include these crop values.", :aggregate_failures do
160
+ expect(picture_url_options[:crop_from]).to be_nil
161
+ expect(picture_url_options[:crop_size]).to be_nil
162
+ end
163
+ end
164
+ end
165
+
166
+ context "having size setting" do
167
+ before do
168
+ allow(record).to receive(:settings) { { size: "30x70" } }
169
+ end
170
+
171
+ it "includes this size." do
172
+ expect(picture_url_options[:size]).to eq "30x70"
173
+ end
174
+ end
175
+
176
+ context "having crop setting" do
177
+ before do
178
+ allow(record).to receive(:settings) { { crop: true } }
179
+ end
180
+
181
+ it "includes this setting" do
182
+ expect(picture_url_options[:crop]).to be true
183
+ end
184
+ end
185
+
186
+ context "without picture assigned" do
187
+ let(:picture) { nil }
188
+
189
+ it { is_expected.to be_a(Hash) }
190
+ end
191
+ end
192
+
193
+ describe "#thumbnail_url" do
194
+ subject(:thumbnail_url) { record.thumbnail_url }
195
+
196
+ let(:settings) do
197
+ {}
198
+ end
199
+
200
+ let(:picture) { create(:alchemy_picture) }
201
+
202
+ before do
203
+ allow(record).to receive(:settings) { settings }
204
+ end
205
+
206
+ it "includes the image's original file format." do
207
+ expect(thumbnail_url).to match(/\.png/)
208
+ end
209
+
210
+ it "flattens the image." do
211
+ expect(picture).to receive(:url).with(hash_including(flatten: true))
212
+ thumbnail_url
213
+ end
214
+
215
+ context "when crop is enabled in the settings" do
216
+ let(:settings) do
217
+ { crop: true }
218
+ end
219
+
220
+ context "and crop sizes are present" do
221
+ before do
222
+ allow(record).to receive(:crop_size).and_return("200x200")
223
+ allow(record).to receive(:crop_from).and_return("10x10")
224
+ end
225
+
226
+ it "passes these crop sizes to the picture's url method." do
227
+ expect(picture).to receive(:url).with(
228
+ hash_including(
229
+ crop_from: "10x10",
230
+ crop_size: "200x200",
231
+ crop: true,
232
+ ),
233
+ )
234
+ thumbnail_url
235
+ end
236
+ end
237
+
238
+ context "when no crop sizes are present" do
239
+ it "it does not pass crop sizes to the picture's url method and enables center cropping." do
240
+ expect(picture).to receive(:url).with(
241
+ hash_including(
242
+ crop_from: nil,
243
+ crop_size: nil,
244
+ crop: true,
245
+ ),
246
+ )
247
+ thumbnail_url
248
+ end
249
+ end
250
+ end
251
+
252
+ context "when cropping is disabled in the settings" do
253
+ let(:settings) do
254
+ { crop: false }
255
+ end
256
+
257
+ context "but crop sizes are present" do
258
+ before do
259
+ allow(record).to receive(:crop_size).and_return("200x200")
260
+ allow(record).to receive(:crop_from).and_return("10x10")
261
+ end
262
+
263
+ it "it disables cropping." do
264
+ expect(picture).to receive(:url).with(
265
+ hash_including(
266
+ crop_size: nil,
267
+ crop_from: nil,
268
+ crop: false,
269
+ ),
270
+ )
271
+ thumbnail_url
272
+ end
273
+ end
274
+ end
275
+
276
+ context "without picture assigned" do
277
+ let(:picture) { nil }
278
+
279
+ it { is_expected.to be_nil }
280
+ end
281
+
282
+ context "if picture.url returns nil" do
283
+ before do
284
+ expect(picture).to receive(:url) { nil }
285
+ end
286
+
287
+ it "returns missing image url" do
288
+ is_expected.to eq "alchemy/missing-image.svg"
289
+ end
290
+ end
291
+ end
292
+
293
+ describe "#thumbnail_url_options" do
294
+ subject(:thumbnail_url_options) { record.thumbnail_url_options }
295
+
296
+ let(:settings) { {} }
297
+ let(:picture) { nil }
298
+
299
+ before do
300
+ allow(record).to receive(:settings) { settings }
301
+ end
302
+
303
+ context "with picture assigned" do
304
+ let(:picture) do
305
+ create(:alchemy_picture)
306
+ end
307
+
308
+ it "includes the image's original file format." do
309
+ expect(thumbnail_url_options[:format]).to eq("png")
310
+ end
311
+
312
+ it "flattens the image." do
313
+ expect(thumbnail_url_options[:flatten]).to be(true)
314
+ end
315
+ end
316
+
317
+ context "when cropping is enabled in settings" do
318
+ let(:settings) do
319
+ { crop: true }
320
+ end
321
+
322
+ context "and crop values are present" do
323
+ before do
324
+ expect(record).to receive(:crop_size).at_least(:once) { "200x200" }
325
+ expect(record).to receive(:crop_from).at_least(:once) { "10x10" }
326
+ end
327
+
328
+ it "includes these crop values" do
329
+ expect(thumbnail_url_options).to match(
330
+ hash_including(
331
+ crop_from: "10x10",
332
+ crop_size: "200x200",
333
+ crop: true,
334
+ )
335
+ )
336
+ end
337
+ end
338
+
339
+ context "and no crop values are present" do
340
+ it "does not include crop values but enables center cropping" do
341
+ expect(thumbnail_url_options).to match(
342
+ hash_including(
343
+ crop_from: nil,
344
+ crop_size: nil,
345
+ crop: true,
346
+ )
347
+ )
348
+ end
349
+ end
350
+ end
351
+
352
+ context "when cropping is disabled in settings" do
353
+ let(:settings) do
354
+ { crop: false }
355
+ end
356
+
357
+ context "but crop values are present" do
358
+ before do
359
+ allow(record).to receive(:crop_size) { "200x200" }
360
+ allow(record).to receive(:crop_from) { "10x10" }
361
+ end
362
+
363
+ it "does not include crop values" do
364
+ expect(thumbnail_url_options).to match(
365
+ hash_including(
366
+ crop_from: nil,
367
+ crop_size: nil,
368
+ crop: false,
369
+ )
370
+ )
371
+ end
372
+ end
373
+ end
374
+
375
+ context "without picture assigned" do
376
+ let(:picture) { nil }
377
+
378
+ it "returns default thumbnail options" do
379
+ is_expected.to eq(
380
+ crop: false,
381
+ crop_from: nil,
382
+ crop_size: nil,
383
+ flatten: true,
384
+ format: "jpg",
385
+ size: "160x120",
386
+ )
387
+ end
388
+ end
389
+ end
390
+
391
+ describe "#image_cropper_settings" do
392
+ let(:picture) { nil }
393
+
394
+ subject { record.image_cropper_settings }
395
+
396
+ context "with no picture assigned" do
397
+ it { is_expected.to eq({}) }
398
+ end
399
+
400
+ context "with picture assigned" do
401
+ let(:picture) { build_stubbed(:alchemy_picture) }
402
+
403
+ let(:default_mask) do
404
+ [
405
+ 0,
406
+ 0,
407
+ 300,
408
+ 250,
409
+ ]
410
+ end
411
+
412
+ let(:settings) { {} }
413
+
414
+ before do
415
+ picture.image_file_width = 300
416
+ picture.image_file_height = 250
417
+ allow(record).to receive(:settings) { settings }
418
+ end
419
+
420
+ context "with no render_size present" do
421
+ before do
422
+ expect(record).to receive(:render_size).at_least(:once).and_return(nil)
423
+ end
424
+
425
+ context "with sizes in settings" do
426
+ let(:settings) do
427
+ { size: "300x250" }
428
+ end
429
+
430
+ it "sets sizes to given values" do
431
+ expect(subject[:min_size]).to eq([300, 250])
432
+ end
433
+ end
434
+
435
+ context "with no sizes in settings" do
436
+ it "sets sizes to zero" do
437
+ expect(subject[:min_size]).to eq([0, 0])
438
+ end
439
+ end
440
+ end
441
+
442
+ context "with render_size present in record" do
443
+ it "sets sizes from these values" do
444
+ expect(record).to receive(:render_size).at_least(:once).and_return("30x25")
445
+ expect(subject[:min_size]).to eq([30, 25])
446
+ end
447
+
448
+ context "when width or height is not fixed" do
449
+ it "infers the height from the image file preserving the aspect ratio" do
450
+ expect(record).to receive(:render_size).at_least(:once).and_return("30x")
451
+ expect(subject[:min_size]).to eq([30, 25])
452
+ end
453
+
454
+ context "and aspect ratio set" do
455
+ let(:settings) do
456
+ { fixed_ratio: "2" }
457
+ end
458
+
459
+ it "does not infer the height from the image file preserving the aspect ratio" do
460
+ expect(record).to receive(:render_size).at_least(:once).and_return("x25")
461
+ expect(subject[:min_size]).to eq([50, 25])
462
+ end
463
+ end
464
+ end
465
+
466
+ context "when width or height is not fixed and an aspect ratio is given" do
467
+ context "and aspect ratio set" do
468
+ let(:settings) do
469
+ { fixed_ratio: "0.5" }
470
+ end
471
+
472
+ it "width is given, it infers the height from width and ratio" do
473
+ expect(record).to receive(:render_size).at_least(:once).and_return("30x")
474
+ expect(subject[:min_size]).to eq([30, 60])
475
+ end
476
+ end
477
+
478
+ it "infers the height from the image file preserving the aspect ratio" do
479
+ expect(record).to receive(:render_size).at_least(:once).and_return("x25")
480
+ expect(subject[:min_size]).to eq([30, 25])
481
+ end
482
+ end
483
+ end
484
+
485
+ context "no crop sizes present in record" do
486
+ it "assigns default mask boxes" do
487
+ expect(subject[:default_box]).to eq(default_mask)
488
+ end
489
+ end
490
+
491
+ context "crop sizes present in record" do
492
+ let(:mask) { [0, 0, 120, 160] }
493
+
494
+ before do
495
+ allow(record).to receive(:crop_from).and_return("0x0")
496
+ allow(record).to receive(:crop_size).and_return("120x160")
497
+ end
498
+
499
+ it "assigns cropping boxes" do
500
+ expect(subject[:default_box]).to eq(default_mask)
501
+ end
502
+ end
503
+
504
+ context "with fixed_ratio set to false" do
505
+ let(:settings) do
506
+ { fixed_ratio: false }
507
+ end
508
+
509
+ it "sets ratio to false" do
510
+ expect(subject[:ratio]).to eq(false)
511
+ end
512
+ end
513
+
514
+ context "with fixed_ratio set to a non float string" do
515
+ let(:settings) do
516
+ { fixed_ratio: "123,45" }
517
+ end
518
+
519
+ it "raises an error" do
520
+ expect { subject }.to raise_exception(ArgumentError)
521
+ end
522
+ end
523
+
524
+ context "with no fixed_ratio set" do
525
+ let(:settings) do
526
+ { size: "80x60" }
527
+ end
528
+
529
+ it "sets a fixed ratio from sizes" do
530
+ expect(subject[:ratio]).to eq(80.0 / 60.0)
531
+ end
532
+ end
533
+
534
+ context "with size set to different values" do
535
+ let(:settings) { { crop: true, size: size } }
536
+
537
+ before do
538
+ picture.image_file_width = 200
539
+ picture.image_file_height = 100
540
+ end
541
+
542
+ context "size 200x50" do
543
+ let(:size) { "200x50" }
544
+
545
+ it "default box should be [0, 25, 200, 75]" do
546
+ expect(subject[:default_box]).to eq([0, 25, 200, 75])
547
+ end
548
+ end
549
+
550
+ context "size 0x0" do
551
+ let(:size) { "0x0" }
552
+
553
+ it "it should not crop the picture" do
554
+ expect(subject[:default_box]).to eq([0, 0, 200, 100])
555
+ end
556
+ end
557
+
558
+ context "size 50x100" do
559
+ let(:size) { "50x100" }
560
+
561
+ it "the hash should be {x1: 75, y1: 0, x2: 125, y2: 100}" do
562
+ expect(subject[:default_box]).to eq([75, 0, 125, 100])
563
+ end
564
+ end
565
+
566
+ context "size 50x50" do
567
+ let(:size) { "50x50" }
568
+
569
+ it "the hash should be {x1: 50, y1: 0, x2: 150, y2: 100}" do
570
+ expect(subject[:default_box]).to eq([50, 0, 150, 100])
571
+ end
572
+ end
573
+
574
+ context "size 400x200" do
575
+ let(:size) { "400x200" }
576
+
577
+ it "the hash should be {x1: 0, y1: 0, x2: 200, y2: 100}" do
578
+ expect(subject[:default_box]).to eq([0, 0, 200, 100])
579
+ end
580
+ end
581
+
582
+ context "size 400x100" do
583
+ let(:size) { "400x100" }
584
+
585
+ it "the hash should be {x1: 0, y1: 25, x2: 200, y2: 75}" do
586
+ expect(subject[:default_box]).to eq([0, 25, 200, 75])
587
+ end
588
+ end
589
+
590
+ context "size 200x200" do
591
+ let(:size) { "200x200" }
592
+
593
+ it "the hash should be {x1: 50, y1: 0, x2: 150, y2: 100}" do
594
+ expect(subject[:default_box]).to eq([50, 0, 150, 100])
595
+ end
596
+ end
597
+ end
598
+ end
599
+ end
600
+
601
+ describe "#allow_image_cropping?" do
602
+ let(:picture) do
603
+ stub_model(Alchemy::Picture, image_file_width: 400, image_file_height: 300)
604
+ end
605
+
606
+ subject { record.allow_image_cropping? }
607
+
608
+ it { is_expected.to be_falsy }
609
+
610
+ context "with picture assigned" do
611
+ before do
612
+ allow(record).to receive(:picture) { picture }
613
+ end
614
+
615
+ it { is_expected.to be_falsy }
616
+
617
+ context "and with image larger than crop size" do
618
+ before do
619
+ allow(picture).to receive(:can_be_cropped_to?) { true }
620
+ end
621
+
622
+ it { is_expected.to be_falsy }
623
+
624
+ context "with crop set to true" do
625
+ before do
626
+ allow(record).to receive(:settings) { { crop: true } }
627
+ end
628
+
629
+ context "if picture.image_file is nil" do
630
+ before do
631
+ expect(picture).to receive(:image_file) { nil }
632
+ end
633
+
634
+ it { is_expected.to be_falsy }
635
+ end
636
+
637
+ context "if picture.image_file is present" do
638
+ let(:picture) { build_stubbed(:alchemy_picture) }
639
+
640
+ it { is_expected.to be(true) }
641
+ end
642
+ end
643
+ end
644
+ end
645
+ end
646
+ end