Merge branch 'trunk' of github.com:woocommerce/woocommerce-blocks into trunk

This commit is contained in:
Luigi 2023-09-20 12:19:21 +02:00
commit 4de017c5b9
9 changed files with 493 additions and 47 deletions

View File

@ -4,51 +4,71 @@
import { __, _n, sprintf } from '@wordpress/i18n';
import { speak } from '@wordpress/a11y';
import { Component } from '@wordpress/element';
import PropTypes from 'prop-types';
import { Review } from '@woocommerce/base-components/reviews/types';
/**
* Internal dependencies
*/
import { getSortArgs } from './utils';
import FrontendBlock from './frontend-block';
import { ReviewBlockAttributes } from './attributes';
type FrontendContainerBlockProps = {
attributes: ReviewBlockAttributes;
};
/**
* Container of the block rendered in the frontend.
*/
class FrontendContainerBlock extends Component {
constructor() {
super( ...arguments );
class FrontendContainerBlock extends Component<
FrontendContainerBlockProps,
{ orderby: string; reviewsToDisplay: number }
> {
constructor( props: FrontendContainerBlockProps ) {
super( props );
const { attributes } = this.props;
this.state = {
orderby: attributes.orderby,
reviewsToDisplay: parseInt( attributes.reviewsOnPageLoad, 10 ),
orderby: attributes?.orderby,
reviewsToDisplay: this.getReviewsOnPageLoad(),
};
this.onAppendReviews = this.onAppendReviews.bind( this );
this.onChangeOrderby = this.onChangeOrderby.bind( this );
}
onAppendReviews() {
getReviewsOnPageLoad() {
const { attributes } = this.props;
return typeof attributes.reviewsOnPageLoad === 'number'
? attributes.reviewsOnPageLoad
: parseInt( attributes.reviewsOnPageLoad, 10 );
}
getReviewsOnLoadMore() {
const { attributes } = this.props;
return typeof attributes.reviewsOnLoadMore === 'number'
? attributes.reviewsOnLoadMore
: parseInt( attributes.reviewsOnLoadMore, 10 );
}
onAppendReviews() {
const { reviewsToDisplay } = this.state;
this.setState( {
reviewsToDisplay:
reviewsToDisplay + parseInt( attributes.reviewsOnLoadMore, 10 ),
reviewsToDisplay: reviewsToDisplay + this.getReviewsOnLoadMore(),
} );
}
onChangeOrderby( event ) {
const { attributes } = this.props;
onChangeOrderby( event: React.ChangeEvent< HTMLSelectElement > ) {
this.setState( {
orderby: event.target.value,
reviewsToDisplay: parseInt( attributes.reviewsOnPageLoad, 10 ),
reviewsToDisplay: this.getReviewsOnPageLoad(),
} );
}
onReviewsAppended( { newReviews } ) {
onReviewsAppended( { newReviews }: { newReviews: Review[] } ) {
speak(
sprintf(
/* translators: %d is the count of reviews loaded. */
@ -83,6 +103,7 @@ class FrontendContainerBlock extends Component {
const { order, orderby } = getSortArgs( this.state.orderby );
return (
// @ts-expect-error - TODO: Refactor WrappedComponent
<FrontendBlock
attributes={ attributes }
categoryIds={ categoryIds }
@ -101,11 +122,4 @@ class FrontendContainerBlock extends Component {
}
}
FrontendContainerBlock.propTypes = {
/**
* The attributes for this block.
*/
attributes: PropTypes.object.isRequired,
};
export default FrontendContainerBlock;

View File

@ -6,7 +6,7 @@ import { renderFrontend } from '@woocommerce/base-utils';
/**
* Internal dependencies
*/
import FrontendContainerBlock from './frontend-container-block.js';
import FrontendContainerBlock from './frontend-container-block';
const selector = `
.wp-block-woocommerce-all-reviews,
@ -14,7 +14,7 @@ const selector = `
.wp-block-woocommerce-reviews-by-category
`;
const getProps = ( el ) => {
const getProps = ( el: HTMLElement ) => {
const showOrderby = el.dataset.showOrderby === 'true';
const showLoadMore = el.dataset.showLoadMore === 'true';
@ -32,6 +32,4 @@ const getProps = ( el ) => {
};
};
// @ts-ignore
// Current typing does not work with non-functional components
renderFrontend( { selector, Block: FrontendContainerBlock, getProps } );

View File

@ -60,6 +60,7 @@ const blocks = {
'product-gallery-large-image-next-previous': {
customDir:
'product-gallery/inner-blocks/product-gallery-large-image-next-previous',
isExperimental: true,
},
'product-gallery-pager': {
customDir: 'product-gallery/inner-blocks/product-gallery-pager',
@ -166,7 +167,7 @@ const entries = {
...getBlockEntries( 'index.{t,j}s{,x}' ),
},
frontend: {
reviews: './assets/js/blocks/reviews/frontend.js',
reviews: './assets/js/blocks/reviews/frontend.ts',
...getBlockEntries( 'frontend.{t,j}s{,x}' ),
'mini-cart-component':
'./assets/js/blocks/mini-cart/component-frontend.tsx',

View File

@ -34,11 +34,11 @@ The majority of our feature flagging is blocks, this is a list of them:
### Experimental flag
- Product Gallery ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/e3fe996251b270d45ecc73207ea4ad587c2dbc78/src/BlockTypesController.php#L232) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/e3fe996251b270d45ecc73207ea4ad587c2dbc78/bin/webpack-entries.js#L50-L52C3) | [BlockTemplatesController](https://github.com/woocommerce/woocommerce-blocks/blob/211960f753d093f2f819273e130b34f893a784cd/src/BlockTemplatesController.php/#L467-L469)).
- Product Gallery Thumbnails ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/04af396b9aec5a915ad98188eded53e723a051d3/src/BlockTypesController.php#L234) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/04af396b9aec5a915ad98188eded53e723a051d3/bin/webpack-entries.js#L57-L60)).
- Product Average Rating ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/1111e2fb9d6f5074df96a444b99e2fc00e4eb8d1/src/BlockTypesController.php#L229) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/1111e2fb9d6f5074df96a444b99e2fc00e4eb8d1/bin/webpack-entries.js#L68-L70))
- Product Rating Stars ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/src/BlockTypesController.php#L230) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/bin/webpack-entries.js#L68-L70))
- Product Rating Counter ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/src/BlockTypesController.php#L229) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/bin/webpack-entries.js#L71-L73))
- Product Gallery ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/src/BlockTypesController.php#L234) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/bin/webpack-entries.js#L53-L55) | [BlockTemplatesController](https://github.com/woocommerce/woocommerce-blocks/blob/211960f753d093f2f819273e130b34f893a784cd/src/BlockTemplatesController.php/#L467-L469)).
- Product Gallery Large Image ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/src/BlockTypesController.php#L235) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/bin/webpack-entries.js#L56-L59)).
- Product Gallery Next/Previous Buttons ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/src/BlockTypesController.php#L236) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/bin/webpack-entries.js#L60-L63)).
- Product Gallery Pager ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/src/BlockTypesController.php#L237) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/bin/webpack-entries.js#L64-L67)).
- Product Gallery Thumbnails ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/src/BlockTypesController.php#L238) | [webpack flag](https://github.com/woocommerce/woocommerce-blocks/blob/7f0d55d54885f436778f04a6389e92b8785d5c68/bin/webpack-entries.js#L68-L71)).
- ⚛️ Add to cart ([JS flag](https://github.com/woocommerce/woocommerce-blocks/blob/dfd2902bd8a247b5d048577db6753c5e901fc60f/assets/js/atomic/blocks/product-elements/add-to-cart/index.ts#L26-L29)).
- Order Route ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/b4a9dc9334f82c09f533b0f88c947b5c34e4e546/src/StoreApi/RoutesController.php#L65-L67))
- Checkout Order Route ([PHP flag](https://github.com/woocommerce/woocommerce-blocks/blob/b4ba06a6242cbb6a64d2ec554a263ebe60d8d3af/src/StoreApi/RoutesController.php#L67))

View File

@ -10,6 +10,7 @@ use Automattic\WooCommerce\Blocks\Templates\ProductSearchResultsTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateMigrationUtils;
/**
@ -429,8 +430,12 @@ class BlockTemplatesController {
}
}
$new_content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
$template->content = $new_content;
if ( post_password_required() ) {
$template->content = SingleProductTemplate::add_password_form( $template->content );
} else {
$new_content = SingleProductTemplateCompatibility::add_compatibility_layer( $template->content );
$template->content = $new_content;
}
}
}

View File

@ -5,7 +5,7 @@ use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
/**
* ProductGalleryLargeImage class.
* ProductGalleryThumbnails class.
*/
class ProductGalleryThumbnails extends AbstractBlock {
/**
@ -49,15 +49,14 @@ class ProductGalleryThumbnails extends AbstractBlock {
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
$product = wc_get_product( $post_id );
$post_thumbnail_id = $product->get_image_id();
$html = '';
$post_id = $block->context['postId'] ?? '';
$product = wc_get_product( $post_id );
if ( $product ) {
$post_thumbnail_id = $product->get_image_id();
$product_gallery_images = ProductGalleryUtils::get_product_gallery_images( $post_id, 'thumbnail', array() );
if ( $product_gallery_images && $post_thumbnail_id ) {
$html = '';
$number_of_thumbnails = isset( $block->context['thumbnailsNumberOfThumbnails'] ) ? $block->context['thumbnailsNumberOfThumbnails'] : 3;
$thumbnails_count = 1;
@ -83,17 +82,18 @@ class ProductGalleryThumbnails extends AbstractBlock {
$thumbnails_count++;
}
}
return sprintf(
'<div class="wc-block-components-product-gallery-thumbnails wp-block-woocommerce-product-gallery-thumbnails %1$s" style="%2$s">
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
$html
);
return sprintf(
'<div class="wc-block-components-product-gallery-thumbnails wp-block-woocommerce-product-gallery-thumbnails %1$s" style="%2$s">
%3$s
</div>',
esc_attr( $classes_and_styles['classes'] ),
esc_attr( $classes_and_styles['styles'] ),
$html
);
}
}
return;
}
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace Automattic\WooCommerce\Blocks\Templates;
/**
* SingleProductTemplae class.
*
* @internal
*/
class SingleProductTemplate {
/**
* Replace the first single product template block with the password form. Remove all other single product template blocks.
*
* @param array $parsed_blocks Array of parsed block objects.
* @param boolean $is_already_replaced If the password form has already been added.
* @return array Parsed blocks
*/
private static function replace_first_single_product_template_block_with_password_form( $parsed_blocks, $is_already_replaced ) {
// We want to replace the first single product template block with the password form. We also want to remove all other single product template blocks.
// This array doesn't contains all the blocks. For example, it missing the breadcrumbs blocks: it doesn't make sense replace the breadcrumbs with the password form.
$single_product_template_blocks = array( 'woocommerce/product-image-gallery', 'woocommerce/product-details', 'woocommerce/add-to-cart-form', 'woocommerce/product-meta', 'woocommerce/product-rating', 'woocommerce/product-price', 'woocommerce/related-products' );
return array_reduce(
$parsed_blocks,
function( $carry, $block ) use ( $single_product_template_blocks ) {
if ( in_array( $block['blockName'], $single_product_template_blocks, true ) ) {
if ( $carry['is_already_replaced'] ) {
return array(
'blocks' => $carry['blocks'],
'html_block' => null,
'removed' => true,
'is_already_replaced' => true,
);
}
return array(
'blocks' => $carry['blocks'],
'html_block' => parse_blocks( '<!-- wp:html -->' . get_the_password_form() . '<!-- /wp:html -->' )[0],
'removed' => false,
'is_already_replaced' => $carry['is_already_replaced'],
);
}
if ( isset( $block['innerBlocks'] ) && count( $block['innerBlocks'] ) > 0 ) {
$index = 0;
$new_inner_blocks = array();
$new_inner_contents = $block['innerContent'];
foreach ( $block['innerContent'] as $inner_content ) {
// Don't process the closing tag of the block.
if ( count( $block['innerBlocks'] ) === $index ) {
break;
}
$blocks = self::replace_first_single_product_template_block_with_password_form( array( $block['innerBlocks'][ $index ] ), $carry['is_already_replaced'] );
$new_blocks = $blocks['blocks'];
$html_block = $blocks['html_block'];
$is_removed = $blocks['removed'];
$carry['is_already_replaced'] = $blocks['is_already_replaced'];
if ( isset( $html_block ) ) {
$new_inner_blocks = array_merge( $new_inner_blocks, $new_blocks, array( $html_block ) );
$carry['is_already_replaced'] = true;
} else {
$new_inner_blocks = array_merge( $new_inner_blocks, $new_blocks );
}
if ( $is_removed ) {
unset( $new_inner_contents[ $index ] );
// The last element of the inner contents contains the closing tag of the block. We don't want to remove it.
if ( $index + 1 < count( $new_inner_contents ) ) {
unset( $new_inner_contents[ $index + 1 ] );
}
$new_inner_contents = array_values( $new_inner_contents );
}
$index++;
}
$block['innerBlocks'] = $new_inner_blocks;
$block['innerContent'] = $new_inner_contents;
return array(
'blocks' => array_merge( $carry['blocks'], array( $block ) ),
'html_block' => null,
'removed' => false,
'is_already_replaced' => $carry['is_already_replaced'],
);
}
return array(
'blocks' => array_merge( $carry['blocks'], array( $block ) ),
'html_block' => null,
'removed' => false,
'is_already_replaced' => $carry['is_already_replaced'],
);
},
array(
'blocks' => array(),
'html_block' => null,
'removed' => false,
'is_already_replaced' => $is_already_replaced,
)
);
}
/**
* Add password form to the Single Product Template.
*
* @param string $content The content of the template.
* @return string
*/
public static function add_password_form( $content ) {
$parsed_blocks = parse_blocks( $content );
$blocks = self::replace_first_single_product_template_block_with_password_form( $parsed_blocks, false );
$serialized_blocks = serialize_blocks( $blocks['blocks'] );
return $serialized_blocks;
}
}

View File

@ -0,0 +1,61 @@
/**
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
import { cli } from '@woocommerce/e2e-utils';
const product = {
name: 'Protected Product',
slug: 'protected-product',
password: 'password',
};
test.describe( 'Single Product Template', () => {
let id: null | string = null;
test.beforeEach( async ( { admin, page } ) => {
await admin.visitAdminPage( `/post-new.php?post_type=product` );
const input = page.locator( '#title' );
await input.fill( product.name );
await page.getByRole( 'button', { name: 'Edit visibility' } ).click();
await page.locator( '#visibility-radio-password' ).click();
await page.locator( '#post_password' ).fill( product.password );
await page.waitForResponse( ( response ) =>
response.url().includes( 'admin-ajax.php' )
);
await page.locator( '#publish.button-primary' ).click();
await page.waitForSelector(
'#woocommerce-product-updated-message-view-product__link'
);
const url = new URL( page.url() );
const queryParams = new URLSearchParams( url.search );
id = queryParams.get( 'post' );
} );
test.afterAll( async () => {
await cli(
`npm run wp-env run tests-cli -- wp post delete ${ id } --force`
);
} );
test.describe(
`should render a password input when the product is protected `,
() =>
test( 'add product specific classes to the body', async ( {
page,
} ) => {
await page.goto( `/product/${ product.slug }` );
const placeholder = page.getByText(
'This content is password protected. To view it please enter your password below:'
);
await expect( placeholder ).toBeVisible();
await page.getByLabel( 'Password' ).fill( 'password' );
await page.getByRole( 'button', { name: 'Enter' } ).click();
await expect( placeholder ).toBeHidden();
} )
);
} );

View File

@ -0,0 +1,247 @@
<?php
namespace Automattic\WooCommerce\Blocks\Tests\Templates;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
use \WP_UnitTestCase;
/**
* Tests the SingleProductTemplateCompatibility class
*
*/
class SingleProductTemplateTests extends WP_UnitTestCase {
/**
* Test that the password form isn't added to the Single Product Template.
*
*/
public function test_no_remove_block_when_no_single_product_is_in_the_template() {
$default_single_product_template = '
<!-- wp:template-part {"slug":"header","theme":"twentytwentythree","tagName":"header"} /-->
<!-- wp:group {"layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:woocommerce/legacy-template {"template":"single-product"} /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","theme":"twentytwentythree","tagName":"footer"} /-->';
$expected_single_product_template = '
<!-- wp:template-part {"slug":"header","theme":"twentytwentythree","tagName":"header"} /-->
<!-- wp:group {"layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:woocommerce/legacy-template {"template":"single-product"} /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","theme":"twentytwentythree","tagName":"footer"} /-->';
$result = SingleProductTemplate::add_password_form(
$default_single_product_template
);
$result_without_withespace = preg_replace( '/\s+/', '', $result );
$expected_single_product_template_without_whitespace = preg_replace(
'/\s+/',
'',
$expected_single_product_template
);
$this->assertEquals(
$result_without_withespace,
$expected_single_product_template_without_whitespace,
''
);
}
/**
* Test that the password form is added to the Single Product Template.
*/
public function test_replace_single_product_blocks_with_input_form() {
$default_single_product_template = '
<!-- wp:template-part {"slug":"header","theme":"twentytwentythree","tagName":"header"} /-->
<!-- wp:group {"layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:woocommerce/product-image-gallery {"layout":{"inherit":true,"type":"constrained"}} /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","theme":"twentytwentythree","tagName":"footer"} /-->';
$expected_single_product_template = sprintf(
'
<!-- wp:template-part {"slug":"header","theme":"twentytwentythree","tagName":"header"} /-->
<!-- wp:group {"layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:html -->%s<!-- /wp:html -->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","theme":"twentytwentythree","tagName":"footer"} /-->',
get_the_password_form()
);
$result = SingleProductTemplate::add_password_form(
$default_single_product_template
);
$result_without_withespace = preg_replace( '/\s+/', '', $result );
$result_without_withespace_without_custom_pwbox_ids = preg_replace(
'/pwbox-\d+/',
'',
$result_without_withespace
);
$expected_single_product_template_without_whitespace = preg_replace(
'/\s+/',
'',
$expected_single_product_template
);
$expected_single_product_template_without_whitespace_without_custom_pwbox_ids = preg_replace(
'/pwbox-\d+/',
'',
$expected_single_product_template_without_whitespace
);
$this->assertEquals(
$result_without_withespace_without_custom_pwbox_ids,
$expected_single_product_template_without_whitespace_without_custom_pwbox_ids,
''
);
}
/**
* Test that the password form is added to the Single Product Template with the default template.
*/
public function test_replace_default_template_single_product_blocks_with_input_form() {
$default_single_product_template = '
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:woocommerce/breadcrumbs /-->
<!-- wp:woocommerce/store-notices /-->
<!-- wp:columns {"align":"wide"} -->
<div class="wp-block-columns alignwide">
<!-- wp:column {"width":"512px"} -->
<div class="wp-block-column" style="flex-basis:512px">
<!-- wp:woocommerce/product-image-gallery /-->
</div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:post-title {"level": 1, "__woocommerceNamespace":"woocommerce/product-query/product-title"} /-->
<!-- wp:woocommerce/product-rating {"isDescendentOfSingleProductTemplate":true} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfSingleProductTemplate":true, "fontSize":"large"} /-->
<!-- wp:post-excerpt {"__woocommerceNamespace":"woocommerce/product-query/product-summary"} /-->
<!-- wp:woocommerce/add-to-cart-form /-->
<!-- wp:woocommerce/product-meta -->
<div class="wp-block-woocommerce-product-meta">
<!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap"}} -->
<div class="wp-block-group">
<!-- wp:woocommerce/product-sku {"isDescendentOfSingleProductTemplate":true} /-->
<!-- wp:post-terms {"term":"product_cat","prefix":"Category: "} /-->
<!-- wp:post-terms {"term":"product_tag","prefix":"Tags: "} /-->
</div>
<!-- /wp:group -->
</div>
<!-- /wp:woocommerce/product-meta -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
<!-- wp:woocommerce/product-details {"align":"wide"} /-->
<!-- wp:woocommerce/related-products {"align":"wide"} -->
<div class="wp-block-woocommerce-related-products alignwide">
<!-- wp:query {"queryId":0,"query":{"perPage":5,"pages":0,"offset":0,"postType":"product","order":"asc","orderBy":"title","author":"","search":"","exclude":[],"sticky":"","inherit":false},"displayLayout":{"type":"flex","columns":5},"namespace":"woocommerce/related-products","lock":{"remove":true,"move":true}} -->
<div class="wp-block-query">
<!-- wp:heading -->
<h2 class="wp-block-heading">Related products</h2>
<!-- /wp:heading -->
<!-- wp:post-template {"className":"products-block-post-template","__woocommerceNamespace":"woocommerce/product-query/product-template"} -->
<!-- wp:woocommerce/product-image {"isDescendentOfQueryLoop":true} /-->
<!-- wp:post-title {"textAlign":"center","level":3,"fontSize":"medium","__woocommerceNamespace":"woocommerce/product-query/product-title"} /-->
<!-- wp:woocommerce/product-price {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- wp:woocommerce/product-button {"isDescendentOfQueryLoop":true,"textAlign":"center","fontSize":"small","style":{"spacing":{"margin":{"bottom":"1rem"}}}} /-->
<!-- /wp:post-template -->
</div>
<!-- /wp:query -->
</div>
<!-- /wp:woocommerce/related-products -->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->
';
$expected_single_product_template = sprintf(
'
<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"layout":{"inherit":true,"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:woocommerce/breadcrumbs /-->
<!-- wp:woocommerce/store-notices /-->
<!-- wp:columns {"align":"wide"} -->
<div class="wp-block-columns alignwide">
<!-- wp:column {"width":"512px"} -->
<div class="wp-block-column" style="flex-basis:512px">
<!-- wp:html -->%s<!-- /wp:html -->
</div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:post-title {"level": 1, "__woocommerceNamespace":"woocommerce/product-query/product-title"} /-->
<!-- wp:post-excerpt {"__woocommerceNamespace":"woocommerce/product-query/product-summary"} /-->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->',
get_the_password_form()
);
$result = SingleProductTemplate::add_password_form(
$default_single_product_template
);
$result_without_withespace = preg_replace( '/\s+/', '', $result );
$result_without_withespace_without_custom_pwbox_ids = preg_replace(
'/pwbox-\d+/',
'',
$result_without_withespace
);
$expected_single_product_template_without_whitespace = preg_replace(
'/\s+/',
'',
$expected_single_product_template
);
$expected_single_product_template_without_whitespace_without_custom_pwbox_ids = preg_replace(
'/pwbox-\d+/',
'',
$expected_single_product_template_without_whitespace
);
$this->assertEquals(
$result_without_withespace_without_custom_pwbox_ids,
$expected_single_product_template_without_whitespace_without_custom_pwbox_ids,
''
);
}
}