Fix Classic Template block registration on WP 6.6 (#48730)

* Fix Classic Template block registration on WP 6.6

* Update Compatibility Layer docs

* Add changelog file

* Remove unused function

* Simplify logic
This commit is contained in:
Albert Juhé Lluveras 2024-06-26 12:20:22 +02:00 committed by GitHub
parent d9e837a8ca
commit 0205513556
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 291 additions and 91 deletions

View File

@ -7,7 +7,6 @@ import {
getBlockType,
registerBlockType,
unregisterBlockType,
parse,
} from '@wordpress/blocks';
import type { BlockEditProps } from '@wordpress/blocks';
import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings';
@ -19,19 +18,11 @@ import {
import { Button, Placeholder, Popover } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { box, Icon } from '@wordpress/icons';
import {
useDispatch,
subscribe,
useSelect,
select,
dispatch,
} from '@wordpress/data';
import { useDispatch, subscribe, useSelect, select } from '@wordpress/data';
import { useEffect, useState } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { useEntityRecord } from '@wordpress/core-data';
import { debounce } from '@woocommerce/base-utils';
import { woo } from '@woocommerce/icons';
import { isNumber } from '@woocommerce/types';
/**
* Internal dependencies
@ -296,7 +287,7 @@ const registerClassicTemplateBlock = ( {
template,
inserter,
}: {
template?: string;
template?: string | null;
inserter: boolean;
} ) => {
/**
@ -370,101 +361,83 @@ const registerClassicTemplateBlock = ( {
} );
};
/**
* Attempts to recover the Classic Template block if it fails to render on the Single Product template
* due to the user resetting customizations without refreshing the page.
*
* When the Classic Template block fails to render, it is replaced by the 'core/missing' block, which
* displays an error message stating that the WooCommerce Classic template block is unsupported.
*
* This function replaces the 'core/missing' block with the original Classic Template block that failed
* to render, allowing the block to be displayed correctly.
*
* @see {@link https://github.com/woocommerce/woocommerce-blocks/issues/9637|Issue: Block error is displayed on clearing customizations for Woo Templates}
*
*/
const tryToRecoverClassicTemplateBlockWhenItFailsToRender = debounce( () => {
const blocks = select( 'core/block-editor' ).getBlocks();
const blocksIncludingInnerBlocks = blocks.flatMap( ( block ) => [
block,
...block.innerBlocks,
] );
const classicTemplateThatFailedToRender = blocksIncludingInnerBlocks.find(
( block ) =>
block.name === 'core/missing' &&
block.attributes.originalName === BLOCK_SLUG
);
if ( classicTemplateThatFailedToRender ) {
const blockToReplaceClassicTemplateBlockThatFailedToRender = parse(
classicTemplateThatFailedToRender.attributes.originalContent
);
if ( blockToReplaceClassicTemplateBlockThatFailedToRender ) {
dispatch( 'core/block-editor' ).replaceBlock(
classicTemplateThatFailedToRender.clientId,
blockToReplaceClassicTemplateBlockThatFailedToRender
);
}
}
}, 100 );
// @todo Refactor when there will be possible to show a block according on a template/post with a Gutenberg API. https://github.com/WordPress/gutenberg/pull/41718
let currentTemplateId: string | undefined;
let previousEditedTemplate: string | number | null = null;
let isBlockRegistered = false;
let isBlockInInserter = false;
const handleRegisterClassicTemplateBlock = ( {
template,
inserter,
}: {
template: string | null;
inserter: boolean;
} ) => {
if ( isBlockRegistered ) {
unregisterBlockType( BLOCK_SLUG );
}
isBlockInInserter = inserter;
isBlockRegistered = true;
registerClassicTemplateBlock( {
template,
inserter,
} );
};
subscribe( () => {
const previousTemplateId = currentTemplateId;
const store = select( 'core/edit-site' );
// With GB 16.3.0 the return type can be a number: https://github.com/WordPress/gutenberg/issues/53230
const editedPostId = store?.getEditedPostId() as
| string
| number
| undefined;
const editorStore = select( 'core/editor' );
// We use blockCount to know if we are editing a template or in the navigation.
const blockCount = editorStore?.getBlockCount() as number;
const templateSlug = editorStore?.getEditedPostSlug() as string | null;
const editedTemplate = blockCount && blockCount > 0 ? templateSlug : null;
currentTemplateId = isNumber( editedPostId ) ? undefined : editedPostId;
// Skip if we are in the same template, except if the block hasn't been registered yet.
if ( isBlockRegistered && previousEditedTemplate === editedTemplate ) {
return;
}
previousEditedTemplate = editedTemplate;
const parsedTemplate = currentTemplateId?.split( '//' )[ 1 ];
if ( parsedTemplate === null || parsedTemplate === undefined ) {
// Handle the case when we are not editing a template (ie: in the navigation screen).
if ( ! editedTemplate ) {
if ( ! isBlockRegistered ) {
handleRegisterClassicTemplateBlock( {
template: editedTemplate,
inserter: false,
} );
}
return;
}
const block = getBlockType( BLOCK_SLUG );
const isBlockRegistered = Boolean( block );
const templateSupportsClassicTemplateBlock =
hasTemplateSupportForClassicTemplateBlock( editedTemplate, TEMPLATES );
if (
isBlockRegistered &&
hasTemplateSupportForClassicTemplateBlock( parsedTemplate, TEMPLATES )
) {
tryToRecoverClassicTemplateBlockWhenItFailsToRender();
}
if ( previousTemplateId === currentTemplateId ) {
// Handle the case when we are editing a template that doesn't support the Classic Template block (ie: Blog Home).
if ( ! templateSupportsClassicTemplateBlock && isBlockInInserter ) {
handleRegisterClassicTemplateBlock( {
template: editedTemplate,
inserter: false,
} );
return;
}
if (
isBlockRegistered &&
( ! hasTemplateSupportForClassicTemplateBlock(
parsedTemplate,
TEMPLATES
) ||
isClassicTemplateBlockRegisteredWithAnotherTitle(
block,
parsedTemplate
) )
) {
unregisterBlockType( BLOCK_SLUG );
currentTemplateId = undefined;
// Handle the case when we are editing a template that does support the Classic Template block (ie: Product Catalog).
if ( templateSupportsClassicTemplateBlock && ! isBlockInInserter ) {
handleRegisterClassicTemplateBlock( {
template: editedTemplate,
inserter: true,
} );
return;
}
// Handle the case when we are editing a template that does support the Classic Template block but it's currently registered with another title (ie: navigating from the Product Catalog template to the Product Search Results template).
if (
! isBlockRegistered &&
hasTemplateSupportForClassicTemplateBlock( parsedTemplate, TEMPLATES )
templateSupportsClassicTemplateBlock &&
isClassicTemplateBlockRegisteredWithAnotherTitle(
getBlockType( BLOCK_SLUG ),
editedTemplate
)
) {
registerClassicTemplateBlock( {
template: parsedTemplate,
handleRegisterClassicTemplateBlock( {
template: editedTemplate,
inserter: true,
} );
}

View File

@ -6,8 +6,9 @@ The Compatibility Layer is disabled when either of classic template blocks are a
- `Product (Classic)`,
- `Product Attribute (Classic)`,
- `Product Taxonomy (Classic)`,
- `Product Category (Classic)`,
- `Product Tag (Classic)`,
- `Product's Custom Taxonomy (Classic)`,
- `Product Search Results (Classic)`,
- `Product Grid (Classic)`.

View File

@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { test, expect, wpCLI, BlockData } from '@woocommerce/e2e-utils';
import { test, expect, wpCLI, BlockData, Editor } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
@ -11,6 +11,17 @@ const blockData: Partial< BlockData > = {
name: 'woocommerce/legacy-template',
};
const classicTemplateBlockNames = [
'WooCommerce Classic Template',
'Product (Classic)',
'Product Attribute (Classic)',
'Product Category (Classic)',
'Product Tag (Classic)',
"Product's Custom Taxonomy (Classic)",
'Product Search Results (Classic)',
'Product Grid (Classic)',
];
const templates = [
{
title: 'Single Product',
@ -46,6 +57,47 @@ const templates = [
},
];
const getClassicTemplateBlocksInInserter = async ( {
editor,
}: {
editor: Editor;
} ) => {
await editor.openGlobalBlockInserter();
await editor.page
.getByLabel( 'Search for blocks and patterns' )
.fill( 'classic' );
// Wait for blocks search to have finished.
await expect(
editor.page.getByRole( 'heading', {
name: 'Available to install',
exact: true,
} )
).toBeVisible();
const inserterBlocks = editor.page.getByRole( 'listbox', {
name: 'Blocks',
exact: true,
} );
const options = inserterBlocks.locator( 'role=option' );
// Filter out blocks that don't match one of the possible Classic Template block names (case-insensitive).
const classicTemplateBlocks = await options.evaluateAll(
( elements, blockNames ) => {
const blockOptions = elements.filter( ( element ) => {
return blockNames.some(
( name ) => element.textContent === name
);
} );
return blockOptions.map( ( element ) => element.textContent );
},
classicTemplateBlockNames
);
return classicTemplateBlocks;
};
test.describe( `${ blockData.name } Block `, () => {
test.beforeEach( async () => {
await wpCLI(
@ -53,6 +105,176 @@ test.describe( `${ blockData.name } Block `, () => {
);
} );
test( `is registered/unregistered when navigating from a non-WC template to a WC template and back`, async ( {
admin,
editor,
} ) => {
await admin.visitSiteEditor( {
postId: `twentytwentyfour//home`,
postType: 'wp_template',
canvas: 'edit',
} );
let classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks ).toHaveLength( 0 );
await editor.page.getByLabel( 'Open Navigation' ).click();
await editor.page
.getByLabel( 'Product Catalog', { exact: true } )
.click();
classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks ).toHaveLength( 1 );
await editor.page.getByLabel( 'Open Navigation' ).click();
await editor.page.getByLabel( 'Blog Home', { exact: true } ).click();
classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks ).toHaveLength( 0 );
} );
test( `is registered/unregistered when navigating from a WC template to a non-WC template and back`, async ( {
admin,
editor,
} ) => {
await admin.visitSiteEditor( {
postId: `woocommerce/woocommerce//archive-product`,
postType: 'wp_template',
canvas: 'edit',
} );
let classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks ).toHaveLength( 1 );
await editor.page.getByLabel( 'Open Navigation' ).click();
await editor.page.getByLabel( 'Blog Home', { exact: true } ).click();
classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks ).toHaveLength( 0 );
await editor.page.getByLabel( 'Open Navigation' ).click();
await editor.page
.getByLabel( 'Product Catalog', { exact: true } )
.click();
classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks ).toHaveLength( 1 );
} );
test( `updates block title when navigating between WC templates`, async ( {
admin,
editor,
} ) => {
await admin.visitSiteEditor( {
postId: `woocommerce/woocommerce//archive-product`,
postType: 'wp_template',
canvas: 'edit',
} );
let classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks[ 0 ] ).toBe( 'Product Grid (Classic)' );
await editor.page.getByLabel( 'Open Navigation' ).click();
await editor.page
.getByLabel( 'Product Search Results', { exact: true } )
.click();
classicTemplateBlocks = await getClassicTemplateBlocksInInserter( {
editor,
} );
expect( classicTemplateBlocks[ 0 ] ).toBe(
'Product Search Results (Classic)'
);
} );
test( `is not available when editing template parts`, async ( {
admin,
editor,
} ) => {
await admin.visitSiteEditor( {
postId: `twentytwentyfour//header`,
postType: 'wp_template_part',
canvas: 'edit',
} );
const classicTemplateBlocks = await getClassicTemplateBlocksInInserter(
{
editor,
}
);
expect( classicTemplateBlocks ).toHaveLength( 0 );
} );
// @see https://github.com/woocommerce/woocommerce-blocks/issues/9637
test( `is still available after resetting a modified WC template`, async ( {
admin,
editor,
} ) => {
await admin.visitSiteEditor( {
postId: `woocommerce/woocommerce//single-product`,
postType: 'wp_template',
canvas: 'edit',
} );
await editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: 'Hello World' },
} );
await editor.saveSiteEditorEntities( {
isOnlyCurrentEntityDirty: true,
} );
await editor.page.getByLabel( 'Open Navigation' ).click();
// Reset the template.
await editor.page.getByPlaceholder( 'Search' ).fill( 'Single Product' );
const resetNotice = editor.page
.getByLabel( 'Dismiss this notice' )
.getByText( `"Single Product" reset.` );
const searchResults = editor.page.getByLabel( 'Actions' );
await expect.poll( async () => await searchResults.count() ).toBe( 1 );
await searchResults.first().click();
await editor.page.getByRole( 'menuitem', { name: 'Reset' } ).click();
await editor.page.getByRole( 'button', { name: 'Reset' } ).click();
await expect( resetNotice ).toBeVisible();
// Open the template again.
await editor.page.getByRole( 'menuitem', { name: 'Edit' } ).click();
// Verify the Classic Template block is still registered.
const classicTemplateBlocks = await getClassicTemplateBlocksInInserter(
{
editor,
}
);
expect( classicTemplateBlocks ).toHaveLength( 1 );
} );
for ( const template of templates ) {
test( `is rendered on ${ template.title } template`, async ( {
admin,

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Fix Classic Template block registration on WP 6.6