diff --git a/app/controllers/alchemy/admin/essence_pictures_controller.rb b/app/controllers/alchemy/admin/essence_pictures_controller.rb
index 7b8486008a..95769647fd 100644
--- a/app/controllers/alchemy/admin/essence_pictures_controller.rb
+++ b/app/controllers/alchemy/admin/essence_pictures_controller.rb
@@ -3,9 +3,11 @@
module Alchemy
module Admin
class EssencePicturesController < Alchemy::Admin::BaseController
+ include CropAction
+
authorize_resource class: Alchemy::EssencePicture
- before_action :load_essence_picture, only: [:edit, :crop, :update]
+ before_action :load_essence_picture, only: [:edit, :update]
before_action :load_content, only: [:edit, :update]
helper "alchemy/admin/contents"
@@ -15,16 +17,6 @@ class EssencePicturesController < Alchemy::Admin::BaseController
def edit
end
- def crop
- @picture = Picture.find_by(id: params[:picture_id])
- if @picture
- @essence_picture.picture = @picture
- @settings = @essence_picture.image_cropper_settings
- else
- @no_image_notice = Alchemy.t(:no_image_for_cropper_found)
- end
- end
-
def update
@essence_picture.update(essence_picture_params)
end
@@ -35,6 +27,10 @@ def load_essence_picture
@essence_picture = EssencePicture.find(params[:id])
end
+ def load_croppable_resource
+ @croppable_resource = EssencePicture.find(params[:id])
+ end
+
def load_content
@content = Content.find(params[:content_id])
end
diff --git a/app/controllers/concerns/alchemy/admin/crop_action.rb b/app/controllers/concerns/alchemy/admin/crop_action.rb
new file mode 100644
index 0000000000..d755fffbd3
--- /dev/null
+++ b/app/controllers/concerns/alchemy/admin/crop_action.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Alchemy
+ module Admin
+ module CropAction
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :load_croppable_resource, only: [:crop]
+ end
+
+ def crop
+ @picture = Alchemy::Picture.find_by(id: params[:picture_id])
+ if @picture
+ @croppable_resource.picture = @picture
+ @settings = @croppable_resource.image_cropper_settings
+ @element = @croppable_resource.element
+ else
+ @no_image_notice = Alchemy.t(:no_image_for_cropper_found)
+ end
+
+ render template: "alchemy/admin/crop"
+ end
+ end
+ end
+end
diff --git a/app/models/alchemy/essence_picture.rb b/app/models/alchemy/essence_picture.rb
index 8a66b2f9c6..2d26f12cbc 100644
--- a/app/models/alchemy/essence_picture.rb
+++ b/app/models/alchemy/essence_picture.rb
@@ -23,6 +23,8 @@
module Alchemy
class EssencePicture < BaseRecord
+ include Alchemy::PictureThumbnails
+
acts_as_essence ingredient_column: :picture, belongs_to: {
class_name: "Alchemy::Picture",
foreign_key: :picture_id,
@@ -30,86 +32,9 @@ class EssencePicture < BaseRecord
optional: true,
}
- delegate :image_file_width, :image_file_height, :image_file, to: :picture, allow_nil: true
- before_save :fix_crop_values
- before_save :replace_newlines
-
- # The url to show the picture.
- #
- # Takes all values like +name+ and crop sizes (+crop_from+, +crop_size+ from the build in graphical image cropper)
- # and also adds the security token.
- #
- # You typically want to set the size the picture should be resized to.
- #
- # === Example:
- #
- # essence_picture.picture_url(size: '200x300', crop: true, format: 'gif')
- # # '/pictures/1/show/200x300/crop/cats.gif?sh=765rfghj'
- #
- # @option options size [String]
- # The size the picture should be resized to.
- #
- # @option options format [String]
- # The format the picture should be rendered in.
- # Defaults to the +image_output_format+ from the +Alchemy::Config+.
- #
- # @option options crop [Boolean]
- # If set to true the picture will be cropped to fit the size value.
- #
- # @return [String]
- def picture_url(options = {})
- return if picture.nil?
-
- picture.url(picture_url_options.merge(options)) || "missing-image.png"
- end
+ delegate :settings, to: :content
- # Picture rendering options
- #
- # Returns the +default_render_format+ of the associated +Alchemy::Picture+
- # together with the +crop_from+ and +crop_size+ values
- #
- # @return [HashWithIndifferentAccess]
- def picture_url_options
- return {} if picture.nil?
-
- crop = crop_values_present? || content.settings[:crop]
-
- {
- format: picture.default_render_format,
- crop: !!crop,
- crop_from: crop_from.presence,
- crop_size: crop_size.presence,
- size: content.settings[:size],
- }.with_indifferent_access
- end
-
- # Returns an url for the thumbnail representation of the assigned picture
- #
- # It takes cropping values into account, so it always represents the current
- # image displayed in the frontend.
- #
- # @return [String]
- def thumbnail_url
- return if picture.nil?
-
- picture.url(thumbnail_url_options) || "alchemy/missing-image.svg"
- end
-
- # Thumbnail rendering options
- #
- # @return [HashWithIndifferentAccess]
- def thumbnail_url_options
- crop = crop_values_present? || content.settings[:crop]
-
- {
- size: "160x120",
- crop: !!crop,
- crop_from: crop_from.presence || default_crop_from&.join("x"),
- crop_size: crop_size.presence || default_crop_size&.join("x"),
- flatten: true,
- format: picture&.image_file_format || "jpg",
- }
- end
+ before_save :replace_newlines
# The name of the picture used as preview text in element editor views.
#
@@ -130,101 +55,8 @@ def serialized_ingredient
picture_url(content.settings)
end
- # Show image cropping link for content
- def allow_image_cropping?
- content && content.settings[:crop] && picture &&
- picture.can_be_cropped_to?(
- content.settings[:size],
- content.settings[:upsample],
- ) && !!picture.image_file
- end
-
- def crop_values_present?
- crop_from.present? && crop_size.present?
- end
-
- # Settings for the graphical JS image cropper
-
- def image_cropper_settings
- Alchemy::ImageCropperSettings.new(
- render_size: dimensions_from_string(render_size.presence || content.settings[:size]),
- default_crop_from: default_crop_from,
- default_crop_size: default_crop_size,
- fixed_ratio: content.settings[:fixed_ratio],
- image_width: picture&.image_file_width,
- image_height: picture&.image_file_height,
- ).to_h
- end
-
private
- def fix_crop_values
- %i(crop_from crop_size).each do |crop_value|
- if self[crop_value].is_a?(String)
- write_attribute crop_value, normalize_crop_value(crop_value)
- end
- end
- end
-
- def default_crop_size
- return nil unless content.settings[:crop] && content.settings[:size]
-
- mask = inferred_dimensions_from_string(content.settings[:size])
- zoom = thumbnail_zoom_factor(mask)
- return nil if zoom.zero?
-
- [(mask[0] / zoom), (mask[1] / zoom)].map(&:round)
- end
-
- def thumbnail_zoom_factor(mask)
- [
- mask[0].to_f / (image_file_width || 1),
- mask[1].to_f / (image_file_height || 1),
- ].max
- end
-
- def default_crop_from
- return nil unless content.settings[:crop]
- return nil if default_crop_size.nil?
-
- [
- ((image_file_width || 0) - default_crop_size[0]) / 2,
- ((image_file_height || 0) - default_crop_size[1]) / 2,
- ].map(&:round)
- end
-
- def dimensions_from_string(string)
- return if string.nil?
-
- string.split("x", 2).map(&:to_i)
- end
-
- def inferred_dimensions_from_string(string)
- return if string.nil?
-
- width, height = dimensions_from_string(string)
- ratio = image_file_width.to_f / image_file_height.to_i
-
- if width.zero? && ratio.is_a?(Float)
- width = height * ratio
- end
-
- if height.zero? && ratio.is_a?(Float)
- height = width / ratio
- end
-
- [width.to_i, height.to_i]
- end
-
- def normalize_crop_value(crop_value)
- self[crop_value].split("x").map { |n| normalize_number(n) }.join("x")
- end
-
- def normalize_number(number)
- number = number.to_f.round
- number.negative? ? 0 : number
- end
-
def replace_newlines
return nil if caption.nil?
diff --git a/app/models/concerns/alchemy/picture_thumbnails.rb b/app/models/concerns/alchemy/picture_thumbnails.rb
new file mode 100644
index 0000000000..c6fc6578eb
--- /dev/null
+++ b/app/models/concerns/alchemy/picture_thumbnails.rb
@@ -0,0 +1,185 @@
+# frozen_string_literal: true
+
+module Alchemy
+ # Picture thumbnails and cropping concerns
+ module PictureThumbnails
+ extend ActiveSupport::Concern
+
+ included do
+ before_save :fix_crop_values
+
+ delegate :image_file_width, :image_file_height, :image_file, to: :picture, allow_nil: true
+ end
+
+ # The url to show the picture.
+ #
+ # Takes all values like +name+ and crop sizes (+crop_from+, +crop_size+ from the build in graphical image cropper)
+ # and also adds the security token.
+ #
+ # You typically want to set the size the picture should be resized to.
+ #
+ # === Example:
+ #
+ # essence_picture.picture_url(size: '200x300', crop: true, format: 'gif')
+ # # '/pictures/1/show/200x300/crop/cats.gif?sh=765rfghj'
+ #
+ # @option options size [String]
+ # The size the picture should be resized to.
+ #
+ # @option options format [String]
+ # The format the picture should be rendered in.
+ # Defaults to the +image_output_format+ from the +Alchemy::Config+.
+ #
+ # @option options crop [Boolean]
+ # If set to true the picture will be cropped to fit the size value.
+ #
+ # @return [String]
+ def picture_url(options = {})
+ return if picture.nil?
+
+ picture.url(picture_url_options.merge(options)) || "missing-image.png"
+ end
+
+ # Picture rendering options
+ #
+ # Returns the +default_render_format+ of the associated +Alchemy::Picture+
+ # together with the +crop_from+ and +crop_size+ values
+ #
+ # @return [HashWithIndifferentAccess]
+ def picture_url_options
+ return {} if picture.nil?
+
+ crop = crop_values_present? || settings[:crop]
+
+ {
+ format: picture.default_render_format,
+ crop: !!crop,
+ crop_from: crop_from.presence,
+ crop_size: crop_size.presence,
+ size: settings[:size],
+ }.with_indifferent_access
+ end
+
+ # Returns an url for the thumbnail representation of the assigned picture
+ #
+ # It takes cropping values into account, so it always represents the current
+ # image displayed in the frontend.
+ #
+ # @return [String]
+ def thumbnail_url
+ return if picture.nil?
+
+ picture.url(thumbnail_url_options) || "alchemy/missing-image.svg"
+ end
+
+ # Thumbnail rendering options
+ #
+ # @return [HashWithIndifferentAccess]
+ def thumbnail_url_options
+ crop = crop_values_present? || settings[:crop]
+
+ {
+ size: "160x120",
+ crop: !!crop,
+ crop_from: crop_from.presence || default_crop_from&.join("x"),
+ crop_size: crop_size.presence || default_crop_size&.join("x"),
+ flatten: true,
+ format: picture&.image_file_format || "jpg",
+ }
+ end
+
+ # Settings for the graphical JS image cropper
+ def image_cropper_settings
+ Alchemy::ImageCropperSettings.new(
+ render_size: dimensions_from_string(render_size.presence || settings[:size]),
+ default_crop_from: default_crop_from,
+ default_crop_size: default_crop_size,
+ fixed_ratio: settings[:fixed_ratio],
+ image_width: picture&.image_file_width,
+ image_height: picture&.image_file_height,
+ ).to_h
+ end
+
+ # Show image cropping link for content
+ def allow_image_cropping?
+ settings[:crop] && picture &&
+ picture.can_be_cropped_to?(
+ settings[:size],
+ settings[:upsample],
+ ) && !!picture.image_file
+ end
+
+ private
+
+ def crop_values_present?
+ crop_from.present? && crop_size.present?
+ end
+
+ def default_crop_size
+ return nil unless settings[:crop] && settings[:size]
+
+ mask = inferred_dimensions_from_string(settings[:size])
+ zoom = thumbnail_zoom_factor(mask)
+ return nil if zoom.zero?
+
+ [(mask[0] / zoom), (mask[1] / zoom)].map(&:round)
+ end
+
+ def thumbnail_zoom_factor(mask)
+ [
+ mask[0].to_f / (image_file_width || 1),
+ mask[1].to_f / (image_file_height || 1),
+ ].max
+ end
+
+ def default_crop_from
+ return nil unless settings[:crop]
+ return nil if default_crop_size.nil?
+
+ [
+ ((image_file_width || 0) - default_crop_size[0]) / 2,
+ ((image_file_height || 0) - default_crop_size[1]) / 2,
+ ].map(&:round)
+ end
+
+ def dimensions_from_string(string)
+ return if string.nil?
+
+ string.split("x", 2).map(&:to_i)
+ end
+
+ def inferred_dimensions_from_string(string)
+ return if string.nil?
+
+ width, height = dimensions_from_string(string)
+ ratio = image_file_width.to_f / image_file_height.to_i
+
+ if width.zero? && ratio.is_a?(Float)
+ width = height * ratio
+ end
+
+ if height.zero? && ratio.is_a?(Float)
+ height = width / ratio
+ end
+
+ [width.to_i, height.to_i]
+ end
+
+ def fix_crop_values
+ %i[crop_from crop_size].each do |crop_value|
+ if public_send(crop_value).is_a?(String)
+ public_send("#{crop_value}=", normalize_crop_value(crop_value))
+ end
+ end
+ end
+
+ def normalize_crop_value(crop_value)
+ public_send(crop_value).split("x").map { |n| normalize_number(n) }.join("x")
+ end
+
+ def normalize_number(number)
+ number = number.to_f.round
+ number.negative? ? 0 : number
+ end
+ end
+end
diff --git a/app/views/alchemy/admin/essence_pictures/crop.html.erb b/app/views/alchemy/admin/crop.html.erb
similarity index 96%
rename from app/views/alchemy/admin/essence_pictures/crop.html.erb
rename to app/views/alchemy/admin/crop.html.erb
index 8f596f1f9d..9a4050aec3 100644
--- a/app/views/alchemy/admin/essence_pictures/crop.html.erb
+++ b/app/views/alchemy/admin/crop.html.erb
@@ -29,7 +29,7 @@
"<%= params[:crop_from_form_field_id] %>",
"<%= params[:crop_size_form_field_id] %>",
],
- <%= @essence_picture.element.id %>
+ <%= @element.id %>
);
});
diff --git a/config/routes.rb b/config/routes.rb
index 52459034db..43500c4634 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -76,12 +76,14 @@
resources :essence_audios, only: [:edit, :update]
- resources :essence_pictures, only: [:edit, :update] do
+ concern :croppable do
member do
get :crop
end
end
+ resources :essence_pictures, only: [:edit, :update], concerns: [:croppable]
+
resources :essence_files, only: [:edit, :update]
resources :essence_videos, only: [:edit, :update]
diff --git a/lib/alchemy/test_support/having_crop_action_examples.rb b/lib/alchemy/test_support/having_crop_action_examples.rb
new file mode 100644
index 0000000000..e8c6bf6c56
--- /dev/null
+++ b/lib/alchemy/test_support/having_crop_action_examples.rb
@@ -0,0 +1,170 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for "having crop action" do |args|
+ describe "#crop" do
+ let(:picture) { Alchemy::Picture.new }
+
+ before do
+ expect(args[:model_class]).to receive(:find).and_return(croppable_resource)
+ end
+
+ context "with no picture assigned" do
+ it "renders error message" do
+ get :crop, params: { id: 1 }
+ expect(assigns(:no_image_notice)).to eq(Alchemy.t(:no_image_for_cropper_found))
+ end
+ end
+
+ context "with picture assigned" do
+ subject { get :crop, params: { id: 1, picture_id: picture.id } }
+
+ let(:default_mask) do
+ [
+ 0,
+ 0,
+ 300,
+ 250,
+ ]
+ end
+
+ let(:settings) { {} }
+
+ before do
+ picture.image_file_width = 300
+ picture.image_file_height = 250
+ allow(croppable_resource).to receive(:settings) { settings }
+ expect(Alchemy::Picture).to receive(:find_by) { picture }
+ end
+
+ context "with no render_size present in croppable_resource" do
+ before do
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return(nil)
+ end
+
+ context "with sizes in settings" do
+ let(:settings) do
+ { size: "300x250" }
+ end
+
+ it "sets sizes to given values" do
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([300, 250])
+ end
+ end
+
+ context "with no sizes in settings" do
+ it "sets sizes to zero" do
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([0, 0])
+ end
+ end
+ end
+
+ context "with render_size present in croppable_resource" do
+ it "sets sizes from these values" do
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("30x25")
+
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([30, 25])
+ end
+
+ context "when width or height is not fixed" do
+ it "infers the height from the image file preserving the aspect ratio" do
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("30x")
+
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([30, 25])
+ end
+
+ context "and aspect ratio set on the settings" do
+ let(:settings) do
+ { fixed_ratio: "2" }
+ end
+
+ it "does not infer the height from the image file preserving the aspect ratio" do
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("x25")
+
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([50, 25])
+ end
+ end
+ end
+
+ context "when width or height is not fixed and an aspect ratio is given" do
+ context "and aspect ratio set on the settings" do
+ let(:settings) do
+ { fixed_ratio: "0.5" }
+ end
+
+ it "width is given, it infers the height from width and ratio" do
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("30x")
+
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([30, 60])
+ end
+ end
+
+ it "infers the height from the image file preserving the aspect ratio" do
+ expect(croppable_resource).to receive(:render_size).at_least(:once).and_return("x25")
+
+ subject
+ expect(assigns(:settings)[:min_size]).to eq([30, 25])
+ end
+ end
+ end
+
+ context "no crop sizes present in croppable_resource" do
+ it "assigns default mask boxes" do
+ subject
+ expect(assigns(:settings)[:default_box]).to eq(default_mask)
+ end
+ end
+
+ context "crop sizes present in croppable_resource" do
+ let(:mask) { [0, 0, 120, 160] }
+
+ before do
+ allow(croppable_resource).to receive(:crop_from).and_return("0x0")
+ allow(croppable_resource).to receive(:crop_size).and_return("120x160")
+ end
+
+ it "assigns cropping boxes" do
+ subject
+ expect(assigns(:settings)[:default_box]).to eq(default_mask)
+ end
+ end
+
+ context "with fixed_ratio set to false" do
+ let(:settings) do
+ { fixed_ratio: false }
+ end
+
+ it "sets ratio to false" do
+ subject
+ expect(assigns(:settings)[:ratio]).to eq(false)
+ end
+ end
+
+ context "with fixed_ratio set to a non float string" do
+ let(:settings) do
+ { fixed_ratio: "123,45" }
+ end
+
+ it "raises error" do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "with no fixed_ratio set" do
+ let(:settings) do
+ { size: "80x60" }
+ end
+
+ it "sets a fixed ratio from sizes" do
+ subject
+ expect(assigns(:settings)[:ratio]).to eq(80.0 / 60.0)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/alchemy/test_support/having_picture_thumbnails_examples.rb b/lib/alchemy/test_support/having_picture_thumbnails_examples.rb
new file mode 100644
index 0000000000..c17a99f1c5
--- /dev/null
+++ b/lib/alchemy/test_support/having_picture_thumbnails_examples.rb
@@ -0,0 +1,581 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for "having picture thumbnails" do
+ it "should not store negative values for crop values" do
+ record.crop_from = "-1x100"
+ record.crop_size = "-20x30"
+ record.save
+ expect(record.crop_from).to eq("0x100")
+ expect(record.crop_size).to eq("0x30")
+ end
+
+ it "should not store float values for crop values" do
+ record.crop_from = "0.05x104.5"
+ record.crop_size = "99.5x203.4"
+ record.save
+ expect(record.crop_from).to eq("0x105")
+ expect(record.crop_size).to eq("100x203")
+ end
+
+ it "should not store empty strings for nil crop values" do
+ record.crop_from = nil
+ record.crop_size = nil
+ record.save
+ expect(record.crop_from).to eq(nil)
+ expect(record.crop_size).to eq(nil)
+ end
+
+ describe "#picture_url" do
+ subject(:picture_url) { record.picture_url(options) }
+
+ let(:options) { {} }
+ let(:picture) { create(:alchemy_picture) }
+
+ context "with no format in the options" do
+ it "includes the image's default render format." do
+ expect(picture_url).to match(/\.png/)
+ end
+ end
+
+ context "with format in the options" do
+ let(:options) { { format: "gif" } }
+
+ it "takes this as format." do
+ expect(picture_url).to match(/\.gif/)
+ end
+ end
+
+ context "when crop values are present" do
+ before do
+ allow(record).to receive(:crop_from) { "10x10" }
+ allow(record).to receive(:crop_size) { "200x200" }
+ end
+
+ it "passes these crop values to the picture's url method." do
+ expect(picture).to receive(:url).with(
+ hash_including(crop_from: "10x10", crop_size: "200x200"),
+ )
+ picture_url
+ end
+
+ context "but with crop values in the options" do
+ let(:options) do
+ { crop_from: "30x30", crop_size: "75x75" }
+ end
+
+ it "passes these crop values instead." do
+ expect(picture).to receive(:url).with(
+ hash_including(crop_from: "30x30", crop_size: "75x75"),
+ )
+ picture_url
+ end
+ end
+ end
+
+ context "with other options" do
+ let(:options) { { foo: "baz" } }
+
+ context "and the image does not need to be processed" do
+ before do
+ allow(record).to receive(:settings) { {} }
+ end
+
+ it "adds them to the url" do
+ expect(picture_url).to match(/\?foo=baz/)
+ end
+ end
+ end
+
+ context "without picture assigned" do
+ let(:picture) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context "if picture.url returns nil" do
+ before do
+ expect(picture).to receive(:url) { nil }
+ end
+
+ it "returns missing image url" do
+ is_expected.to eq "missing-image.png"
+ end
+ end
+ end
+
+ describe "#picture_url_options" do
+ subject(:picture_url_options) { record.picture_url_options }
+
+ let(:picture) { build_stubbed(:alchemy_picture) }
+
+ it { is_expected.to be_a(HashWithIndifferentAccess) }
+
+ it "includes the pictures default render format." do
+ expect(picture).to receive(:default_render_format) { "img" }
+ expect(picture_url_options[:format]).to eq("img")
+ end
+
+ context "with crop values present" do
+ before do
+ allow(record).to receive(:crop_from) { "10x10" }
+ allow(record).to receive(:crop_size) { "200x200" }
+ end
+
+ it "includes these crop values.", :aggregate_failures do
+ expect(picture_url_options[:crop_from]).to eq "10x10"
+ expect(picture_url_options[:crop_size]).to eq "200x200"
+ end
+
+ it "includes {crop: true}" do
+ expect(picture_url_options[:crop]).to be true
+ end
+ end
+
+ # Regression spec for issue #1279
+ context "with crop values being empty strings" do
+ before do
+ allow(record).to receive(:crop_from) { "" }
+ allow(record).to receive(:crop_size) { "" }
+ end
+
+ it "does not include these crop values.", :aggregate_failures do
+ expect(picture_url_options[:crop_from]).to be_nil
+ expect(picture_url_options[:crop_size]).to be_nil
+ end
+
+ it "includes {crop: false}" do
+ expect(picture_url_options[:crop]).to be false
+ end
+ end
+
+ context "having size setting" do
+ before do
+ allow(record).to receive(:settings) { { size: "30x70" } }
+ end
+
+ it "includes this size." do
+ expect(picture_url_options[:size]).to eq "30x70"
+ end
+ end
+
+ context "having crop setting" do
+ before do
+ allow(record).to receive(:settings) { { crop: true } }
+ end
+
+ it "includes this setting" do
+ expect(picture_url_options[:crop]).to be true
+ end
+ end
+
+ context "without picture assigned" do
+ let(:picture) { nil }
+
+ it { is_expected.to be_a(Hash) }
+ end
+ end
+
+ describe "#thumbnail_url" do
+ subject(:thumbnail_url) { record.thumbnail_url }
+
+ let(:settings) do
+ {}
+ end
+
+ let(:picture) { create(:alchemy_picture) }
+
+ before do
+ allow(record).to receive(:settings) { settings }
+ end
+
+ it "includes the image's original file format." do
+ expect(thumbnail_url).to match(/\.png/)
+ end
+
+ it "flattens the image." do
+ expect(picture).to receive(:url).with(hash_including(flatten: true))
+ thumbnail_url
+ end
+
+ context "when crop sizes are present" do
+ before do
+ allow(record).to receive(:crop_size).and_return("200x200")
+ allow(record).to receive(:crop_from).and_return("10x10")
+ end
+
+ it "passes these crop sizes to the picture's url method." do
+ expect(picture).to receive(:url).with(
+ hash_including(crop_from: "10x10", crop_size: "200x200", crop: true),
+ )
+ thumbnail_url
+ end
+ end
+
+ context "when no crop sizes are present" do
+ it "it does not pass crop sizes to the picture's url method and disables cropping." do
+ expect(picture).to receive(:url).with(
+ hash_including(crop_from: nil, crop_size: nil, crop: false),
+ )
+ thumbnail_url
+ end
+
+ context "when crop is explicitely enabled in the settings" do
+ let(:settings) do
+ { crop: true }
+ end
+
+ it "it enables cropping." do
+ expect(picture).to receive(:url).with(
+ hash_including(crop: true),
+ )
+ thumbnail_url
+ end
+ end
+ end
+
+ context "without picture assigned" do
+ let(:picture) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context "if picture.url returns nil" do
+ before do
+ expect(picture).to receive(:url) { nil }
+ end
+
+ it "returns missing image url" do
+ is_expected.to eq "alchemy/missing-image.svg"
+ end
+ end
+ end
+
+ describe "#thumbnail_url_options" do
+ subject(:thumbnail_url_options) { record.thumbnail_url_options }
+
+ let(:settings) { {} }
+ let(:picture) { nil }
+
+ before do
+ allow(record).to receive(:settings) { settings }
+ end
+
+ context "with picture assigned" do
+ let(:picture) do
+ create(:alchemy_picture)
+ end
+
+ it "includes the image's original file format." do
+ expect(thumbnail_url_options[:format]).to eq("png")
+ end
+
+ it "flattens the image." do
+ expect(thumbnail_url_options[:flatten]).to be(true)
+ end
+ end
+
+ context "when crop values are present" do
+ before do
+ expect(record).to receive(:crop_size).at_least(:once) { "200x200" }
+ expect(record).to receive(:crop_from).at_least(:once) { "10x10" }
+ end
+
+ it "includes these crop values" do
+ expect(thumbnail_url_options).to match(
+ hash_including(crop_from: "10x10", crop_size: "200x200", crop: true)
+ )
+ end
+ end
+
+ context "when no crop values are present" do
+ it "does not include these crop values" do
+ expect(thumbnail_url_options).to_not match(
+ hash_including(crop_from: "10x10", crop_size: "200x200", crop: true)
+ )
+ end
+
+ context "when crop is explicitely enabled in the settings" do
+ let(:settings) do
+ { crop: true }
+ end
+
+ it "it enables cropping." do
+ expect(thumbnail_url_options).to match(
+ hash_including(crop: true),
+ )
+ end
+ end
+ end
+
+ context "without picture assigned" do
+ let(:picture) { nil }
+
+ it "returns default thumbnail options" do
+ is_expected.to eq(
+ crop: false,
+ crop_from: nil,
+ crop_size: nil,
+ flatten: true,
+ format: "jpg",
+ size: "160x120",
+ )
+ end
+ end
+ end
+
+ describe "#image_cropper_settings" do
+ let(:picture) { nil }
+
+ subject { record.image_cropper_settings }
+
+ context "with no picture assigned" do
+ it { is_expected.to eq({}) }
+ end
+
+ context "with picture assigned" do
+ let(:picture) { build_stubbed(:alchemy_picture) }
+
+ let(:default_mask) do
+ [
+ 0,
+ 0,
+ 300,
+ 250,
+ ]
+ end
+
+ let(:settings) { {} }
+
+ before do
+ picture.image_file_width = 300
+ picture.image_file_height = 250
+ allow(record).to receive(:settings) { settings }
+ end
+
+ context "with no render_size present" do
+ before do
+ expect(record).to receive(:render_size).at_least(:once).and_return(nil)
+ end
+
+ context "with sizes in settings" do
+ let(:settings) do
+ { size: "300x250" }
+ end
+
+ it "sets sizes to given values" do
+ expect(subject[:min_size]).to eq([300, 250])
+ end
+ end
+
+ context "with no sizes in settngs" do
+ it "sets sizes to zero" do
+ expect(subject[:min_size]).to eq([0, 0])
+ end
+ end
+ end
+
+ context "with render_size present in record" do
+ it "sets sizes from these values" do
+ expect(record).to receive(:render_size).at_least(:once).and_return("30x25")
+ expect(subject[:min_size]).to eq([30, 25])
+ end
+
+ context "when width or height is not fixed" do
+ it "infers the height from the image file preserving the aspect ratio" do
+ expect(record).to receive(:render_size).at_least(:once).and_return("30x")
+ expect(subject[:min_size]).to eq([30, 25])
+ end
+
+ context "and aspect ratio set" do
+ let(:settings) do
+ { fixed_ratio: "2" }
+ end
+
+ it "does not infer the height from the image file preserving the aspect ratio" do
+ expect(record).to receive(:render_size).at_least(:once).and_return("x25")
+ expect(subject[:min_size]).to eq([50, 25])
+ end
+ end
+ end
+
+ context "when width or height is not fixed and an aspect ratio is given" do
+ context "and aspect ratio set" do
+ let(:settings) do
+ { fixed_ratio: "0.5" }
+ end
+
+ it "width is given, it infers the height from width and ratio" do
+ expect(record).to receive(:render_size).at_least(:once).and_return("30x")
+ expect(subject[:min_size]).to eq([30, 60])
+ end
+ end
+
+ it "infers the height from the image file preserving the aspect ratio" do
+ expect(record).to receive(:render_size).at_least(:once).and_return("x25")
+ expect(subject[:min_size]).to eq([30, 25])
+ end
+ end
+ end
+
+ context "no crop sizes present in record" do
+ it "assigns default mask boxes" do
+ expect(subject[:default_box]).to eq(default_mask)
+ end
+ end
+
+ context "crop sizes present in record" do
+ let(:mask) { [0, 0, 120, 160] }
+
+ before do
+ allow(record).to receive(:crop_from).and_return("0x0")
+ allow(record).to receive(:crop_size).and_return("120x160")
+ end
+
+ it "assigns cropping boxes" do
+ expect(subject[:default_box]).to eq(default_mask)
+ end
+ end
+
+ context "with fixed_ratio set to false" do
+ let(:settings) do
+ { fixed_ratio: false }
+ end
+
+ it "sets ratio to false" do
+ expect(subject[:ratio]).to eq(false)
+ end
+ end
+
+ context "with fixed_ratio set to a non float string" do
+ let(:settings) do
+ { fixed_ratio: "123,45" }
+ end
+
+ it "raises an error" do
+ expect { subject }.to raise_exception(ArgumentError)
+ end
+ end
+
+ context "with no fixed_ratio set" do
+ let(:settings) do
+ { size: "80x60" }
+ end
+
+ it "sets a fixed ratio from sizes" do
+ expect(subject[:ratio]).to eq(80.0 / 60.0)
+ end
+ end
+
+ context "with size set to different values" do
+ let(:settings) { { crop: true, size: size } }
+
+ before do
+ picture.image_file_width = 200
+ picture.image_file_height = 100
+ end
+
+ context "size 200x50" do
+ let(:size) { "200x50" }
+
+ it "default box should be [0, 25, 200, 75]" do
+ expect(subject[:default_box]).to eq([0, 25, 200, 75])
+ end
+ end
+
+ context "size 0x0" do
+ let(:size) { "0x0" }
+
+ it "it should not crop the picture" do
+ expect(subject[:default_box]).to eq([0, 0, 200, 100])
+ end
+ end
+
+ context "size 50x100" do
+ let(:size) { "50x100" }
+
+ it "the hash should be {x1: 75, y1: 0, x2: 125, y2: 100}" do
+ expect(subject[:default_box]).to eq([75, 0, 125, 100])
+ end
+ end
+
+ context "size 50x50" do
+ let(:size) { "50x50" }
+
+ it "the hash should be {x1: 50, y1: 0, x2: 150, y2: 100}" do
+ expect(subject[:default_box]).to eq([50, 0, 150, 100])
+ end
+ end
+
+ context "size 400x200" do
+ let(:size) { "400x200" }
+
+ it "the hash should be {x1: 0, y1: 0, x2: 200, y2: 100}" do
+ expect(subject[:default_box]).to eq([0, 0, 200, 100])
+ end
+ end
+
+ context "size 400x100" do
+ let(:size) { "400x100" }
+
+ it "the hash should be {x1: 0, y1: 25, x2: 200, y2: 75}" do
+ expect(subject[:default_box]).to eq([0, 25, 200, 75])
+ end
+ end
+
+ context "size 200x200" do
+ let(:size) { "200x200" }
+
+ it "the hash should be {x1: 50, y1: 0, x2: 150, y2: 100}" do
+ expect(subject[:default_box]).to eq([50, 0, 150, 100])
+ end
+ end
+ end
+ end
+ end
+
+ describe "#allow_image_cropping?" do
+ let(:picture) do
+ stub_model(Alchemy::Picture, image_file_width: 400, image_file_height: 300)
+ end
+
+ subject { record.allow_image_cropping? }
+
+ it { is_expected.to be_falsy }
+
+ context "with picture assigned" do
+ before do
+ allow(record).to receive(:picture) { picture }
+ end
+
+ it { is_expected.to be_falsy }
+
+ context "and with image larger than crop size" do
+ before do
+ allow(picture).to receive(:can_be_cropped_to?) { true }
+ end
+
+ it { is_expected.to be_falsy }
+
+ context "with crop set to true" do
+ before do
+ allow(record).to receive(:settings) { { crop: true } }
+ end
+
+ context "if picture.image_file is nil" do
+ before do
+ expect(picture).to receive(:image_file) { nil }
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context "if picture.image_file is present" do
+ let(:picture) { build_stubbed(:alchemy_picture) }
+
+ it { is_expected.to be(true) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/alchemy/admin/essence_pictures_controller_spec.rb b/spec/controllers/alchemy/admin/essence_pictures_controller_spec.rb
index 299d99c025..18ac49faf6 100644
--- a/spec/controllers/alchemy/admin/essence_pictures_controller_spec.rb
+++ b/spec/controllers/alchemy/admin/essence_pictures_controller_spec.rb
@@ -25,169 +25,8 @@ module Alchemy
end
end
- describe "#crop" do
- before do
- expect(EssencePicture).to receive(:find).and_return(essence)
- end
-
- context "with no picture assigned" do
- it "renders error message" do
- get :crop, params: { id: 1 }
- expect(assigns(:no_image_notice)).to eq(Alchemy.t(:no_image_for_cropper_found))
- end
- end
-
- context "with picture assigned" do
- subject { get :crop, params: { id: 1, picture_id: picture.id } }
-
- let(:default_mask) do
- [
- 0,
- 0,
- 300,
- 250,
- ]
- end
-
- let(:settings) { {} }
-
- before do
- picture.image_file_width = 300
- picture.image_file_height = 250
- allow(content).to receive(:settings) { settings }
- expect(Picture).to receive(:find_by) { picture }
- end
-
- context "with no render_size present in essence" do
- before do
- expect(essence).to receive(:render_size).at_least(:once).and_return(nil)
- end
-
- context "with sizes in content settings" do
- let(:settings) do
- { size: "300x250" }
- end
-
- it "sets sizes to given values" do
- subject
- expect(assigns(:settings)[:min_size]).to eq([300, 250])
- end
- end
-
- context "with no sizes in content settngs" do
- it "sets sizes to zero" do
- subject
- expect(assigns(:settings)[:min_size]).to eq([0, 0])
- end
- end
- end
-
- context "with render_size present in essence" do
- it "sets sizes from these values" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("30x25")
-
- subject
- expect(assigns(:settings)[:min_size]).to eq([30, 25])
- end
-
- context "when width or height is not fixed" do
- it "infers the height from the image file preserving the aspect ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("30x")
-
- subject
- expect(assigns(:settings)[:min_size]).to eq([30, 25])
- end
-
- context "and aspect ratio set on the contents settings" do
- let(:settings) do
- { fixed_ratio: "2" }
- end
-
- it "does not infer the height from the image file preserving the aspect ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("x25")
-
- subject
- expect(assigns(:settings)[:min_size]).to eq([50, 25])
- end
- end
- end
-
- context "when width or height is not fixed and an aspect ratio is given" do
- context "and aspect ratio set on the contents setting" do
- let(:settings) do
- { fixed_ratio: "0.5" }
- end
-
- it "width is given, it infers the height from width and ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("30x")
-
- subject
- expect(assigns(:settings)[:min_size]).to eq([30, 60])
- end
- end
-
- it "infers the height from the image file preserving the aspect ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("x25")
-
- subject
- expect(assigns(:settings)[:min_size]).to eq([30, 25])
- end
- end
- end
-
- context "no crop sizes present in essence" do
- it "assigns default mask boxes" do
- subject
- expect(assigns(:settings)[:default_box]).to eq(default_mask)
- end
- end
-
- context "crop sizes present in essence" do
- let(:mask) { [0, 0, 120, 160] }
-
- before do
- allow(essence).to receive(:crop_from).and_return("0x0")
- allow(essence).to receive(:crop_size).and_return("120x160")
- end
-
- it "assigns cropping boxes" do
- subject
- expect(assigns(:settings)[:default_box]).to eq(default_mask)
- end
- end
-
- context "with fixed_ratio set to false" do
- let(:settings) do
- { fixed_ratio: false }
- end
-
- it "sets ratio to false" do
- subject
- expect(assigns(:settings)[:ratio]).to eq(false)
- end
- end
-
- context "with fixed_ratio set to a non float string" do
- let(:settings) do
- { fixed_ratio: "123,45" }
- end
-
- it "raises error" do
- expect { subject }.to raise_error(ArgumentError)
- end
- end
-
- context "with no fixed_ratio set" do
- let(:settings) do
- { size: "80x60" }
- end
-
- it "sets a fixed ratio from sizes" do
- subject
- expect(assigns(:settings)[:ratio]).to eq(80.0 / 60.0)
- end
- end
- end
+ it_behaves_like "having crop action", model_class: Alchemy::EssencePicture do
+ let(:croppable_resource) { essence }
end
describe "#update" do
diff --git a/spec/models/alchemy/essence_picture_spec.rb b/spec/models/alchemy/essence_picture_spec.rb
index 43a2e1523d..85690e6365 100644
--- a/spec/models/alchemy/essence_picture_spec.rb
+++ b/spec/models/alchemy/essence_picture_spec.rb
@@ -32,25 +32,9 @@ module Alchemy
end
end
- it "should not store negative values for crop values" do
- essence = EssencePicture.new(crop_from: "-1x100", crop_size: "-20x30")
- essence.save!
- expect(essence.crop_from).to eq("0x100")
- expect(essence.crop_size).to eq("0x30")
- end
-
- it "should not store float values for crop values" do
- essence = EssencePicture.new(crop_from: "0.05x104.5", crop_size: "99.5x203.4")
- essence.save!
- expect(essence.crop_from).to eq("0x105")
- expect(essence.crop_size).to eq("100x203")
- end
-
- it "should not store empty strings for nil crop values" do
- essence = EssencePicture.new(crop_from: nil, crop_size: nil)
- essence.save!
- expect(essence.crop_from).to eq(nil)
- expect(essence.crop_size).to eq(nil)
+ it_behaves_like "having picture thumbnails" do
+ let(:picture) { build(:alchemy_picture) }
+ let(:record) { build(:alchemy_essence_picture, :with_content, picture: picture) }
end
it "should convert newlines in caption into
s" do
@@ -59,527 +43,6 @@ module Alchemy
expect(essence.caption).to eq("hello
kitty")
end
- describe "#picture_url" do
- subject(:picture_url) { essence.picture_url(options) }
-
- let(:options) { {} }
- let(:picture) { create(:alchemy_picture) }
- let(:essence) { create(:alchemy_essence_picture, :with_content, picture: picture) }
-
- context "with no format in the options" do
- it "includes the image's default render format." do
- expect(picture_url).to match(/\.png/)
- end
- end
-
- context "with format in the options" do
- let(:options) { { format: "gif" } }
-
- it "takes this as format." do
- expect(picture_url).to match(/\.gif/)
- end
- end
-
- context "when crop sizes are present" do
- let(:essence) do
- create(:alchemy_essence_picture, :with_content, picture: picture, crop_size: "200x200", crop_from: "10x10")
- end
-
- it "passes these crop sizes to the picture's url method." do
- expect(picture).to receive(:url).with(
- hash_including(crop_from: "10x10", crop_size: "200x200"),
- )
- picture_url
- end
-
- context "but with crop sizes in the options" do
- let(:options) do
- { crop_from: "30x30", crop_size: "75x75" }
- end
-
- it "passes these crop sizes instead." do
- expect(picture).to receive(:url).with(
- hash_including(crop_from: "30x30", crop_size: "75x75"),
- )
- picture_url
- end
- end
- end
-
- context "with other options" do
- let(:options) { { foo: "baz" } }
-
- it "adds them to the url" do
- expect(picture_url).to match /\?foo=baz/
- end
- end
-
- context "without picture assigned" do
- let(:picture) { nil }
-
- it { is_expected.to be_nil }
- end
-
- context "if picture.url returns nil" do
- before do
- expect(picture).to receive(:url) { nil }
- end
-
- it "returns missing image url" do
- is_expected.to eq "missing-image.png"
- end
- end
- end
-
- describe "#picture_url_options" do
- subject(:picture_url_options) { essence.picture_url_options }
-
- let(:picture) { build_stubbed(:alchemy_picture) }
- let(:essence) { build_stubbed(:alchemy_essence_picture, :with_content, picture: picture) }
-
- it { is_expected.to be_a(HashWithIndifferentAccess) }
-
- it "includes the pictures default render format." do
- expect(picture).to receive(:default_render_format) { "img" }
- expect(picture_url_options[:format]).to eq("img")
- end
-
- context "with crop sizes present" do
- let(:essence) do
- create(:alchemy_essence_picture, :with_content, picture: picture, crop_size: "200x200", crop_from: "10x10")
- end
-
- it "includes these crop sizes.", :aggregate_failures do
- expect(picture_url_options[:crop_from]).to eq "10x10"
- expect(picture_url_options[:crop_size]).to eq "200x200"
- end
-
- it "includes {crop: true}" do
- expect(picture_url_options[:crop]).to be true
- end
- end
-
- # Regression spec for issue #1279
- context "with crop sizes being empty strings" do
- let(:essence) do
- create(:alchemy_essence_picture, :with_content, picture: picture, crop_size: "", crop_from: "")
- end
-
- it "does not include these crop sizes.", :aggregate_failures do
- expect(picture_url_options[:crop_from]).to be_nil
- expect(picture_url_options[:crop_size]).to be_nil
- end
-
- it "includes {crop: false}" do
- expect(picture_url_options[:crop]).to be false
- end
- end
-
- context "with content having size setting" do
- before do
- expect(essence.content).to receive(:settings).twice { { size: "30x70" } }
- end
-
- it "includes this size." do
- expect(picture_url_options[:size]).to eq "30x70"
- end
- end
-
- context "with content having crop setting" do
- before do
- expect(essence.content).to receive(:settings).twice { { crop: true } }
- end
-
- it "includes this setting" do
- expect(picture_url_options[:crop]).to be true
- end
- end
-
- context "without picture assigned" do
- let(:picture) { nil }
-
- it { is_expected.to be_a(Hash) }
- end
- end
-
- describe "#thumbnail_url" do
- subject(:thumbnail_url) { essence.thumbnail_url }
-
- let(:settings) do
- {}
- end
-
- let(:picture) do
- create(:alchemy_picture)
- end
-
- let(:essence) do
- create(:alchemy_essence_picture, picture: picture)
- end
-
- let(:content) do
- create(:alchemy_content, essence: essence)
- end
-
- before do
- allow(content).to receive(:settings) { settings }
- end
-
- it "includes the image's original file format." do
- expect(thumbnail_url).to match(/\.png/)
- end
-
- it "flattens the image." do
- expect(picture).to receive(:url).with(hash_including(flatten: true))
- thumbnail_url
- end
-
- context "when crop sizes are present" do
- before do
- allow(essence).to receive(:crop_size).and_return("200x200")
- allow(essence).to receive(:crop_from).and_return("10x10")
- end
-
- it "passes these crop sizes to the picture's url method." do
- expect(picture).to receive(:url).with(
- hash_including(crop_from: "10x10", crop_size: "200x200", crop: true),
- )
- thumbnail_url
- end
- end
-
- context "when no crop sizes are present" do
- it "it does not pass crop sizes to the picture's url method and disables cropping." do
- expect(picture).to receive(:url).with(
- hash_including(crop_from: nil, crop_size: nil, crop: false),
- )
- thumbnail_url
- end
-
- context "when crop is explicitely enabled in the settings" do
- let(:settings) do
- { crop: true }
- end
-
- it "it enables cropping." do
- expect(picture).to receive(:url).with(
- hash_including(crop: true),
- )
- thumbnail_url
- end
- end
- end
-
- context "without picture assigned" do
- let(:picture) { nil }
-
- it { is_expected.to be_nil }
- end
-
- context "if picture.url returns nil" do
- before do
- expect(picture).to receive(:url) { nil }
- end
-
- it "returns missing image url" do
- is_expected.to eq "alchemy/missing-image.svg"
- end
- end
- end
-
- describe "#thumbnail_url_options" do
- subject(:thumbnail_url_options) { essence.thumbnail_url_options }
-
- let(:settings) { {} }
- let(:picture) { nil }
-
- let(:essence) do
- build_stubbed(:alchemy_essence_picture, picture: picture)
- end
-
- let(:content) do
- build_stubbed(:alchemy_content, essence: essence)
- end
-
- before do
- allow(content).to receive(:settings) { settings }
- end
-
- context "with picture assigned" do
- let(:picture) do
- create(:alchemy_picture)
- end
-
- it "includes the image's original file format." do
- expect(thumbnail_url_options[:format]).to eq("png")
- end
-
- it "flattens the image." do
- expect(thumbnail_url_options[:flatten]).to be(true)
- end
- end
-
- context "when crop values are present" do
- before do
- expect(essence).to receive(:crop_size).at_least(:once) { "200x200" }
- expect(essence).to receive(:crop_from).at_least(:once) { "10x10" }
- end
-
- it "includes these crop values" do
- expect(thumbnail_url_options).to match(
- hash_including(crop_from: "10x10", crop_size: "200x200", crop: true)
- )
- end
- end
-
- context "when no crop values are present" do
- it "does not include these crop values" do
- expect(thumbnail_url_options).to_not match(
- hash_including(crop_from: "10x10", crop_size: "200x200", crop: true)
- )
- end
-
- context "when crop is explicitely enabled in the settings" do
- let(:settings) do
- { crop: true }
- end
-
- it "it enables cropping." do
- expect(thumbnail_url_options).to match(
- hash_including(crop: true),
- )
- end
- end
- end
-
- context "without picture assigned" do
- let(:picture) { nil }
-
- it "returns default thumbnail options" do
- is_expected.to eq(
- crop: false,
- crop_from: nil,
- crop_size: nil,
- flatten: true,
- format: "jpg",
- size: "160x120",
- )
- end
- end
- end
-
- describe "#image_cropper_settings" do
- let(:content) { essence.content }
- let(:essence) { build_stubbed(:alchemy_essence_picture, :with_content, picture: picture) }
- let(:picture) { nil }
-
- subject { essence.image_cropper_settings }
-
- context "with no picture assigned" do
- it { is_expected.to eq({}) }
- end
-
- context "with picture assigned" do
- let(:picture) { build_stubbed(:alchemy_picture) }
-
- let(:default_mask) do
- [
- 0,
- 0,
- 300,
- 250,
- ]
- end
-
- let(:settings) { {} }
-
- before do
- picture.image_file_width = 300
- picture.image_file_height = 250
- allow(content).to receive(:settings) { settings }
- end
-
- context "with no render_size present in essence" do
- before do
- expect(essence).to receive(:render_size).at_least(:once).and_return(nil)
- end
-
- context "with sizes in content settings" do
- let(:settings) do
- { size: "300x250" }
- end
-
- it "sets sizes to given values" do
- expect(subject[:min_size]).to eq([300, 250])
- end
- end
-
- context "with no sizes in content settngs" do
- it "sets sizes to zero" do
- expect(subject[:min_size]).to eq([0, 0])
- end
- end
- end
-
- context "with render_size present in essence" do
- it "sets sizes from these values" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("30x25")
- expect(subject[:min_size]).to eq([30, 25])
- end
-
- context "when width or height is not fixed" do
- it "infers the height from the image file preserving the aspect ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("30x")
- expect(subject[:min_size]).to eq([30, 25])
- end
-
- context "and aspect ratio set on the contents settings" do
- let(:settings) do
- { fixed_ratio: "2" }
- end
-
- it "does not infer the height from the image file preserving the aspect ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("x25")
- expect(subject[:min_size]).to eq([50, 25])
- end
- end
- end
-
- context "when width or height is not fixed and an aspect ratio is given" do
- context "and aspect ratio set on the contents setting" do
- let(:settings) do
- { fixed_ratio: "0.5" }
- end
-
- it "width is given, it infers the height from width and ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("30x")
- expect(subject[:min_size]).to eq([30, 60])
- end
- end
-
- it "infers the height from the image file preserving the aspect ratio" do
- expect(essence).to receive(:render_size).at_least(:once).and_return("x25")
- expect(subject[:min_size]).to eq([30, 25])
- end
- end
- end
-
- context "no crop sizes present in essence" do
- it "assigns default mask boxes" do
- expect(subject[:default_box]).to eq(default_mask)
- end
- end
-
- context "crop sizes present in essence" do
- let(:mask) { [0, 0, 120, 160] }
-
- before do
- allow(essence).to receive(:crop_from).and_return("0x0")
- allow(essence).to receive(:crop_size).and_return("120x160")
- end
-
- it "assigns cropping boxes" do
- expect(subject[:default_box]).to eq(default_mask)
- end
- end
-
- context "with fixed_ratio set to false" do
- let(:settings) do
- { fixed_ratio: false }
- end
-
- it "sets ratio to false" do
- expect(subject[:ratio]).to eq(false)
- end
- end
-
- context "with fixed_ratio set to a non float string" do
- let(:settings) do
- { fixed_ratio: "123,45" }
- end
-
- it "raises an error" do
- expect { subject }.to raise_exception(ArgumentError)
- end
- end
-
- context "with no fixed_ratio set" do
- let(:settings) do
- { size: "80x60" }
- end
-
- it "sets a fixed ratio from sizes" do
- expect(subject[:ratio]).to eq(80.0 / 60.0)
- end
- end
-
- context "with size set to different values" do
- let(:settings) { { crop: true, size: size } }
-
- before do
- picture.image_file_width = 200
- picture.image_file_height = 100
- end
-
- context "size 200x50" do
- let(:size) { "200x50" }
-
- it "default box should be [0, 25, 200, 75]" do
- expect(subject[:default_box]).to eq([0, 25, 200, 75])
- end
- end
-
- context "size 0x0" do
- let(:size) { "0x0" }
-
- it "it should not crop the picture" do
- expect(subject[:default_box]).to eq([0, 0, 200, 100])
- end
- end
-
- context "size 50x100" do
- let(:size) { "50x100" }
-
- it "the hash should be {x1: 75, y1: 0, x2: 125, y2: 100}" do
- expect(subject[:default_box]).to eq([75, 0, 125, 100])
- end
- end
-
- context "size 50x50" do
- let(:size) { "50x50" }
-
- it "the hash should be {x1: 50, y1: 0, x2: 150, y2: 100}" do
- expect(subject[:default_box]).to eq([50, 0, 150, 100])
- end
- end
-
- context "size 400x200" do
- let(:size) { "400x200" }
-
- it "the hash should be {x1: 0, y1: 0, x2: 200, y2: 100}" do
- expect(subject[:default_box]).to eq([0, 0, 200, 100])
- end
- end
-
- context "size 400x100" do
- let(:size) { "400x100" }
-
- it "the hash should be {x1: 0, y1: 25, x2: 200, y2: 75}" do
- expect(subject[:default_box]).to eq([0, 25, 200, 75])
- end
- end
-
- context "size 200x200" do
- let(:size) { "200x200" }
-
- it "the hash should be {x1: 50, y1: 0, x2: 150, y2: 100}" do
- expect(subject[:default_box]).to eq([50, 0, 150, 100])
- end
- end
- end
- end
- end
-
describe "#preview_text" do
let(:picture) { mock_model(Picture, name: "Cute Cat Kittens") }
let(:essence) { EssencePicture.new }
@@ -636,59 +99,5 @@ module Alchemy
end
end
end
-
- describe "#allow_image_cropping?" do
- let(:essence_picture) { stub_model(Alchemy::EssencePicture) }
- let(:content) { stub_model(Alchemy::Content) }
- let(:picture) { stub_model(Alchemy::Picture) }
-
- subject { essence_picture.allow_image_cropping? }
-
- it { is_expected.to be_falsy }
-
- context "with content existing?" do
- before do
- allow(essence_picture).to receive(:content) { content }
- end
-
- it { is_expected.to be_falsy }
-
- context "with picture assigned" do
- before do
- allow(essence_picture).to receive(:picture) { picture }
- end
-
- it { is_expected.to be_falsy }
-
- context "and with image larger than crop size" do
- before do
- allow(picture).to receive(:can_be_cropped_to?) { true }
- end
-
- it { is_expected.to be_falsy }
-
- context "with crop set to true" do
- before do
- allow(content).to receive(:settings) { { crop: true } }
- end
-
- context "if picture.image_file is nil" do
- before do
- expect(picture).to receive(:image_file) { nil }
- end
-
- it { is_expected.to be_falsy }
- end
-
- context "if picture.image_file is present" do
- let(:picture) { build_stubbed(:alchemy_picture) }
-
- it { is_expected.to be(true) }
- end
- end
- end
- end
- end
- end
end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index a0ecab5163..ce96f578c1 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -20,6 +20,8 @@
require "alchemy/test_support"
require "alchemy/test_support/config_stubbing"
require "alchemy/test_support/essence_shared_examples"
+require "alchemy/test_support/having_crop_action_examples"
+require "alchemy/test_support/having_picture_thumbnails_examples"
require "alchemy/test_support/integration_helpers"
require "alchemy/test_support/shared_contexts"
require "alchemy/test_support/shared_uploader_examples"