Commit bc1c529f4b for woocommerce
commit bc1c529f4b8126c323a0a3bda6cf431c008863b8
Author: Amit Raj <77401999+amitraj2203@users.noreply.github.com>
Date: Mon Jan 19 21:17:51 2026 +0530
Migrate WooCommerce Accordion block usages to WordPress core Accordion (#62787)
* Add deprecation notice and conversion logic for WooCommerce Accordion block
* Add changelog file
* Refactor accordion block conversion logic to remove incompatible attributes
* Fix deprecated block edit logic to use blockEditorStore and handle missing attributes
* Move convertInnerBlocks function inside DeprecatedBlockEdit
* Refactor accordion block tests to verify deprecation notice and conversion to core accordion
* Add test for deprecated blocks
* Add test for WP version below 6.9
diff --git a/plugins/woocommerce/changelog/feat-62121-migrate-wc-accordion b/plugins/woocommerce/changelog/feat-62121-migrate-wc-accordion
new file mode 100644
index 0000000000..0b62a27efb
--- /dev/null
+++ b/plugins/woocommerce/changelog/feat-62121-migrate-wc-accordion
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Add migration UI and conversion logic to upgrade WooCommerce Accordion blocks to the WordPress core Accordion block in WP 6.9+.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/accordion/accordion-group/edit.js b/plugins/woocommerce/client/blocks/assets/js/blocks/accordion/accordion-group/edit.js
index 56e6b4f8b3..558621dc71 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/accordion/accordion-group/edit.js
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/accordion/accordion-group/edit.js
@@ -5,16 +5,164 @@ import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
+ Warning,
+ store as blockEditorStore,
} from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
-import { PanelBody, ToggleControl } from '@wordpress/components';
+import { PanelBody, ToggleControl, Button } from '@wordpress/components';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { createBlock } from '@wordpress/blocks';
+import { isWpVersion } from '@woocommerce/settings';
const ACCORDION_BLOCK_NAME = 'woocommerce/accordion-item';
const ACCORDION_BLOCK = {
name: ACCORDION_BLOCK_NAME,
};
-export default function Edit( { attributes: { autoclose }, setAttributes } ) {
+/**
+ * Deprecation notice component for the WooCommerce Accordion block.
+ *
+ * @param {Object} props - Component props.
+ * @param {string} props.clientId - The block client ID.
+ *
+ * @return {JSX.Element} The deprecation notice component.
+ */
+function DeprecatedBlockEdit( { clientId } ) {
+ const { replaceBlocks } = useDispatch( blockEditorStore );
+
+ const { currentBlockAttributes, innerBlocks } = useSelect(
+ ( select ) => {
+ const blockEditor = select( blockEditorStore );
+ return {
+ currentBlockAttributes:
+ blockEditor.getBlockAttributes( clientId ),
+ innerBlocks: blockEditor.getBlocks( clientId ),
+ };
+ },
+ [ clientId ]
+ );
+
+ /**
+ * Recursively convert WooCommerce accordion blocks to WordPress core accordion blocks.
+ *
+ * @param {Array<*>} blocks - The inner blocks to convert.
+ *
+ * @return {Array<*>} The converted blocks.
+ */
+ const convertInnerBlocks = ( blocks ) => {
+ // Define attributes to REMOVE for each block type.
+ const attributesToRemove = {
+ 'woocommerce/accordion-header': [
+ 'icon',
+ 'textAlignment',
+ 'levelOptions',
+ ],
+ 'woocommerce/accordion-panel': [
+ 'allowedBlocks',
+ 'isSelected',
+ 'openByDefault',
+ ],
+ };
+
+ return blocks.map( ( block ) => {
+ let newBlockName = block.name;
+ const newAttributes = { ...block.attributes };
+
+ // Map WooCommerce block names to WordPress core block names.
+ if ( block.name === 'woocommerce/accordion-item' ) {
+ newBlockName = 'core/accordion-item';
+ // No attribute changes needed.
+ } else if ( block.name === 'woocommerce/accordion-header' ) {
+ newBlockName = 'core/accordion-heading';
+
+ // Convert icon to showIcon.
+ if ( block.attributes.icon !== undefined ) {
+ newAttributes.showIcon = block.attributes.icon !== false;
+ }
+
+ // Remove incompatible attributes.
+ const headerAttrs =
+ attributesToRemove[ 'woocommerce/accordion-header' ];
+ headerAttrs.forEach( ( attr ) => {
+ delete newAttributes[ attr ];
+ } );
+ } else if ( block.name === 'woocommerce/accordion-panel' ) {
+ newBlockName = 'core/accordion-panel';
+
+ // Remove incompatible attributes.
+ const panelAttrs =
+ attributesToRemove[ 'woocommerce/accordion-panel' ];
+ panelAttrs.forEach( ( attr ) => {
+ delete newAttributes[ attr ];
+ } );
+ }
+
+ // Recursively convert inner blocks.
+ const convertedInnerBlocks = block.innerBlocks?.length
+ ? convertInnerBlocks( block.innerBlocks )
+ : [];
+
+ return createBlock(
+ newBlockName,
+ newAttributes,
+ convertedInnerBlocks
+ );
+ } );
+ };
+
+ const updateBlock = () => {
+ if ( ! currentBlockAttributes ) {
+ return;
+ }
+
+ const convertedInnerBlocks = convertInnerBlocks( innerBlocks );
+
+ // Filter accordion-group attributes - remove 'allowedBlocks'.
+ const { allowedBlocks, ...filteredGroupAttributes } =
+ currentBlockAttributes;
+
+ replaceBlocks(
+ clientId,
+ createBlock(
+ 'core/accordion',
+ filteredGroupAttributes,
+ convertedInnerBlocks
+ )
+ );
+ };
+
+ const actions = [
+ <Button key="update" onClick={ updateBlock } variant="primary">
+ { __( 'Upgrade Block', 'woocommerce' ) }
+ </Button>,
+ ];
+
+ return (
+ <Warning actions={ actions } className="wc-block-components-actions">
+ { __(
+ 'This version of the Accordion block is outdated. Upgrade to continue using.',
+ 'woocommerce'
+ ) }
+ </Warning>
+ );
+}
+
+/**
+ * Edit component for the WooCommerce Accordion Group block.
+ *
+ * @param {Object} props - Component props.
+ * @param {Object} props.attributes - Block attributes.
+ * @param {boolean} props.attributes.autoclose - Whether to auto-close other accordions.
+ * @param {Function} props.setAttributes - Function to set block attributes.
+ * @param {string} props.clientId - The block client ID.
+ *
+ * @return {JSX.Element} The edit component.
+ */
+export default function Edit( {
+ attributes: { autoclose },
+ setAttributes,
+ clientId,
+} ) {
const blockProps = useBlockProps();
const innerBlocksProps = useInnerBlocksProps( blockProps, {
@@ -23,6 +171,12 @@ export default function Edit( { attributes: { autoclose }, setAttributes } ) {
directInsert: true,
} );
+ // Show deprecation notice for WordPress 6.9+.
+ if ( isWpVersion( '6.9', '>=' ) ) {
+ return <DeprecatedBlockEdit clientId={ clientId } />;
+ }
+
+ // Original edit UI for WordPress 6.8 and below.
return (
<>
<InspectorControls key="setting">
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/accordion/accordion.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/accordion/accordion.block_theme.spec.ts
index c1bb2906a8..d9866e3906 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/accordion/accordion.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/accordion/accordion.block_theme.spec.ts
@@ -1,214 +1,206 @@
/**
* External dependencies
*/
-import { expect, test as base } from '@woocommerce/e2e-utils';
-
-/**
- * Internal dependencies
- */
-import { AccordionPage } from './accordion.page';
+import { expect, test } from '@woocommerce/e2e-utils';
const blockData = {
slug: 'woocommerce/accordion-group',
};
-const test = base.extend< { pageObject: AccordionPage } >( {
- pageObject: async (
- { page, editor, frontendUtils, requestUtils },
- use
- ) => {
- const pageObject = new AccordionPage( {
- page,
- editor,
- frontendUtils,
- requestUtils,
- } );
- await use( pageObject );
+const accordionInnerBlocks = [
+ {
+ name: 'woocommerce/accordion-item',
+ innerBlocks: [
+ {
+ name: 'woocommerce/accordion-header',
+ attributes: {
+ title: 'First Accordion Header',
+ },
+ },
+ {
+ name: 'woocommerce/accordion-panel',
+ innerBlocks: [
+ {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'First accordion content',
+ },
+ },
+ ],
+ },
+ ],
},
-} );
+ {
+ name: 'woocommerce/accordion-item',
+ innerBlocks: [
+ {
+ name: 'woocommerce/accordion-header',
+ attributes: {
+ title: 'Second Accordion Header',
+ },
+ },
+ {
+ name: 'woocommerce/accordion-panel',
+ innerBlocks: [
+ {
+ name: 'core/paragraph',
+ attributes: {
+ content: 'Second accordion content',
+ },
+ },
+ ],
+ },
+ ],
+ },
+];
-test.describe( `${ blockData.slug } Block`, () => {
+test.describe( `${ blockData.slug } Block - Deprecation`, () => {
test.beforeEach( async ( { admin } ) => {
await admin.createNewPost();
} );
- test( 'can be inserted in Post Editor and it is visible on the frontend when feature flag is enabled', async ( {
+ test( 'shows deprecation notice and converts all inner blocks to core accordion on upgrade (WP 6.9+)', async ( {
editor,
frontendUtils,
+ page,
+ wpCoreVersion,
} ) => {
- await editor.insertBlock( { name: blockData.slug } );
- const blockLocator = await editor.getBlockByName( blockData.slug );
- await expect(
- blockLocator.getByLabel( 'Accordion title' )
- ).toHaveCount( 2 );
- await editor.publishAndVisitPost();
- const blockLocatorFrontend = await frontendUtils.getBlockByName(
- blockData.slug
- );
- await expect( blockLocatorFrontend.getByRole( 'button' ) ).toHaveCount(
- 2
+ // eslint-disable-next-line playwright/no-skipped-test
+ test.skip(
+ wpCoreVersion < 6.9,
+ 'This test requires WordPress 6.9 or later'
);
- } );
- test( 'can add title and panel content', async ( {
- editor,
- frontendUtils,
- pageObject,
- } ) => {
- await pageObject.insertAccordionGroup( [
- {
- title: 'Accordion title 1',
- content: 'Test paragraph content for first panel',
- },
- {
- title: 'Accordion title 2',
- content: 'Test paragraph content for second panel',
- },
- ] );
- await editor.publishAndVisitPost();
- const blockLocatorFrontend = await frontendUtils.getBlockByName(
- blockData.slug
- );
+ // Insert WooCommerce accordion block with inner blocks and content.
+ await editor.insertBlock( {
+ name: blockData.slug,
+ innerBlocks: accordionInnerBlocks,
+ } );
+
+ // Wait for the deprecation notice to appear.
await expect(
- blockLocatorFrontend.getByText( 'Accordion title 1' )
+ editor.canvas.getByText(
+ 'This version of the Accordion block is outdated. Upgrade to continue using.'
+ )
).toBeVisible();
+
+ // Verify legacy block still renders as expected before upgrade. Save as draft and preview it.
+ await editor.saveDraft();
+ const postId = await page.evaluate( () => {
+ return window.wp.data.select( 'core/editor' ).getCurrentPostId();
+ } );
+ await page.goto( `/?p=${ postId }&preview=true` );
+ const legacyAccordionFrontend = frontendUtils.page.locator(
+ '.wp-block-woocommerce-accordion-group'
+ );
+ await expect( legacyAccordionFrontend ).toBeVisible();
+
+ // Verify legacy accordion has accordion items with buttons.
+ const legacyAccordionButtons =
+ legacyAccordionFrontend.getByRole( 'button' );
+ const legacyItemCount = await legacyAccordionButtons.count();
+ expect( legacyItemCount ).toBe( 2 );
+
+ // Verify the content is visible.
await expect(
- blockLocatorFrontend.getByText( 'Accordion title 2' )
+ legacyAccordionFrontend.getByText( 'First Accordion Header' )
).toBeVisible();
await expect(
- blockLocatorFrontend.getByText(
- 'Test paragraph content for first panel'
- )
- ).toBeAttached();
- await expect(
- blockLocatorFrontend.getByText(
- 'Test paragraph content for second panel'
- )
- ).toBeAttached();
- } );
+ legacyAccordionFrontend.getByText( 'Second Accordion Header' )
+ ).toBeVisible();
- test( 'can toggle panel visibility', async ( {
- editor,
- frontendUtils,
- pageObject,
- } ) => {
- await pageObject.insertAccordionGroup( [
- {
- title: 'Accordion title',
- content: 'Test paragraph content for first panel',
- },
- {
- title: 'Accordion title 2',
- content: 'Test paragraph content for second panel',
- },
- ] );
- await editor.publishAndVisitPost();
- const blockLocatorFrontend = await frontendUtils.getBlockByName(
- blockData.slug
+ // Go back to editor.
+ await page.goBack();
+
+ // Verify upgrade button is displayed.
+ const upgradeButton = editor.canvas.getByRole( 'button', {
+ name: 'Upgrade Block',
+ } );
+ await expect( upgradeButton ).toBeVisible();
+
+ // Click the upgrade button.
+ await upgradeButton.click();
+
+ // Verify the block was converted to core/accordion.
+ const coreAccordion = await editor.getBlockByName( 'core/accordion' );
+ await expect( coreAccordion ).toBeVisible();
+
+ // Verify the WooCommerce accordion block is no longer present.
+ const wooAccordion = editor.canvas.locator(
+ '[data-type="woocommerce/accordion-group"]'
);
- await expect(
- blockLocatorFrontend.getByText(
- 'Test paragraph content for first panel'
- )
- ).not.toBeInViewport();
- await blockLocatorFrontend.getByRole( 'button' ).first().click();
- await expect(
- blockLocatorFrontend.getByText(
- 'Test paragraph content for first panel'
- )
- ).toBeInViewport();
- } );
+ await expect( wooAccordion ).toHaveCount( 0 );
- test( 'can set panel to open by default and should close when clicked', async ( {
- editor,
- frontendUtils,
- pageObject,
- } ) => {
- await pageObject.insertAccordionGroup( [
- {
- title: 'Accordion title',
- content: 'Test paragraph content 1',
- },
- {
- title: 'Accordion title 2',
- content: 'Test paragraph content 2',
- },
- ] );
- const accordionPanel = await editor.getBlockByName(
- 'woocommerce/accordion-item'
+ // Verify all inner blocks are converted correctly.
+ // Check that accordion items exist (woocommerce/accordion-item → core/accordion-item).
+ const coreAccordionItems = editor.canvas.locator(
+ '[data-type="core/accordion-item"]'
+ );
+ const itemCount = await coreAccordionItems.count();
+ expect( itemCount ).toBeGreaterThan( 0 );
+
+ // Check accordion headings (woocommerce/accordion-header → core/accordion-heading).
+ const coreAccordionHeadings = editor.canvas.locator(
+ '[data-type="core/accordion-heading"]'
+ );
+ await expect( coreAccordionHeadings ).toHaveCount( itemCount );
+
+ // Check accordion panels (woocommerce/accordion-panel → core/accordion-panel).
+ const coreAccordionPanels = editor.canvas.locator(
+ '[data-type="core/accordion-panel"]'
);
- await editor.selectBlocks( accordionPanel.first() );
+ await expect( coreAccordionPanels ).toHaveCount( itemCount );
- // Open block settings sidebar and check "Open by default" option
- await editor.openDocumentSettingsSidebar();
- await editor.page
- .getByLabel( 'Settings' )
- .getByRole( 'checkbox', { name: 'Open by default' } )
- .check();
+ // Verify no WooCommerce accordion inner blocks remain.
+ const wooAccordionItems = editor.canvas.locator(
+ '[data-type="woocommerce/accordion-item"]'
+ );
+ await expect( wooAccordionItems ).toHaveCount( 0 );
+
+ const wooAccordionHeaders = editor.canvas.locator(
+ '[data-type="woocommerce/accordion-header"]'
+ );
+ await expect( wooAccordionHeaders ).toHaveCount( 0 );
- // Publish and visit post and check that the panel is hidden.
+ const wooAccordionPanels = editor.canvas.locator(
+ '[data-type="woocommerce/accordion-panel"]'
+ );
+ await expect( wooAccordionPanels ).toHaveCount( 0 );
+
+ // Publish the post.
await editor.publishAndVisitPost();
- const blockLocatorFrontend = await frontendUtils.getBlockByName(
- blockData.slug
+
+ // Verify the core accordion block is visible on the frontend.
+ const accordionFrontend = frontendUtils.page.locator(
+ '.wp-block-accordion'
);
- await expect(
- blockLocatorFrontend.getByText( 'Test paragraph content 1' )
- ).toBeInViewport();
- await blockLocatorFrontend.getByRole( 'button' ).first().click();
- await expect(
- blockLocatorFrontend.getByText( 'Test paragraph content 1' )
- ).not.toBeInViewport();
+ await expect( accordionFrontend ).toBeVisible();
+
+ // Verify accordion buttons are present.
+ const accordionButtons = accordionFrontend.getByRole( 'button' );
+ await expect( accordionButtons ).toHaveCount( itemCount );
} );
- test( 'can set to auto close when another panel is clicked', async ( {
+ test( 'does not show deprecation notice in WordPress 6.8 or earlier', async ( {
editor,
- frontendUtils,
- pageObject,
+ wpCoreVersion,
} ) => {
- await pageObject.insertAccordionGroup( [
- {
- title: 'Accordion title 1',
- content: 'Test paragraph content 1',
- },
- {
- title: 'Accordion title 2',
- content: 'Test paragraph content 2',
- },
- {
- title: 'Accordion title 3',
- content: 'Test paragraph content 3',
- },
- ] );
- const accordionPanel = await editor.getBlockByName(
- 'woocommerce/accordion-group'
+ // eslint-disable-next-line playwright/no-skipped-test
+ test.skip(
+ wpCoreVersion >= 6.9,
+ 'This test is only for WordPress 6.8 or earlier'
);
- await editor.selectBlocks( accordionPanel.first() );
-
- // Open block settings sidebar and check "Open by default" option
- await editor.openDocumentSettingsSidebar();
- await editor.page
- .getByLabel( 'Settings' )
- .getByRole( 'checkbox', {
- name: 'Auto-close',
- } )
- .check();
-
- // Publish and visit post and check that the panel is hidden.
- await editor.publishAndVisitPost();
- const blockLocatorFrontend = await frontendUtils.getBlockByName(
- blockData.slug
+
+ // Insert WooCommerce accordion block with inner blocks and content.
+ await editor.insertBlock( {
+ name: blockData.slug,
+ innerBlocks: accordionInnerBlocks,
+ } );
+
+ // Verify deprecation notice is NOT shown.
+ const deprecationNotice = editor.canvas.getByText(
+ 'This version of the Accordion block is outdated. Upgrade to continue using.'
);
- await blockLocatorFrontend.getByRole( 'button' ).first().click();
- await expect(
- blockLocatorFrontend.getByText( 'Test paragraph content 1' )
- ).toBeInViewport();
- await blockLocatorFrontend.getByRole( 'button' ).nth( 1 ).click();
- await expect(
- blockLocatorFrontend.getByText( 'Test paragraph content 1' )
- ).not.toBeInViewport();
- await blockLocatorFrontend.getByRole( 'button' ).nth( 2 ).click();
- await expect(
- blockLocatorFrontend.getByText( 'Test paragraph content 2' )
- ).not.toBeInViewport();
+ await expect( deprecationNotice ).toBeHidden();
} );
} );