require 'spec_helper' describe Spree::LineItem, type: :model do let(:order) { create :order_with_line_items, line_items_count: 1 } let(:line_item) { order.line_items.first } before { create(:store) } describe 'Validations' do it { expect(line_item).to validate_numericality_of(:quantity).only_integer.with_message(Spree.t('validation.must_be_int')) } it { expect(line_item).to validate_presence_of(:order) } describe 'ensure_proper_currency' do context 'order is present' do context "when line_item's currency matches with order's" do it { expect(line_item).to be_valid } end context "when line_item's currency does not matches with order's" do before do line_item.currency = "Invalid Currency" end it { expect(line_item).not_to be_valid } end end end end describe '#ensure_valid_quantity' do context 'quantity.nil?' do before do line_item.quantity = nil line_item.valid? end it { expect(line_item.quantity).to be_zero } end context 'quantity < 0' do before do line_item.quantity = -1 line_item.valid? end it { expect(line_item.quantity).to be_zero } end context 'quantity = 0' do before do line_item.quantity = 0 line_item.valid? end it { expect(line_item.quantity).to be_zero } end context 'quantity > 0' do let(:original_quantity) { 1 } before do line_item.quantity = original_quantity line_item.valid? end it { expect(line_item.quantity).to eq(original_quantity) } end end context '#save' do it 'touches the order' do expect(line_item.order).to receive(:touch) line_item.touch end end context "#discontinued" do it "fetches discontinued products" do line_item.product.discontinue! expect(line_item.reload.product).to be_a Spree::Product end it "fetches discontinued variants" do line_item.variant.discontinue! expect(line_item.reload.variant).to be_a Spree::Variant end end context "#destroy" do let!(:line_item) { order.line_items.first } it "returns inventory when a line item is destroyed" do is_expected.to callback(:verify_order_inventory).before(:destroy) end it "deletes inventory units" do expect { line_item.destroy }.to change { line_item.inventory_units.count }.from(1).to(0) end end context "#save" do context "line item changes" do before do line_item.quantity = line_item.quantity + 1 end it "triggers adjustment total recalculation" do expect(line_item).to receive(:update_tax_charge) # Regression test for https://github.com/spree/spree/issues/4671 expect(line_item).to receive(:recalculate_adjustments) line_item.save end end context "line item does not change" do it "does not trigger adjustment total recalculation" do expect(line_item).not_to receive(:recalculate_adjustments) line_item.save end end context "target_shipment is provided" do it "verifies inventory" do line_item.target_shipment = Spree::Shipment.new expect_any_instance_of(Spree::OrderInventory).to receive(:verify) line_item.save end end end context "#create" do let(:variant) { create(:variant) } before do create(:tax_rate, zone: order.tax_zone, tax_category: variant.tax_category) end context "when order has a tax zone" do before do expect(order.tax_zone).to be_present end it "creates a tax adjustment" do order.contents.add(variant) line_item = order.find_line_item_by_variant(variant) expect(line_item.adjustments.tax.count).to eq(1) end end context "when order does not have a tax zone" do before do order.bill_address = nil order.ship_address = nil order.save expect(order.reload.tax_zone).to be_nil end it "does not create a tax adjustment" do order.contents.add(variant) line_item = order.find_line_item_by_variant(variant) expect(line_item.adjustments.tax.count).to eq(0) end end end # Test for #3391 context '#copy_price' do it "copies over a variant's prices" do line_item.price = nil line_item.cost_price = nil line_item.currency = nil line_item.copy_price variant = line_item.variant expect(line_item.price).to eq(variant.price) expect(line_item.cost_price).to eq(variant.cost_price) expect(line_item.currency).to eq(variant.currency) end end # test for copying prices when the vat changes context "#update_price" do it "copies over a variants differing price for another vat zone" do expect(line_item.variant).to receive(:price_including_vat_for).and_return(12) line_item.price = 10 line_item.update_price expect(line_item.price).to eq(12) end end # Test for #3481 context '#copy_tax_category' do it "copies over a variant's tax category" do line_item.tax_category = nil line_item.copy_tax_category expect(line_item.tax_category).to eq(line_item.variant.tax_category) end end describe '#discounted_amount' do it "returns the amount minus any discounts" do line_item.price = 10 line_item.quantity = 2 line_item.taxable_adjustment_total = -5 expect(line_item.discounted_amount).to eq(15) end end describe '.currency' do it 'returns the globally configured currency' do line_item.currency == 'USD' end end describe "#discounted_money" do it "should return a money object with the discounted amount" do expect(line_item.discounted_money.to_s).to eq "$10.00" end end describe "#money" do before do line_item.price = 3.50 line_item.quantity = 2 end it "returns a Spree::Money representing the total for this line item" do expect(line_item.money.to_s).to eq("$7.00") end end describe '#single_money' do before do line_item.price = 3.50 line_item.quantity = 2 end it "returns a Spree::Money representing the price for one variant" do expect(line_item.single_money.to_s).to eq("$3.50") end end context "has inventory (completed order so items were already unstocked)" do let(:order) { Spree::Order.create(email: 'spree@example.com') } let(:variant) { create(:variant) } context "nothing left on stock" do before do variant.stock_items.update_all count_on_hand: 5, backorderable: false order.contents.add(variant, 5) order.create_proposed_shipments order.finalize! order.reload end it "allows to decrease item quantity" do line_item = order.line_items.first line_item.quantity -= 1 line_item.target_shipment = order.shipments.first line_item.valid? expect(line_item.errors_on(:quantity).size).to eq(0) end it "doesnt allow to increase item quantity" do line_item = order.line_items.first line_item.quantity += 2 line_item.target_shipment = order.shipments.first line_item.valid? expect(line_item.errors_on(:quantity).size).to eq(1) end end context "2 items left on stock" do before do variant.stock_items.update_all count_on_hand: 7, backorderable: false order.contents.add(variant, 5) order.create_proposed_shipments order.finalize! order.reload end it "allows to increase quantity up to stock availability" do line_item = order.line_items.first line_item.quantity += 2 line_item.target_shipment = order.shipments.first line_item.valid? expect(line_item.errors_on(:quantity).size).to eq(0) end it "doesnt allow to increase quantity over stock availability" do line_item = order.line_items.first line_item.quantity += 3 line_item.target_shipment = order.shipments.first line_item.valid? expect(line_item.errors_on(:quantity).size).to eq(1) end end end context "currency same as order.currency" do it "is a valid line item" do line_item = order.line_items.first line_item.currency = order.currency line_item.valid? expect(line_item.error_on(:currency).size).to eq(0) end end context "currency different than order.currency" do it "is not a valid line item" do line_item = order.line_items.first line_item.currency = "no currency" line_item.valid? expect(line_item.error_on(:currency).size).to eq(1) end end describe "#options=" do it "can handle updating a blank line item with no order" do line_item.options = { price: 123 } end it "updates the data provided in the options" do line_item.options = { price: 123 } expect(line_item.price).to eq 123 end it "updates the price based on the options provided" do expect(line_item).to receive(:gift_wrap=).with(true) expect(line_item.variant).to receive(:gift_wrap_price_modifier_amount_in).with("USD", true).and_return 1.99 line_item.options = { gift_wrap: true } expect(line_item.price).to eq 21.98 end end describe "precision of pre_tax_amount" do let(:line_item) { create :line_item, pre_tax_amount: 4.2051 } it "keeps four digits of precision even when reloading" do # prevent it from updating pre_tax_amount allow_any_instance_of(Spree::LineItem).to receive(:update_tax_charge) expect(line_item.reload.pre_tax_amount).to eq(4.2051) end end end