Commit e537af0aa56 for woocommerce
commit e537af0aa562438608bd9de1130739a33eec92db
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Thu Sep 17 22:03:51 2026 +0300
[tests] Add RTL coverage for the block email editor sidebar settings (#68631)
* test(email-editor): Cover block editor sidebar settings with RTL
The block email editor's sidebar panel decides what a merchant sets
on a single email: whether it is active or manual, its subject and
preheader, and its CC and BCC recipients. Every one of those paths
ran only in the browser, through
email-editor-settings-sidebar.spec.ts, which this commit does not
touch and which keeps all three of its titles.
Add a React Testing Library suite for the panel itself. It covers
the active and manual status labels, the status telemetry, the
payloads the entity store receives for subject and preheader, and
the CC and BCC toggles, inputs, clear handlers and their telemetry.
Nothing is removed here, so no browser title changes and no coverage
moves. This is the lower-layer gate the portfolio asks for before
that spec is trimmed, which a later change will do.
Two limits worth stating. The suite stubs the rich text component,
so it covers the subject and preheader payloads but not the
insertion of personalization tags into them. And with the entity
store mocked, every write ends in a spy: nothing here proves a
setting survives a save and a reload, which is what the browser
spec still does.
Consolidates the mega-branch slice:
- Slice 062: test(email-editor): Cover sidebar settings policies
Refs TESTOPS-288
Refs #68046
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(js): Assert the status toggle after the change, drop a dead check
Two review points from the sidebar settings suite.
The CC/BCC test asserted that `merchant@example.com` was not displayed,
but both `woocommerceData` and `editedWooCommerceData` set `recipient`
to null in that test, so the string was never going to render and the
assertion could not fail. Removed rather than explained.
The status test stopped at the `editEntityRecord` call and the tracks
event, and never checked that the toggle caught up. It now advances the
saved record and rerenders, the same way the manual-status case below
it does, then asserts the button reads `Change status: Inactive`.
Confirmed it bites: forcing the status mapping to skip the disabled
branch fails that assertion and leaves the other two tests green.
Refs TESTOPS-288
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/testops-288-email-editor-sidebar-settings b/plugins/woocommerce/changelog/testops-288-email-editor-sidebar-settings
new file mode 100644
index 00000000000..144ff3a7613
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-288-email-editor-sidebar-settings
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Add RTL coverage for the block email editor's sidebar settings panel.
+
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/email-editor-integration/__tests__/sidebar-settings.test.tsx b/plugins/woocommerce/client/admin/client/wp-admin-scripts/email-editor-integration/__tests__/sidebar-settings.test.tsx
new file mode 100644
index 00000000000..d15cc7f7c4a
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/email-editor-integration/__tests__/sidebar-settings.test.tsx
@@ -0,0 +1,427 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import type { ComponentType, JSX, ReactNode } from 'react';
+
+type WooCommerceData = Record< string, unknown >;
+type RichTextWithButtonProps = {
+ attributeName: string;
+ attributeValue: string;
+ updateProperty: ( name: string, value: string | boolean ) => void;
+ label: string;
+ placeholder: string;
+ help?: ReactNode;
+};
+type SidebarFilter = (
+ RichTextWithButton: ComponentType< RichTextWithButtonProps >,
+ tracking: {
+ recordEvent: jest.Mock;
+ debouncedRecordEvent: jest.Mock;
+ }
+) => ComponentType;
+
+const mockRegisteredPlugins = new Map<
+ string,
+ { render: () => JSX.Element }
+>();
+const mockSidebarFilters: SidebarFilter[] = [];
+
+const mockEntityState: {
+ woocommerceData: WooCommerceData;
+ editedWooCommerceData: Record< string, unknown >;
+} = {
+ woocommerceData: {},
+ editedWooCommerceData: {},
+};
+const mockEditEntityRecord = jest.fn();
+const mockEmailEditorRecordEvent = jest.fn();
+const mockEntitySubscribers = new Set< () => void >();
+
+jest.mock( '@wordpress/core-data', () => ( {
+ store: { name: 'core' },
+ useEntityProp: () => {
+ const { useEffect, useReducer } =
+ jest.requireActual( '@wordpress/element' );
+ const [ , refresh ] = useReducer( ( count: number ) => count + 1, 0 );
+ useEffect( () => {
+ mockEntitySubscribers.add( refresh );
+ return () => mockEntitySubscribers.delete( refresh );
+ }, [ refresh ] );
+ return [ mockEntityState.woocommerceData ];
+ },
+} ) );
+
+jest.mock( '@wordpress/data', () => ( {
+ ...jest.requireActual( '@wordpress/data' ),
+ select: () => ( {
+ getEditedEntityRecord: () => ( {
+ woocommerce_data: mockEntityState.editedWooCommerceData,
+ } ),
+ } ),
+ dispatch: () => ( {
+ editEntityRecord: mockEditEntityRecord,
+ } ),
+} ) );
+
+jest.mock( '@wordpress/hooks', () => ( {
+ addFilter: (
+ filterName: string,
+ _namespace: string,
+ callback: SidebarFilter
+ ) => {
+ if (
+ filterName ===
+ 'woocommerce_email_editor_setting_sidebar_extension_component'
+ ) {
+ mockSidebarFilters.push( callback );
+ }
+ },
+} ) );
+
+jest.mock( '@wordpress/plugins', () => ( {
+ registerPlugin: ( name: string, settings: { render: () => JSX.Element } ) =>
+ mockRegisteredPlugins.set( name, settings ),
+} ) );
+
+jest.mock( '@woocommerce/email-editor', () => ( {
+ EmailActionsFill: ( { children }: { children: ReactNode } ) => children,
+ TemplateSelection: () => null,
+ recordEvent: mockEmailEditorRecordEvent,
+} ) );
+
+/**
+ * Internal dependencies
+ */
+import { modifySidebar } from '../sidebar_settings';
+
+const defaultWooCommerceData: WooCommerceData = {
+ recipient: 'merchant@example.com',
+ cc: null,
+ bcc: null,
+ preheader: 'Order update preview',
+ email_type: 'new_order',
+ subject: 'New order',
+ subject_full: null,
+ subject_partial: null,
+ default_subject: 'Default subject',
+ is_manual: false,
+ enabled: true,
+};
+
+const RichTextWithButton = ( {
+ attributeName,
+ attributeValue,
+ updateProperty,
+ label,
+}: {
+ attributeName: string;
+ attributeValue: string;
+ updateProperty: ( name: string, value: string | boolean ) => void;
+ label: string;
+} ) => (
+ <label htmlFor={ `rich-text-${ attributeName }` }>
+ { label }
+ <input
+ id={ `rich-text-${ attributeName }` }
+ value={ attributeValue }
+ onChange={ ( event ) =>
+ updateProperty( attributeName, event.target.value )
+ }
+ />
+ </label>
+);
+
+const renderSettings = ( {
+ recordEvent = jest.fn(),
+ debouncedRecordEvent = jest.fn(),
+}: {
+ recordEvent?: jest.Mock;
+ debouncedRecordEvent?: jest.Mock;
+} = {} ) => {
+ modifySidebar();
+
+ const SidebarSettings = mockSidebarFilters.at( -1 )!( RichTextWithButton, {
+ recordEvent,
+ debouncedRecordEvent,
+ } );
+ const EmailStatus = mockRegisteredPlugins.get(
+ 'woocommerce-email-editor-email-status'
+ )!.render;
+
+ return {
+ ...render(
+ <>
+ <EmailStatus />
+ <SidebarSettings />
+ </>
+ ),
+ recordEvent,
+ debouncedRecordEvent,
+ SidebarSettings,
+ EmailStatus,
+ };
+};
+
+describe( 'Email editor sidebar settings', () => {
+ beforeEach( () => {
+ mockRegisteredPlugins.clear();
+ mockSidebarFilters.length = 0;
+ mockEntitySubscribers.clear();
+ mockEditEntityRecord.mockClear();
+ mockEditEntityRecord.mockImplementation(
+ (
+ _kind: string,
+ _name: string,
+ _id: string,
+ { woocommerce_data }: { woocommerce_data: WooCommerceData }
+ ) => {
+ mockEntityState.woocommerceData = woocommerce_data;
+ mockEntityState.editedWooCommerceData = woocommerce_data;
+ mockEntitySubscribers.forEach( ( refresh ) => refresh() );
+ }
+ );
+ mockEmailEditorRecordEvent.mockClear();
+ mockEntityState.woocommerceData = { ...defaultWooCommerceData };
+ mockEntityState.editedWooCommerceData = {
+ ...defaultWooCommerceData,
+ unrelated_setting: 'preserved',
+ };
+ window.WooCommerceEmailEditor = {
+ current_post_id: '42',
+ current_post_type: 'woo_email',
+ email_types: [],
+ sender_settings: { from_name: '', from_address: '' },
+ };
+ } );
+
+ it( 'updates email status and tracks it', async () => {
+ const { rerender } = renderSettings();
+ const initialEditedWooCommerceData = {
+ ...mockEntityState.editedWooCommerceData,
+ };
+
+ expect(
+ screen.getByRole( 'button', { name: 'Change status: Active' } )
+ ).toBeEnabled();
+
+ await userEvent.click(
+ screen.getByRole( 'button', { name: 'Change status: Active' } )
+ );
+ await userEvent.click(
+ screen.getByRole( 'radio', { name: 'Inactive' } )
+ );
+
+ expect( mockEditEntityRecord ).toHaveBeenLastCalledWith(
+ 'postType',
+ 'woo_email',
+ '42',
+ {
+ woocommerce_data: {
+ ...initialEditedWooCommerceData,
+ unrelated_setting: 'preserved',
+ enabled: false,
+ },
+ }
+ );
+ expect( mockEmailEditorRecordEvent ).toHaveBeenLastCalledWith(
+ 'email_status_changed',
+ { status: 'inactive' }
+ );
+
+ // The edit goes through `editEntityRecord`, which is mocked, so the saved
+ // record has to be moved on by hand before the toggle can reflect it.
+ mockEntityState.woocommerceData = {
+ ...defaultWooCommerceData,
+ enabled: false,
+ };
+ rerender(
+ <>
+ { mockRegisteredPlugins
+ .get( 'woocommerce-email-editor-email-status' )!
+ .render() }
+ </>
+ );
+
+ expect(
+ screen.getByRole( 'button', { name: 'Change status: Inactive' } )
+ ).toBeEnabled();
+
+ mockEntityState.woocommerceData = {
+ ...defaultWooCommerceData,
+ is_manual: true,
+ };
+ rerender(
+ <>
+ { mockRegisteredPlugins
+ .get( 'woocommerce-email-editor-email-status' )!
+ .render() }
+ </>
+ );
+
+ expect(
+ screen.getByRole( 'button', {
+ name: 'Change status: Manually sent',
+ } )
+ ).toBeDisabled();
+ } );
+
+ it( 'passes subject and preheader values through the sidebar data owner', async () => {
+ renderSettings();
+ const initialEditedWooCommerceData = {
+ ...mockEntityState.editedWooCommerceData,
+ };
+
+ const subject = screen.getByRole( 'textbox', { name: 'Subject' } );
+ const preheader = screen.getByRole( 'textbox', {
+ name: 'Preview text',
+ } );
+ await userEvent.clear( subject );
+ await userEvent.type( subject, 'Your order is on its way' );
+ await userEvent.clear( preheader );
+ await userEvent.type( preheader, 'Track it from your account' );
+
+ expect( mockEditEntityRecord ).toHaveBeenNthCalledWith(
+ 1,
+ 'postType',
+ 'woo_email',
+ '42',
+ {
+ woocommerce_data: {
+ ...initialEditedWooCommerceData,
+ subject: '',
+ },
+ }
+ );
+ expect( mockEditEntityRecord ).toHaveBeenLastCalledWith(
+ 'postType',
+ 'woo_email',
+ '42',
+ {
+ woocommerce_data: {
+ ...initialEditedWooCommerceData,
+ subject: 'Your order is on its way',
+ preheader: 'Track it from your account',
+ },
+ }
+ );
+ } );
+
+ it( 'updates and clears CC and BCC recipients', async () => {
+ const recordEvent = jest.fn();
+ const debouncedRecordEvent = jest.fn();
+ mockEntityState.woocommerceData = {
+ ...defaultWooCommerceData,
+ recipient: null,
+ cc: null,
+ bcc: null,
+ };
+ mockEntityState.editedWooCommerceData = {
+ ...defaultWooCommerceData,
+ recipient: null,
+ cc: null,
+ bcc: null,
+ unrelated_setting: 'preserved',
+ };
+ const { rerender, SidebarSettings, EmailStatus } = renderSettings( {
+ recordEvent,
+ debouncedRecordEvent,
+ } );
+ const initialEditedWooCommerceData = {
+ ...mockEntityState.editedWooCommerceData,
+ };
+
+ expect(
+ screen.getByText( 'This email is sent to Customer.' )
+ ).toBeInTheDocument();
+
+ await userEvent.click(
+ screen.getByRole( 'checkbox', { name: 'Add CC' } )
+ );
+ expect( screen.getByTestId( 'email_cc' ) ).toBeInTheDocument();
+ expect( recordEvent ).toHaveBeenCalledWith( 'email_cc_toggle_clicked', {
+ isEnabled: true,
+ } );
+
+ await userEvent.click(
+ screen.getByRole( 'checkbox', { name: 'Add BCC' } )
+ );
+ expect( screen.getByTestId( 'email_bcc' ) ).toBeInTheDocument();
+ expect( recordEvent ).toHaveBeenCalledWith(
+ 'email_bcc_toggle_clicked',
+ { isEnabled: true }
+ );
+
+ await userEvent.type(
+ screen.getByTestId( 'email_cc' ),
+ 'copy@example.com'
+ );
+ await userEvent.type(
+ screen.getByTestId( 'email_bcc' ),
+ 'hidden@example.com'
+ );
+
+ expect( mockEditEntityRecord ).toHaveBeenLastCalledWith(
+ 'postType',
+ 'woo_email',
+ '42',
+ {
+ woocommerce_data: {
+ ...initialEditedWooCommerceData,
+ cc: 'copy@example.com',
+ bcc: 'hidden@example.com',
+ },
+ }
+ );
+ expect( debouncedRecordEvent ).toHaveBeenCalledWith(
+ 'email_cc_input_updated',
+ { value: 'copy@example.com' }
+ );
+ expect( debouncedRecordEvent ).toHaveBeenLastCalledWith(
+ 'email_bcc_input_updated',
+ { value: 'hidden@example.com' }
+ );
+
+ await userEvent.click(
+ screen.getByRole( 'checkbox', { name: 'Add CC' } )
+ );
+ await userEvent.click(
+ screen.getByRole( 'checkbox', { name: 'Add BCC' } )
+ );
+
+ expect( mockEditEntityRecord ).toHaveBeenLastCalledWith(
+ 'postType',
+ 'woo_email',
+ '42',
+ {
+ woocommerce_data: {
+ ...initialEditedWooCommerceData,
+ cc: '',
+ bcc: '',
+ },
+ }
+ );
+ expect( recordEvent ).toHaveBeenCalledWith( 'email_cc_toggle_clicked', {
+ isEnabled: false,
+ } );
+ expect( recordEvent ).toHaveBeenCalledWith(
+ 'email_bcc_toggle_clicked',
+ { isEnabled: false }
+ );
+
+ mockEntityState.woocommerceData = {
+ ...defaultWooCommerceData,
+ recipient: 'team@example.com',
+ };
+ rerender(
+ <>
+ <EmailStatus />
+ <SidebarSettings />
+ </>
+ );
+ expect(
+ screen.getByDisplayValue( 'team@example.com' )
+ ).toBeInTheDocument();
+ } );
+} );