Commit fbb3c8ddc10 for woocommerce
commit fbb3c8ddc10c37e431d6d4e6d39516af8779d966
Author: Rostislav Wolný <1082140+costasovo@users.noreply.github.com>
Date: Wed Sep 2 12:53:57 2026 +0200
[Email Editor] Restore Personalization Tags on the Button block (#68217)
* Restore Personalization Tags toolbar button on the Button block
The shortcode format was registered with interactive: true, and blocks
whose RichText uses withoutInteractiveFormatting (the Button block text)
drop interactive formats entirely, so the format's toolbar button never
rendered there. That made the Personalization Tags modal and its
"Set as URL" action unreachable for buttons. The flag was introduced as
part of a type cleanup, not intentionally; the format renders a plain
span placeholder, which is not interactive content.
diff --git a/packages/js/email-editor/changelog/fix-button-block-personalization-tags-toolbar b/packages/js/email-editor/changelog/fix-button-block-personalization-tags-toolbar
new file mode 100644
index 00000000000..3cdd541384e
--- /dev/null
+++ b/packages/js/email-editor/changelog/fix-button-block-personalization-tags-toolbar
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Restore the Personalization Tags toolbar button on the Button block by registering the shortcode format as non-interactive
diff --git a/packages/js/email-editor/src/blocks/core/rich-text.tsx b/packages/js/email-editor/src/blocks/core/rich-text.tsx
index e5799ec91e5..9bf3f9d68c3 100644
--- a/packages/js/email-editor/src/blocks/core/rich-text.tsx
+++ b/packages/js/email-editor/src/blocks/core/rich-text.tsx
@@ -214,7 +214,10 @@ function extendRichTextFormats() {
className: 'woocommerce-email-editor-personalization-tags',
tagName: 'span',
attributes: {},
- interactive: true,
+ // Must stay non-interactive: blocks using `withoutInteractiveFormatting`
+ // (e.g. the Button block) drop interactive formats entirely, which would
+ // hide the Personalization Tags toolbar button there.
+ interactive: false,
edit: PersonalizationTagsButton,
} );
diff --git a/packages/js/email-editor/src/blocks/core/test/rich-text.spec.tsx b/packages/js/email-editor/src/blocks/core/test/rich-text.spec.tsx
new file mode 100644
index 00000000000..bab03b3056e
--- /dev/null
+++ b/packages/js/email-editor/src/blocks/core/test/rich-text.spec.tsx
@@ -0,0 +1,82 @@
+import '../../../components/test/__mocks__/setup-shared-mocks';
+
+/**
+ * External dependencies
+ */
+import { registerFormatType } from '@wordpress/rich-text';
+
+/**
+ * Internal dependencies
+ */
+import { extendRichTextFormats } from '../rich-text';
+
+jest.mock( '@wordpress/rich-text', () => ( {
+ registerFormatType: jest.fn(),
+ unregisterFormatType: jest.fn(),
+} ) );
+
+jest.mock( '@wordpress/components', () => ( {
+ ToolbarButton: () => null,
+ ToolbarGroup: () => null,
+} ) );
+
+jest.mock( '../../../store', () => ( {
+ storeName: 'email-editor',
+} ) );
+
+jest.mock( '../../../events', () => ( {
+ recordEvent: jest.fn(),
+} ) );
+
+jest.mock(
+ '../../../components/personalization-tags/personalization-tags-modal',
+ () => ( {
+ PersonalizationTagsModal: () => null,
+ } )
+);
+
+jest.mock(
+ '../../../components/personalization-tags/personalization-tags-popover',
+ () => ( {
+ PersonalizationTagsPopover: () => null,
+ } )
+);
+
+jest.mock(
+ '../../../components/personalization-tags/personalization-tags-link-popover',
+ () => ( {
+ PersonalizationTagsLinkPopover: () => null,
+ } )
+);
+
+const registerFormatTypeMock = registerFormatType as jest.Mock;
+
+describe( 'extendRichTextFormats', () => {
+ beforeEach( () => {
+ registerFormatTypeMock.mockClear();
+ extendRichTextFormats();
+ } );
+
+ it( 'registers the personalization tags format as non-interactive', () => {
+ // Blocks using `withoutInteractiveFormatting` (e.g. the Button block)
+ // drop interactive formats entirely, which would hide the
+ // Personalization Tags toolbar button there.
+ expect( registerFormatTypeMock ).toHaveBeenCalledWith(
+ 'woocommerce-email-editor/shortcode',
+ expect.objectContaining( {
+ interactive: false,
+ edit: expect.any( Function ),
+ } )
+ );
+ } );
+
+ it( 'registers the link format as interactive', () => {
+ // The link format renders a real `a` element and is applied
+ // programmatically via `applyFormat`, so unlike the shortcode format it
+ // has no toolbar `edit` component that the interactive flag could hide.
+ expect( registerFormatTypeMock ).toHaveBeenCalledWith(
+ 'woocommerce-email-editor/link-shortcode',
+ expect.objectContaining( { interactive: true, edit: null } )
+ );
+ } );
+} );
diff --git a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts
index bbb37251ced..3a7920ee7cf 100644
--- a/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/email-editor/email-editor-loads.spec.ts
@@ -3,7 +3,10 @@
*/
import { expect, test } from '../../fixtures/fixtures';
import { ADMIN_STATE_PATH } from '../../playwright.config';
-import { disableEmailEditor } from './helpers/enable-email-editor-feature';
+import {
+ disableEmailEditor,
+ enableEmailEditor,
+} from './helpers/enable-email-editor-feature';
import { accessTheEmailEditor } from '../../utils/email';
test.describe( 'WooCommerce Email Editor Core', () => {
@@ -76,7 +79,7 @@ test.describe( 'WooCommerce Email Editor Core', () => {
// eslint-disable-next-line playwright/no-wait-for-selector -- wait for the tab to be loaded.
await newPage.waitForSelector( '.wp-block-heading' );
await page.close(); // close the original tab.
- await expect( newPage.url() ).toContain( 'preview=true' );
+ expect( newPage.url() ).toContain( 'preview=true' );
await expect( newPage.locator( 'body' ) ).toContainText(
'New order: #12345'
);
@@ -146,4 +149,70 @@ test.describe( 'WooCommerce Email Editor Core', () => {
'Hello world from Woo plugin'
);
} );
+
+ test( 'Can use personalization tags in the Button block', async ( {
+ page,
+ baseURL,
+ } ) => {
+ // Enable via API so the test does not depend on the enable test having
+ // run in the same worker (a worker restart runs afterAll, which
+ // disables the feature).
+ await enableEmailEditor( baseURL );
+ await accessTheEmailEditor( page, 'New order' );
+ const canvas = page
+ .locator( 'iframe[name="editor-canvas"]' )
+ .contentFrame();
+
+ // Set the insertion point inside the email content. The heading is not
+ // touched by the other tests in this suite, unlike the first paragraph.
+ await canvas.getByLabel( 'Block: Heading' ).click();
+
+ // Insert a Button block.
+ await page.getByRole( 'button', { name: 'Block Inserter' } ).click();
+ await page
+ .getByRole( 'searchbox', { name: 'Search' } )
+ .fill( 'Buttons' );
+ await page
+ .getByRole( 'option', { name: 'Buttons', exact: true } )
+ .click();
+
+ // Focus the button text. The Personalization Tags toolbar button must be
+ // available there — the button text field uses
+ // `withoutInteractiveFormatting`, which hides the button when the
+ // personalization tags format is registered as interactive.
+ await canvas
+ .getByRole( 'document', { name: 'Block: Button', exact: true } )
+ .click();
+ const personalizationTagsButton = page
+ .getByRole( 'toolbar', { name: 'Block tools' } )
+ .getByRole( 'button', { name: 'Personalization Tags' } );
+ await expect( personalizationTagsButton ).toBeVisible();
+
+ // Set a URL personalization tag as the button link.
+ await personalizationTagsButton.click();
+ await expect(
+ page.getByRole( 'heading', { name: 'Personalization Tags' } )
+ ).toBeVisible();
+ await page
+ .locator(
+ '.woocommerce-personalization-tags-modal-category-group-item'
+ )
+ .filter( { hasText: 'Payment URL' } )
+ .getByRole( 'button', { name: 'Set as URL' } )
+ .click();
+ await expect(
+ page.getByRole( 'heading', { name: 'Personalization Tags' } )
+ ).toBeHidden();
+
+ await expect
+ .poll( () =>
+ page.evaluate(
+ () =>
+ window.wp.data
+ .select( 'core/block-editor' )
+ .getSelectedBlock()?.attributes?.url
+ )
+ )
+ .toBe( '[woocommerce/order-payment-url]' );
+ } );
} );