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"