Commit 325bc63f8a4 for woocommerce
commit 325bc63f8a457b57995c972258d14e858c6b5f24
Author: Ryan Tang <ryan.diginomad@gmail.com>
Date: Thu Jun 18 21:27:31 2026 +0800
Update offline payment method settings forms to use DataForm (#65731)
* refactor(settings-payments): migrate cheque settings form to DataForm
Replace the hand-rolled CheckboxControl/TextControl/TextareaControl field
rendering with @wordpress/dataviews DataForm, using declarative field
definitions and shared custom edit controls (checkbox and textarea are
not built into DataForm 4.x).
Behavior is unchanged and locked by characterization tests written
against the previous implementation: field rendering, loading
placeholders, save payload shape, and hasChanges gating.
* refactor(settings-payments): migrate cash on delivery settings form to DataForm
Same approach as the cheque migration: declarative field definitions
with shared edit controls, plus a COD-specific edit control for the
shipping methods TreeSelectControl (its options come from the gateway
settings, so it stays local to this form).
Behavior locked by characterization tests written against the previous
implementation, including the enable_for_methods value passthrough.
* refactor(settings-payments): migrate direct bank transfer settings form to DataForm
Same approach as the cheque migration for the four settings fields.
The bank accounts section (BankAccountsList) and the parallel accounts
save flow are intentionally unchanged - only the hand-rolled field
rendering moves to DataForm.
Behavior locked by characterization tests, including the dual-payload
save (updatePaymentGateway + updateOptions for accounts).
* Add changelog
* Add keyboard navigation and save-failure tests to offline payment forms
Addresses CodeRabbit review feedback: per the admin client testing
guidelines, component tests should cover keyboard accessibility
(userEvent.tab() focus order through the form fields and the save
button) and the save error path (error notice on a rejected
updatePaymentGateway call).
* refactor: move @wordpress/dataviews stylesheet import to settings-embed entry
DataForm depends on the @wordpress/dataviews stylesheet. It was imported
indirectly via settings-email/style.scss. Since this PR adds DataForm to the
payment settings UI (also rendered under the settings-embed entry), move the
import to the shared settings-embed entry stylesheet (settings-ui.scss) and
drop the settings-email-specific import to avoid the accidental coupling.
Both settings-email and settings-payments render under settings-embed/index.tsx,
so settings-email still receives the styles from the shared entry bundle.
* Update plugins/woocommerce/changelog/61900-update-offline-payment-forms-dataform
Update changelog
---------
Co-authored-by: Ryan Tang <24728770+ryandiginomad@users.noreply.github.com>
Co-authored-by: Daniel Mallory <daniel.mallory@automattic.com>
diff --git a/plugins/woocommerce/changelog/61900-update-offline-payment-forms-dataform b/plugins/woocommerce/changelog/61900-update-offline-payment-forms-dataform
new file mode 100644
index 00000000000..c5b835db4e8
--- /dev/null
+++ b/plugins/woocommerce/changelog/61900-update-offline-payment-forms-dataform
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Migrate offline payment settings forms to DataForm.
diff --git a/plugins/woocommerce/client/admin/client/settings-email/style.scss b/plugins/woocommerce/client/admin/client/settings-email/style.scss
index 6bf88c67b84..6cf0bdea479 100644
--- a/plugins/woocommerce/client/admin/client/settings-email/style.scss
+++ b/plugins/woocommerce/client/admin/client/settings-email/style.scss
@@ -1,5 +1,3 @@
-@import "@wordpress/dataviews/build-style/style.css";
-
.wc-settings-row-disabled {
opacity: 0.5;
pointer-events: none;
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/dataform-controls.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/dataform-controls.tsx
new file mode 100644
index 00000000000..d7c0b26e99e
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/dataform-controls.tsx
@@ -0,0 +1,66 @@
+/**
+ * External dependencies
+ */
+import {
+ CheckboxControl,
+ TextControl,
+ TextareaControl,
+} from '@wordpress/components';
+import type { DataFormControlProps } from '@wordpress/dataviews';
+
+/**
+ * The shape of the form values used by the offline payment method settings forms.
+ */
+export type OfflineFormValues = Record< string, string | boolean | string[] >;
+
+/**
+ * A DataForm edit control that renders a `CheckboxControl`, preserving the
+ * markup of the previous hand-rolled offline payment method forms.
+ */
+export const CheckboxEdit = ( {
+ data,
+ field,
+ onChange,
+}: DataFormControlProps< OfflineFormValues > ) => (
+ <CheckboxControl
+ label={ field.label }
+ help={ field.description }
+ checked={ Boolean( field.getValue( { item: data } ) ) }
+ onChange={ ( checked ) => onChange( { [ field.id ]: checked } ) }
+ />
+);
+
+/**
+ * A DataForm edit control that renders a `TextControl`, preserving the
+ * markup of the previous hand-rolled offline payment method forms.
+ */
+export const TextEdit = ( {
+ data,
+ field,
+ onChange,
+}: DataFormControlProps< OfflineFormValues > ) => (
+ <TextControl
+ label={ field.label }
+ help={ field.description }
+ placeholder={ field.placeholder }
+ value={ String( field.getValue( { item: data } ) ?? '' ) }
+ onChange={ ( value ) => onChange( { [ field.id ]: value } ) }
+ />
+);
+
+/**
+ * A DataForm edit control that renders a `TextareaControl`, preserving the
+ * markup of the previous hand-rolled offline payment method forms.
+ */
+export const TextareaEdit = ( {
+ data,
+ field,
+ onChange,
+}: DataFormControlProps< OfflineFormValues > ) => (
+ <TextareaControl
+ label={ field.label }
+ help={ field.description }
+ value={ String( field.getValue( { item: data } ) ?? '' ) }
+ onChange={ ( value ) => onChange( { [ field.id ]: value } ) }
+ />
+);
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx
index 0a246a747c4..c780447e4b2 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-bacs.tsx
@@ -3,18 +3,15 @@
*/
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
-import {
- CheckboxControl,
- TextControl,
- TextareaControl,
- Button,
-} from '@wordpress/components';
-import { useState, useEffect } from '@wordpress/element';
+import { Button } from '@wordpress/components';
+import { useState, useEffect, useMemo } from '@wordpress/element';
import {
paymentGatewaysStore,
optionsStore,
paymentSettingsStore,
} from '@woocommerce/data';
+import { DataForm } from '@wordpress/dataviews';
+import type { Field } from '@wordpress/dataviews';
/**
* Internal dependencies
@@ -24,6 +21,12 @@ import { Settings } from '~/settings-payments/components/settings';
import { FieldPlaceholder } from '~/settings-payments/components/field-placeholder';
import { BankAccountsList } from '~/settings-payments/components/bank-accounts-list';
import { BankAccount } from '~/settings-payments/components/bank-accounts-list/types';
+import {
+ CheckboxEdit,
+ TextEdit,
+ TextareaEdit,
+ type OfflineFormValues,
+} from './dataform-controls';
/**
* This page is used to manage the settings for the BACS (Direct bank transfer) payment gateway.
@@ -64,9 +67,7 @@ export const SettingsPaymentsBacs = () => {
};
}, [] );
- const [ formValues, setFormValues ] = useState<
- Record< string, string | boolean | string[] >
- >( {} );
+ const [ formValues, setFormValues ] = useState< OfflineFormValues >( {} );
const [ isSaving, setIsSaving ] = useState( false );
const [ hasChanges, setHasChanges ] = useState( false );
@@ -94,6 +95,48 @@ export const SettingsPaymentsBacs = () => {
const { updateOptions } = useDispatch( optionsStore );
const { updatePaymentGateway } = useDispatch( paymentGatewaysStore );
+ const fields: Field< OfflineFormValues >[] = useMemo(
+ () => [
+ {
+ id: 'enabled',
+ label: __( 'Enable direct bank transfers', 'woocommerce' ),
+ Edit: CheckboxEdit,
+ },
+ {
+ id: 'title',
+ label: __( 'Title', 'woocommerce' ),
+ description: __(
+ 'Payment method name that the customer will see during checkout.',
+ 'woocommerce'
+ ),
+ placeholder: __(
+ 'Direct bank transfer payments',
+ 'woocommerce'
+ ),
+ Edit: TextEdit,
+ },
+ {
+ id: 'description',
+ label: __( 'Description', 'woocommerce' ),
+ description: __(
+ 'Payment method description that the customer will see during checkout.',
+ 'woocommerce'
+ ),
+ Edit: TextareaEdit,
+ },
+ {
+ id: 'instructions',
+ label: __( 'Instructions', 'woocommerce' ),
+ description: __(
+ 'Instructions that will be added to the thank you page and emails.',
+ 'woocommerce'
+ ),
+ Edit: TextareaEdit,
+ },
+ ],
+ []
+ );
+
const saveSettings = async () => {
if ( ! bacsSettings ) {
return;
@@ -166,80 +209,30 @@ export const SettingsPaymentsBacs = () => {
) }
>
{ isLoading ? (
- <FieldPlaceholder size="small" />
- ) : (
- <CheckboxControl
- label={ __(
- 'Enable direct bank transfers',
- 'woocommerce'
- ) }
- checked={ Boolean( formValues.enabled ) }
- onChange={ ( checked ) => {
- setFormValues( {
- ...formValues,
- enabled: checked,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="medium" />
- ) : (
- <TextControl
- label={ __( 'Title', 'woocommerce' ) }
- help={ __(
- 'Payment method name that the customer will see during checkout.',
- 'woocommerce'
- ) }
- placeholder={ __(
- 'Direct bank transfer payments',
- 'woocommerce'
- ) }
- value={ String( formValues.title ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- title: value,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="large" />
+ <>
+ <FieldPlaceholder size="small" />
+ <FieldPlaceholder size="medium" />
+ <FieldPlaceholder size="large" />
+ <FieldPlaceholder size="large" />
+ </>
) : (
- <TextareaControl
- label={ __( 'Description', 'woocommerce' ) }
- help={ __(
- 'Payment method description that the customer will see during checkout.',
- 'woocommerce'
- ) }
- value={ String( formValues.description ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- description: value,
- } );
- setHasChanges( true );
+ <DataForm
+ data={ formValues }
+ fields={ fields }
+ form={ {
+ type: 'regular',
+ fields: [
+ 'enabled',
+ 'title',
+ 'description',
+ 'instructions',
+ ],
} }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="large" />
- ) : (
- <TextareaControl
- label={ __( 'Instructions', 'woocommerce' ) }
- help={ __(
- 'Instructions that will be added to the thank you page and emails.',
- 'woocommerce'
- ) }
- value={ String( formValues.instructions ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- instructions: value,
- } );
+ onChange={ ( edits: OfflineFormValues ) => {
+ setFormValues( ( values ) => ( {
+ ...values,
+ ...edits,
+ } ) );
setHasChanges( true );
} }
/>
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cheque.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cheque.tsx
index c86d4be20f4..9289b9bb1ee 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cheque.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cheque.tsx
@@ -1,16 +1,13 @@
/**
* External dependencies
*/
-import {
- Button,
- CheckboxControl,
- TextControl,
- TextareaControl,
-} from '@wordpress/components';
+import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { paymentGatewaysStore, paymentSettingsStore } from '@woocommerce/data';
-import { useState, useEffect } from '@wordpress/element';
+import { useState, useEffect, useMemo } from '@wordpress/element';
+import { DataForm } from '@wordpress/dataviews';
+import type { Field } from '@wordpress/dataviews';
/**
* Internal dependencies
@@ -18,6 +15,12 @@ import { useState, useEffect } from '@wordpress/element';
import '../settings-payments-body.scss';
import { Settings } from '~/settings-payments/components/settings';
import { FieldPlaceholder } from '~/settings-payments/components/field-placeholder';
+import {
+ CheckboxEdit,
+ TextEdit,
+ TextareaEdit,
+ type OfflineFormValues,
+} from './dataform-controls';
/**
* This page is used to manage the settings for the Cheque payment gateway.
@@ -48,9 +51,7 @@ export const SettingsPaymentsCheque = () => {
invalidateResolutionForPaymentSettings,
} = useDispatch( paymentSettingsStore );
- const [ formValues, setFormValues ] = useState<
- Record< string, string | boolean | string[] >
- >( {} );
+ const [ formValues, setFormValues ] = useState< OfflineFormValues >( {} );
const [ isSaving, setIsSaving ] = useState( false );
const [ hasChanges, setHasChanges ] = useState( false );
@@ -65,6 +66,45 @@ export const SettingsPaymentsCheque = () => {
}
}, [ chequeSettings ] );
+ const fields: Field< OfflineFormValues >[] = useMemo(
+ () => [
+ {
+ id: 'enabled',
+ label: __( 'Enable check payments', 'woocommerce' ),
+ Edit: CheckboxEdit,
+ },
+ {
+ id: 'title',
+ label: __( 'Title', 'woocommerce' ),
+ description: __(
+ 'Payment method name that the customer will see during checkout.',
+ 'woocommerce'
+ ),
+ placeholder: __( 'Check payments', 'woocommerce' ),
+ Edit: TextEdit,
+ },
+ {
+ id: 'description',
+ label: __( 'Description', 'woocommerce' ),
+ description: __(
+ 'Payment method description that the customer will see during checkout.',
+ 'woocommerce'
+ ),
+ Edit: TextareaEdit,
+ },
+ {
+ id: 'instructions',
+ label: __( 'Instructions', 'woocommerce' ),
+ description: __(
+ 'Instructions that will be added to the thank you page and emails.',
+ 'woocommerce'
+ ),
+ Edit: TextareaEdit,
+ },
+ ],
+ []
+ );
+
const saveSettings = () => {
if ( ! chequeSettings ) {
return;
@@ -120,80 +160,30 @@ export const SettingsPaymentsCheque = () => {
) }
>
{ isLoading ? (
- <FieldPlaceholder size="small" />
- ) : (
- <CheckboxControl
- label={ __(
- 'Enable check payments',
- 'woocommerce'
- ) }
- checked={ Boolean( formValues.enabled ) }
- onChange={ ( checked ) => {
- setFormValues( {
- ...formValues,
- enabled: checked,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="medium" />
+ <>
+ <FieldPlaceholder size="small" />
+ <FieldPlaceholder size="medium" />
+ <FieldPlaceholder size="large" />
+ <FieldPlaceholder size="large" />
+ </>
) : (
- <TextControl
- label={ __( 'Title', 'woocommerce' ) }
- help={ __(
- 'Payment method name that the customer will see during checkout.',
- 'woocommerce'
- ) }
- placeholder={ __(
- 'Check payments',
- 'woocommerce'
- ) }
- value={ String( formValues.title ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- title: value,
- } );
- setHasChanges( true );
+ <DataForm
+ data={ formValues }
+ fields={ fields }
+ form={ {
+ type: 'regular',
+ fields: [
+ 'enabled',
+ 'title',
+ 'description',
+ 'instructions',
+ ],
} }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="large" />
- ) : (
- <TextareaControl
- label={ __( 'Description', 'woocommerce' ) }
- help={ __(
- 'Payment method description that the customer will see during checkout.',
- 'woocommerce'
- ) }
- value={ String( formValues.description ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- description: value,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="large" />
- ) : (
- <TextareaControl
- label={ __( 'Instructions', 'woocommerce' ) }
- help={ __(
- 'Instructions that will be added to the thank you page and emails.',
- 'woocommerce'
- ) }
- value={ String( formValues.instructions ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- instructions: value,
- } );
+ onChange={ ( edits: OfflineFormValues ) => {
+ setFormValues( ( values ) => ( {
+ ...values,
+ ...edits,
+ } ) );
setHasChanges( true );
} }
/>
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cod.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cod.tsx
index a2d1621f37d..88c122ac6a8 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cod.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/settings-payments-cod.tsx
@@ -1,17 +1,14 @@
/**
* External dependencies
*/
-import {
- Button,
- CheckboxControl,
- TextControl,
- TextareaControl,
-} from '@wordpress/components';
+import { Button } from '@wordpress/components';
import { TreeSelectControl } from '@woocommerce/components';
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { paymentGatewaysStore, paymentSettingsStore } from '@woocommerce/data';
-import { useState, useEffect } from '@wordpress/element';
+import { useState, useEffect, useMemo } from '@wordpress/element';
+import { DataForm } from '@wordpress/dataviews';
+import type { Field } from '@wordpress/dataviews';
/**
* Internal dependencies
@@ -20,6 +17,12 @@ import '../settings-payments-body.scss';
import { mapShippingMethodsOptions } from '~/settings-payments/offline/utils';
import { Settings } from '~/settings-payments/components/settings';
import { FieldPlaceholder } from '~/settings-payments/components/field-placeholder';
+import {
+ CheckboxEdit,
+ TextEdit,
+ TextareaEdit,
+ type OfflineFormValues,
+} from './dataform-controls';
/**
* This page is used to manage the settings for the Cash on delivery payment gateway.
@@ -48,9 +51,7 @@ export const SettingsPaymentsCod = () => {
invalidateResolutionForPaymentSettings,
} = useDispatch( paymentSettingsStore );
- const [ formValues, setFormValues ] = useState<
- Record< string, string | boolean | string[] >
- >( {} );
+ const [ formValues, setFormValues ] = useState< OfflineFormValues >( {} );
const [ isSaving, setIsSaving ] = useState( false );
const [ hasChanges, setHasChanges ] = useState( false );
@@ -73,6 +74,89 @@ export const SettingsPaymentsCod = () => {
}
}, [ codSettings ] );
+ const shippingMethodsOptions = useMemo(
+ () =>
+ codSettings?.settings.enable_for_methods?.options
+ ? mapShippingMethodsOptions(
+ codSettings.settings.enable_for_methods.options
+ )
+ : [],
+ [ codSettings ]
+ );
+
+ const fields: Field< OfflineFormValues >[] = useMemo(
+ () => [
+ {
+ id: 'enabled',
+ label: __( 'Enable cash on delivery payments', 'woocommerce' ),
+ Edit: CheckboxEdit,
+ },
+ {
+ id: 'title',
+ label: __( 'Title', 'woocommerce' ),
+ description: __(
+ 'Payment method name that the customer will see during checkout.',
+ 'woocommerce'
+ ),
+ placeholder: __( 'Cash on delivery payments', 'woocommerce' ),
+ Edit: TextEdit,
+ },
+ {
+ id: 'description',
+ label: __( 'Description', 'woocommerce' ),
+ description: __(
+ 'Payment method description that the customer will see during checkout.',
+ 'woocommerce'
+ ),
+ Edit: TextareaEdit,
+ },
+ {
+ id: 'instructions',
+ label: __( 'Instructions', 'woocommerce' ),
+ description: __(
+ 'Instructions that will be added to the thank you page and emails.',
+ 'woocommerce'
+ ),
+ Edit: TextareaEdit,
+ },
+ {
+ id: 'enable_for_methods',
+ label: __( 'Enable for shipping methods', 'woocommerce' ),
+ description: __(
+ 'Select shipping methods for which this payment method is enabled.',
+ 'woocommerce'
+ ),
+ // COD-specific edit control: renders the shipping methods
+ // multi-select using the options that ship with the gateway.
+ Edit: ( { data, field, onChange } ) => {
+ const value = field.getValue( { item: data } );
+ return (
+ <TreeSelectControl
+ label={ field.label }
+ help={ field.description }
+ options={ shippingMethodsOptions }
+ value={ Array.isArray( value ) ? value : [] }
+ onChange={ ( newValue: string[] ) =>
+ onChange( { [ field.id ]: newValue } )
+ }
+ selectAllLabel={ false }
+ />
+ );
+ },
+ },
+ {
+ id: 'enable_for_virtual',
+ label: __( 'Accept for virtual orders', 'woocommerce' ),
+ description: __(
+ 'Accept cash on delivery if the order is virtual',
+ 'woocommerce'
+ ),
+ Edit: CheckboxEdit,
+ },
+ ],
+ [ shippingMethodsOptions ]
+ );
+
const saveSettings = () => {
if ( ! codSettings ) {
return;
@@ -132,142 +216,34 @@ export const SettingsPaymentsCod = () => {
) }
>
{ isLoading ? (
- <FieldPlaceholder size="small" />
- ) : (
- <CheckboxControl
- label={ __(
- 'Enable cash on delivery payments',
- 'woocommerce'
- ) }
- checked={ Boolean( formValues.enabled ) }
- onChange={ ( checked ) => {
- setFormValues( {
- ...formValues,
- enabled: checked,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="medium" />
+ <>
+ <FieldPlaceholder size="small" />
+ <FieldPlaceholder size="medium" />
+ <FieldPlaceholder size="large" />
+ <FieldPlaceholder size="large" />
+ <FieldPlaceholder size="medium" />
+ <FieldPlaceholder size="small" />
+ </>
) : (
- <TextControl
- label={ __( 'Title', 'woocommerce' ) }
- help={ __(
- 'Payment method name that the customer will see during checkout.',
- 'woocommerce'
- ) }
- placeholder={ __(
- 'Cash on delivery payments',
- 'woocommerce'
- ) }
- value={ String( formValues.title ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- title: value,
- } );
- setHasChanges( true );
+ <DataForm
+ data={ formValues }
+ fields={ fields }
+ form={ {
+ type: 'regular',
+ fields: [
+ 'enabled',
+ 'title',
+ 'description',
+ 'instructions',
+ 'enable_for_methods',
+ 'enable_for_virtual',
+ ],
} }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="large" />
- ) : (
- <TextareaControl
- label={ __( 'Description', 'woocommerce' ) }
- help={ __(
- 'Payment method description that the customer will see during checkout.',
- 'woocommerce'
- ) }
- value={ String( formValues.description ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- description: value,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="large" />
- ) : (
- <TextareaControl
- label={ __( 'Instructions', 'woocommerce' ) }
- help={ __(
- 'Instructions that will be added to the thank you page and emails.',
- 'woocommerce'
- ) }
- value={ String( formValues.instructions ) }
- onChange={ ( value ) => {
- setFormValues( {
- ...formValues,
- instructions: value,
- } );
- setHasChanges( true );
- } }
- />
- ) }
- { isLoading || ! codSettings ? (
- <FieldPlaceholder size="medium" />
- ) : (
- <TreeSelectControl
- label={ __(
- 'Enable for shipping methods',
- 'woocommerce'
- ) }
- help={ __(
- 'Select shipping methods for which this payment method is enabled.',
- 'woocommerce'
- ) }
- options={
- codSettings.settings.enable_for_methods
- ?.options
- ? mapShippingMethodsOptions(
- codSettings.settings
- .enable_for_methods.options
- )
- : []
- }
- value={
- Array.isArray(
- formValues.enable_for_methods
- )
- ? formValues.enable_for_methods
- : []
- }
- onChange={ ( value: string[] ) => {
- setFormValues( {
- ...formValues,
- enable_for_methods: value,
- } );
- setHasChanges( true );
- } }
- selectAllLabel={ false }
- />
- ) }
- { isLoading ? (
- <FieldPlaceholder size="small" />
- ) : (
- <CheckboxControl
- label={ __(
- 'Accept for virtual orders',
- 'woocommerce'
- ) }
- help={ __(
- 'Accept cash on delivery if the order is virtual',
- 'woocommerce'
- ) }
- checked={ Boolean(
- formValues.enable_for_virtual
- ) }
- onChange={ ( checked ) => {
- setFormValues( {
- ...formValues,
- enable_for_virtual: checked,
- } );
+ onChange={ ( edits: OfflineFormValues ) => {
+ setFormValues( ( values ) => ( {
+ ...values,
+ ...edits,
+ } ) );
setHasChanges( true );
} }
/>
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx
new file mode 100644
index 00000000000..1cebc50602c
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-bacs.test.tsx
@@ -0,0 +1,239 @@
+/**
+ * External dependencies
+ */
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useSelect, useDispatch } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { SettingsPaymentsBacs } from '../settings-payments-bacs';
+
+jest.mock( '@wordpress/data', () => ( {
+ ...jest.requireActual( '@wordpress/data' ),
+ useSelect: jest.fn(),
+ useDispatch: jest.fn(),
+} ) );
+
+jest.mock( '~/settings-payments/components/bank-accounts-list', () => ( {
+ BankAccountsList: () => <div data-testid="bank-accounts-list" />,
+} ) );
+
+const bacsSettings = {
+ enabled: true,
+ description: 'Make your payment directly into our bank account.',
+ settings: {
+ title: { value: 'Direct bank transfer' },
+ instructions: { value: 'Use your order ID as the payment reference.' },
+ },
+};
+
+const accountsOption = [
+ {
+ id: 'extra-field-that-should-not-be-saved',
+ account_name: 'Main account',
+ account_number: '12345678',
+ bank_name: 'Test Bank',
+ sort_code: '12-34-56',
+ iban: 'GB29NWBK60161331926819',
+ bic: 'NWBKGB2L',
+ country_code: 'GB',
+ },
+];
+
+describe( 'SettingsPaymentsBacs', () => {
+ let updatePaymentGateway: jest.Mock;
+ let updateOptions: jest.Mock;
+
+ beforeEach( () => {
+ updatePaymentGateway = jest.fn().mockResolvedValue( {} );
+ updateOptions = jest.fn().mockResolvedValue( {} );
+ ( useDispatch as jest.Mock ).mockReturnValue( {
+ createSuccessNotice: jest.fn(),
+ createErrorNotice: jest.fn(),
+ updatePaymentGateway,
+ updateOptions,
+ invalidateResolution: jest.fn(),
+ invalidateResolutionForStoreSelector: jest.fn(),
+ } );
+ ( useSelect as jest.Mock ).mockReturnValue( {
+ bacsSettings,
+ isLoading: false,
+ accountsOption,
+ isLoadingAccounts: false,
+ } );
+ } );
+
+ it( 'renders all settings fields with stored values', () => {
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByLabelText( 'Enable direct bank transfers' )
+ ).toBeChecked();
+ expect( screen.getByLabelText( 'Title' ) ).toHaveValue(
+ 'Direct bank transfer'
+ );
+ expect( screen.getByLabelText( 'Description' ) ).toHaveValue(
+ 'Make your payment directly into our bank account.'
+ );
+ expect( screen.getByLabelText( 'Instructions' ) ).toHaveValue(
+ 'Use your order ID as the payment reference.'
+ );
+ } );
+
+ it( 'renders the bank accounts section', () => {
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByTestId( 'bank-accounts-list' )
+ ).toBeInTheDocument();
+ } );
+
+ it( 'renders placeholders while loading', () => {
+ ( useSelect as jest.Mock ).mockReturnValue( {
+ bacsSettings: null,
+ isLoading: true,
+ accountsOption: undefined,
+ isLoadingAccounts: true,
+ } );
+
+ const { container } = render( <SettingsPaymentsBacs /> );
+
+ expect(
+ container.querySelectorAll( '.woocommerce-field-placeholder' )
+ .length
+ ).toBeGreaterThan( 0 );
+ expect( screen.queryByLabelText( 'Title' ) ).not.toBeInTheDocument();
+ expect(
+ screen.queryByTestId( 'bank-accounts-list' )
+ ).not.toBeInTheDocument();
+ } );
+
+ it( 'disables save until a change is made', () => {
+ render( <SettingsPaymentsBacs /> );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeDisabled();
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Bank transfer payments' },
+ } );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeEnabled();
+ } );
+
+ it( 'saves the edited values with the expected payload shape', async () => {
+ render( <SettingsPaymentsBacs /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Bank transfer payments' },
+ } );
+ fireEvent.click(
+ screen.getByLabelText( 'Enable direct bank transfers' )
+ );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect( updatePaymentGateway ).toHaveBeenCalledWith( 'bacs', {
+ enabled: false,
+ description:
+ 'Make your payment directly into our bank account.',
+ settings: {
+ title: 'Bank transfer payments',
+ instructions: 'Use your order ID as the payment reference.',
+ },
+ } );
+ } );
+
+ expect( updateOptions ).toHaveBeenCalledWith( {
+ woocommerce_bacs_accounts: [
+ {
+ account_name: 'Main account',
+ account_number: '12345678',
+ bank_name: 'Test Bank',
+ sort_code: '12-34-56',
+ iban: 'GB29NWBK60161331926819',
+ bic: 'NWBKGB2L',
+ country_code: 'GB',
+ },
+ ],
+ } );
+ } );
+
+ it( 'disables save again after a successful save', async () => {
+ render( <SettingsPaymentsBacs /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Bank transfer payments' },
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeDisabled();
+ } );
+ } );
+
+ it( 'supports keyboard navigation through the form fields', () => {
+ render( <SettingsPaymentsBacs /> );
+
+ // Make a change first so the Save button is enabled (and tabbable).
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Edited title' },
+ } );
+
+ userEvent.tab();
+ expect(
+ screen.getByLabelText( 'Enable direct bank transfers' )
+ ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Title' ) ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Description' ) ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Instructions' ) ).toHaveFocus();
+ userEvent.tab();
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toHaveFocus();
+ } );
+
+ it( 'shows an error notice when saving fails', async () => {
+ const createErrorNotice = jest.fn();
+ updatePaymentGateway.mockRejectedValueOnce(
+ new Error( 'save failed' )
+ );
+ ( useDispatch as jest.Mock ).mockReturnValue( {
+ createSuccessNotice: jest.fn(),
+ createErrorNotice,
+ updatePaymentGateway,
+ updateOptions: jest.fn().mockResolvedValue( {} ),
+ invalidateResolution: jest.fn(),
+ invalidateResolutionForStoreSelector: jest.fn(),
+ } );
+
+ render( <SettingsPaymentsBacs /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Edited title' },
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect( createErrorNotice ).toHaveBeenCalledWith(
+ 'Failed to update settings'
+ );
+ } );
+ } );
+} );
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-cheque.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-cheque.test.tsx
new file mode 100644
index 00000000000..0a0dbde35b7
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-cheque.test.tsx
@@ -0,0 +1,186 @@
+/**
+ * External dependencies
+ */
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useSelect, useDispatch } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { SettingsPaymentsCheque } from '../settings-payments-cheque';
+
+jest.mock( '@wordpress/data', () => ( {
+ ...jest.requireActual( '@wordpress/data' ),
+ useSelect: jest.fn(),
+ useDispatch: jest.fn(),
+} ) );
+
+const chequeSettings = {
+ enabled: true,
+ description: 'Take payments in person via checks.',
+ settings: {
+ title: { value: 'Check payments' },
+ instructions: { value: 'Send the check to our address.' },
+ },
+};
+
+describe( 'SettingsPaymentsCheque', () => {
+ let updatePaymentGateway: jest.Mock;
+
+ beforeEach( () => {
+ updatePaymentGateway = jest.fn().mockResolvedValue( {} );
+ ( useDispatch as jest.Mock ).mockReturnValue( {
+ createSuccessNotice: jest.fn(),
+ createErrorNotice: jest.fn(),
+ updatePaymentGateway,
+ invalidateResolution: jest.fn(),
+ invalidateResolutionForStoreSelector: jest.fn(),
+ } );
+ ( useSelect as jest.Mock ).mockReturnValue( {
+ chequeSettings,
+ isLoading: false,
+ } );
+ } );
+
+ it( 'renders all settings fields with stored values', () => {
+ render( <SettingsPaymentsCheque /> );
+
+ expect(
+ screen.getByLabelText( 'Enable check payments' )
+ ).toBeChecked();
+ expect( screen.getByLabelText( 'Title' ) ).toHaveValue(
+ 'Check payments'
+ );
+ expect( screen.getByLabelText( 'Description' ) ).toHaveValue(
+ 'Take payments in person via checks.'
+ );
+ expect( screen.getByLabelText( 'Instructions' ) ).toHaveValue(
+ 'Send the check to our address.'
+ );
+ } );
+
+ it( 'renders placeholders while loading', () => {
+ ( useSelect as jest.Mock ).mockReturnValue( {
+ chequeSettings: null,
+ isLoading: true,
+ } );
+
+ const { container } = render( <SettingsPaymentsCheque /> );
+
+ expect(
+ container.querySelectorAll( '.woocommerce-field-placeholder' )
+ .length
+ ).toBeGreaterThan( 0 );
+ expect( screen.queryByLabelText( 'Title' ) ).not.toBeInTheDocument();
+ } );
+
+ it( 'disables save until a change is made', () => {
+ render( <SettingsPaymentsCheque /> );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeDisabled();
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Cheque payments' },
+ } );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeEnabled();
+ } );
+
+ it( 'saves the edited values with the expected payload shape', async () => {
+ render( <SettingsPaymentsCheque /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Cheque payments' },
+ } );
+ fireEvent.click( screen.getByLabelText( 'Enable check payments' ) );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect( updatePaymentGateway ).toHaveBeenCalledWith( 'cheque', {
+ enabled: false,
+ description: 'Take payments in person via checks.',
+ settings: {
+ title: 'Cheque payments',
+ instructions: 'Send the check to our address.',
+ },
+ } );
+ } );
+ } );
+
+ it( 'disables save again after a successful save', async () => {
+ render( <SettingsPaymentsCheque /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Cheque payments' },
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeDisabled();
+ } );
+ } );
+
+ it( 'supports keyboard navigation through the form fields', () => {
+ render( <SettingsPaymentsCheque /> );
+
+ // Make a change first so the Save button is enabled (and tabbable).
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Edited title' },
+ } );
+
+ userEvent.tab();
+ expect(
+ screen.getByLabelText( 'Enable check payments' )
+ ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Title' ) ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Description' ) ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Instructions' ) ).toHaveFocus();
+ userEvent.tab();
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toHaveFocus();
+ } );
+
+ it( 'shows an error notice when saving fails', async () => {
+ const createErrorNotice = jest.fn();
+ updatePaymentGateway.mockRejectedValueOnce(
+ new Error( 'save failed' )
+ );
+ ( useDispatch as jest.Mock ).mockReturnValue( {
+ createSuccessNotice: jest.fn(),
+ createErrorNotice,
+ updatePaymentGateway,
+ invalidateResolution: jest.fn(),
+ invalidateResolutionForStoreSelector: jest.fn(),
+ } );
+
+ render( <SettingsPaymentsCheque /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Edited title' },
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect( createErrorNotice ).toHaveBeenCalledWith(
+ 'Failed to update settings'
+ );
+ } );
+ } );
+} );
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-cod.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-cod.test.tsx
new file mode 100644
index 00000000000..bde2c458f4b
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/offline/test/settings-payments-cod.test.tsx
@@ -0,0 +1,217 @@
+/**
+ * External dependencies
+ */
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useSelect, useDispatch } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import { SettingsPaymentsCod } from '../settings-payments-cod';
+
+jest.mock( '@wordpress/data', () => ( {
+ ...jest.requireActual( '@wordpress/data' ),
+ useSelect: jest.fn(),
+ useDispatch: jest.fn(),
+} ) );
+
+const codSettings = {
+ enabled: true,
+ description: 'Pay with cash upon delivery.',
+ settings: {
+ title: { value: 'Cash on delivery' },
+ instructions: { value: 'Pay with cash upon delivery.' },
+ enable_for_methods: {
+ value: [ 'flat_rate:1' ],
+ options: {
+ 'Flat rate': {
+ 'flat_rate:1': 'Flat rate (#1)',
+ },
+ 'Free shipping': {
+ 'free_shipping:2': 'Free shipping (#2)',
+ },
+ },
+ },
+ enable_for_virtual: { value: 'yes' },
+ },
+};
+
+describe( 'SettingsPaymentsCod', () => {
+ let updatePaymentGateway: jest.Mock;
+
+ beforeEach( () => {
+ updatePaymentGateway = jest.fn().mockResolvedValue( {} );
+ ( useDispatch as jest.Mock ).mockReturnValue( {
+ createSuccessNotice: jest.fn(),
+ createErrorNotice: jest.fn(),
+ updatePaymentGateway,
+ invalidateResolution: jest.fn(),
+ invalidateResolutionForStoreSelector: jest.fn(),
+ } );
+ ( useSelect as jest.Mock ).mockReturnValue( {
+ codSettings,
+ isLoading: false,
+ } );
+ } );
+
+ it( 'renders all settings fields with stored values', () => {
+ render( <SettingsPaymentsCod /> );
+
+ expect(
+ screen.getByLabelText( 'Enable cash on delivery payments' )
+ ).toBeChecked();
+ expect( screen.getByLabelText( 'Title' ) ).toHaveValue(
+ 'Cash on delivery'
+ );
+ expect( screen.getByLabelText( 'Description' ) ).toHaveValue(
+ 'Pay with cash upon delivery.'
+ );
+ expect( screen.getByLabelText( 'Instructions' ) ).toHaveValue(
+ 'Pay with cash upon delivery.'
+ );
+ expect(
+ screen.getByLabelText( 'Enable for shipping methods' )
+ ).toBeInTheDocument();
+ // The stored shipping method selection is rendered as a tag.
+ expect( screen.getByText( 'Flat rate (#1)' ) ).toBeInTheDocument();
+ expect(
+ screen.getByLabelText( 'Accept for virtual orders' )
+ ).toBeChecked();
+ } );
+
+ it( 'renders placeholders while loading', () => {
+ ( useSelect as jest.Mock ).mockReturnValue( {
+ codSettings: null,
+ isLoading: true,
+ } );
+
+ const { container } = render( <SettingsPaymentsCod /> );
+
+ expect(
+ container.querySelectorAll( '.woocommerce-field-placeholder' )
+ .length
+ ).toBeGreaterThan( 0 );
+ expect( screen.queryByLabelText( 'Title' ) ).not.toBeInTheDocument();
+ } );
+
+ it( 'disables save until a change is made', () => {
+ render( <SettingsPaymentsCod /> );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeDisabled();
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'COD payments' },
+ } );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeEnabled();
+ } );
+
+ it( 'saves the edited values with the expected payload shape', async () => {
+ render( <SettingsPaymentsCod /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'COD payments' },
+ } );
+ fireEvent.click( screen.getByLabelText( 'Accept for virtual orders' ) );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect( updatePaymentGateway ).toHaveBeenCalledWith( 'cod', {
+ enabled: true,
+ description: 'Pay with cash upon delivery.',
+ settings: {
+ title: 'COD payments',
+ instructions: 'Pay with cash upon delivery.',
+ enable_for_methods: [ 'flat_rate:1' ],
+ enable_for_virtual: 'no',
+ },
+ } );
+ } );
+ } );
+
+ it( 'disables save again after a successful save', async () => {
+ render( <SettingsPaymentsCod /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'COD payments' },
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ ).toBeDisabled();
+ } );
+ } );
+
+ it( 'supports keyboard navigation through the form fields', () => {
+ render( <SettingsPaymentsCod /> );
+
+ // Make a change first so the Save button is enabled (and tabbable).
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Edited title' },
+ } );
+
+ userEvent.tab();
+ expect(
+ screen.getByLabelText( 'Enable cash on delivery payments' )
+ ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Title' ) ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Description' ) ).toHaveFocus();
+ userEvent.tab();
+ expect( screen.getByLabelText( 'Instructions' ) ).toHaveFocus();
+ // The shipping methods tree select and the virtual orders checkbox
+ // sit between Instructions and Save; tab until Save receives focus.
+ const saveButton = screen.getByRole( 'button', {
+ name: 'Save changes',
+ } );
+ for (
+ let i = 0;
+ i < 6 && saveButton.ownerDocument.activeElement !== saveButton;
+ i++
+ ) {
+ userEvent.tab();
+ }
+ expect( saveButton ).toHaveFocus();
+ } );
+
+ it( 'shows an error notice when saving fails', async () => {
+ const createErrorNotice = jest.fn();
+ updatePaymentGateway.mockRejectedValueOnce(
+ new Error( 'save failed' )
+ );
+ ( useDispatch as jest.Mock ).mockReturnValue( {
+ createSuccessNotice: jest.fn(),
+ createErrorNotice,
+ updatePaymentGateway,
+ invalidateResolution: jest.fn(),
+ invalidateResolutionForStoreSelector: jest.fn(),
+ } );
+
+ render( <SettingsPaymentsCod /> );
+
+ fireEvent.change( screen.getByLabelText( 'Title' ), {
+ target: { value: 'Edited title' },
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', { name: 'Save changes' } )
+ );
+
+ await waitFor( () => {
+ expect( createErrorNotice ).toHaveBeenCalledWith(
+ 'Failed to update settings'
+ );
+ } );
+ } );
+} );
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
index 460fb7c13bb..d7b57058491 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
@@ -1,4 +1,5 @@
@import "@wordpress/admin-ui/build-style/style.css";
+@import "@wordpress/dataviews/build-style/style.css";
body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
background-color: #fff;