Skip to content

Commit

Permalink
Avoid duplicate templates appearing on the Site Editor when the WooCo…
Browse files Browse the repository at this point in the history
…mmerce template and the theme template have been customized by the user (#44000)

* Avoid duplicate templates appearing on the Site Editor when the WooCommerce template and the theme template have been customized by the user

* Add tests

* Add changefile(s) from automation for the following project(s): woocommerce-blocks, woocommerce

* Clean up templates after running all tests to increase speed

* Fix comment linting

* Fix addToCart() util with the classic template

* Remove unnecessary condition

---------

Co-authored-by: github-actions <github-actions@github.com>
  • Loading branch information
Aljullu and github-actions committed Jan 25, 2024
1 parent dc6af08 commit 66c3467
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 4 deletions.
100 changes: 100 additions & 0 deletions plugins/woocommerce-blocks/tests/e2e/tests/templates/constants.ts
@@ -0,0 +1,100 @@
/**
* External dependencies
*/
import type { Page, Response } from '@playwright/test';
import type { FrontendUtils } from '@woocommerce/e2e-utils';

/**
* Internal dependencies
*/
import { SIMPLE_VIRTUAL_PRODUCT_NAME } from '../checkout/constants';
import { CheckoutPage } from '../checkout/checkout.page';

type TemplateCustomizationTest = {
visitPage: ( props: {
frontendUtils: FrontendUtils;
page: Page;
} ) => Promise< void | Response | null >;
templateName: string;
templatePath: string;
templateType: string;
defaultTemplate?: {
templateName: string;
templatePath: string;
};
};

export const CUSTOMIZABLE_WC_TEMPLATES: TemplateCustomizationTest[] = [
{
visitPage: async ( { frontendUtils } ) =>
await frontendUtils.goToShop(),
templateName: 'Product Catalog',
templatePath: 'archive-product',
templateType: 'wp_template',
},
{
visitPage: async ( { page } ) =>
await page.goto( '/?s=shirt&post_type=product' ),
templateName: 'Product Search Results',
templatePath: 'product-search-results',
templateType: 'wp_template',
},
{
visitPage: async ( { page } ) => await page.goto( '/color/blue' ),
templateName: 'Products by Attribute',
templatePath: 'taxonomy-product_attribute',
templateType: 'wp_template',
},
{
visitPage: async ( { page } ) =>
await page.goto( '/product-category/clothing' ),
templateName: 'Products by Category',
templatePath: 'taxonomy-product_cat',
templateType: 'wp_template',
},
{
visitPage: async ( { page } ) =>
await page.goto( '/product-tag/recommended/' ),
templateName: 'Products by Tag',
templatePath: 'taxonomy-product_tag',
templateType: 'wp_template',
},
{
visitPage: async ( { page } ) => await page.goto( '/product/hoodie' ),
templateName: 'Single Product',
templatePath: 'single-product',
templateType: 'wp_template',
},
{
visitPage: async ( { frontendUtils } ) =>
await frontendUtils.goToCart(),
templateName: 'Page: Cart',
templatePath: 'page-cart',
templateType: 'wp_template',
},
{
visitPage: async ( { frontendUtils } ) => {
await frontendUtils.goToShop();
await frontendUtils.addToCart();
await frontendUtils.goToCheckout();
},
templateName: 'Page: Checkout',
templatePath: 'page-checkout',
templateType: 'wp_template',
},
{
visitPage: async ( { frontendUtils, page } ) => {
const checkoutPage = new CheckoutPage( { page } );
await frontendUtils.goToShop();
await frontendUtils.addToCart( SIMPLE_VIRTUAL_PRODUCT_NAME );
await frontendUtils.goToCheckout();
await checkoutPage.fillInCheckoutWithTestData();
await checkoutPage.placeOrder();
},
templateName: 'Order Confirmation',
templatePath: 'order-confirmation',
templateType: 'wp_template',
},
];

export const WC_TEMPLATES_SLUG = 'woocommerce/woocommerce';
@@ -0,0 +1,93 @@
/**
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
import { deleteAllTemplates } from '@wordpress/e2e-test-utils';
import {
BLOCK_THEME_SLUG,
BLOCK_THEME_WITH_TEMPLATES_SLUG,
cli,
} from '@woocommerce/e2e-utils';

/**
* Internal dependencies
*/
import { CUSTOMIZABLE_WC_TEMPLATES, WC_TEMPLATES_SLUG } from './constants';

const userText = 'Hello World in the template';
const woocommerceTemplateUserText = 'Hello World in the WooCommerce template';

CUSTOMIZABLE_WC_TEMPLATES.forEach( ( testData ) => {
test.describe( `${ testData.templateName } template`, async () => {
test.afterAll( async () => {
await deleteAllTemplates( 'wp_template' );
} );

test( `user-modified ${ testData.templateName } template based on the theme template has priority over the user-modified template based on the default WooCommerce template`, async ( {
admin,
frontendUtils,
editorUtils,
page,
} ) => {
// Edit the WooCommerce default template
await admin.visitSiteEditor( {
postId: `${ WC_TEMPLATES_SLUG }//${ testData.templatePath }`,
postType: testData.templateType,
} );
await editorUtils.enterEditMode();
await editorUtils.closeWelcomeGuideModal();
await editorUtils.editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: woocommerceTemplateUserText },
} );
await editorUtils.saveTemplate();

await cli(
`npm run wp-env run tests-cli -- wp theme activate ${ BLOCK_THEME_WITH_TEMPLATES_SLUG }`
);

// Edit the theme template.
await admin.visitSiteEditor( {
postId: `${ BLOCK_THEME_WITH_TEMPLATES_SLUG }//${ testData.templatePath }`,
postType: testData.templateType,
} );
await editorUtils.enterEditMode();
await editorUtils.closeWelcomeGuideModal();
await editorUtils.editor.insertBlock( {
name: 'core/paragraph',
attributes: { content: userText },
} );
await editorUtils.saveTemplate();

// Verify the template is the one modified by the user based on the theme.
await testData.visitPage( { frontendUtils, page } );
await expect( page.getByText( userText ).first() ).toBeVisible();
await expect(
page.getByText( woocommerceTemplateUserText )
).toHaveCount( 0 );

// Revert edition and verify the user-modified WC template is used.
// Note: we need to revert it from the admin (instead of calling
// `deleteAllTemplates()`). This way, we verify there are no
// duplicate templates with the same name.
// See: https://github.com/woocommerce/woocommerce/issues/42220
await admin.visitAdminPage(
'site-editor.php',
`path=/${ testData.templateType }/all`
);
await editorUtils.revertTemplateCustomizations(
testData.templateName
);
await testData.visitPage( { frontendUtils, page } );

await expect(
page.getByText( woocommerceTemplateUserText ).first()
).toBeVisible();
await expect( page.getByText( userText ) ).toHaveCount( 0 );

await cli(
`npm run wp-env run tests-cli -- wp theme activate ${ BLOCK_THEME_SLUG }`
);
} );
} );
} );
Expand Up @@ -29,10 +29,10 @@ export class FrontendUtils {
async addToCart( itemName = '' ) {
await this.page.waitForLoadState( 'domcontentloaded' );
if ( itemName !== '' ) {
// We can't use `getByRole()` here because the Add to Cart button
// might be a button (in blocks) or a link (in the legacy template).
await this.page
.getByRole( 'button', {
name: `Add to cart: “${ itemName }”`,
} )
.getByLabel( `Add to cart: “${ itemName }”` )
.click();
} else {
await this.page.click( 'text=Add to cart' );
Expand Down
@@ -0,0 +1,57 @@
<!-- wp:template-part {"slug":"header"} /-->

<!-- wp:paragraph -->
<p>Order Confirmation template loaded from theme</p>
<!-- /wp:paragraph -->

<!-- wp:group {"tagName":"main","layout":{"inherit":true,"type":"constrained"}} -->
<main class="wp-block-group"><!-- wp:woocommerce/order-confirmation-status {"fontSize":"large"} /-->

<!-- wp:woocommerce/order-confirmation-summary /-->

<!-- wp:woocommerce/order-confirmation-totals-wrapper {"align":"wide"} -->
<!-- wp:pattern {"slug":"woocommerce/order-confirmation-totals-heading"} /-->

<!-- wp:woocommerce/order-confirmation-totals {"lock":{"remove":true}} /-->
<!-- /wp:woocommerce/order-confirmation-totals-wrapper -->

<!-- wp:woocommerce/order-confirmation-downloads-wrapper {"align":"wide"} -->
<!-- wp:pattern {"slug":"woocommerce/order-confirmation-downloads-heading"} /-->

<!-- wp:woocommerce/order-confirmation-downloads {"lock":{"remove":true}} /-->
<!-- /wp:woocommerce/order-confirmation-downloads-wrapper -->

<!-- wp:columns {"align":"wide","className":"woocommerce-order-confirmation-address-wrapper"} -->
<div class="wp-block-columns alignwide woocommerce-order-confirmation-address-wrapper">
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:woocommerce/order-confirmation-shipping-wrapper {"align":"wide"} -->
<!-- wp:pattern {"slug":"woocommerce/order-confirmation-shipping-heading"} /-->

<!-- wp:woocommerce/order-confirmation-shipping-address {"lock":{"remove":true}} /-->
<!-- /wp:woocommerce/order-confirmation-shipping-wrapper -->
</div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:woocommerce/order-confirmation-billing-wrapper {"align":"wide"} -->
<!-- wp:pattern {"slug":"woocommerce/order-confirmation-billing-heading"} /-->

<!-- wp:woocommerce/order-confirmation-billing-address {"lock":{"remove":true}} /-->
<!-- /wp:woocommerce/order-confirmation-billing-wrapper -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->

<!-- wp:woocommerce/order-confirmation-additional-fields-wrapper {"align":"wide"} -->
<!-- wp:pattern {"slug":"woocommerce/order-confirmation-additional-fields-heading"} /-->
<!-- wp:woocommerce/order-confirmation-additional-fields /-->
<!-- /wp:woocommerce/order-confirmation-additional-fields-wrapper -->

<!-- wp:woocommerce/order-confirmation-additional-information /-->
</main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer"} /-->
@@ -0,0 +1,13 @@
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:paragraph -->
<p>Page: Cart template loaded from theme</p>
<!-- /wp:paragraph -->
<!-- wp:woocommerce/page-content-wrapper {"page":"cart"} -->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:post-title {"align":"wide", "level":1} /-->
<!-- wp:post-content {"align":"wide"} /-->
</main>
<!-- /wp:group -->
<!-- /wp:woocommerce/page-content-wrapper -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
@@ -0,0 +1,12 @@
<!-- wp:template-part {"slug":"checkout-header","theme":"woocommerce/woocommerce","tagName":"header"} /-->
<!-- wp:paragraph -->
<p>Page: Checkout template loaded from theme</p>
<!-- /wp:paragraph -->
<!-- wp:woocommerce/page-content-wrapper {"page":"checkout"} -->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- wp:post-title {"align":"wide", "level":1} /-->
<!-- wp:post-content {"align":"wide"} /-->
</main>
<!-- /wp:group -->
<!-- /wp:woocommerce/page-content-wrapper -->
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

Avoid duplicate templates appearing on the Site Editor when the WooCommerce template and the theme template have been customized by the user
8 changes: 7 additions & 1 deletion plugins/woocommerce/src/Blocks/BlockTemplatesController.php
Expand Up @@ -365,6 +365,7 @@ public function add_block_templates( $query_result, $query, $template_type ) {
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
$slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
$template_files = $this->get_block_templates( $slugs, $template_type );
$theme_slug = wp_get_theme()->get_stylesheet();

// @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
foreach ( $template_files as $template_file ) {
Expand Down Expand Up @@ -398,7 +399,7 @@ public function add_block_templates( $query_result, $query, $template_type ) {
}

$is_not_custom = false === array_search(
wp_get_theme()->get_stylesheet() . '//' . $template_file->slug,
$theme_slug . '//' . $template_file->slug,
array_column( $query_result, 'id' ),
true
);
Expand All @@ -416,6 +417,11 @@ public function add_block_templates( $query_result, $query, $template_type ) {
// This only affects saved templates that were saved BEFORE a theme template with the same slug was added.
$query_result = BlockTemplateUtils::remove_theme_templates_with_custom_alternative( $query_result );

// There is the chance that the user customized the default template, installed a theme with a custom template
// and customized that one as well. When that happens, duplicates might appear in the list.
// See: https://github.com/woocommerce/woocommerce/issues/42220.
$query_result = BlockTemplateUtils::remove_duplicate_customized_templates( $query_result, $theme_slug );

/**
* WC templates from theme aren't included in `$this->get_block_templates()` but are handled by Gutenberg.
* We need to do additional search through all templates file to update title and description for WC
Expand Down
35 changes: 35 additions & 0 deletions plugins/woocommerce/src/Blocks/Utils/BlockTemplateUtils.php
Expand Up @@ -690,6 +690,41 @@ function( $template ) use ( $customised_template_slugs ) {
);
}

/**
* Removes customized templates that shouldn't be available. That means customized templates based on the
* WooCommerce default template when there is a customized template based on the theme template.
*
* @param \WP_Block_Template[]|\stdClass[] $templates List of templates to run the filter on.
* @param string $theme_slug Slug of the theme currently active.
*
* @return array Filtered list of templates with only relevant templates available.
*/
public static function remove_duplicate_customized_templates( $templates, $theme_slug ) {
$filtered_templates = array_filter(
$templates,
function( $template ) use ( $templates, $theme_slug ) {
if ( $template->theme === $theme_slug ) {
// This is a customized template based on the theme template, so it should be returned.
return true;
}
// This is a template customized from the WooCommerce default template.
// Only return it if there isn't a customized version of the theme template.
$is_there_a_customized_theme_template = array_filter(
$templates,
function( $theme_template ) use ( $template, $theme_slug ) {
return $theme_template->slug === $template->slug && $theme_template->theme === $theme_slug;
}
);
if ( $is_there_a_customized_theme_template ) {
return false;
}
return true;
},
);

return $filtered_templates;
}

/**
* Returns whether the blockified templates should be used or not.
* First, we need to make sure WordPress version is higher than 6.1 (lowest that supports Products block).
Expand Down

0 comments on commit 66c3467

Please sign in to comment.