From 7f79f6211b89170d291668966c008ffbd9ea320a Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Tue, 5 May 2026 15:39:12 +1000 Subject: [PATCH 1/9] refactor(other): use react 18 new rendering system --- jest-setup.ts | 4 ++-- .../src/app/checkout/renderCheckout.test.tsx | 18 +++++++++++++----- .../core/src/app/checkout/renderCheckout.tsx | 13 ++++++++++--- .../order/renderOrderConfirmation.test.tsx | 18 +++++++++++++----- .../src/app/order/renderOrderConfirmation.tsx | 13 ++++++++++--- .../creditCard/getCreditCardInputStyles.tsx | 19 ++++++++++++------- .../getCreditCardInputStyles.tsx | 19 ++++++++++++------- 7 files changed, 72 insertions(+), 32 deletions(-) diff --git a/jest-setup.ts b/jest-setup.ts index eaf3123e36..f72326cedd 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -55,8 +55,8 @@ beforeAll(() => { console.error = (...args: unknown[]) => { const message = args.map(String).join(); - // FIXME: Remove these ignored errors once we have enabled react 18 features - if (/Formik|createRoot|React.act|findDOMNode/.test(message)) { + // Suppress known third-party warnings unrelated to our code + if (/Formik|React.act/.test(message)) { return; } diff --git a/packages/core/src/app/checkout/renderCheckout.test.tsx b/packages/core/src/app/checkout/renderCheckout.test.tsx index 9cb6caf65d..0a00a315a8 100644 --- a/packages/core/src/app/checkout/renderCheckout.test.tsx +++ b/packages/core/src/app/checkout/renderCheckout.test.tsx @@ -1,4 +1,4 @@ -import React, { type FunctionComponent } from 'react'; +import React, { act, type FunctionComponent } from 'react'; import { configurePublicPath } from '../common/bundler'; @@ -54,7 +54,9 @@ describe('renderCheckout()', () => { }); it('configures public path before mounting app component', () => { - renderCheckout(options); + act(() => { + renderCheckout(options); + }); expect(configurePublicPath).toHaveBeenCalledWith(options.publicPath); @@ -62,13 +64,17 @@ describe('renderCheckout()', () => { }); it('passes props to app component', () => { - renderCheckout(options); + act(() => { + renderCheckout(options); + }); expect(CheckoutApp).toHaveBeenCalledWith(options, {}); }); it('does not configure `whyDidYouRender` if not in development mode', () => { - renderCheckout(options); + act(() => { + renderCheckout(options); + }); expect(require('@welldone-software/why-did-you-render')).not.toHaveBeenCalled(); @@ -80,7 +86,9 @@ describe('renderCheckout()', () => { process.env.NODE_ENV = 'development'; - renderCheckout(options); + act(() => { + renderCheckout(options); + }); expect(require('@welldone-software/why-did-you-render')).toHaveBeenCalledWith(React, { trackAllPureComponents: false, diff --git a/packages/core/src/app/checkout/renderCheckout.tsx b/packages/core/src/app/checkout/renderCheckout.tsx index f3392abbf8..94b9ba9563 100644 --- a/packages/core/src/app/checkout/renderCheckout.tsx +++ b/packages/core/src/app/checkout/renderCheckout.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { configurePublicPath } from '../common/bundler'; @@ -22,8 +22,15 @@ export default function renderCheckout({ // first before importing the app component and its dependencies. const { default: CheckoutApp } = require('./CheckoutApp'); - ReactDOM.render( + const container = document.getElementById(containerId); + + if (!container) { + throw new Error(`Unable to find checkout container: #${containerId}`); + } + + const root = createRoot(container); + + root.render( , - document.getElementById(containerId), ); } diff --git a/packages/core/src/app/order/renderOrderConfirmation.test.tsx b/packages/core/src/app/order/renderOrderConfirmation.test.tsx index 9c270c750f..367144b3be 100644 --- a/packages/core/src/app/order/renderOrderConfirmation.test.tsx +++ b/packages/core/src/app/order/renderOrderConfirmation.test.tsx @@ -1,4 +1,4 @@ -import React, { type FunctionComponent } from 'react'; +import React, { act, type FunctionComponent } from 'react'; import { configurePublicPath } from '../common/bundler'; @@ -54,7 +54,9 @@ describe('renderOrderConfirmation()', () => { }); it('configures public path before mounting app component', () => { - renderOrderConfirmation(options); + act(() => { + renderOrderConfirmation(options); + }); expect(configurePublicPath).toHaveBeenCalledWith(options.publicPath); @@ -62,13 +64,17 @@ describe('renderOrderConfirmation()', () => { }); it('passes props to app component', () => { - renderOrderConfirmation(options); + act(() => { + renderOrderConfirmation(options); + }); expect(OrderConfirmationApp).toHaveBeenCalledWith(options, {}); }); it('does not configure `whyDidYouRender` if not in development mode', () => { - renderOrderConfirmation(options); + act(() => { + renderOrderConfirmation(options); + }); expect(require('@welldone-software/why-did-you-render')).not.toHaveBeenCalled(); @@ -80,7 +86,9 @@ describe('renderOrderConfirmation()', () => { process.env.NODE_ENV = 'development'; - renderOrderConfirmation(options); + act(() => { + renderOrderConfirmation(options); + }); expect(require('@welldone-software/why-did-you-render')).toHaveBeenCalledWith(React, { collapseGroups: true, diff --git a/packages/core/src/app/order/renderOrderConfirmation.tsx b/packages/core/src/app/order/renderOrderConfirmation.tsx index 6aa101b918..49ab0e491c 100644 --- a/packages/core/src/app/order/renderOrderConfirmation.tsx +++ b/packages/core/src/app/order/renderOrderConfirmation.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { configurePublicPath } from '../common/bundler'; @@ -29,12 +29,19 @@ export default function renderOrderConfirmation({ }); } - ReactDOM.render( + const container = document.getElementById(containerId); + + if (!container) { + throw new Error(`Unable to find order confirmation container: #${containerId}`); + } + + const root = createRoot(container); + + root.render( , - document.getElementById(containerId), ); } diff --git a/packages/core/src/app/payment/creditCard/getCreditCardInputStyles.tsx b/packages/core/src/app/payment/creditCard/getCreditCardInputStyles.tsx index 09816685c4..b4003df0f0 100644 --- a/packages/core/src/app/payment/creditCard/getCreditCardInputStyles.tsx +++ b/packages/core/src/app/payment/creditCard/getCreditCardInputStyles.tsx @@ -1,6 +1,6 @@ import { noop } from 'lodash'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { getAppliedStyles } from '@bigcommerce/checkout/dom-utils'; import { FormContext } from '@bigcommerce/checkout/ui'; @@ -30,6 +30,8 @@ export default function getCreditCardInputStyles( parentContainer.appendChild(container); return new Promise((resolve) => { + const root = createRoot(container); + const callbackRef = (element: HTMLInputElement | null) => { if (!element) { return; @@ -37,14 +39,18 @@ export default function getCreditCardInputStyles( resolve(getAppliedStyles(element, properties)); - ReactDOM.unmountComponentAtNode(container); + // Defer cleanup so we don't unmount during React's commit phase, + // which would happen if this function is called from a lifecycle method. + queueMicrotask(() => { + root.unmount(); - if (container.parentElement) { - container.parentElement.removeChild(container); - } + if (container.parentElement) { + container.parentElement.removeChild(container); + } + }); }; - ReactDOM.render( + root.render( , - container, ); }); } diff --git a/packages/instrument-utils/src/creditCard/getCreditCardInputStyles/getCreditCardInputStyles.tsx b/packages/instrument-utils/src/creditCard/getCreditCardInputStyles/getCreditCardInputStyles.tsx index b33a70efbd..4f471836d9 100644 --- a/packages/instrument-utils/src/creditCard/getCreditCardInputStyles/getCreditCardInputStyles.tsx +++ b/packages/instrument-utils/src/creditCard/getCreditCardInputStyles/getCreditCardInputStyles.tsx @@ -1,6 +1,6 @@ import { noop } from 'lodash'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { getAppliedStyles } from '@bigcommerce/checkout/dom-utils'; import { FormContext, FormFieldContainer, TextInput } from '@bigcommerce/checkout/ui'; @@ -28,6 +28,8 @@ export default function getCreditCardInputStyles( parentContainer.appendChild(container); return new Promise((resolve) => { + const root = createRoot(container); + const callbackRef = (element: HTMLInputElement | null) => { if (!element) { return; @@ -35,14 +37,18 @@ export default function getCreditCardInputStyles( resolve(getAppliedStyles(element, properties)); - ReactDOM.unmountComponentAtNode(container); + // Defer cleanup so we don't unmount during React's commit phase, + // which would happen if this function is called from a lifecycle method. + queueMicrotask(() => { + root.unmount(); - if (container.parentElement) { - container.parentElement.removeChild(container); - } + if (container.parentElement) { + container.parentElement.removeChild(container); + } + }); }; - ReactDOM.render( + root.render( , - container, ); }); } From bef27a9e6ff924a24d2e27b9a1772fe6291b60dc Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Tue, 5 May 2026 16:50:25 +1000 Subject: [PATCH 2/9] tests(other): fix unit tests for bluesnap and worldpay --- .../src/BlueSnapV2PaymentMethod.test.tsx | 12 +++--- .../WorldPayCreditCardPaymentMethod.test.tsx | 40 +++++++++---------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx b/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx index a3c3a8cb69..41579d6adc 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx @@ -5,11 +5,10 @@ import { createLanguageService, type PaymentInitializeOptions, } from '@bigcommerce/checkout-sdk'; -import { act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Formik } from 'formik'; import { noop } from 'lodash'; -import React, { type FunctionComponent } from 'react'; +import React, { act, type FunctionComponent } from 'react'; import { CheckoutProvider, @@ -108,9 +107,11 @@ describe('when using BlueSnapV2 payment', () => { iframe.setAttribute('data-test', 'my-iframe'); - act(() => initializeOptions.bluesnapv2?.onLoad(iframe, jest.fn())); + act(() => { + initializeOptions.bluesnapv2?.onLoad(iframe, jest.fn()); + }); - await new Promise((resolve) => process.nextTick(resolve)); + await screen.findByTestId('my-iframe'); const user = userEvent.setup(); @@ -129,8 +130,6 @@ describe('when using BlueSnapV2 payment', () => { initializeOptions.bluesnapv2?.onLoad(undefined, jest.fn()); }); - await new Promise((resolve) => process.nextTick(resolve)); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); expect(screen.queryByTestId('modal-body')).not.toBeInTheDocument(); expect(screen.queryByTestId('my-iframe')).not.toBeInTheDocument(); @@ -152,7 +151,6 @@ describe('when using BlueSnapV2 payment', () => { const user = userEvent.setup(); - await new Promise((resolve) => process.nextTick(resolve)); await user.click(screen.getByText('Close')); expect(cancelBlueSnapV2Payment).toHaveBeenCalledTimes(1); diff --git a/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx b/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx index 69a578bce7..c26b3f5cc5 100644 --- a/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx +++ b/packages/worldpay-access-integration/src/WorldPayCreditCardPaymentMethod.test.tsx @@ -9,8 +9,7 @@ import { import { createWorldpayAccessPaymentStrategy } from '@bigcommerce/checkout-sdk/integrations/worldpayaccess'; import { Formik } from 'formik'; import { noop } from 'lodash'; -import React, { type FunctionComponent } from 'react'; -import { act } from 'react-dom/test-utils'; +import React, { act, type FunctionComponent } from 'react'; import { CheckoutProvider, @@ -124,17 +123,17 @@ describe('WorldpayCreditCardPaymentMethod', () => { render(); - await new Promise((resolve) => process.nextTick(resolve)); - - expect(checkoutService.initializePayment).toHaveBeenCalledWith( - expect.objectContaining({ - gatewayId: undefined, - methodId: 'worldpayaccess', - integrations: [createWorldpayAccessPaymentStrategy], - worldpay: { - onLoad: expect.any(Function), - }, - }), + await waitFor(() => + expect(checkoutService.initializePayment).toHaveBeenCalledWith( + expect.objectContaining({ + gatewayId: undefined, + methodId: 'worldpayaccess', + integrations: [createWorldpayAccessPaymentStrategy], + worldpay: { + onLoad: expect.any(Function), + }, + }), + ), ); }); @@ -166,7 +165,7 @@ describe('WorldpayCreditCardPaymentMethod', () => { it('renders modal that hosts worldpay payment page', async () => { render(); - await new Promise((resolve) => process.nextTick(resolve)); + await waitFor(() => expect(checkoutService.initializePayment).toHaveBeenCalled()); const initializeOptions = (checkoutService.initializePayment as jest.Mock).mock.calls[0][0]; @@ -174,16 +173,14 @@ describe('WorldpayCreditCardPaymentMethod', () => { initializeOptions.worldpay.onLoad(document.createElement('iframe'), jest.fn()); }); - await new Promise((resolve) => process.nextTick(resolve)); - - expect(screen.getByTestId('modal-body')).toBeInTheDocument(); + await screen.findByTestId('modal-body'); expect(screen.getByText('Name on Card')).toBeInTheDocument(); }); it('renders modal', async () => { render(); - await new Promise((resolve) => process.nextTick(resolve)); + await waitFor(() => expect(checkoutService.initializePayment).toHaveBeenCalled()); const initializeOptions = (checkoutService.initializePayment as jest.Mock).mock.calls[0][0]; @@ -193,8 +190,7 @@ describe('WorldpayCreditCardPaymentMethod', () => { initializeOptions.worldpay.onLoad(iframe, jest.fn()); }); - await new Promise((resolve) => process.nextTick(resolve)); - expect(screen.getByTestId('modal-body')).toBeInTheDocument(); + await screen.findByTestId('modal-body'); }); it('renders field container with focus styles', () => { @@ -210,7 +206,7 @@ describe('WorldpayCreditCardPaymentMethod', () => { render(); - await new Promise((resolve) => process.nextTick(resolve)); + await waitFor(() => expect(checkoutService.initializePayment).toHaveBeenCalled()); const initializeOptions = (checkoutService.initializePayment as jest.Mock).mock.calls[0][0]; @@ -221,7 +217,7 @@ describe('WorldpayCreditCardPaymentMethod', () => { ); }); - await new Promise((resolve) => process.nextTick(resolve)); + await screen.findByText('Close'); fireEvent.click(screen.getByText('Close')); expect(cancelWorldpayPayment).toHaveBeenCalledTimes(1); From 6903798c79d262b5b55e4c4617a81c22b9121047 Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Tue, 5 May 2026 17:02:36 +1000 Subject: [PATCH 3/9] tests(other): fix lint issues with BlueSnapPayment test --- .../src/BlueSnapV2PaymentMethod.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx b/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx index 41579d6adc..9e99eaa5a8 100644 --- a/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx +++ b/packages/bluesnap-direct-integration/src/BlueSnapV2PaymentMethod.test.tsx @@ -121,7 +121,7 @@ describe('when using BlueSnapV2 payment', () => { expect(screen.getByTestId('my-iframe')).toBeInTheDocument(); }); - it('renders modal but does not append bluesnap payment page because is empty', async () => { + it('renders modal but does not append bluesnap payment page because is empty', () => { render(); const initializeOptions = initializePayment.mock.calls[0][0]; From 3f348722f22a29be69a643ee8a9a2dc637c23f63 Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Tue, 5 May 2026 17:20:41 +1000 Subject: [PATCH 4/9] tests(other): try fixing e2e tests --- .../src/app/checkout/hooks/useLoadCheckout.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/core/src/app/checkout/hooks/useLoadCheckout.ts b/packages/core/src/app/checkout/hooks/useLoadCheckout.ts index 4635551687..986fab6328 100644 --- a/packages/core/src/app/checkout/hooks/useLoadCheckout.ts +++ b/packages/core/src/app/checkout/hooks/useLoadCheckout.ts @@ -8,6 +8,7 @@ import { yieldToMain } from '../../common/utility'; export const useLoadCheckout = (checkoutId: string, initialState?: CheckoutInitialState): {isLoadingCheckout: boolean} => { const { checkoutService, checkoutState: { data } } = useCheckout(); const [ isLoadingCheckout, setIsLoadingCheckout ] = useState(!data.getCheckout()); + const [ , setLoadError ] = useState(null); const { extensionService } = useExtensions(); const fetchData = async () => { @@ -45,9 +46,16 @@ export const useLoadCheckout = (checkoutId: string, initialState?: CheckoutIniti }; const hydrateInitialState = async (initialState: CheckoutInitialState) => { - await yieldToMain(); - await checkoutService.hydrateInitialState(initialState); - setIsLoadingCheckout(false); + // yieldToMain() uses window.scheduler.yield() when available (Chromium). + // Under React 18 concurrent rendering the scheduler can defer this + // indefinitely in automated/test environments, so wrap the state update + // in a try/finally so it always fires even if the yield is slow. + try { + await yieldToMain(); + } finally { + await checkoutService.hydrateInitialState(initialState); + setIsLoadingCheckout(false); + } } useEffect(() => { @@ -59,8 +67,11 @@ export const useLoadCheckout = (checkoutId: string, initialState?: CheckoutIniti // If the initial data has not been preloaded from the server, we need to make API calls to fetch it. fetchDataWithRetry() .then(() => setIsLoadingCheckout(false)) - .catch((error) => { - throw error; + .catch((error: Error) => { + // Surface the error through React's error boundary mechanism + // instead of throwing an unhandled rejection (which crashes the + // page under React 18's createRoot / concurrent rendering). + setLoadError(() => { throw error; }); }); } else { hydrateInitialState(initialState); From 8bbc4da8fdb0fd4328d9d14fcd71e5ba6e3458a9 Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Wed, 6 May 2026 14:19:51 +1000 Subject: [PATCH 5/9] Revert "tests(other): try fixing e2e tests" This reverts commit 3f348722f22a29be69a643ee8a9a2dc637c23f63. --- .../src/app/checkout/hooks/useLoadCheckout.ts | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/core/src/app/checkout/hooks/useLoadCheckout.ts b/packages/core/src/app/checkout/hooks/useLoadCheckout.ts index 986fab6328..4635551687 100644 --- a/packages/core/src/app/checkout/hooks/useLoadCheckout.ts +++ b/packages/core/src/app/checkout/hooks/useLoadCheckout.ts @@ -8,7 +8,6 @@ import { yieldToMain } from '../../common/utility'; export const useLoadCheckout = (checkoutId: string, initialState?: CheckoutInitialState): {isLoadingCheckout: boolean} => { const { checkoutService, checkoutState: { data } } = useCheckout(); const [ isLoadingCheckout, setIsLoadingCheckout ] = useState(!data.getCheckout()); - const [ , setLoadError ] = useState(null); const { extensionService } = useExtensions(); const fetchData = async () => { @@ -46,16 +45,9 @@ export const useLoadCheckout = (checkoutId: string, initialState?: CheckoutIniti }; const hydrateInitialState = async (initialState: CheckoutInitialState) => { - // yieldToMain() uses window.scheduler.yield() when available (Chromium). - // Under React 18 concurrent rendering the scheduler can defer this - // indefinitely in automated/test environments, so wrap the state update - // in a try/finally so it always fires even if the yield is slow. - try { - await yieldToMain(); - } finally { - await checkoutService.hydrateInitialState(initialState); - setIsLoadingCheckout(false); - } + await yieldToMain(); + await checkoutService.hydrateInitialState(initialState); + setIsLoadingCheckout(false); } useEffect(() => { @@ -67,11 +59,8 @@ export const useLoadCheckout = (checkoutId: string, initialState?: CheckoutIniti // If the initial data has not been preloaded from the server, we need to make API calls to fetch it. fetchDataWithRetry() .then(() => setIsLoadingCheckout(false)) - .catch((error: Error) => { - // Surface the error through React's error boundary mechanism - // instead of throwing an unhandled rejection (which crashes the - // page under React 18's createRoot / concurrent rendering). - setLoadError(() => { throw error; }); + .catch((error) => { + throw error; }); } else { hydrateInitialState(initialState); From 6c023997399b6d66766172fe7169f627812da34d Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Wed, 6 May 2026 16:33:06 +1000 Subject: [PATCH 6/9] tests: fix tests using flushsync which is not ideal --- packages/contexts/src/checkout/CheckoutProvider.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/contexts/src/checkout/CheckoutProvider.tsx b/packages/contexts/src/checkout/CheckoutProvider.tsx index eda2e4a641..8bc1a45111 100644 --- a/packages/contexts/src/checkout/CheckoutProvider.tsx +++ b/packages/contexts/src/checkout/CheckoutProvider.tsx @@ -7,6 +7,7 @@ import React, { useRef, useState, } from 'react'; +import { flushSync } from 'react-dom'; import { CapabilitiesProvider } from '../capabilities'; import CheckoutContext from './CheckoutContext'; @@ -31,8 +32,14 @@ const CheckoutProviderV2: React.FC = ({ const unsubscribeRef = useRef<(() => void) | undefined>(); useEffect(() => { + // flushSync ensures checkout service state updates are applied synchronously, + // so stepsRef.current in CheckoutPage is up-to-date before any async + // continuation (e.g. onContinueAsGuest) reads from it. Without this, React 18 + // Concurrent Mode batches the setState and defers the re-render, causing + // navigateToNextIncompleteStep to read stale step statuses and navigate to the + // wrong step (Customer instead of Shipping) after guest checkout. unsubscribeRef.current = checkoutService.subscribe((newCheckoutState) => - setCheckoutState(newCheckoutState), + flushSync(() => setCheckoutState(newCheckoutState)), ); return () => { @@ -80,8 +87,9 @@ const CheckoutProviderV1: React.FC = ({ ); useEffect(() => { + // See comment in CheckoutProviderV2 for why flushSync is needed here. unsubscribeRef.current = checkoutService.subscribe((newCheckoutState) => - setCheckoutState(newCheckoutState), + flushSync(() => setCheckoutState(newCheckoutState)), ); return () => { From 0221049506450bfcb7c0f7bc80bb0dba978f8c6e Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Wed, 6 May 2026 17:05:18 +1000 Subject: [PATCH 7/9] chore: bring back findDomNode supression as it isn't fixed --- jest-setup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jest-setup.ts b/jest-setup.ts index f72326cedd..c9a7f7abf1 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -55,8 +55,8 @@ beforeAll(() => { console.error = (...args: unknown[]) => { const message = args.map(String).join(); - // Suppress known third-party warnings unrelated to our code - if (/Formik|React.act/.test(message)) { + // Suppress known third-party warnings + if (/Formik|React.act|findDOMNode/.test(message)) { return; } From 4db8f5dae3dec04e5b9da8522344b07ff9d3aab1 Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Thu, 7 May 2026 09:44:19 +1000 Subject: [PATCH 8/9] chore(other): update suppression comment wording --- jest-setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest-setup.ts b/jest-setup.ts index c9a7f7abf1..b965fecfe0 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -55,7 +55,7 @@ beforeAll(() => { console.error = (...args: unknown[]) => { const message = args.map(String).join(); - // Suppress known third-party warnings + // FIXME: Remove after we resolve these third-party warnings if (/Formik|React.act|findDOMNode/.test(message)) { return; } From a14158917c410a71a36863f1a86502573d60eb9b Mon Sep 17 00:00:00 2001 From: bc-maxy Date: Tue, 12 May 2026 16:14:15 +1000 Subject: [PATCH 9/9] tests(core): add tests to confirm hard error when container is not present --- packages/core/src/app/checkout/renderCheckout.test.tsx | 6 ++++++ .../core/src/app/order/renderOrderConfirmation.test.tsx | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/core/src/app/checkout/renderCheckout.test.tsx b/packages/core/src/app/checkout/renderCheckout.test.tsx index 0a00a315a8..92793c826c 100644 --- a/packages/core/src/app/checkout/renderCheckout.test.tsx +++ b/packages/core/src/app/checkout/renderCheckout.test.tsx @@ -53,6 +53,12 @@ describe('renderCheckout()', () => { publicPath = ''; }); + it('throws when container element does not exist', () => { + expect(() => { + renderCheckout({ ...options, containerId: 'non-existent-id' }); + }).toThrow('Unable to find checkout container: #non-existent-id'); + }); + it('configures public path before mounting app component', () => { act(() => { renderCheckout(options); diff --git a/packages/core/src/app/order/renderOrderConfirmation.test.tsx b/packages/core/src/app/order/renderOrderConfirmation.test.tsx index 367144b3be..9d031694bf 100644 --- a/packages/core/src/app/order/renderOrderConfirmation.test.tsx +++ b/packages/core/src/app/order/renderOrderConfirmation.test.tsx @@ -53,6 +53,12 @@ describe('renderOrderConfirmation()', () => { publicPath = ''; }); + it('throws when container element does not exist', () => { + expect(() => { + renderOrderConfirmation({ ...options, containerId: 'non-existent-id' }); + }).toThrow('Unable to find order confirmation container: #non-existent-id'); + }); + it('configures public path before mounting app component', () => { act(() => { renderOrderConfirmation(options);