mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-18 00:17:25 +00:00
327 lines
13 KiB
Ruby
327 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'system_helper'
|
|
|
|
RSpec.describe "full-page cart" do
|
|
include AuthenticationHelper
|
|
include WebHelper
|
|
include ShopWorkflow
|
|
include UIComponentHelper
|
|
|
|
describe "viewing the cart" do
|
|
let!(:zone) { create(:zone_with_member) }
|
|
let(:distributor) {
|
|
create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true)
|
|
}
|
|
let(:supplier) { create(:supplier_enterprise) }
|
|
let!(:order_cycle) {
|
|
create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor],
|
|
coordinator: create(:distributor_enterprise),
|
|
variants: [product_with_tax.variants.first,
|
|
product_with_fee.variants.first])
|
|
}
|
|
let(:enterprise_fee) {
|
|
create(:enterprise_fee, amount: 11.00, tax_category: product_with_tax.tax_category)
|
|
}
|
|
let(:product_with_tax) {
|
|
create(:taxed_product, supplier_id: supplier.id, zone:, price: 110.00, tax_rate_amount: 0.1,
|
|
included_in_price: true)
|
|
}
|
|
let(:product_with_fee) {
|
|
create(:simple_product, supplier_id: supplier.id, price: 0.86, on_hand: 100)
|
|
}
|
|
let(:order) { create(:order, order_cycle:, distributor:) }
|
|
|
|
before do
|
|
pick_order order
|
|
end
|
|
|
|
describe "continue shopping" do
|
|
it "shows a button leading back to the shop" do
|
|
# Set up a shopfront message to test that we are not going to the
|
|
# home tab.
|
|
distributor.preferred_shopfront_message = "Test driven farming"
|
|
|
|
add_product_to_cart order, product_with_fee, quantity: 2
|
|
visit main_app.cart_path
|
|
|
|
expect(page).to have_link "Continue shopping"
|
|
|
|
click_link "Continue shopping"
|
|
|
|
expect(page).not_to have_link "Continue shopping"
|
|
expect(page).to have_link "Shop"
|
|
expect(page).not_to have_content distributor.preferred_shopfront_message
|
|
end
|
|
end
|
|
|
|
describe "product description" do
|
|
it "does not link to the product page" do
|
|
add_product_to_cart order, product_with_fee, quantity: 2
|
|
visit main_app.cart_path
|
|
expect(page).not_to have_selector '.item-thumb-image a'
|
|
end
|
|
end
|
|
|
|
describe "when a product is soft-deleted" do
|
|
it "shows the cart without errors" do
|
|
add_product_to_cart order, product_with_tax, quantity: 1
|
|
add_product_to_cart order, product_with_fee, quantity: 2
|
|
product_with_fee.destroy
|
|
|
|
visit main_app.cart_path
|
|
expect(page).to have_selector '.cart-item-price'
|
|
end
|
|
end
|
|
|
|
describe "percentage fees" do
|
|
let(:percentage_fee) {
|
|
create(:enterprise_fee,
|
|
calculator: Calculator::FlatPercentPerItem.new(preferred_flat_percent: 20))
|
|
}
|
|
|
|
before do
|
|
add_enterprise_fee percentage_fee
|
|
add_product_to_cart order, product_with_fee, quantity: 8
|
|
visit main_app.cart_path
|
|
end
|
|
|
|
it "rounds fee calculations correctly" do
|
|
# $0.86 + 20% = $1.032
|
|
# Fractional cents should be immediately rounded down and not carried through
|
|
expect(page).to have_selector '.cart-item-price', text: with_currency(1.03)
|
|
expect(page).to have_selector '.cart-item-total', text: with_currency(8.24)
|
|
expect(page).to have_selector '.order-total.item-total', text: with_currency(8.24)
|
|
expect(page).to have_selector '.order-total.grand-total', text: with_currency(8.24)
|
|
end
|
|
end
|
|
|
|
describe "admin and handling flat fees" do
|
|
context "when there are fees" do
|
|
let(:handling_fee) {
|
|
create(:enterprise_fee, calculator: Calculator::FlatRate.new(preferred_amount: 1),
|
|
enterprise: order_cycle.coordinator, fee_type: 'admin')
|
|
}
|
|
|
|
before do
|
|
add_enterprise_fee handling_fee
|
|
add_product_to_cart order, product_with_fee, quantity: 3
|
|
visit main_app.cart_path
|
|
end
|
|
|
|
it "shows enterprise fees row row" do
|
|
expect(page).to have_selector('#cart-detail')
|
|
expect(page).to have_content("Whole order - #{handling_fee.name} fee by distributor " \
|
|
"#{order_cycle.coordinator.name}")
|
|
expect(page).to have_selector '.cart-item-price',
|
|
text: with_currency(0.86)
|
|
expect(page).to have_selector '.order-total.item-total',
|
|
text: with_currency(2.58)
|
|
expect(page).to have_selector '.order-adjustment .total',
|
|
text: with_currency(1.00)
|
|
expect(page).to have_selector '.order-total.grand-total',
|
|
text: with_currency(3.58) # price * 3 + 1
|
|
end
|
|
end
|
|
|
|
context "when there are no admin and handling fees" do
|
|
before do
|
|
add_product_to_cart order, product_with_fee, quantity: 2
|
|
visit main_app.cart_path
|
|
end
|
|
|
|
it "hides admin and handlings row" do
|
|
expect(page).to have_selector('#cart-detail')
|
|
expect(page).not_to have_content('Admin & Handling')
|
|
expect(page).to have_selector '.cart-item-price', text: with_currency(0.86)
|
|
expect(page).to have_selector '.order-total.grand-total',
|
|
text: with_currency(1.72) # price * 3
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "admin weight calculated fees" do
|
|
context "order with 2 line items" do
|
|
let(:admin_fee) {
|
|
create(:enterprise_fee, calculator: Calculator::Weight.new(
|
|
preferred_per_unit: 1, preferred_unit_from_list: "kg"
|
|
), enterprise: order_cycle.coordinator, fee_type: 'admin')
|
|
}
|
|
|
|
before do
|
|
product_with_fee.variants.first.update(unit_value: '2000.0')
|
|
product_with_tax.variants.first.update(unit_value: '5000.0')
|
|
|
|
add_enterprise_fee admin_fee
|
|
|
|
cart_service = CartService.new(order)
|
|
cart_service.populate(variants: { product_with_fee.variants.first.id => 3,
|
|
product_with_tax.variants.first.id => 3 })
|
|
order.recreate_all_fees!
|
|
|
|
visit main_app.cart_path
|
|
end
|
|
|
|
it "shows the correct weight calculations" do
|
|
expect(page).to have_selector('#cart-detail')
|
|
expect(page).to have_selector '.cart-item-price',
|
|
text: with_currency(2.86) # price + (1eur * 2kg)
|
|
expect(page).to have_selector '.cart-item-price',
|
|
text: with_currency(115.0) # price + (1eur * 5kg)
|
|
expect(page).to have_selector '.order-total.grand-total',
|
|
text: with_currency(353.58) # above * 3 items
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "updating quantities" do
|
|
let(:li) { order.line_items.reload.last }
|
|
let(:variant) { product_with_tax.variants.first }
|
|
let(:variant2) { product_with_fee.variants.first }
|
|
|
|
before do
|
|
order.contents.add(product_with_tax.variants.first)
|
|
end
|
|
|
|
describe "when on_hand is zero but variant is on demand" do
|
|
it "allows updating the quantity" do
|
|
variant.update!(on_hand: 0, on_demand: true)
|
|
visit main_app.cart_path
|
|
|
|
fill_in "order_line_items_attributes_0_quantity", with: '5'
|
|
expect(page).to have_field "order_line_items_attributes_0_quantity", with: '5'
|
|
end
|
|
end
|
|
|
|
describe "with insufficient stock available" do
|
|
it "prevents user from entering invalid values" do
|
|
order.contents.add(product_with_fee.variants.first)
|
|
|
|
variant.update!(on_hand: 2, on_demand: false)
|
|
variant2.update!(on_hand: 3, on_demand: false)
|
|
visit main_app.cart_path
|
|
|
|
within "tr.variant-#{variant.id}" do
|
|
fill_in "order_line_items_attributes_0_quantity", with: '4'
|
|
end
|
|
expect(page).to have_content "Insufficient stock available, only 2 remaining"
|
|
expect(page).to have_field "order_line_items_attributes_0_quantity", with: '2'
|
|
|
|
within "tr.variant-#{variant2.id}" do
|
|
fill_in "order_line_items_attributes_1_quantity", with: '4'
|
|
end
|
|
expect(page).to have_content "Insufficient stock available, only 3 remaining"
|
|
expect(page).to have_field "order_line_items_attributes_1_quantity", with: '3'
|
|
end
|
|
|
|
it "shows the quantities saved, not those submitted" do
|
|
# Given we load the page with 3 on hand, then the number available drops to 2
|
|
variant.update! on_demand: false
|
|
variant.update! on_hand: 3
|
|
visit main_app.cart_path
|
|
variant.update! on_hand: 2
|
|
|
|
fill_in "order_line_items_attributes_0_quantity", with: '4'
|
|
click_button 'Update'
|
|
expect(page).to have_field "order_line_items_attributes_0_quantity", with: '1'
|
|
end
|
|
|
|
describe "full UX for correcting selected quantities with insufficient stock" do
|
|
before do
|
|
add_product_to_cart order, product_with_tax, quantity: 5
|
|
variant.update! on_hand: 4, on_demand: false
|
|
end
|
|
|
|
it "gives clear user feedback during the correcting process" do
|
|
visit main_app.cart_path
|
|
|
|
# shows a relevant Flash message
|
|
expect(page).to have_selector ".alert-box",
|
|
text: 'An item in your cart has become unavailable. ' \
|
|
'Please update the selected quantities.'
|
|
|
|
# "Continue Shopping" and "Checkout" buttons are disabled
|
|
expect(page).to have_selector "a.continue-shopping[aria-disabled=true]"
|
|
expect(page).to have_selector "a#checkout-link[aria-disabled=true]"
|
|
|
|
# Quantity field clearly marked as invalid and "Update" button is not highlighted
|
|
expect(page).to have_selector "#order_line_items_attributes_0_quantity.ng-invalid-stock"
|
|
expect(page).not_to have_selector "#update-button.alert"
|
|
|
|
fill_in "order_line_items_attributes_0_quantity", with: 4
|
|
|
|
# Quantity field not marked as invalid and "Update" button is
|
|
# highlighted after correction
|
|
expect(page).not_to have_selector(
|
|
"#order_line_items_attributes_0_quantity.ng-invalid-stock"
|
|
)
|
|
expect(page).to have_selector "#update-button.alert"
|
|
|
|
click_button 'Update'
|
|
|
|
# "Continue Shopping" and "Checkout" buttons are not disabled after cart is updated
|
|
expect(page).not_to have_selector "a.continue-shopping[aria-disabled=true]"
|
|
expect(page).not_to have_selector "a#checkout-link[aria-disabled=true]"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when ordered in the same order cycle" do
|
|
let(:address) { create(:address) }
|
|
let(:user) { create(:user, bill_address: address, ship_address: address) }
|
|
let!(:prev_order1) {
|
|
create(:completed_order_with_totals, order_cycle:, distributor:,
|
|
user:)
|
|
}
|
|
let!(:prev_order2) {
|
|
create(:completed_order_with_totals, order_cycle:, distributor:,
|
|
user:)
|
|
}
|
|
|
|
before do
|
|
order.user = user
|
|
order.save
|
|
order.distributor.allow_order_changes = true
|
|
order.distributor.save
|
|
add_product_to_cart order, product_with_tax
|
|
login_as user
|
|
visit main_app.cart_path
|
|
end
|
|
|
|
it "shows already ordered line items" do
|
|
item1 = prev_order1.line_items.first
|
|
item2 = prev_order2.line_items.first
|
|
|
|
expect(page).not_to have_content item1.variant.name
|
|
expect(page).not_to have_content item2.variant.name
|
|
|
|
expect(page).to have_link 'Edit confirmed items', href: spree.account_path
|
|
find("td.toggle-bought").click
|
|
|
|
expect(page).to have_content item1.variant.name
|
|
expect(page).to have_content item2.variant.name
|
|
page.find(".line-item-#{item1.id} td.bought-item-delete a").click
|
|
expect(page).not_to have_content item1.variant.name
|
|
expect(page).to have_content item2.variant.name
|
|
|
|
visit main_app.cart_path
|
|
|
|
find("td.toggle-bought").click
|
|
expect(page).not_to have_content item1.variant.name
|
|
expect(page).to have_content item2.variant.name
|
|
end
|
|
|
|
context "with a single editable order" do # Regression test for #8191
|
|
before do
|
|
prev_order2.destroy
|
|
end
|
|
|
|
it "doesn't throw an error" do
|
|
expect{ visit main_app.cart_path }.not_to raise_error
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|