Files
openfoodnetwork/spec/models/spree/variant_spec.rb
David Cook c165ade4ba Avoid unnecessary save
Actually, the variant factory is still adding an extra save. We should refactor Variant to avoid that.. but the afternoon slump has got me.
2026-03-11 11:09:12 +11:00

1035 lines
32 KiB
Ruby

# frozen_string_literal: false
require 'spree/localized_number'
RSpec.describe Spree::Variant do
subject(:variant) { build(:variant) }
it { is_expected.to have_many :semantic_links }
it { is_expected.to belong_to(:product).required }
it { is_expected.to belong_to(:supplier).required }
it { is_expected.to belong_to(:owner).optional }
it { is_expected.to have_many(:inventory_units) }
it { is_expected.to have_many(:line_items) }
it { is_expected.to have_many(:stock_items) }
it { is_expected.to have_many(:images) }
it { is_expected.to have_one(:default_price) }
it { is_expected.to have_many(:prices) }
it { is_expected.to have_many(:exchange_variants) }
it { is_expected.to have_many(:exchanges).through(:exchange_variants) }
it { is_expected.to have_many(:variant_overrides) }
it { is_expected.to have_many(:inventory_items) }
it { is_expected.to have_many(:supplier_properties).through(:supplier) }
it { is_expected.to have_many(:source_variants).through(:variant_links_as_target) }
it { is_expected.to have_many(:target_variants).through(:variant_links_as_source) }
describe "shipping category" do
it "sets a shipping category if none provided" do
variant = build(:variant, shipping_category: nil)
expect(variant).to be_valid
expect(variant.shipping_category).not_to be_nil
end
end
describe "supplier properties" do
subject { create(:variant) }
it "has no supplier properties to start with" do
expect(subject.supplier_properties).to eq []
end
it "includes the supplier's properties" do
subject.supplier.set_producer_property("certified", "yes")
expect(subject.supplier_properties.map(&:presentation)).to eq ["certified"]
end
end
describe "validations" do
describe "variant_unit" do
subject(:variant) { build(:variant) }
it { is_expected.to validate_presence_of :variant_unit }
context "when the unit is items" do
subject(:variant) { build(:variant, variant_unit: "items", variant_unit_name: "box") }
it "is valid with only unit value set" do
variant.unit_value = 1
variant.unit_description = nil
expect(variant).to be_valid
end
it "is valid with only unit description set" do
variant.unit_value = nil
variant.unit_description = 'Medium'
expect(variant).to be_valid
end
it "sets unit_value to 1.0 before validation if it's nil" do
variant.unit_value = nil
variant.unit_description = nil
expect(variant).to be_valid
expect(variant.unit_value).to eq 1.0
end
end
context "when the product's unit is non-weight" do
subject(:variant) { build(:variant, variant_unit: "volume") }
it "sets weight to decimal before save if it's integer" do
variant.weight = 1
variant.save!
expect(variant.weight).to eq 1.0
end
it "sets weight to 0.0 before save if it's nil" do
variant.weight = nil
variant.save!
expect(variant.weight).to eq 0.0
end
it "sets weight to 0.0 if input is a non numerical string" do
variant.weight = "BANANAS!"
variant.save!
expect(variant.weight).to eq 0.0
end
it "sets weight to correct decimal value if input is numerical string" do
variant.weight = "2"
variant.save!
expect(variant.weight).to eq 2.0
end
end
end
describe "price" do
it { is_expected.to validate_presence_of :price }
it "should validate price is greater than 0" do
variant.price = -1
expect(variant).not_to be_valid
end
it "should validate price is 0" do
variant.price = 0
expect(variant).to be_valid
end
it "should validate unit_value is greater than 0" do
variant.unit_value = 0
expect(variant).not_to be_valid
end
end
describe "unit_value" do
subject(:variant) { build(:variant, variant_unit: "item", unit_value: "") }
it { is_expected.not_to validate_presence_of(:unit_value) }
%w(weight volume).each do |unit|
context "when variant_unit is #{unit}" do
subject(:variant) { build(:variant, variant_unit: unit) }
it { is_expected.to validate_presence_of(:unit_value) }
it { is_expected.to validate_numericality_of(:unit_value).is_greater_than(0) }
end
end
end
describe "unit_description" do
subject(:variant) { build(:variant) }
it { expect(variant).to be_valid }
it { is_expected.not_to validate_presence_of(:unit_description) }
context "when variant_unit is set and unit_value is nil" do
subject(:variant) {
build(:variant, variant_unit: "item", unit_value: nil, unit_description: "box")
}
it { is_expected.to validate_presence_of(:unit_description) }
end
end
describe "variant_unit_scale" do
subject(:variant) { build(:variant, variant_unit: "box") }
it { is_expected.not_to validate_presence_of :variant_unit_scale }
%w(weight volume).each do |unit|
context "when variant_unit is #{unit}" do
subject(:variant) { build(:variant, variant_unit: unit, variant_unit_scale: 1.0) }
it { is_expected.to validate_presence_of :variant_unit_scale }
end
end
end
describe "variant_unit_name" do
subject(:variant) { build(:variant) }
it { is_expected.not_to validate_presence_of :variant_unit_name }
context "when variant_unit is items" do
subject(:variant) { build(:variant, variant_unit: "items") }
it { is_expected.to validate_presence_of :variant_unit_name }
end
end
describe "variant_unit_scale" do
subject(:variant) { build(:variant, variant_unit: "box") }
it { is_expected.not_to validate_presence_of :variant_unit_scale }
%w(weight volume).each do |unit|
context "when variant_unit is #{unit}" do
subject(:variant) { build(:variant, variant_unit: unit, variant_unit_scale: 1.0) }
it { is_expected.to validate_presence_of :variant_unit_scale }
end
end
end
describe "variant_unit_name" do
subject(:variant) { build(:variant) }
it { is_expected.not_to validate_presence_of :variant_unit_name }
context "when variant_unit is items" do
subject(:variant) { build(:variant, variant_unit: "items") }
it { is_expected.to validate_presence_of :variant_unit_name }
end
end
describe "tax category" do
# `build_stubbed` avoids creating a tax category in the database.
subject(:variant) { build_stubbed(:variant) }
it "is valid when empty by default" do
expect(variant.tax_category).to eq nil
expect(variant).to be_valid
end
it "loads the default tax category" do
default = create(:tax_category, is_default: true)
expect(variant.tax_category).to eq default
expect {
variant.tax_category = nil
}.not_to change {
variant.tax_category
}
expect(variant).to be_valid
end
it "doesn't load any tax category" do
non_default = create(:tax_category, is_default: false)
expect(variant.tax_category).to eq nil
end
context "when a tax category is required" do
before { Spree::Config.products_require_tax_category = true }
it { is_expected.to validate_presence_of :tax_category }
end
end
end
describe "#changed?" do
subject(:variant) { create(:variant) }
it { is_expected.not_to be_changed }
it "is changed when basic fields are changed" do
subject.display_name = "blah"
expect(subject).to be_changed
end
describe "default_price" do
it "price" do
subject.price = 100
expect(subject).to be_changed
end
it "currency" do
subject.currency = "USD"
expect(subject).to be_changed
end
end
end
context "price parsing" do
context "price=" do
context "with decimal point" do
it "captures the proper amount for a formatted price" do
variant.price = '1,599.99'
expect(variant.price).to eq 1599.99
end
end
context "with decimal comma" do
it "captures the proper amount for a formatted price" do
I18n.with_locale(:es) do
variant.price = '1.599,99'
expect(variant.price).to eq 1599.99
end
end
end
context "with a numeric price" do
it "uses the price as is" do
I18n.with_locale(:es) do
variant.price = 1599.99
expect(variant.price).to eq 1599.99
end
end
end
end
end
context "#currency" do
it "returns the globally configured currency" do
variant.save!
expect(variant.currency).to eq "AUD"
end
end
context "#display_amount" do
it "returns a Spree::Money" do
variant.price = 21.22
expect(variant.display_amount.to_s).to eq "$21.22"
end
end
context "#cost_currency" do
context "when cost currency is nil" do
before { variant.cost_currency = nil }
it "populates cost currency with the default value on save" do
variant.save!
expect(variant.cost_currency).to eq "AUD"
end
end
end
describe '.price_in' do
before do
variant.prices << create(:price, variant:, currency: "EUR", amount: 33.33)
end
subject { variant.price_in(currency).display_amount }
context "when currency is not specified" do
let(:currency) { nil }
it "returns 0" do
expect(subject.to_s).to eq "$0.00"
end
end
context "when currency is EUR" do
let(:currency) { 'EUR' }
it "returns the value in EUR" do
expect(subject.to_s).to eq "€33.33"
end
end
context "when currency is AUD" do
let(:currency) { 'AUD' }
it "returns the value in AUD" do
expect(subject.to_s).to eq "$19.99"
end
end
end
describe '.amount_in' do
before do
variant.prices << create(:price, variant:, currency: "EUR", amount: 33.33)
end
subject { variant.amount_in(currency) }
context "when currency is not specified" do
let(:currency) { nil }
it "returns nil" do
expect(subject).to be_nil
end
end
context "when currency is EUR" do
let(:currency) { 'EUR' }
it "returns the value in EUR" do
expect(subject).to eq 33.33
end
end
context "when currency is AUD" do
let(:currency) { 'AUD' }
it "returns the value in AUD" do
expect(subject).to eq 19.99
end
end
end
describe '#in_stock?' do
# Stock data can only be stored against a persisted variant.
subject(:variant) { create(:variant) }
context 'when stock_items are not backorderable' do
before do
allow_any_instance_of(Spree::StockItem).to receive_messages(backorderable: false)
end
context 'when stock_items in stock' do
before do
allow_any_instance_of(Spree::StockItem).to receive_messages(count_on_hand: 10)
end
it 'returns true if stock_items in stock' do
expect(variant.in_stock?).to be_truthy
end
end
context 'when stock_items out of stock' do
before do
allow_any_instance_of(Spree::StockItem).to receive_messages(backorderable: false)
allow_any_instance_of(Spree::Stock::Quantifier).to receive_messages(total_on_hand: 0)
end
it 'return false if stock_items out of stock' do
expect(variant.in_stock?).to be_falsy
end
end
context 'when providing quantity param' do
before do
variant.stock_items.first.update_attribute(:count_on_hand, 10)
end
it 'returns correct value' do
expect(variant.in_stock?).to be_truthy
expect(variant.in_stock?(2)).to be_truthy
expect(variant.in_stock?(10)).to be_truthy
expect(variant.in_stock?(11)).to be_falsy
end
end
end
context 'when stock_items are backorderable' do
before do
allow_any_instance_of(Spree::StockItem).to receive_messages(backorderable?: true)
end
context 'when stock_items out of stock' do
before do
allow_any_instance_of(Spree::StockItem).to receive_messages(count_on_hand: 0)
end
it 'returns true if stock_items in stock' do
expect(variant.in_stock?).to be_truthy
end
end
end
end
describe '#total_on_hand' do
it 'matches quantifier total_on_hand' do
variant = build(:variant)
expect(variant.total_on_hand).to eq(Spree::Stock::Quantifier.new(variant).total_on_hand)
end
end
describe "scopes" do
# TODO rename describer below with scope names
describe "finding variants in a distributor" do
let!(:d1) { create(:distributor_enterprise) }
let!(:d2) { create(:distributor_enterprise) }
let!(:p1) { create(:simple_product) }
let!(:p2) { create(:simple_product) }
let!(:oc1) { create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first]) }
let!(:oc2) { create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first]) }
it "shows variants in an order cycle distribution" do
expect(Spree::Variant.in_distributor(d1)).to eq([p1.variants.first])
end
it "doesn't show duplicates" do
oc_dup = create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first])
expect(Spree::Variant.in_distributor(d1)).to eq([p1.variants.first])
end
end
describe "finding variants in an order cycle" do
let!(:d1) { create(:distributor_enterprise) }
let!(:d2) { create(:distributor_enterprise) }
let!(:p1) { create(:product) }
let!(:p2) { create(:product) }
let!(:oc1) { create(:simple_order_cycle, distributors: [d1], variants: [p1.variants.first]) }
let!(:oc2) { create(:simple_order_cycle, distributors: [d2], variants: [p2.variants.first]) }
it "shows variants in an order cycle" do
expect(Spree::Variant.in_order_cycle(oc1)).to eq([p1.variants.first])
end
it "doesn't show duplicates" do
ex = create(:exchange, order_cycle: oc1, sender: oc1.coordinator, receiver: d2)
ex.variants << p1.variants.first
expect(Spree::Variant.in_order_cycle(oc1)).to eq([p1.variants.first])
end
end
describe "finding variants for an order cycle and hub" do
let(:oc) { create(:simple_order_cycle) }
let(:s) { create(:supplier_enterprise) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let(:p1) { create(:simple_product) }
let(:p2) { create(:simple_product) }
let(:v1) { create(:variant, product: p1) }
let(:v2) { create(:variant, product: p2) }
let(:p_external) { create(:simple_product) }
let(:v_external) { create(:variant, product: p_external) }
let!(:ex_in) {
create(:exchange, order_cycle: oc, sender: s, receiver: oc.coordinator,
incoming: true, variants: [v1, v2])
}
let!(:ex_out1) {
create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d1,
incoming: false, variants: [v1])
}
let!(:ex_out2) {
create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d2,
incoming: false, variants: [v2])
}
it "returns variants in the order cycle and distributor" do
expect(p1.variants.for_distribution(oc, d1)).to eq([v1])
expect(p2.variants.for_distribution(oc, d2)).to eq([v2])
end
it "does not return variants in the order cycle but not the distributor" do
expect(p1.variants.for_distribution(oc, d2)).to be_empty
expect(p2.variants.for_distribution(oc, d1)).to be_empty
end
it "does not return variants not in the order cycle" do
expect(p_external.variants.for_distribution(oc, d1)).to be_empty
end
end
describe "finding variants based on visiblity in inventory" do
let(:enterprise) { create(:distributor_enterprise) }
let!(:new_variant) { create(:variant) }
let!(:hidden_variant) { create(:variant) }
let!(:visible_variant) { create(:variant) }
let!(:hidden_inventory_item) {
create(:inventory_item, enterprise:, variant: hidden_variant, visible: false )
}
let!(:visible_inventory_item) {
create(:inventory_item, enterprise:, variant: visible_variant, visible: true )
}
context "finding variants that are not hidden from an enterprise's inventory" do
context "when the enterprise given is nil" do
let!(:variants) { Spree::Variant.not_hidden_for(nil) }
it "returns an empty list" do
expect(variants).to eq []
end
end
context "when an enterprise is given" do
let!(:variants) { Spree::Variant.not_hidden_for(enterprise) }
it "lists any variants that are not listed as visible=false" do
expect(variants).to include new_variant, visible_variant
expect(variants).not_to include hidden_variant
end
context "when inventory items exist for other enterprises" do
let(:other_enterprise) { create(:distributor_enterprise) }
let!(:new_inventory_item) {
create(:inventory_item, enterprise: other_enterprise, variant: new_variant,
visible: true )
}
let!(:hidden_inventory_item2) {
create(:inventory_item, enterprise: other_enterprise, variant: visible_variant,
visible: false )
}
let!(:visible_inventory_item2) {
create(:inventory_item, enterprise: other_enterprise, variant: hidden_variant,
visible: true )
}
it "lists any variants not listed as visible=false only for the relevant enterprise" do
expect(variants).to include new_variant, visible_variant
expect(variants).not_to include hidden_variant
end
end
end
end
context "finding variants that are visible in an enterprise's inventory" do
let!(:variants) { Spree::Variant.visible_for(enterprise) }
it "lists any variants that are listed as visible=true" do
expect(variants).to include visible_variant
expect(variants).not_to include new_variant, hidden_variant
end
end
end
describe ".with_properties" do
let!(:variant_without_wanted_property_on_supplier) {
create(:variant, supplier: supplier_without_wanted_property)
}
let!(:variant_with_wanted_property_on_supplier) {
create(:variant, supplier: supplier_with_wanted_property)
}
let(:supplier_with_wanted_property) {
create(:supplier_enterprise, properties: [wanted_property])
}
let(:supplier_without_wanted_property) {
create(:supplier_enterprise, properties: [unwanted_property])
}
let(:wanted_property) { create(:property, presentation: 'Certified Organic') }
let(:unwanted_property) { create(:property, presentation: 'Latest Hype') }
it "returns no products without a property id" do
expect(Spree::Variant.with_properties([])).to eq []
end
it "returns only variants with the wanted property set on supplier" do
expect(
Spree::Variant.with_properties([wanted_property.id])
).to match_array [variant_with_wanted_property_on_supplier]
end
end
end
describe "indexing variants by id" do
let!(:v1) { create(:variant) }
let!(:v2) { create(:variant) }
let!(:v3) { create(:variant) }
it "indexes variants by id" do
expect(Spree::Variant.where(id: [v1, v2, v3]).indexed).to eq(
v1.id => v1, v2.id => v2, v3.id => v3
)
end
end
describe ".linked_to" do
let!(:variant_unlinked) { create(:variant) }
let!(:variant_linked) { create(:variant, semantic_links: [link]) }
let!(:variant_linked_unrelated) { create(:variant, semantic_links: [unrelated_link]) }
let(:link) { SemanticLink.new(semantic_id: "#my_precious") }
let(:unrelated_link) { SemanticLink.new(semantic_id: "#other") }
it "finds a variant by link" do
expect(Spree::Variant.linked_to("#my_precious"))
.to eq variant_linked
end
end
describe "generating the product and variant name" do
let(:product) { variant.product }
context "when full_name starts with the product name" do
before do
product.name = "Apple"
variant.display_as = "Apple Pink Lady"
end
it "does not show the product name twice" do
expect(variant.product_and_full_name).to eq "Apple Pink Lady"
end
end
context "when full_name does not start with the product name" do
before do
product.name = "Apple"
variant.display_as = "Pink Lady"
end
it "prepends the product name to the full name" do
expect(variant.product_and_full_name).to eq "Apple - Pink Lady"
end
end
context "handling nil values for related naming attributes" do
it "returns empty string or product name" do
product.name = "Apple"
product.display_as = nil
variant.variant_unit = "items"
variant.display_as = nil
variant.display_name = nil
expect(variant.full_name).to eq ""
expect(variant.product_and_full_name).to eq product.name
end
it "uses the display name correctly" do
product.name = "Apple"
product.display_as = nil
variant.variant_unit = "items"
variant.display_as = nil
variant.unit_presentation = nil
variant.display_name = "Green"
expect(variant.full_name).to eq "Green"
expect(variant.product_and_full_name).to eq "Apple - Green"
end
end
end
describe "calculating the price with enterprise fees" do
it "returns the price plus the fees" do
distributor = instance_double(Enterprise)
order_cycle = instance_double(OrderCycle)
variant = Spree::Variant.new price: 100
expect(variant).to receive(:fees_for).with(distributor, order_cycle) { 23 }
expect(variant.price_with_fees(distributor, order_cycle)).to eq(123)
end
end
describe "calculating the fees" do
it "delegates to EnterpriseFeeCalculator" do
distributor = instance_double(Enterprise)
order_cycle = instance_double(OrderCycle)
variant = Spree::Variant.new
expect_any_instance_of(OpenFoodNetwork::EnterpriseFeeCalculator)
.to receive(:fees_for).with(variant) { 23 }
expect(variant.fees_for(distributor, order_cycle)).to eq(23)
end
end
describe "calculating fees broken down by fee type" do
it "delegates to EnterpriseFeeCalculator" do
distributor = instance_double(Enterprise)
order_cycle = instance_double(OrderCycle)
variant = Spree::Variant.new
fees = instance_double(EnterpriseFee)
expect_any_instance_of(OpenFoodNetwork::EnterpriseFeeCalculator)
.to receive(:fees_by_type_for).with(variant) { fees }
expect(variant.fees_by_type_for(distributor, order_cycle)).to eq(fees)
end
end
describe "unit value/description" do
let(:v) { Spree::Variant.new(unit_presentation: "small" ) }
describe "generating the full name" do
it "returns unit_to_display when display_name is blank" do
v.display_name = ""
expect(v.full_name).to eq("small")
end
it "returns display_name when it contains unit_to_display" do
v.display_name = "a small apple"
expect(v.full_name).to eq "a small apple"
end
it "returns unit_to_display when it contains display_name" do
v.display_name = "small"
v.unit_presentation = "small size"
expect(v.full_name).to eq "small size"
end
it "returns a combination otherwise" do
v.display_name = "apple"
expect(v.full_name).to eq "apple (small)"
end
it "is resilient to regex chars" do
v.display_name = ")))"
v.unit_presentation = ")))"
expect(v.full_name).to eq(")))")
end
end
describe "getting name for display" do
it "returns display_name if present" do
v.display_name = "foo"
expect(v.name_to_display).to eq("foo")
end
it "returns product name if display_name is empty" do
v.product = Spree::Product.new(name: "Apple")
v.display_name = nil
expect(v.name_to_display).to eq "Apple"
v.display_name = ""
expect(v.name_to_display).to eq "Apple"
end
end
describe "getting unit for display" do
it "returns display_as if present" do
v.display_as = "foo"
expect(v.unit_to_display).to eq("foo")
end
it "returns options_text if display_as is blank" do
v.display_as = nil
expect(v.unit_to_display).to eq("small")
v.display_as = ""
expect(v.unit_to_display).to eq("small")
end
end
describe "setting the variant's weight from the unit value" do
it "sets the variant's weight when unit is weight" do
v = create(:variant, weight: 0)
v.update!(
variant_unit: 'weight', variant_unit_scale: 1, unit_value: 10, unit_description: 'foo'
)
expect(v.reload.weight).to eq(0.01)
end
it "does nothing when unit is not weight" do
v = create(:variant, weight: 123, variant_unit: 'volume')
v.update! variant_unit: 'volume', variant_unit_scale: 1, unit_value: 10,
unit_description: 'foo'
expect(v.reload.weight).to eq(123)
end
it "does nothing when unit_value is not set" do
v = create(:variant, weight: 123, variant_unit: 'volume')
# Although invalid, this calls the before_validation callback, which would
# error if not handling unit_value == nil case
expect(
v.update(variant_unit: "weight", variant_unit_scale: 1, unit_value: nil,
unit_description: "foo")
).to be false
expect(v.reload.weight).to eq(123)
end
end
context "when the variant already has a value set" do
let!(:v) {
create(:variant, variant_unit: 'weight', variant_unit_scale: 1, unit_value: 5,
unit_description: 'bar')
}
it "assigns the new option value" do
expect(v.unit_presentation).to eq "5g bar"
v.update!(unit_value: 10, unit_description: 'foo')
expect(v.unit_presentation).to eq "10g foo"
end
end
context "when the variant does not have a display_as value set" do
let!(:v) {
create(:variant, variant_unit: 'weight', variant_unit_scale: 1, unit_value: 5,
unit_description: 'bar', display_as: '')
}
it "requests the new value from OptionValueName" do
expect_any_instance_of(VariantUnits::OptionValueNamer)
.to receive(:name).exactly(1).times.and_call_original
v.update(unit_value: 10, unit_description: 'foo')
expect(v.unit_presentation).to eq "10g foo"
end
end
context "when the variant has a display_as value set" do
let!(:v) {
create(:variant, variant_unit: 'weight', variant_unit_scale: 1, unit_value: 5,
unit_description: 'bar', display_as: 'FOOS!')
}
it "does not request the new value from OptionValueName" do
expect_any_instance_of(VariantUnits::OptionValueNamer).not_to receive(:name)
v.update!(unit_value: 10, unit_description: 'foo')
expect(v.unit_presentation).to eq("FOOS!")
end
end
end
context "extends LocalizedNumber" do
subject! { build_stubbed(:variant) }
it_behaves_like "a model using the LocalizedNumber module", [:price, :weight]
end
context "in a circular order cycle setup" do
let(:enterprise1) { create(:distributor_enterprise, is_primary_producer: true) }
let(:enterprise2) { create(:distributor_enterprise, is_primary_producer: true) }
let(:variant1) { create(:variant) }
let(:variant2) { create(:variant) }
let!(:order_cycle) do
enterprise1.supplied_variants << variant1
enterprise2.supplied_variants << variant2
create(
:simple_order_cycle,
coordinator: enterprise1,
suppliers: [enterprise1, enterprise2],
distributors: [enterprise1, enterprise2],
variants: [variant1, variant2]
)
end
it "saves without infinite loop" do
expect(variant1.update(price: 1)).to be_truthy
end
end
describe "destruction" do
it "destroys exchange variants" do
variant = create(:variant)
exchange = create(:exchange, variants: [variant])
variant.destroy
expect(exchange.reload.variant_ids).to be_empty
end
it "touches the supplier" do
supplier = create(:supplier_enterprise, updated_at: 1.hour.ago)
variant = create(:variant, supplier:)
expect { variant.destroy }.to change { supplier.reload.updated_at }
end
it "touches distributors" do
variant = create(:variant)
updated_at = 1.hour.ago
distributor1 = create(:distributor_enterprise, updated_at:)
distributor2 = create(:distributor_enterprise, updated_at:)
create(:simple_order_cycle, distributors: [distributor1], variants: [variant])
create(:simple_order_cycle, distributors: [distributor2], variants: [variant])
expect { variant.destroy }
.to change { distributor1.reload.updated_at }
.and change { distributor2.reload.updated_at }
end
end
describe "#ensure_unit_value" do
let(:variant) { create(:variant, variant_unit: "weight") }
context "when variant_unit value is changed from weight to items" do
it "sets the variant's unit_value to 1" do
variant.update(variant_unit: "items")
expect(variant.unit_value).to eq 1
end
end
context "trying to set an invalid unit_value" do
it "does not allow NaN" do
variant.update(unit_value: Float::NAN)
expect(variant.reload.unit_value).to eq(1.0)
end
end
end
describe "#default_price" do
let(:variant) { create(:variant) }
let(:default_price) { variant.default_price }
context "when the default price is soft-deleted" do
it "can access the default price" do
price_id = default_price.id
default_price.destroy
expect(variant.reload.default_price).to be_a Spree::Price
expect(variant.default_price.id).to eq price_id
end
end
end
describe "after save callback" do
let(:variant) { create(:variant) }
it "updates units and unit_presenation when saved change to variant unit" do
variant.variant_unit = 'items'
variant.variant_unit_scale = nil
variant.variant_unit_name = 'loaf'
variant.save!
expect(variant.variant_unit_name).to eq 'loaf'
expect(variant.unit_presentation).to eq "1 loaf"
variant.update(variant_unit_name: 'bag')
expect(variant.variant_unit_name).to eq 'bag'
expect(variant.unit_presentation).to eq "1 bag"
variant.variant_unit = 'weight'
variant.variant_unit_scale = 1
variant.save!
expect(variant.variant_unit).to eq 'weight'
expect(variant.unit_presentation).to eq "1g"
expect(variant.variant_unit_name).to eq('')
variant.update(variant_unit: 'volume')
expect(variant.variant_unit).to eq 'volume'
expect(variant.unit_presentation).to eq "1L"
variant.update(display_as: 'My display')
expect(variant.unit_presentation).to eq "My display"
end
end
describe "#create_sourced_variant" do
let(:user) { create(:user, enterprises: [enterprise]) }
let(:supplier) { variant.supplier }
let(:enterprise) { create(:enterprise) }
context "with create_sourced_variants permissions on supplier" do
let!(:enterprise_relationship) {
create(:enterprise_relationship,
parent: supplier,
child: enterprise,
permissions_list: [:create_sourced_variants])
}
let(:variant) { create(:variant, price: 10.95, on_demand: false, on_hand: 5) }
it "clones the variant, retaining a link to the source" do
sourced_variant = variant.create_sourced_variant(user)
expect(sourced_variant.source_variants).to eq [variant]
expect(sourced_variant.owner).to eq enterprise
expect(sourced_variant.price).to eq 10.95
expect(sourced_variant.on_demand).to eq false
expect(sourced_variant.on_hand).to eq 5
end
end
end
end