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();
+	} );
+} );