Merge pull request #5733 from luisramos0/base_ctrl

Move lib/spree to OFN
This commit is contained in:
Luis Ramos
2020-08-19 18:35:17 +01:00
committed by GitHub
25 changed files with 1369 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
require 'cancan'
module Spree
class BaseController < ApplicationController
include Spree::Core::ControllerHelpers::Auth
include Spree::Core::ControllerHelpers::RespondWith
include Spree::Core::ControllerHelpers::SSL
include Spree::Core::ControllerHelpers::Common
respond_to :html
end
end
require 'spree/i18n/initializer'

58
lib/spree/core.rb Normal file
View File

@@ -0,0 +1,58 @@
# frozen_string_literal: true
require 'rails/all'
require 'active_merchant'
require 'acts_as_list'
require 'awesome_nested_set'
require 'cancan'
require 'kaminari'
require 'mail'
require 'paperclip'
require 'paranoia'
require 'ransack'
require 'state_machine'
module Spree
mattr_accessor :user_class
def self.user_class
if @@user_class.is_a?(Class)
raise "Spree.user_class MUST be a String object, not a Class object."
end
return unless @@user_class.is_a?(String)
@@user_class.constantize
end
# Used to configure Spree.
#
# Example:
#
# Spree.config do |config|
# config.site_name = "An awesome Spree site"
# end
#
# This method is defined within the core gem on purpose.
# Some people may only wish to use the Core part of Spree.
def self.config
yield(Spree::Config)
end
end
require 'spree/core/version'
require 'spree/core/engine'
require 'spree/i18n'
require 'spree/money'
require 'spree/core/delegate_belongs_to'
require 'spree/core/ext/active_record'
require 'spree/core/permalinks'
require 'spree/core/token_resource'
require 'spree/core/calculated_adjustments'
require 'spree/core/product_duplicator'
ActiveRecord::Base.class_eval do
include CollectiveIdea::Acts::NestedSet
end

View File

@@ -0,0 +1,81 @@
# frozen_string_literal: true
module Spree
module Core
module CalculatedAdjustments
def self.included(klass)
klass.class_eval do
has_one :calculator, class_name: "Spree::Calculator", as: :calculable, dependent: :destroy
accepts_nested_attributes_for :calculator
validates :calculator, presence: true
def self.calculators
spree_calculators.__send__(model_name_without_spree_namespace)
end
def calculator_type
calculator.class.to_s if calculator
end
def calculator_type=(calculator_type)
klass = calculator_type.constantize if calculator_type
self.calculator = klass.new if klass && !calculator.is_a?(klass)
end
# Creates a new adjustment for the target object
# (which is any class that has_many :adjustments) and sets amount based on the
# calculator as applied to the given calculable (Order, LineItems[], Shipment, etc.)
# By default the adjustment will not be considered mandatory
def create_adjustment(label, target, calculable, mandatory = false, state = "closed")
# Adjustment calculations done on Spree::Shipment objects MUST
# be done on their to_package'd variants instead
# It's only the package that contains the correct information.
# See https://github.com/spree/spree_active_shipping/pull/96 et. al
old_calculable = calculable
calculable = calculable.to_package if calculable.is_a?(Spree::Shipment)
amount = compute_amount(calculable)
return if amount.zero? && !mandatory
target.adjustments.create(
amount: amount,
source: old_calculable,
originator: self,
label: label,
mandatory: mandatory,
state: state
)
end
# Updates the amount of the adjustment using our Calculator and
# calling the +compute+ method with the +calculable+
# referenced passed to the method.
def update_adjustment(adjustment, calculable)
# Adjustment calculations done on Spree::Shipment objects MUST
# be done on their to_package'd variants instead
# It's only the package that contains the correct information.
# See https://github.com/spree/spree_active_shipping/pull/96 et. al
calculable = calculable.to_package if calculable.is_a?(Spree::Shipment)
adjustment.update_column(:amount, compute_amount(calculable))
end
# Calculate the amount to be used when creating an adjustment
# NOTE: May be overriden by classes where this module is included into.
# Such as Spree::Promotion::Action::CreateAdjustment.
def compute_amount(calculable)
calculator.compute(calculable)
end
def self.model_name_without_spree_namespace
to_s.tableize.gsub('/', '_').sub('spree_', '')
end
private_class_method :model_name_without_spree_namespace
def self.spree_calculators
Rails.application.config.spree.calculators
end
private_class_method :spree_calculators
end
end
end
end
end

View File

@@ -0,0 +1,94 @@
# frozen_string_literal: true
##
# Creates methods on object which delegate to an association proxy.
# see delegate_belongs_to for two uses
#
# Todo - integrate with ActiveRecord::Dirty to make sure changes to delegate object are noticed
# Should do
# class User < ActiveRecord::Base; delegate_belongs_to :contact, :firstname; end
# class Contact < ActiveRecord::Base; end
# u = User.first
# u.changed? # => false
# u.firstname = 'Bobby'
# u.changed? # => true
#
# Right now the second call to changed? would return false
#
# Todo - add has_one support. fairly straightforward addition
##
module DelegateBelongsTo
extend ActiveSupport::Concern
module ClassMethods
@@default_rejected_delegate_columns = ['created_at', 'created_on', 'updated_at',
'updated_on', 'lock_version', 'type', 'id',
'position', 'parent_id', 'lft', 'rgt']
mattr_accessor :default_rejected_delegate_columns
##
# Creates methods for accessing and setting attributes on an association. Uses same
# default list of attributes as delegates_to_association.
# delegate_belongs_to :contact
# delegate_belongs_to :contact, [:defaults] ## same as above, and useless
# delegate_belongs_to :contact, [:defaults, :address, :fullname], :class_name => 'VCard'
##
def delegate_belongs_to(association, *attrs)
opts = attrs.extract_options!
initialize_association :belongs_to, association, opts
attrs = get_association_column_names(association) if attrs.empty?
attrs.concat get_association_column_names(association) if attrs.delete :defaults
attrs.each do |attr|
class_def attr do |*args|
if args.empty?
__send__(:delegator_for, association).__send__(attr)
else
__send__(:delegator_for, association).__send__(attr, *args)
end
end
class_def "#{attr}=" do |val|
__send__(:delegator_for, association).__send__("#{attr}=", val)
end
end
end
protected
def get_association_column_names(association, without_default_rejected_delegate_columns = true)
association_klass = reflect_on_association(association).klass
methods = association_klass.column_names
if without_default_rejected_delegate_columns
methods.reject!{ |x| default_rejected_delegate_columns.include?(x.to_s) }
end
methods
rescue
[]
end
##
# initialize_association :belongs_to, :contact
def initialize_association(type, association, opts = {})
unless [:belongs_to].include?(type.to_s.to_sym)
raise 'Illegal or unimplemented association type.'
end
__send__(type, association, opts) if reflect_on_association(association).nil?
end
private
def class_def(name, method = nil, &blk)
class_eval { method.nil? ? define_method(name, &blk) : define_method(name, method) }
end
end
def delegator_for(association)
if __send__(association).nil?
__send__("#{association}=", self.class.reflect_on_association(association).klass.new)
end
__send__(association)
end
protected :delegator_for
end
ActiveRecord::Base.include(DelegateBelongsTo)

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
module Spree
module Core
class Environment
class Calculators
include EnvironmentExtension
attr_accessor :shipping_methods, :tax_rates
end
end
end
end

View File

@@ -0,0 +1,27 @@
# frozen_string_literal: true
module Spree
module Core
module EnvironmentExtension
extend ActiveSupport::Concern
def add_class(name)
instance_variable_set "@#{name}", Set.new
create_method( "#{name}=".to_sym ) { |val|
instance_variable_set( "@" + name, val)
}
create_method(name.to_sym) do
instance_variable_get( "@" + name )
end
end
private
def create_method(name, &block)
self.class.__send__(:define_method, name, &block)
end
end
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Spree
module Core
class GatewayError < RuntimeError; end
end
end

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
# Allows us to intercept any outbound mail message and make last minute changes
# (such as specifying a "from" address or sending to a test email account)
#
# See http://railscasts.com/episodes/206-action-mailer-in-rails-3 for more details.
module Spree
module Core
class MailInterceptor
def self.delivering_email(message)
return unless MailSettings.override?
if Config[:intercept_email].present?
message.subject = "#{message.to} #{message.subject}"
message.to = Config[:intercept_email]
end
return if Config[:mail_bcc].blank?
message.bcc ||= Config[:mail_bcc]
end
end
end
end

View File

@@ -0,0 +1,63 @@
# frozen_string_literal: true
module Spree
module Core
class MailSettings
MAIL_AUTH = ['None', 'plain', 'login', 'cram_md5'].freeze
SECURE_CONNECTION_TYPES = ['None', 'SSL', 'TLS'].freeze
# Override the Rails application mail settings based on preferences
# This makes it possible to configure the mail settings through an admin
# interface instead of requiring changes to the Rails envrionment file
def self.init
new.override! if override?
end
def self.override?
Config.override_actionmailer_config
end
def override!
if Config.enable_mail_delivery
ActionMailer::Base.default_url_options[:host] ||= Config.site_url
ActionMailer::Base.smtp_settings = mail_server_settings
ActionMailer::Base.perform_deliveries = true
else
ActionMailer::Base.perform_deliveries = false
end
end
private
def mail_server_settings
settings = if need_authentication?
basic_settings.merge(user_credentials)
else
basic_settings
end
settings.merge(enable_starttls_auto: secure_connection?)
end
def user_credentials
{ user_name: Config.smtp_username,
password: Config.smtp_password }
end
def basic_settings
{ address: Config.mail_host,
domain: Config.mail_domain,
port: Config.mail_port,
authentication: Config.mail_auth_type }
end
def need_authentication?
Config.mail_auth_type != 'None'
end
def secure_connection?
Config.secure_connection_type == 'TLS'
end
end
end
end

View File

@@ -0,0 +1,75 @@
# frozen_string_literal: true
require 'stringex'
module Spree
module Core
module Permalinks
extend ActiveSupport::Concern
included do
class_attribute :permalink_options
end
module ClassMethods
def make_permalink(options = {})
options[:field] ||= :permalink
self.permalink_options = options
return unless connected? &&
table_exists? &&
column_names.include?(permalink_options[:field].to_s)
before_validation(on: :create) { save_permalink }
end
def find_by_param(value, *args)
__send__("find_by_#{permalink_field}", value, *args)
end
def find_by_param!(value, *args)
__send__("find_by_#{permalink_field}!", value, *args)
end
def permalink_field
permalink_options[:field]
end
def permalink_prefix
permalink_options[:prefix] || ""
end
def permalink_order
order = permalink_options[:order]
"#{order} ASC," if order
end
end
def generate_permalink
"#{self.class.permalink_prefix}#{Array.new(9) { rand(9) }.join}"
end
def save_permalink(permalink_value = to_param)
with_lock do
permalink_value ||= generate_permalink
field = self.class.permalink_field
# Do other links exist with this permalink?
other = self.class.
where("#{self.class.table_name}.#{field} LIKE ?", "#{permalink_value}%")
if other.any?
# Find the existing permalink with the highest number, and increment that number.
# (If none of the existing permalinks have a number, this will evaluate to 1.)
number = other.map { |o| o.__send__(field)[/-(\d+)$/, 1].to_i }.max + 1
permalink_value += "-#{number}"
end
write_attribute(field, permalink_value)
end
end
end
end
end
ActiveRecord::Base.include(Spree::Core::Permalinks)
ActiveRecord::Relation.include(Spree::Core::Permalinks)

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
module Spree
module Core
# This module exists to reduce duplication in S3 settings between
# the Image and Taxon models in Spree
module S3Support
extend ActiveSupport::Concern
included do
def self.supports_s3(field)
# Load user defined paperclip settings
config = Spree::Config
return unless config[:use_s3]
s3_creds = { access_key_id: config[:s3_access_key],
secret_access_key: config[:s3_secret],
bucket: config[:s3_bucket] }
attachment_definitions[field][:storage] = :s3
attachment_definitions[field][:s3_credentials] = s3_creds
attachment_definitions[field][:s3_headers] = ActiveSupport::JSON.
decode(config[:s3_headers])
attachment_definitions[field][:bucket] = config[:s3_bucket]
if config[:s3_protocol].present?
attachment_definitions[field][:s3_protocol] = config[:s3_protocol].downcase
end
return if config[:s3_host_alias].blank?
attachment_definitions[field][:s3_host_alias] = config[:s3_host_alias]
end
end
end
end
end

View File

@@ -0,0 +1,28 @@
# frozen_string_literal: true
module Spree
module Core
module TokenResource
module ClassMethods
def token_resource
has_one :tokenized_permission, as: :permissable
delegate :token, to: :tokenized_permission, allow_nil: true
after_create :create_token
end
end
def create_token
permission = build_tokenized_permission
permission.token = token = ::SecureRandom.hex(8)
permission.save!
token
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
end
end
ActiveRecord::Base.class_eval { include Spree::Core::TokenResource }

38
lib/spree/i18n.rb Normal file
View File

@@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'i18n'
require 'active_support/core_ext/array/extract_options'
require 'spree/i18n/base'
module Spree
extend ActionView::Helpers::TranslationHelper
class << self
# Add spree namespace and delegate to Rails TranslationHelper for some nice
# extra functionality. e.g return reasonable strings for missing translations
def translate(*args)
@virtual_path = virtual_path
options = args.extract_options!
options[:scope] = [*options[:scope]].unshift(:spree)
args << options
super(*args)
end
alias_method :t, :translate
def context
Spree::ViewContext.context
end
def virtual_path
return unless context
path = context.instance_variable_get("@virtual_path")
return unless path
path.gsub(/spree/, '')
end
end
end

19
lib/spree/i18n/base.rb Normal file
View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
module Spree
module ViewContext
def self.context=(context)
@context = context
end
def self.context
@context
end
def view_context
super.tap do |context|
Spree::ViewContext.context = context
end
end
end
end

View File

@@ -0,0 +1,3 @@
# frozen_string_literal: true
Spree::BaseController.include(Spree::ViewContext)

42
lib/spree/money.rb Normal file
View File

@@ -0,0 +1,42 @@
# frozen_string_literal: false
require 'money'
module Spree
class Money
attr_reader :money
delegate :cents, to: :money
def initialize(amount, options = {})
@money = ::Money.parse([amount, (options[:currency] || Spree::Config[:currency])].join)
@options = {}
@options[:with_currency] = Spree::Config[:display_currency]
@options[:symbol_position] = Spree::Config[:currency_symbol_position].to_sym
@options[:no_cents] = Spree::Config[:hide_cents]
@options[:decimal_mark] = Spree::Config[:currency_decimal_mark]
@options[:thousands_separator] = Spree::Config[:currency_thousands_separator]
@options.merge!(options)
# Must be a symbol because the Money gem doesn't do the conversion
@options[:symbol_position] = @options[:symbol_position].to_sym
end
def to_s
@money.format(@options)
end
def to_html(options = { html: true })
output = @money.format(@options.merge(options))
if options[:html]
# 1) prevent blank, breaking spaces
# 2) prevent escaping of HTML character entities
output = output.gsub(" ", "&nbsp;").html_safe
end
output
end
def ==(other)
@money == other.money
end
end
end

View File

@@ -0,0 +1,63 @@
# frozen_string_literal: true
module Spree
class ProductDuplicator
attr_accessor :product
def initialize(product)
@product = product
end
def duplicate
new_product = duplicate_product
# don't dup the actual variants, just the characterising types
new_product.option_types = product.option_types if product.has_variants?
# allow site to do some customization
new_product.__send__(:duplicate_extra, product) if new_product.respond_to?(:duplicate_extra)
new_product.save!
new_product
end
protected
def duplicate_product
product.dup.tap do |new_product|
new_product.name = "COPY OF #{product.name}"
new_product.taxons = product.taxons
new_product.created_at = nil
new_product.deleted_at = nil
new_product.updated_at = nil
new_product.product_properties = reset_properties
new_product.master = duplicate_master
end
end
def duplicate_master
master = product.master
master.dup.tap do |new_master|
new_master.sku = "COPY OF #{master.sku}"
new_master.deleted_at = nil
new_master.images = master.images.map { |image| duplicate_image image }
new_master.price = master.price
new_master.currency = master.currency
end
end
def duplicate_image(image)
new_image = image.dup
new_image.assign_attributes(attachment: image.attachment.clone)
new_image
end
def reset_properties
product.product_properties.map do |prop|
prop.dup.tap do |new_prop|
new_prop.created_at = nil
new_prop.updated_at = nil
end
end
end
end
end

57
lib/spree/responder.rb Normal file
View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
module Spree
class Responder < ::ActionController::Responder #:nodoc:
attr_accessor :on_success, :on_failure
def initialize(controller, resources, options = {})
super
class_name = controller.class.name.to_sym
action_name = options.delete(:action_name)
result = Spree::BaseController.spree_responders[class_name].
try(:[], action_name).
try(:[], self.format.to_sym)
return unless result
self.on_success = handler(controller, result, :success)
self.on_failure = handler(controller, result, :failure)
end
def to_html
if !(on_success || on_failure)
super
return
end
has_errors? ? controller.instance_exec(&on_failure) : controller.instance_exec(&on_success)
end
def to_format
if !(on_success || on_failure)
super
return
end
has_errors? ? controller.instance_exec(&on_failure) : controller.instance_exec(&on_success)
end
private
def handler(controller, result, status)
return result if result.respond_to? :call
case result
when Hash
if result[status].is_a? Symbol
controller.method(result[status])
else
result[status]
end
when Symbol
controller.method(result)
end
end
end
end

View File

@@ -0,0 +1,73 @@
# frozen_string_literal: true
require 'spec_helper'
# Its pretty difficult to test this module in isolation b/c it needs to work in conjunction
# with an actual class that extends ActiveRecord::Base and has a corresponding table in the DB.
# So we'll just test it using Order and ShippingMethod. These classes are including the module.
describe Spree::Core::CalculatedAdjustments do
let(:calculator) { build(:calculator) }
let(:tax_rate) { Spree::TaxRate.new(calculator: calculator) }
before do
allow(calculator).to receive(:compute) { 10 }
allow(calculator).to receive(:[]) { nil }
end
it "should add has_one :calculator relationship" do
assert Spree::ShippingMethod.
reflect_on_all_associations(:has_one).map(&:name).include?(:calculator)
end
context "#create_adjustment and its resulting adjustment" do
let(:order) { Spree::Order.create }
let(:target) { order }
it "should be associated with the target" do
expect(target.adjustments).to receive(:create)
tax_rate.create_adjustment("foo", target, order)
end
it "should have the correct originator and an amount derived from the calculator and supplied calculable" do
adjustment = tax_rate.create_adjustment("foo", target, order)
expect(adjustment).not_to be_nil
expect(adjustment.amount).to eq 10
expect(adjustment.source).to eq order
expect(adjustment.originator).to eq tax_rate
end
it "should be mandatory if true is supplied for that parameter" do
adjustment = tax_rate.create_adjustment("foo", target, order, true)
expect(adjustment).to be_mandatory
end
context "when the calculator returns 0" do
before { allow(calculator).to receive_messages(compute: 0) }
context "when adjustment is mandatory" do
before { tax_rate.create_adjustment("foo", target, order, true) }
it "should create an adjustment" do
expect(Spree::Adjustment.count).to eq 1
end
end
context "when adjustment is not mandatory" do
before { tax_rate.create_adjustment("foo", target, order, false) }
it "should not create an adjustment" do
expect(Spree::Adjustment.count).to eq 0
end
end
end
end
context "#update_adjustment" do
it "should update the adjustment using its calculator (and the specified source)" do
adjustment = double(:adjustment).as_null_object
calculable = double :calculable
expect(adjustment).to receive(:update_column).with(:amount, 10)
tax_rate.update_adjustment(adjustment, calculable)
end
end
end

View File

@@ -0,0 +1,80 @@
# frozen_string_literal: true
require 'spec_helper'
# We'll use the OrderMailer as a quick and easy way to test. IF it works here
# it works for all email (in theory.)
describe Spree::OrderMailer do
let(:order) { Spree::Order.new(email: "customer@example.com") }
let(:message) { Spree::OrderMailer.confirm_email(order) }
before(:all) do
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries.clear
end
context "#deliver" do
before do
ActionMailer::Base.delivery_method = :test
end
after { ActionMailer::Base.deliveries.clear }
it "should use the from address specified in the preference" do
Spree::Config[:mails_from] = "no-reply@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
expect(@email.from).to eq ["no-reply@foobar.com"]
end
it "should use the provided from address" do
Spree::Config[:mails_from] = "preference@foobar.com"
message.from = "override@foobar.com"
message.to = "test@test.com"
message.deliver
email = ActionMailer::Base.deliveries.first
expect(email.from).to eq ["override@foobar.com"]
expect(email.to).to eq ["test@test.com"]
end
it "should add the bcc email when provided" do
Spree::Config[:mail_bcc] = "bcc-foo@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
expect(@email.bcc).to eq ["bcc-foo@foobar.com"]
end
context "when intercept_email is provided" do
it "should strip the bcc recipients" do
expect(message.bcc).to be_blank
end
it "should strip the cc recipients" do
expect(message.cc).to be_blank
end
it "should replace the receipient with the specified address" do
Spree::Config[:intercept_email] = "intercept@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
expect(@email.to).to eq ["intercept@foobar.com"]
end
it "should modify the subject to include the original email" do
Spree::Config[:intercept_email] = "intercept@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
expect(@email.subject.match(/customer@example\.com/)).to be_truthy
end
end
context "when intercept_mode is not provided" do
it "should not modify the recipient" do
Spree::Config[:intercept_email] = ""
message.deliver
@email = ActionMailer::Base.deliveries.first
expect(@email.to).to eq ["customer@example.com"]
end
end
end
end

View File

@@ -0,0 +1,90 @@
# frozen_string_literal: true
require 'spec_helper'
module Spree
module Core
describe MailSettings do
let!(:subject) { MailSettings.new }
context "override option is true" do
before { Config.override_actionmailer_config = true }
context "init" do
it "calls override!" do
expect(MailSettings).to receive(:new).and_return(subject)
expect(subject).to receive(:override!)
MailSettings.init
end
end
context "enable delivery" do
before { Config.enable_mail_delivery = true }
context "overrides appplication defaults" do
context "authentication method is none" do
before do
Config.mail_host = "smtp.example.com"
Config.mail_domain = "example.com"
Config.mail_port = 123
Config.mail_auth_type = MailSettings::SECURE_CONNECTION_TYPES[0]
Config.smtp_username = "schof"
Config.smtp_password = "hellospree!"
Config.secure_connection_type = "TLS"
subject.override!
end
it { expect(ActionMailer::Base.smtp_settings[:address]).to eq "smtp.example.com" }
it { expect(ActionMailer::Base.smtp_settings[:domain]).to eq "example.com" }
it { expect(ActionMailer::Base.smtp_settings[:port]).to eq 123 }
it { expect(ActionMailer::Base.smtp_settings[:authentication]).to eq "None" }
it { expect(ActionMailer::Base.smtp_settings[:enable_starttls_auto]).to be_truthy }
it "doesnt touch user name config" do
expect(ActionMailer::Base.smtp_settings[:user_name]).to be_nil
end
it "doesnt touch password config" do
expect(ActionMailer::Base.smtp_settings[:password]).to be_nil
end
end
end
context "when mail_auth_type is other than none" do
before do
Config.mail_auth_type = "login"
Config.smtp_username = "schof"
Config.smtp_password = "hellospree!"
subject.override!
end
context "overrides user credentials" do
it { expect(ActionMailer::Base.smtp_settings[:user_name]).to eq "schof" }
it { expect(ActionMailer::Base.smtp_settings[:password]).to eq "hellospree!" }
end
end
end
context "do not enable delivery" do
before do
Config.enable_mail_delivery = false
subject.override!
end
it { expect(ActionMailer::Base.perform_deliveries).to be_falsy }
end
end
context "override option is false" do
before { Config.override_actionmailer_config = false }
context "init" do
it "doesnt calls override!" do
expect(subject).not_to receive(:override!)
MailSettings.init
end
end
end
end
end
end

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'spec_helper'
# Its pretty difficult to test this module in isolation b/c it needs to work in conjunction
# with an actual class that extends ActiveRecord::Base and has a corresponding table in the DB.
# So we'll just test it using Order instead since it included the module.
describe Spree::Core::TokenResource do
let(:order) { Spree::Order.new }
let(:permission) { double(Spree::TokenizedPermission) }
it 'should add has_one :tokenized_permission relationship' do
assert Spree::Order.
reflect_on_all_associations(:has_one).map(&:name).include?(:tokenized_permission)
end
context '#token' do
it 'should return the token of the associated permission' do
allow(order).to receive_messages tokenized_permission: permission
allow(permission).to receive_messages token: 'foo'
expect(order.token).to eq 'foo'
end
it 'should return nil if there is no associated permission' do
expect(order.token).to be_nil
end
end
context '#create_token' do
it 'should create a randomized 16 character token' do
token = order.create_token
expect(token.size).to eq 16
end
end
end

127
spec/lib/spree/i18n_spec.rb Normal file
View File

@@ -0,0 +1,127 @@
# frozen_string_literal: true
require 'rspec/expectations'
require 'spree/i18n'
require 'spree/testing_support/i18n'
describe "i18n" do
before do
I18n.backend.store_translations(
:en,
{
spree: {
foo: "bar",
bar: {
foo: "bar within bar scope",
invalid: nil,
legacy_translation: "back in the day..."
},
invalid: nil,
legacy_translation: "back in the day..."
}
}
)
end
it "translates within the spree scope" do
expect(Spree.normal_t(:foo)).to eql("bar")
expect(Spree.translate(:foo)).to eql("bar")
end
it "translates within the spree scope using a path" do
allow(Spree).to receive(:virtual_path).and_return('bar')
expect(Spree.normal_t('.legacy_translation')).to eql("back in the day...")
expect(Spree.translate('.legacy_translation')).to eql("back in the day...")
end
it "raise error without any context when using a path" do
expect {
Spree.normal_t('.legacy_translation')
}.to raise_error
expect {
Spree.translate('.legacy_translation')
}.to raise_error
end
it "prepends a string scope" do
expect(Spree.normal_t(:foo, scope: "bar")).to eql("bar within bar scope")
end
it "prepends to an array scope" do
expect(Spree.normal_t(:foo, scope: ["bar"])).to eql("bar within bar scope")
end
it "returns two translations" do
expect(Spree.normal_t([:foo, 'bar.foo'])).to eql(["bar", "bar within bar scope"])
end
it "returns reasonable string for missing translations" do
expect(Spree.t(:missing_entry)).to include("<span")
end
context "missed + unused translations" do
def key_with_locale(key)
"#{key} (#{I18n.locale})"
end
before do
Spree.used_translations = []
end
context "missed translations" do
def assert_missing_translation(key)
key = key_with_locale(key)
message = Spree.missing_translation_messages.detect { |m| m == key }
expect(message).not_to(be_nil, "expected '#{key}' to be missing, but it wasn't.")
end
it "logs missing translations" do
Spree.t(:missing, scope: [:else, :where])
Spree.check_missing_translations
assert_missing_translation("else")
assert_missing_translation("else.where")
assert_missing_translation("else.where.missing")
end
it "does not log present translations" do
Spree.t(:foo)
Spree.check_missing_translations
expect(Spree.missing_translation_messages).to be_empty
end
it "does not break when asked for multiple translations" do
Spree.t [:foo, 'bar.foo']
Spree.check_missing_translations
expect(Spree.missing_translation_messages).to be_empty
end
end
context "unused translations" do
def assert_unused_translation(key)
key = key_with_locale(key)
message = Spree.unused_translation_messages.detect { |m| m == key }
expect(message).not_to(be_nil, "expected '#{key}' to be unused, but it was used.")
end
def assert_used_translation(key)
key = key_with_locale(key)
message = Spree.unused_translation_messages.detect { |m| m == key }
expect(message).to(be_nil, "expected '#{key}' to be used, but it wasn't.")
end
it "logs translations that aren't used" do
Spree.check_unused_translations
assert_unused_translation("bar.legacy_translation")
assert_unused_translation("legacy_translation")
end
it "does not log used translations" do
Spree.t(:foo)
Spree.check_unused_translations
assert_used_translation("foo")
end
end
end
end

View File

@@ -0,0 +1,135 @@
# frozen_string_literal: false
require 'spec_helper'
describe Spree::Money do
before do
configure_spree_preferences do |config|
config.currency = "USD"
config.currency_symbol_position = :before
config.display_currency = false
end
end
it "formats correctly" do
money = Spree::Money.new(10)
expect(money.to_s).to eq("$10.00")
end
it "can get cents" do
money = Spree::Money.new(10)
expect(money.cents).to eq(1000)
end
context "with currency" do
it "passed in option" do
money = Spree::Money.new(10, with_currency: true, html: false)
expect(money.to_s).to eq("$10.00 USD")
end
it "config option" do
Spree::Config[:display_currency] = true
money = Spree::Money.new(10, html: false)
expect(money.to_s).to eq("$10.00 USD")
end
end
context "hide cents" do
it "hides cents suffix" do
Spree::Config[:hide_cents] = true
money = Spree::Money.new(10)
expect(money.to_s).to eq("$10")
end
it "shows cents suffix" do
Spree::Config[:hide_cents] = false
money = Spree::Money.new(10)
expect(money.to_s).to eq("$10.00")
end
end
context "currency parameter" do
context "when currency is specified in Canadian Dollars" do
it "uses the currency param over the global configuration" do
money = Spree::Money.new(10, currency: 'CAD', with_currency: true, html: false)
expect(money.to_s).to eq("$10.00 CAD")
end
end
context "when currency is specified in Japanese Yen" do
it "uses the currency param over the global configuration" do
money = Spree::Money.new(100, currency: 'JPY', html: false)
expect(money.to_s).to eq("¥100")
end
end
end
context "symbol positioning" do
it "passed in option" do
money = Spree::Money.new(10, symbol_position: :after, html: false)
expect(money.to_s).to eq("10.00 $")
end
it "passed in option string" do
money = Spree::Money.new(10, symbol_position: "after", html: false)
expect(money.to_s).to eq("10.00 $")
end
it "config option" do
Spree::Config[:currency_symbol_position] = :after
money = Spree::Money.new(10, html: false)
expect(money.to_s).to eq("10.00 $")
end
end
context "JPY" do
before do
configure_spree_preferences do |config|
config.currency = "JPY"
config.currency_symbol_position = :before
config.display_currency = false
end
end
it "formats correctly" do
money = Spree::Money.new(1000, html: false)
expect(money.to_s).to eq("¥1,000")
end
end
context "EUR" do
before do
configure_spree_preferences do |config|
config.currency = "EUR"
config.currency_symbol_position = :after
config.display_currency = false
end
end
# Regression test for Spree #2634
it "formats as plain by default" do
money = Spree::Money.new(10)
expect(money.to_s).to eq("10.00 €")
end
# Regression test for Spree #2632
it "acknowledges decimal mark option" do
Spree::Config[:currency_decimal_mark] = ","
money = Spree::Money.new(10)
expect(money.to_s).to eq("10,00 €")
end
# Regression test for Spree #2632
it "acknowledges thousands separator option" do
Spree::Config[:currency_thousands_separator] = "."
money = Spree::Money.new(1000)
expect(money.to_s).to eq("1.000.00 €")
end
it "formats as HTML if asked (nicely) to" do
money = Spree::Money.new(10)
# The HTMLified version of the euro sign
expect(money.to_html).to eq("10.00&nbsp;&#x20AC;")
end
end
end

View File

@@ -0,0 +1,86 @@
# frozen_string_literal: true
require 'spec_helper'
module Spree
describe Spree::ProductDuplicator do
let(:product) do
double 'Product',
name: "foo",
taxons: [],
product_properties: [property],
master: variant,
has_variants?: false
end
let(:new_product) do
double 'New Product',
save!: true
end
let(:property) do
double 'Property'
end
let(:new_property) do
double 'New Property'
end
let(:variant) do
double 'Variant',
sku: "12345",
price: 19.99,
currency: "AUD",
images: [image]
end
let(:new_variant) do
double 'New Variant',
sku: "12345"
end
let(:image) do
double 'Image',
attachment: double('Attachment')
end
let(:new_image) do
double 'New Image'
end
before do
expect(product).to receive(:dup).and_return(new_product)
expect(variant).to receive(:dup).and_return(new_variant)
expect(image).to receive(:dup).and_return(new_image)
expect(property).to receive(:dup).and_return(new_property)
end
it "can duplicate a product" do
duplicator = Spree::ProductDuplicator.new(product)
expect(new_product).to receive(:name=).with("COPY OF foo")
expect(new_product).to receive(:taxons=).with([])
expect(new_product).to receive(:product_properties=).with([new_property])
expect(new_product).to receive(:created_at=).with(nil)
expect(new_product).to receive(:updated_at=).with(nil)
expect(new_product).to receive(:deleted_at=).with(nil)
expect(new_product).to receive(:master=).with(new_variant)
expect(new_variant).to receive(:sku=).with("COPY OF 12345")
expect(new_variant).to receive(:deleted_at=).with(nil)
expect(new_variant).to receive(:images=).with([new_image])
expect(new_variant).to receive(:price=).with(variant.price)
expect(new_variant).to receive(:currency=).with(variant.currency)
expect(image.attachment).to receive(:clone).and_return(image.attachment)
expect(new_image).to receive(:assign_attributes).
with(attachment: image.attachment).
and_return(new_image)
expect(new_property).to receive(:created_at=).with(nil)
expect(new_property).to receive(:updated_at=).with(nil)
duplicator.duplicate
end
end
end