From 82186118a7988f3df2235554706c4b80e1a2a349 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 12 Feb 2026 16:34:05 +1100 Subject: [PATCH 01/21] Remove long inactive specs Several years ago, some checkout features got rewritten and some specs became invalid. They had been set to pending to keep the option of rewriting them one day. Some were re-written. But I'm deleting the remainder. If we haven't "needed" these specs for several years then I question their use. System specs are expensive and should only cover the most common scenarios or the ones we know could go wrong (after a bug report). We can always write new specs if needed. Otherwise they are just adding to maintenance cost. --- spec/system/consumer/multilingual_spec.rb | 197 --------------- .../consumer/shopping/checkout_stripe_spec.rb | 224 ------------------ 2 files changed, 421 deletions(-) delete mode 100644 spec/system/consumer/multilingual_spec.rb delete mode 100644 spec/system/consumer/shopping/checkout_stripe_spec.rb diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb deleted file mode 100644 index 2d5df6446b..0000000000 --- a/spec/system/consumer/multilingual_spec.rb +++ /dev/null @@ -1,197 +0,0 @@ -# frozen_string_literal: true - -require 'system_helper' - -RSpec.describe 'Multilingual' do - include AuthenticationHelper - include WebHelper - include ShopWorkflow - include UIComponentHelper - include CookieHelper - - it 'has three locales available' do - expect(Rails.application.config.i18n[:default_locale]).to eq 'en' - expect(Rails.application.config.i18n[:locale]).to eq 'en' - expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] - end - - it '18n-js fallsback to default language' do - # in backend it doesn't until we change enforce_available_locales to `true` - visit root_path - set_i18n_locale('it') - expect(get_i18n_translation('label_shops')).to eq 'Shops' - end - - context 'can switch language by params' do - it 'in root path' do - visit root_path - expect(pick_i18n_locale).to eq 'en' - expect(get_i18n_translation('label_shops')).to eq 'Shops' - expect(cookies_name).not_to include('locale') - expect(page).to have_content 'SHOPS' - - visit root_path(locale: 'es') - expect(pick_i18n_locale).to eq 'es' - expect(get_i18n_translation('label_shops')).to eq 'Tiendas' - expect_menu_and_cookie_in_es - - # it is not in the list of available of available_locales - visit root_path(locale: 'it') - expect(pick_i18n_locale).to eq 'es' - expect(get_i18n_translation('label_shops')).to eq 'Tiendas' - expect_menu_and_cookie_in_es - end - - context 'with a product in the cart' do - let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } - let!(:order_cycle) { - create(:simple_order_cycle, distributors: [distributor], variants: [product.variants.first]) - } - let(:product) { create(:simple_product) } - let(:order) { create(:order, order_cycle:, distributor:) } - - before do - pick_order order - add_product_to_cart order, product, quantity: 1 - end - - it "in the cart page" do - visit main_app.cart_path(locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content 'Precio' - end - - it "visiting checkout as a guest user" do - visit checkout_path(locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content 'Iniciar sesión' - end - end - end - - context 'with user' do - let(:user) { create(:user) } - - it 'updates user locale from cookie if it is empty' do - visit root_path(locale: 'es') - - expect_menu_and_cookie_in_es - expect(user.locale).to be_nil - login_as user - visit root_path - - expect_menu_and_cookie_in_es - end - - it 'updates user locale and stays in cookie after logout' do - login_as user - - visit root_path(locale: 'es') - user.reload - - expect(user.locale).to eq 'es' - - logout - - expect_menu_and_cookie_in_es - expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' - end - - context "visiting checkout as logged user" do - let!(:zone) { create(:zone_with_member) } - let(:supplier) { create(:supplier_enterprise) } - let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) } - let(:product) { - create(:taxed_product, supplier_id: supplier.id, price: 10, zone:) - } - let(:variant) { product.variants.first } - let!(:order_cycle) { - create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], - coordinator: create(:distributor_enterprise), - variants: [variant]) - } - - let(:free_shipping) { - create(:shipping_method, require_ship_address: false) - } - let!(:payment) { - create(:payment_method, distributors: [distributor], - name: "Payment") - } - let(:order) { - create(:order_ready_for_confirmation, distributor:) - } - before do - pick_order order - login_as user - end - - it "on the details step" do - visit checkout_step_path(:details, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Sus detalles" - end - - it "on the payment step" do - visit checkout_step_path(:payment, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Puede revisar y confirmar su pedido" - end - - it "on the summary step" do - visit checkout_step_path(:summary, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Detalles de entrega" - end - end - end - - describe "using the language switcher UI" do - context "when there is only one language available" do - before do - allow(ENV).to receive(:[]).and_call_original - allow(ENV).to receive(:[]).with("LOCALE").and_return("en") - allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en") - end - - it "hides the dropdown language menu" do - visit root_path - expect(page).not_to have_css 'ul.right li.language-switcher.has-dropdown' - end - end - - context "when there are multiple languages available" do - before do - allow(ENV).to receive(:[]).and_call_original - allow(ENV).to receive(:[]).with("LOCALE").and_return("en") - allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en,es") - end - - it "allows switching language via the main navigation" do - visit root_path - - expect(page).to have_content 'SHOPS' - - find('.language-switcher').click - within '.language-switcher .dropdown' do - expect(page).not_to have_link 'English', href: '/locales/en' - expect(page).to have_link 'Español', href: '/locales/es' - - find('li a[href="/locales/es"]').click - end - - expect_menu_and_cookie_in_es - end - end - end -end - -def expect_menu_and_cookie_in_es - expect(cookies_name['locale']).to have_attributes(value: "es") - expect(page).to have_content 'TIENDAS' -end diff --git a/spec/system/consumer/shopping/checkout_stripe_spec.rb b/spec/system/consumer/shopping/checkout_stripe_spec.rb deleted file mode 100644 index 5d279f1d3b..0000000000 --- a/spec/system/consumer/shopping/checkout_stripe_spec.rb +++ /dev/null @@ -1,224 +0,0 @@ -# frozen_string_literal: true - -require 'system_helper' - -RSpec.describe "Check out with Stripe" do - include AuthenticationHelper - include ShopWorkflow - include CheckoutRequestsHelper - include StripeHelper - include StripeStubs - - let(:distributor) { create(:distributor_enterprise) } - let!(:order_cycle) { - create(:simple_order_cycle, distributors: [distributor], variants: [variant]) - } - let(:product) { create(:product, price: 10) } - let(:variant) { product.variants.first } - let(:order) { - create(:order, order_cycle:, distributor:, bill_address_id: nil, - ship_address_id: nil) - } - - let(:shipping_with_fee) { - create(:shipping_method, require_ship_address: false, name: "Donkeys", - calculator: Calculator::FlatRate.new(preferred_amount: 4.56)) - } - let(:free_shipping) { create(:shipping_method) } - let!(:check_with_fee) { - create(:payment_method, distributors: [distributor], - calculator: Calculator::FlatRate.new(preferred_amount: 5.67)) - } - - around do |example| - with_stripe_setup { example.run } - end - - before do - stripe_enable - pick_order order - add_product_to_cart order, product - distributor.shipping_methods << [shipping_with_fee, free_shipping] - end - - pending "using Stripe SCA" do - let!(:stripe_account) { create(:stripe_account, enterprise: distributor) } - let!(:stripe_sca_payment_method) { - create(:stripe_sca_payment_method, distributors: [distributor]) - } - let!(:shipping_method) { create(:shipping_method) } - let(:error_message) { "Card was declined: insufficient funds." } - - before do - stub_payment_intent_get_request - stub_payment_methods_post_request - end - - context "with guest checkout" do - before do - stub_retrieve_payment_method_request("pm_123") - stub_list_customers_request(email: order.user.email, response: {}) - stub_get_customer_payment_methods_request(customer: "cus_A456", response: {}) - end - - context "when the card is accepted" do - before do - stub_payment_intents_post_request(order:) - stub_successful_capture_request order: - end - - it "completes checkout successfully" do - checkout_with_stripe - - expect(page).to have_content "Confirmed" - expect(page.find("#amount-paid").text).to have_content "$19.99" - - expect(order.reload.completed?).to eq true - expect(order.payments.first.state).to eq "completed" - end - end - - context "when the card is rejected" do - before do - stub_payment_intents_post_request(order:) - stub_failed_capture_request order:, response: { message: error_message } - end - - it "shows an error message from the Stripe response" do - checkout_with_stripe - - expect(page).to have_content error_message - expect(order.reload.state).to eq "cart" - expect(order.payments.first.state).to eq "failed" - end - end - - context "when the card needs extra SCA authorization" do - before do - stripe_redirect_url = checkout_path(payment_intent: "pi_123") - stub_payment_intents_post_request_with_redirect order:, - redirect_url: stripe_redirect_url - end - - describe "and the authorization succeeds" do - before do - stub_successful_capture_request order: - end - - it "completes checkout successfully" do - checkout_with_stripe - - # We make stripe return stripe_redirect_url (which is already sending the user back - # to the checkout) as if the authorization was done. We can then control the actual - # authorization or failure of the payment through the mock - # stub_successful_capture_request - - expect(page).to have_content "Confirmed" - expect(order.reload.completed?).to eq true - expect(order.payments.first.state).to eq "completed" - end - end - - describe "and the authorization fails" do - before do - stub_failed_capture_request order:, response: { message: error_message } - end - - it "shows an error message from the Stripe response" do - checkout_with_stripe - - # We make stripe return stripe_redirect_url (which is already sending the user back to - # the checkout) as if the authorization was done. We can then control the actual - # authorization or failure of the payment through the mock stub_failed_capture_request - expect(page).to have_content error_message - expect(order.reload.state).to eq "cart" - expect(order.payments.first.state).to eq "failed" - end - end - end - - context "with multiple payment attempts; one failed and one succeeded" do - before do - stub_payment_intents_post_request order: - end - - it "records failed payment attempt and allows order completion" do - # First payment attempt is rejected - stub_failed_capture_request(order:, response: { message: error_message }) - checkout_with_stripe - expect(page).to have_content error_message - - expect(order.reload.payments.count).to eq 1 - expect(order.state).to eq "cart" - expect(order.payments.first.state).to eq "failed" - - # Second payment attempt is accepted - stub_successful_capture_request(order:) - place_order - expect(page).to have_content "Confirmed" - - expect(order.reload.payments.count).to eq 2 - expect(order.state).to eq "complete" - expect(order.payments.last.state).to eq "completed" - end - end - end - - context "with a logged in user" do - let(:user) { order.user } - - before do - login_as user - end - - context "saving a card and re-using it" do - before do - stub_retrieve_payment_method_request("pm_123") - stub_list_customers_request(email: order.user.email, response: {}) - stub_get_customer_payment_methods_request(customer: "cus_A456", response: {}) - stub_get_customer_payment_methods_request(customer: "cus_A123", response: {}) - stub_payment_methods_post_request( - request: { payment_method: "pm_123", customer: "cus_A123" }, - response: { pm_id: "pm_123" } - ) - stub_add_metadata_request(payment_method: "pm_123", response: {}) - stub_payment_intents_post_request(order:) - stub_successful_capture_request(order:) - stub_customers_post_request email: "test@test.com" # First checkout with default details - stub_customers_post_request email: user.email # Second checkout with saved user details - stub_payment_method_attach_request - end - - it "allows saving a card and re-using it" do - checkout_with_stripe guest_checkout: false, remember_card: true - - expect(page).to have_content "Confirmed" - expect(order.reload.completed?).to eq true - expect(order.payments.first.state).to eq "completed" - - # Verify card has been saved with correct stripe IDs - user_credit_card = order.reload.user.credit_cards.first - expect(user_credit_card.gateway_payment_profile_id).to eq "pm_123" - expect(user_credit_card.gateway_customer_profile_id).to eq "cus_A123" - - # Prepare a second order - new_order = create(:order, user:, order_cycle:, - distributor:, bill_address_id: nil, - ship_address_id: nil) - pick_order(new_order) - add_product_to_cart(new_order, product, quantity: 10) - stub_payment_intents_post_request order: new_order - stub_successful_capture_request order: new_order - - # Checkout with saved card - visit checkout_path - choose free_shipping.name - choose stripe_sca_payment_method.name - expect(page).to have_content "Use a saved card" - expect(page).to have_select 'selected_card', selected: "Visa x-4242 Exp:10/2050" - place_order - end - end - end - end -end From bcf39acebc6c86ef381905c77534bf16e0fedc3d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 12 Feb 2026 16:51:51 +1100 Subject: [PATCH 02/21] Remove confusing if-branch in shared examples --- spec/system/consumer/checkout/payment_spec.rb | 76 +++++++++---------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/spec/system/consumer/checkout/payment_spec.rb b/spec/system/consumer/checkout/payment_spec.rb index e9374b4a53..bdc06fddc7 100644 --- a/spec/system/consumer/checkout/payment_spec.rb +++ b/spec/system/consumer/checkout/payment_spec.rb @@ -277,7 +277,7 @@ RSpec.describe "As a consumer, I want to checkout my order" do describe "choosing" do shared_examples "different payment methods" do |pay_method| - context "checking out with #{pay_method}", if: pay_method.eql?("Stripe SCA") == false do + context "checking out with #{pay_method}" do before do visit checkout_step_path(:payment) end @@ -291,46 +291,9 @@ RSpec.describe "As a consumer, I want to checkout my order" do expect(order.reload.state).to eq "complete" end end - - context "for Stripe SCA", if: pay_method.eql?("Stripe SCA") do - around do |example| - with_stripe_setup { example.run } - end - - before do - stripe_enable - visit checkout_step_path(:payment) - end - - it "selects Stripe SCA and proceeds to the summary step" do - choose pay_method.to_s - fill_out_card_details - click_on "Next - Order summary" - proceed_to_summary - end - - context "when saving card" do - it "selects Stripe SCA and proceeds to the summary step" do - stub_customers_post_request(email: order.user.email) - stub_payment_method_attach_request - - choose pay_method.to_s - fill_out_card_details - check "Save card for future use" - - click_on "Next - Order summary" - proceed_to_summary - - # Verify card has been saved with correct stripe IDs - user_credit_card = order.reload.user.credit_cards.first - expect(user_credit_card.gateway_payment_profile_id).to eq "pm_123" - expect(user_credit_card.gateway_customer_profile_id).to eq "cus_A123" - end - end - end end - describe "shared examples" do + describe "payment method" do let!(:cash) { create(:payment_method, distributors: [distributor], name: "Cash") } context "Cash" do @@ -365,7 +328,40 @@ RSpec.describe "As a consumer, I want to checkout my order" do create(:stripe_sca_payment_method, distributors: [distributor], name: "Stripe SCA") } - it_behaves_like "different payment methods", "Stripe SCA" + around do |example| + with_stripe_setup { example.run } + end + + before do + stripe_enable + visit checkout_step_path(:payment) + end + + it "selects Stripe SCA and proceeds to the summary step" do + choose "Stripe SCA" + fill_out_card_details + click_on "Next - Order summary" + proceed_to_summary + end + + context "when saving card" do + it "selects Stripe SCA and proceeds to the summary step" do + stub_customers_post_request(email: order.user.email) + stub_payment_method_attach_request + + choose "Stripe SCA" + fill_out_card_details + check "Save card for future use" + + click_on "Next - Order summary" + proceed_to_summary + + # Verify card has been saved with correct stripe IDs + user_credit_card = order.reload.user.credit_cards.first + expect(user_credit_card.gateway_payment_profile_id).to eq "pm_123" + expect(user_credit_card.gateway_customer_profile_id).to eq "cus_A123" + end + end end context "Taler" do From 62af416696ee51edac0bdcac563634982ec26b51 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 13 Feb 2026 13:44:50 +1100 Subject: [PATCH 03/21] Avoid useless page visit causing spec flakiness --- spec/system/consumer/checkout/payment_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/system/consumer/checkout/payment_spec.rb b/spec/system/consumer/checkout/payment_spec.rb index bdc06fddc7..db21ffd81e 100644 --- a/spec/system/consumer/checkout/payment_spec.rb +++ b/spec/system/consumer/checkout/payment_spec.rb @@ -49,7 +49,6 @@ RSpec.describe "As a consumer, I want to checkout my order" do before do login_as(user) - visit checkout_path end context "payment step" do @@ -67,6 +66,7 @@ RSpec.describe "As a consumer, I want to checkout my order" do context "with a transaction fee" do before do + visit checkout_path click_button "Next - Order summary" end @@ -84,6 +84,7 @@ RSpec.describe "As a consumer, I want to checkout my order" do context "after completing the order" do before do + visit checkout_path click_on "Complete order" end it_behaves_like "displays the transaction fee", "order confirmation" From 9ca1a9e33f751ffb648433183eade5330ccb064e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 16 Feb 2026 13:29:53 +1100 Subject: [PATCH 04/21] Allow orders to be paid before checkout Taler puts the payment completion into the hands of the user. So we can't strictly finalise the payment and order together. And in the bigger picture, it should be okay if a payment goes through but we have to abort checkout due to stock issues. Then we want to be able to check out again, using the existing complete payment. Any refunds can be handled later by the shop owner. --- app/models/spree/order.rb | 2 -- spec/models/spree/order_spec.rb | 5 ----- 2 files changed, 7 deletions(-) diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index 741ebe0d55..ed608aa46b 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -674,8 +674,6 @@ module Spree end def process_each_payment - raise Core::GatewayError, Spree.t(:no_pending_payments) if pending_payments.empty? - pending_payments.each do |payment| if payment.amount.zero? && zero_priced_order? payment.update_columns(state: "completed", captured_at: Time.zone.now) diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index d9ae1a59f3..d8fe7e9f2a 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -282,11 +282,6 @@ RSpec.describe Spree::Order do let(:payment) { build(:payment) } before { allow(order).to receive_messages pending_payments: [payment], total: 10 } - it "returns false if no pending_payments available" do - allow(order).to receive_messages pending_payments: [] - expect(order.process_payments!).to be_falsy - end - context "when the processing is sucessful" do it "processes the payments" do expect(payment).to receive(:process!) From 35e03ea3c769d93dd77cb579c0f70f3225b68266 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Mon, 16 Feb 2026 13:42:46 +1100 Subject: [PATCH 05/21] Check stock before completing order after Taler payment --- .../payment_gateways/taler_controller.rb | 23 +++++++- spec/requests/payment_gateways/taler_spec.rb | 54 +++++++++++++++++-- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/app/controllers/payment_gateways/taler_controller.rb b/app/controllers/payment_gateways/taler_controller.rb index aff2799242..9e9e55eff1 100644 --- a/app/controllers/payment_gateways/taler_controller.rb +++ b/app/controllers/payment_gateways/taler_controller.rb @@ -2,15 +2,36 @@ module PaymentGateways class TalerController < BaseController + include OrderStockCheck include OrderCompletion + class StockError < StandardError + end + # The Taler merchant backend has taken the payment. # Now we just need to confirm that and update our local database # before finalising the order. def confirm payment = Spree::Payment.find(params[:payment_id]) + + # Process payment early because it's probably paid already. + # We want to capture that before any validations raise errors. + unless payment.process! + return redirect_to order_failed_route(step: "payment") + end + @order = payment.order - process_payment_completion! + OrderLocker.lock_order_and_variants(@order) do + raise StockError unless sufficient_stock? + + process_payment_completion! + end + rescue Spree::Core::GatewayError => e + flash[:notice] = e.message + redirect_to order_failed_route(step: "payment") + rescue StockError + flash[:notice] = t("checkout.payment_cancelled_due_to_stock") + redirect_to main_app.checkout_step_path(step: "details") end end end diff --git a/spec/requests/payment_gateways/taler_spec.rb b/spec/requests/payment_gateways/taler_spec.rb index 0ddc88b927..bd087b9e82 100644 --- a/spec/requests/payment_gateways/taler_spec.rb +++ b/spec/requests/payment_gateways/taler_spec.rb @@ -3,16 +3,19 @@ require 'spec_helper' RSpec.describe "/payment_gateways/taler/:id" do - it "completes the order", :vcr do - shop = create(:distributor_enterprise) - taler = Spree::PaymentMethod::Taler.create!( + let(:shop) { create(:distributor_enterprise) } + let(:taler) { + Spree::PaymentMethod::Taler.create!( name: "Taler", environment: "test", distributors: [shop], preferred_backend_url: "https://backend.demo.taler.net/instances/sandbox", preferred_api_key: "sandbox", ) - order = create(:order_ready_for_confirmation, payment_method: taler) + } + let!(:order) { create(:order_ready_for_confirmation, payment_method: taler) } + + it "completes the order", :vcr do payment = Spree::Payment.last payment.update!( source: taler, @@ -30,4 +33,47 @@ RSpec.describe "/payment_gateways/taler/:id" do payment.reload expect(payment.state).to eq "completed" end + + it "redirects when payment failed" do + payment = Spree::Payment.last + payment.update!( + source: taler, + payment_method: taler, + response_code: "2026.020-03R3ETNZZ0DVA", + ) + + allow_any_instance_of(Taler::Order) + .to receive(:fetch).with("order_status").and_return("claimed") + + get payment_gateways_confirm_taler_path(payment_id: payment.id) + expect(response).to redirect_to "/checkout/payment" + + payment.reload + expect(payment.state).to eq "failed" + + order.reload + expect(order.state).to eq "confirmation" + end + + it "handles all variants going out of stock" do + payment = Spree::Payment.last + payment.update!( + source: taler, + payment_method: taler, + response_code: "2026.020-03R3ETNZZ0DVA", + ) + + allow_any_instance_of(Taler::Order) + .to receive(:fetch).with("order_status").and_return("paid") + order.line_items[0].variant.on_hand = 0 + + get payment_gateways_confirm_taler_path(payment_id: payment.id) + expect(response).to redirect_to "/checkout/details" + + payment.reload + expect(payment.state).to eq "completed" + + order.reload + expect(order.state).to eq "confirmation" + end end From 0629153362899ceee5fc400a320b62308611c5e4 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 11 Mar 2026 15:51:02 +1100 Subject: [PATCH 06/21] Complete code coverage on Taler error handling --- spec/requests/payment_gateways/taler_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spec/requests/payment_gateways/taler_spec.rb b/spec/requests/payment_gateways/taler_spec.rb index bd087b9e82..26c5c8456a 100644 --- a/spec/requests/payment_gateways/taler_spec.rb +++ b/spec/requests/payment_gateways/taler_spec.rb @@ -34,6 +34,24 @@ RSpec.describe "/payment_gateways/taler/:id" do expect(payment.state).to eq "completed" end + it "redirects when payment invalid" do + payment = Spree::Payment.last + payment.update!( + source: taler, + payment_method: taler, + state: "processing", # invalid state to start processing again + ) + + get payment_gateways_confirm_taler_path(payment_id: payment.id) + expect(response).to redirect_to "/checkout/payment" + + payment.reload + expect(payment.state).to eq "processing" + + order.reload + expect(order.state).to eq "confirmation" + end + it "redirects when payment failed" do payment = Spree::Payment.last payment.update!( From 4b5fd2495fc7966c995f537432214c8f85a0de4e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:03:48 +1100 Subject: [PATCH 07/21] Restore multilingual spec --- spec/system/consumer/multilingual_spec.rb | 197 ++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 spec/system/consumer/multilingual_spec.rb diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb new file mode 100644 index 0000000000..2d5df6446b --- /dev/null +++ b/spec/system/consumer/multilingual_spec.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require 'system_helper' + +RSpec.describe 'Multilingual' do + include AuthenticationHelper + include WebHelper + include ShopWorkflow + include UIComponentHelper + include CookieHelper + + it 'has three locales available' do + expect(Rails.application.config.i18n[:default_locale]).to eq 'en' + expect(Rails.application.config.i18n[:locale]).to eq 'en' + expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] + end + + it '18n-js fallsback to default language' do + # in backend it doesn't until we change enforce_available_locales to `true` + visit root_path + set_i18n_locale('it') + expect(get_i18n_translation('label_shops')).to eq 'Shops' + end + + context 'can switch language by params' do + it 'in root path' do + visit root_path + expect(pick_i18n_locale).to eq 'en' + expect(get_i18n_translation('label_shops')).to eq 'Shops' + expect(cookies_name).not_to include('locale') + expect(page).to have_content 'SHOPS' + + visit root_path(locale: 'es') + expect(pick_i18n_locale).to eq 'es' + expect(get_i18n_translation('label_shops')).to eq 'Tiendas' + expect_menu_and_cookie_in_es + + # it is not in the list of available of available_locales + visit root_path(locale: 'it') + expect(pick_i18n_locale).to eq 'es' + expect(get_i18n_translation('label_shops')).to eq 'Tiendas' + expect_menu_and_cookie_in_es + end + + context 'with a product in the cart' do + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let!(:order_cycle) { + create(:simple_order_cycle, distributors: [distributor], variants: [product.variants.first]) + } + let(:product) { create(:simple_product) } + let(:order) { create(:order, order_cycle:, distributor:) } + + before do + pick_order order + add_product_to_cart order, product, quantity: 1 + end + + it "in the cart page" do + visit main_app.cart_path(locale: 'es') + + expect_menu_and_cookie_in_es + expect(page).to have_content 'Precio' + end + + it "visiting checkout as a guest user" do + visit checkout_path(locale: 'es') + + expect_menu_and_cookie_in_es + expect(page).to have_content 'Iniciar sesión' + end + end + end + + context 'with user' do + let(:user) { create(:user) } + + it 'updates user locale from cookie if it is empty' do + visit root_path(locale: 'es') + + expect_menu_and_cookie_in_es + expect(user.locale).to be_nil + login_as user + visit root_path + + expect_menu_and_cookie_in_es + end + + it 'updates user locale and stays in cookie after logout' do + login_as user + + visit root_path(locale: 'es') + user.reload + + expect(user.locale).to eq 'es' + + logout + + expect_menu_and_cookie_in_es + expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' + end + + context "visiting checkout as logged user" do + let!(:zone) { create(:zone_with_member) } + let(:supplier) { create(:supplier_enterprise) } + let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) } + let(:product) { + create(:taxed_product, supplier_id: supplier.id, price: 10, zone:) + } + let(:variant) { product.variants.first } + let!(:order_cycle) { + create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], + coordinator: create(:distributor_enterprise), + variants: [variant]) + } + + let(:free_shipping) { + create(:shipping_method, require_ship_address: false) + } + let!(:payment) { + create(:payment_method, distributors: [distributor], + name: "Payment") + } + let(:order) { + create(:order_ready_for_confirmation, distributor:) + } + before do + pick_order order + login_as user + end + + it "on the details step" do + visit checkout_step_path(:details, locale: 'es') + + expect_menu_and_cookie_in_es + expect(page).to have_content "Sus detalles" + end + + it "on the payment step" do + visit checkout_step_path(:payment, locale: 'es') + + expect_menu_and_cookie_in_es + expect(page).to have_content "Puede revisar y confirmar su pedido" + end + + it "on the summary step" do + visit checkout_step_path(:summary, locale: 'es') + + expect_menu_and_cookie_in_es + expect(page).to have_content "Detalles de entrega" + end + end + end + + describe "using the language switcher UI" do + context "when there is only one language available" do + before do + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]).with("LOCALE").and_return("en") + allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en") + end + + it "hides the dropdown language menu" do + visit root_path + expect(page).not_to have_css 'ul.right li.language-switcher.has-dropdown' + end + end + + context "when there are multiple languages available" do + before do + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]).with("LOCALE").and_return("en") + allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en,es") + end + + it "allows switching language via the main navigation" do + visit root_path + + expect(page).to have_content 'SHOPS' + + find('.language-switcher').click + within '.language-switcher .dropdown' do + expect(page).not_to have_link 'English', href: '/locales/en' + expect(page).to have_link 'Español', href: '/locales/es' + + find('li a[href="/locales/es"]').click + end + + expect_menu_and_cookie_in_es + end + end + end +end + +def expect_menu_and_cookie_in_es + expect(cookies_name['locale']).to have_attributes(value: "es") + expect(page).to have_content 'TIENDAS' +end From 9513c07c2f6634f706da755221027ebffe752b8b Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:05:23 +1100 Subject: [PATCH 08/21] Remove ineffective test It would pass even if locales were broken. --- spec/support/request/web_helper.rb | 4 ---- spec/system/consumer/multilingual_spec.rb | 7 ------- 2 files changed, 11 deletions(-) diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 3e4bb3a632..39a370d146 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -27,10 +27,6 @@ module WebHelper yield end - def set_i18n_locale(locale = 'en') - page.execute_script("I18n.locale = '#{locale}'") - end - def pick_i18n_locale page.evaluate_script("I18n.locale;") end diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 2d5df6446b..cabeb405c7 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -15,13 +15,6 @@ RSpec.describe 'Multilingual' do expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] end - it '18n-js fallsback to default language' do - # in backend it doesn't until we change enforce_available_locales to `true` - visit root_path - set_i18n_locale('it') - expect(get_i18n_translation('label_shops')).to eq 'Shops' - end - context 'can switch language by params' do it 'in root path' do visit root_path From 60a4d364088d26d062276c31fe19e5a184c29061 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:08:24 +1100 Subject: [PATCH 09/21] Remove expensive without really new coverage --- spec/system/consumer/multilingual_spec.rb | 28 ----------------------- 1 file changed, 28 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index cabeb405c7..f1826c9537 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -34,34 +34,6 @@ RSpec.describe 'Multilingual' do expect(get_i18n_translation('label_shops')).to eq 'Tiendas' expect_menu_and_cookie_in_es end - - context 'with a product in the cart' do - let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } - let!(:order_cycle) { - create(:simple_order_cycle, distributors: [distributor], variants: [product.variants.first]) - } - let(:product) { create(:simple_product) } - let(:order) { create(:order, order_cycle:, distributor:) } - - before do - pick_order order - add_product_to_cart order, product, quantity: 1 - end - - it "in the cart page" do - visit main_app.cart_path(locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content 'Precio' - end - - it "visiting checkout as a guest user" do - visit checkout_path(locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content 'Iniciar sesión' - end - end end context 'with user' do From 3f29cdab3ccff97fce5a3f47d256b5984f954301 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:14:57 +1100 Subject: [PATCH 10/21] Combine specs and add detail --- spec/system/consumer/multilingual_spec.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index f1826c9537..279ad31739 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -48,15 +48,13 @@ RSpec.describe 'Multilingual' do visit root_path expect_menu_and_cookie_in_es - end - it 'updates user locale and stays in cookie after logout' do - login_as user + # The user's locale is not changed if the language was chosen before + # login. Is it a bug or a feature? Probably not important... + expect(user.reload.locale).to eq nil visit root_path(locale: 'es') - user.reload - - expect(user.locale).to eq 'es' + expect(user.reload.locale).to eq 'es' logout From 91c4ba03cd874ae365169edec533c3cfca0af377 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:15:35 +1100 Subject: [PATCH 11/21] Remove more expensive specs --- spec/system/consumer/multilingual_spec.rb | 51 ----------------------- 1 file changed, 51 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 279ad31739..a286977793 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -61,57 +61,6 @@ RSpec.describe 'Multilingual' do expect_menu_and_cookie_in_es expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' end - - context "visiting checkout as logged user" do - let!(:zone) { create(:zone_with_member) } - let(:supplier) { create(:supplier_enterprise) } - let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) } - let(:product) { - create(:taxed_product, supplier_id: supplier.id, price: 10, zone:) - } - let(:variant) { product.variants.first } - let!(:order_cycle) { - create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], - coordinator: create(:distributor_enterprise), - variants: [variant]) - } - - let(:free_shipping) { - create(:shipping_method, require_ship_address: false) - } - let!(:payment) { - create(:payment_method, distributors: [distributor], - name: "Payment") - } - let(:order) { - create(:order_ready_for_confirmation, distributor:) - } - before do - pick_order order - login_as user - end - - it "on the details step" do - visit checkout_step_path(:details, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Sus detalles" - end - - it "on the payment step" do - visit checkout_step_path(:payment, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Puede revisar y confirmar su pedido" - end - - it "on the summary step" do - visit checkout_step_path(:summary, locale: 'es') - - expect_menu_and_cookie_in_es - expect(page).to have_content "Detalles de entrega" - end - end end describe "using the language switcher UI" do From b939d41bf5426de528fddad38e2074f48595ec9e Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:18:15 +1100 Subject: [PATCH 12/21] Remove useless negative spec Any change in markup would invalidate the test. It is unlikely to ever fail and then it would not be a big problem. --- spec/system/consumer/multilingual_spec.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index a286977793..09b7d3c673 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -64,19 +64,6 @@ RSpec.describe 'Multilingual' do end describe "using the language switcher UI" do - context "when there is only one language available" do - before do - allow(ENV).to receive(:[]).and_call_original - allow(ENV).to receive(:[]).with("LOCALE").and_return("en") - allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en") - end - - it "hides the dropdown language menu" do - visit root_path - expect(page).not_to have_css 'ul.right li.language-switcher.has-dropdown' - end - end - context "when there are multiple languages available" do before do allow(ENV).to receive(:[]).and_call_original From c2f725b20ca9bfc585720c1bab07d9ac36dcd57a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:21:27 +1100 Subject: [PATCH 13/21] Remove redundant language setup We tested earlier already that we have the needed languages available. --- spec/system/consumer/multilingual_spec.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 09b7d3c673..63da415a78 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -65,12 +65,6 @@ RSpec.describe 'Multilingual' do describe "using the language switcher UI" do context "when there are multiple languages available" do - before do - allow(ENV).to receive(:[]).and_call_original - allow(ENV).to receive(:[]).with("LOCALE").and_return("en") - allow(ENV).to receive(:[]).with("AVAILABLE_LOCALES").and_return("en,es") - end - it "allows switching language via the main navigation" do visit root_path From d56790b71eb15a525ef6e8433e359fd84d74972a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:23:54 +1100 Subject: [PATCH 14/21] Remove unneeded context blocks Best viewed ignoring whitespace. --- spec/system/consumer/multilingual_spec.rb | 92 +++++++++++------------ 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 63da415a78..ed27c392f9 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -9,78 +9,70 @@ RSpec.describe 'Multilingual' do include UIComponentHelper include CookieHelper + let(:user) { create(:user) } + it 'has three locales available' do expect(Rails.application.config.i18n[:default_locale]).to eq 'en' expect(Rails.application.config.i18n[:locale]).to eq 'en' expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] end - context 'can switch language by params' do - it 'in root path' do - visit root_path - expect(pick_i18n_locale).to eq 'en' - expect(get_i18n_translation('label_shops')).to eq 'Shops' - expect(cookies_name).not_to include('locale') - expect(page).to have_content 'SHOPS' + it 'can switch language by params' do + visit root_path + expect(pick_i18n_locale).to eq 'en' + expect(get_i18n_translation('label_shops')).to eq 'Shops' + expect(cookies_name).not_to include('locale') + expect(page).to have_content 'SHOPS' - visit root_path(locale: 'es') - expect(pick_i18n_locale).to eq 'es' - expect(get_i18n_translation('label_shops')).to eq 'Tiendas' - expect_menu_and_cookie_in_es + visit root_path(locale: 'es') + expect(pick_i18n_locale).to eq 'es' + expect(get_i18n_translation('label_shops')).to eq 'Tiendas' + expect_menu_and_cookie_in_es - # it is not in the list of available of available_locales - visit root_path(locale: 'it') - expect(pick_i18n_locale).to eq 'es' - expect(get_i18n_translation('label_shops')).to eq 'Tiendas' - expect_menu_and_cookie_in_es - end + # it is not in the list of available of available_locales + visit root_path(locale: 'it') + expect(pick_i18n_locale).to eq 'es' + expect(get_i18n_translation('label_shops')).to eq 'Tiendas' + expect_menu_and_cookie_in_es end - context 'with user' do - let(:user) { create(:user) } + it 'updates user locale from cookie if it is empty' do + visit root_path(locale: 'es') - it 'updates user locale from cookie if it is empty' do - visit root_path(locale: 'es') + expect_menu_and_cookie_in_es + expect(user.locale).to be_nil + login_as user + visit root_path - expect_menu_and_cookie_in_es - expect(user.locale).to be_nil - login_as user - visit root_path + expect_menu_and_cookie_in_es - expect_menu_and_cookie_in_es + # The user's locale is not changed if the language was chosen before + # login. Is it a bug or a feature? Probably not important... + expect(user.reload.locale).to eq nil - # The user's locale is not changed if the language was chosen before - # login. Is it a bug or a feature? Probably not important... - expect(user.reload.locale).to eq nil + visit root_path(locale: 'es') + expect(user.reload.locale).to eq 'es' - visit root_path(locale: 'es') - expect(user.reload.locale).to eq 'es' + logout - logout - - expect_menu_and_cookie_in_es - expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' - end + expect_menu_and_cookie_in_es + expect(page).to have_content '¿Estás interesada en entrar en Open Food Network?' end - describe "using the language switcher UI" do - context "when there are multiple languages available" do - it "allows switching language via the main navigation" do - visit root_path + it "allows switching language via the main navigation" do + visit root_path - expect(page).to have_content 'SHOPS' + expect(page).to have_content 'SHOPS' - find('.language-switcher').click - within '.language-switcher .dropdown' do - expect(page).not_to have_link 'English', href: '/locales/en' - expect(page).to have_link 'Español', href: '/locales/es' + find('.language-switcher').click + within '.language-switcher .dropdown' do + expect(page).not_to have_link 'English', href: '/locales/en' + expect(page).to have_link 'Español', href: '/locales/es' - find('li a[href="/locales/es"]').click - end - - expect_menu_and_cookie_in_es - end + find('li a[href="/locales/es"]').click end + + expect_menu_and_cookie_in_es end end From 63988fff4f1c2ab4ceec5db62e0c20da04de790c Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:43:17 +1100 Subject: [PATCH 15/21] Configure test locales like other envs The locale config is set in application.rb from environment variables already. We don't need to repeat that logic in test.rb. And because it was outdated, the language switcher was actually broken in the test environment. We did have an English selector for the fallback `en` even though we were already displaying English as en_TST. And after switchting to Spanish, we could switch back because en_TST was not in the available locales. I now fixed the test with the right assumption and the config to solve the problem. --- .env.test | 3 +++ config/environments/test.rb | 3 --- spec/helpers/i18n_helper_spec.rb | 12 ++++++------ spec/spec_helper.rb | 2 +- spec/system/admin/multilingual_spec.rb | 10 ++-------- spec/system/consumer/authentication_spec.rb | 2 +- spec/system/consumer/multilingual_spec.rb | 14 +++++++------- 7 files changed, 20 insertions(+), 26 deletions(-) diff --git a/.env.test b/.env.test index 1fcf778303..62c4cefc8e 100644 --- a/.env.test +++ b/.env.test @@ -13,6 +13,9 @@ CAPYBARA_MAX_WAIT_TIME="10" # successful fallback to `en`. LOCALE="en_TST" +# For multilingual - ENV doesn't have array so pass it as string with commas +AVAILABLE_LOCALES="en_TST,es,pt" + OFN_REDIS_JOBS_URL="redis://localhost:6379/2" SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/config/environments/test.rb b/config/environments/test.rb index 3d50e5348d..59f7215aad 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -90,10 +90,7 @@ Rails.application.configure do config.i18n.raise_on_missing_translations = true # Tests assume English text on the site. - config.i18n.default_locale = "en" - config.i18n.available_locales = ['en', 'es', 'pt'] config.i18n.fallbacks = [:en] - I18n.locale = config.i18n.locale = config.i18n.default_locale # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true diff --git a/spec/helpers/i18n_helper_spec.rb b/spec/helpers/i18n_helper_spec.rb index 10a082507b..1e8d68d5a6 100644 --- a/spec/helpers/i18n_helper_spec.rb +++ b/spec/helpers/i18n_helper_spec.rb @@ -15,7 +15,7 @@ RSpec.describe I18nHelper do it "sets the default locale" do helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end it "sets the chosen locale" do @@ -36,11 +36,11 @@ RSpec.describe I18nHelper do it "ignores unavailable locales" do allow(helper).to receive(:params) { { locale: "xx" } } helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end it "remembers the last chosen locale" do - allow(helper).to receive(:params) { { locale: "en" } } + allow(helper).to receive(:params) { { locale: "en_TST" } } helper.set_locale allow(helper).to receive(:params) { { locale: "es" } } @@ -71,7 +71,7 @@ RSpec.describe I18nHelper do allow(helper).to receive(:params) { {} } helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end end @@ -82,7 +82,7 @@ RSpec.describe I18nHelper do it "sets the default locale" do helper.set_locale - expect(I18n.locale).to eq :en + expect(I18n.locale).to eq :en_TST end it "sets the chosen locale" do @@ -102,7 +102,7 @@ RSpec.describe I18nHelper do end it "remembers the last chosen locale" do - allow(helper).to receive(:params) { { locale: "en" } } + allow(helper).to receive(:params) { { locale: "en_TST" } } helper.set_locale allow(helper).to receive(:params) { { locale: "es" } } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2078c0a3ca..f69ff6af6b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -152,7 +152,7 @@ RSpec.configure do |config| # Reset locale for all specs. config.around(:each) do |example| - locale = ENV.fetch('LOCALE', 'en_TST') + locale = OpenFoodNetwork::I18nConfig.default_locale I18n.with_locale(locale) { example.run } end diff --git a/spec/system/admin/multilingual_spec.rb b/spec/system/admin/multilingual_spec.rb index 0c54b97cb3..391acb2213 100644 --- a/spec/system/admin/multilingual_spec.rb +++ b/spec/system/admin/multilingual_spec.rb @@ -13,14 +13,8 @@ RSpec.describe 'Multilingual' do visit spree.admin_dashboard_path end - it 'has three locales available' do - expect(Rails.application.config.i18n[:default_locale]).to eq 'en' - expect(Rails.application.config.i18n[:locale]).to eq 'en' - expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] - end - it 'can switch language by params' do - expect(pick_i18n_locale).to eq 'en' + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises' expect(page).to have_content 'My Enterprises' expect(admin_user.locale).to be_nil @@ -36,7 +30,7 @@ RSpec.describe 'Multilingual' do it 'fallbacks to default_locale' do visit spree.admin_dashboard_path(locale: 'it') - expect(pick_i18n_locale).to eq 'en' + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises' expect(page).to have_content 'My Enterprises' expect(admin_user.locale).to be_nil diff --git a/spec/system/consumer/authentication_spec.rb b/spec/system/consumer/authentication_spec.rb index 83cc79fbf0..5a777c809a 100644 --- a/spec/system/consumer/authentication_spec.rb +++ b/spec/system/consumer/authentication_spec.rb @@ -273,7 +273,7 @@ RSpec.describe "Authentication" do expect_logged_in expect(page).to have_content 'SHOP NOW' - expect(user.reload.locale).to eq "en" + expect(user.reload.locale).to eq "en_TST" end end diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index ed27c392f9..051e023a42 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -12,14 +12,14 @@ RSpec.describe 'Multilingual' do let(:user) { create(:user) } it 'has three locales available' do - expect(Rails.application.config.i18n[:default_locale]).to eq 'en' - expect(Rails.application.config.i18n[:locale]).to eq 'en' - expect(Rails.application.config.i18n[:available_locales]).to eq ['en', 'es', 'pt'] + expect(Rails.application.config.i18n[:default_locale]).to eq 'en_TST' + expect(Rails.application.config.i18n[:locale]).to eq 'en_TST' + expect(Rails.application.config.i18n[:available_locales]).to eq ['en_TST', 'es', 'pt', 'en'] end it 'can switch language by params' do visit root_path - expect(pick_i18n_locale).to eq 'en' + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('label_shops')).to eq 'Shops' expect(cookies_name).not_to include('locale') expect(page).to have_content 'SHOPS' @@ -66,10 +66,10 @@ RSpec.describe 'Multilingual' do find('.language-switcher').click within '.language-switcher .dropdown' do - expect(page).not_to have_link 'English', href: '/locales/en' - expect(page).to have_link 'Español', href: '/locales/es' + expect(page).not_to have_link 'English' + expect(page).to have_link 'Español' - find('li a[href="/locales/es"]').click + click_link 'Español' end expect_menu_and_cookie_in_es From 1696dd2de64ea826a16787c3de9c780d4eef1f95 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 13:58:17 +1100 Subject: [PATCH 16/21] DRY language fallback config --- config/application.rb | 7 +++++-- config/environments/development.rb | 2 -- config/environments/production.rb | 4 ---- config/environments/staging.rb | 4 ---- config/environments/test.rb | 3 --- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/config/application.rb b/config/application.rb index 71f47829ff..506430dc1f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -158,12 +158,15 @@ module Openfoodnetwork # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # The default locale is set in the environment. config.i18n.default_locale = OpenFoodNetwork::I18nConfig.default_locale config.i18n.available_locales = OpenFoodNetwork::I18nConfig.available_locales I18n.locale = config.i18n.locale = config.i18n.default_locale + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = [:en] + # Calculate digests for locale files so we can know when they change I18nDigests.build_digests config.i18n.available_locales diff --git a/config/environments/development.rb b/config/environments/development.rb index 0aa1a6eda6..bfc893d885 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -111,8 +111,6 @@ Rails.application.configure do # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true - config.i18n.fallbacks = [:en] - # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true diff --git a/config/environments/production.rb b/config/environments/production.rb index ff8394a821..95ebad2351 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -91,10 +91,6 @@ Rails.application.configure do # Use https in email links config.action_mailer.default_url_options = { protocol: 'https' } - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = [:en] - # Send deprecation notices to registered listeners config.active_support.deprecation = :notify diff --git a/config/environments/staging.rb b/config/environments/staging.rb index b344d4446e..2d28f49a7f 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -60,10 +60,6 @@ Openfoodnetwork::Application.configure do # Enable threaded mode # config.threadsafe! - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found) - config.i18n.fallbacks = [:en] - # Send deprecation notices to registered listeners config.active_support.deprecation = :notify end diff --git a/config/environments/test.rb b/config/environments/test.rb index 59f7215aad..089170d2a2 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -89,9 +89,6 @@ Rails.application.configure do # Raises error for missing translations. config.i18n.raise_on_missing_translations = true - # Tests assume English text on the site. - config.i18n.fallbacks = [:en] - # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true From bf22484addda5d8c1b5155adfa14155f834a7963 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 14:15:11 +1100 Subject: [PATCH 17/21] Add default locale to fallbacks The comment about falling back to the default locale came from the first commit, when the config was just `fallbacks = true`. The fallback logic is a lot more sophisticated now and we can supply the country's default locale as first fallback and our source locale `en` as last resort. It should contain everything. In the future, we may want to support maps like Canadian French can fall back to original French. I18n supports this but providing the config per isntance may be a bit tricky. --- config/application.rb | 5 +---- lib/open_food_network/i18n_config.rb | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config/application.rb b/config/application.rb index 506430dc1f..f20dfa9010 100644 --- a/config/application.rb +++ b/config/application.rb @@ -161,12 +161,9 @@ module Openfoodnetwork # The default locale is set in the environment. config.i18n.default_locale = OpenFoodNetwork::I18nConfig.default_locale config.i18n.available_locales = OpenFoodNetwork::I18nConfig.available_locales + config.i18n.fallbacks = OpenFoodNetwork::I18nConfig.fallbacks I18n.locale = config.i18n.locale = config.i18n.default_locale - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found) - config.i18n.fallbacks = [:en] - # Calculate digests for locale files so we can know when they change I18nDigests.build_digests config.i18n.available_locales diff --git a/lib/open_food_network/i18n_config.rb b/lib/open_food_network/i18n_config.rb index d0d71d0450..6c132442d5 100644 --- a/lib/open_food_network/i18n_config.rb +++ b/lib/open_food_network/i18n_config.rb @@ -20,6 +20,10 @@ module OpenFoodNetwork (selectable_locales + [default_locale, source_locale]).uniq end + def self.fallbacks + [default_locale, source_locale].uniq + end + # The default locale that is used when the user doesn't have a preference. def self.default_locale ENV["LOCALE"] || ENV["I18N_LOCALE"] || source_locale From 5c634c269b10d916d111b9008452841c6d9d17ab Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 14:21:59 +1100 Subject: [PATCH 18/21] Remove unused helpers from spec --- spec/system/consumer/multilingual_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/system/consumer/multilingual_spec.rb b/spec/system/consumer/multilingual_spec.rb index 051e023a42..e6aeccdd9c 100644 --- a/spec/system/consumer/multilingual_spec.rb +++ b/spec/system/consumer/multilingual_spec.rb @@ -5,8 +5,6 @@ require 'system_helper' RSpec.describe 'Multilingual' do include AuthenticationHelper include WebHelper - include ShopWorkflow - include UIComponentHelper include CookieHelper let(:user) { create(:user) } From 7a01409f5cbcea3d002f88cee33a9bf4b95bdb28 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 14:25:44 +1100 Subject: [PATCH 19/21] Save time by avoiding page visit --- spec/system/admin/multilingual_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/system/admin/multilingual_spec.rb b/spec/system/admin/multilingual_spec.rb index 391acb2213..81e441cba2 100644 --- a/spec/system/admin/multilingual_spec.rb +++ b/spec/system/admin/multilingual_spec.rb @@ -10,10 +10,11 @@ RSpec.describe 'Multilingual' do before do login_as admin_user - visit spree.admin_dashboard_path end it 'can switch language by params' do + visit spree.admin_dashboard_path + expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises' expect(page).to have_content 'My Enterprises' From d56b4b410983cc3dca425f47b8fe07e1ffde1c7d Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 14:27:32 +1100 Subject: [PATCH 20/21] Make locale spec more accurate --- spec/system/admin/multilingual_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/admin/multilingual_spec.rb b/spec/system/admin/multilingual_spec.rb index 81e441cba2..523961a3d1 100644 --- a/spec/system/admin/multilingual_spec.rb +++ b/spec/system/admin/multilingual_spec.rb @@ -34,6 +34,6 @@ RSpec.describe 'Multilingual' do expect(pick_i18n_locale).to eq 'en_TST' expect(get_i18n_translation('spree_admin_overview_enterprises_header')).to eq 'My Enterprises' expect(page).to have_content 'My Enterprises' - expect(admin_user.locale).to be_nil + expect(admin_user.reload.locale).to be_nil end end From 8f0d4d23a785c4bb54ae35fc929668a6415b8ec6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 18 Mar 2026 14:54:44 +1100 Subject: [PATCH 21/21] Restore Stripe spec stubs as todo list --- .../consumer/shopping/checkout_stripe_spec.rb | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 spec/system/consumer/shopping/checkout_stripe_spec.rb diff --git a/spec/system/consumer/shopping/checkout_stripe_spec.rb b/spec/system/consumer/shopping/checkout_stripe_spec.rb new file mode 100644 index 0000000000..fe41a0675d --- /dev/null +++ b/spec/system/consumer/shopping/checkout_stripe_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'system_helper' + +RSpec.describe "Check out with Stripe" do + describe "using Stripe SCA" do + context "with guest checkout" do + context "when the card is accepted" do + it "completes checkout successfully" + end + + context "when the card is rejected" do + it "shows an error message from the Stripe response" + end + + context "when the card needs extra SCA authorization" do + describe "and the authorization succeeds" do + it "completes checkout successfully" + end + + describe "and the authorization fails" do + it "shows an error message from the Stripe response" + end + end + + context "with multiple payment attempts; one failed and one succeeded" do + it "records failed payment attempt and allows order completion" + end + end + + context "with a logged in user" do + context "saving a card and re-using it" do + it "allows saving a card and re-using it" + end + end + end +end