Product Gallery Block: add support for the on Sale Badge block (https://github.com/woocommerce/woocommerce-blocks/pull/10764)
* Product Gallery: add support for On Sale Badge Block * add align support * Add E2E tests * set margin via Block Styles * disable experimental flag * add next previous block * restore support file * fix TS error * fix layout * change product
This commit is contained in:
parent
e9ff849e08
commit
22b9f6a952
|
@ -12,6 +12,10 @@ export const blockAttributes: BlockAttributes = {
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
isDescendentOfSingleProductTemplate: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default blockAttributes;
|
||||
|
|
|
@ -26,7 +26,10 @@ export const Block = ( props: Props ): JSX.Element | null => {
|
|||
const { parentClassName } = useInnerBlockLayoutContext();
|
||||
const { product } = useProductDataContext();
|
||||
|
||||
if ( ! product.id || ! product.on_sale ) {
|
||||
if (
|
||||
( ! product.id || ! product.on_sale ) &&
|
||||
! props.isDescendentOfSingleProductTemplate
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,8 @@ import { useEffect } from '@wordpress/element';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import Block from './block';
|
||||
import withProductSelector from '../shared/with-product-selector';
|
||||
import {
|
||||
BLOCK_TITLE as label,
|
||||
BLOCK_ICON as icon,
|
||||
BLOCK_DESCRIPTION as description,
|
||||
} from './constants';
|
||||
import type { BlockAttributes } from './types';
|
||||
import { useIsDescendentOfSingleProductTemplate } from '../shared/use-is-descendent-of-single-product-template';
|
||||
|
||||
const Edit = ( {
|
||||
attributes,
|
||||
|
@ -31,9 +26,20 @@ const Edit = ( {
|
|||
};
|
||||
const isDescendentOfQueryLoop = Number.isFinite( context.queryId );
|
||||
|
||||
const { isDescendentOfSingleProductTemplate } =
|
||||
useIsDescendentOfSingleProductTemplate();
|
||||
|
||||
useEffect(
|
||||
() => setAttributes( { isDescendentOfQueryLoop } ),
|
||||
[ setAttributes, isDescendentOfQueryLoop ]
|
||||
() =>
|
||||
setAttributes( {
|
||||
isDescendentOfQueryLoop,
|
||||
isDescendentOfSingleProductTemplate,
|
||||
} ),
|
||||
[
|
||||
setAttributes,
|
||||
isDescendentOfQueryLoop,
|
||||
isDescendentOfSingleProductTemplate,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -43,4 +49,4 @@ const Edit = ( {
|
|||
);
|
||||
};
|
||||
|
||||
export default withProductSelector( { icon, label, description } )( Edit );
|
||||
export default Edit;
|
||||
|
|
|
@ -32,6 +32,7 @@ const blockConfig: BlockConfiguration = {
|
|||
'woocommerce/single-product',
|
||||
'core/post-template',
|
||||
'woocommerce/product-template',
|
||||
'woocommerce/product-gallery',
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
.wp-block-woocommerce-product-sale-badge {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.wc-block-components-product-sale-badge {
|
||||
margin: 0 auto $gap-small;
|
||||
@include font-size(small);
|
||||
padding: em($gap-smallest) em($gap-small);
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
width: fit-content;
|
||||
border: 1px solid #43454b;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
|
@ -15,6 +19,16 @@
|
|||
z-index: 9;
|
||||
position: static;
|
||||
|
||||
&--align-left {
|
||||
align-self: auto;
|
||||
}
|
||||
&--align-center {
|
||||
align-self: center;
|
||||
}
|
||||
&--align-right {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
span {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
|
|
|
@ -7,6 +7,7 @@ import { __experimentalGetSpacingClassesAndStyles } from '@wordpress/block-edito
|
|||
|
||||
export const supports = {
|
||||
html: false,
|
||||
align: true,
|
||||
...( isFeaturePluginBuild() && {
|
||||
color: {
|
||||
gradients: true,
|
||||
|
@ -31,6 +32,7 @@ export const supports = {
|
|||
width: true,
|
||||
__experimentalSkipSerialization: true,
|
||||
},
|
||||
// @todo: Improve styles support when WordPress 6.4 is released. https://make.wordpress.org/core/2023/07/17/introducing-the-block-selectors-api/
|
||||
...( typeof __experimentalGetSpacingClassesAndStyles === 'function' && {
|
||||
spacing: {
|
||||
margin: true,
|
||||
|
|
|
@ -2,4 +2,5 @@ export interface BlockAttributes {
|
|||
productId: number;
|
||||
align: 'left' | 'center' | 'right';
|
||||
isDescendentOfQueryLoop?: boolean | undefined;
|
||||
isDescendentOfSingleProductTemplate?: boolean;
|
||||
}
|
||||
|
|
|
@ -36,6 +36,33 @@ const TEMPLATE: InnerBlockTemplate[] = [
|
|||
[
|
||||
'woocommerce/product-gallery-large-image',
|
||||
getInnerBlocksLockAttributes( 'lock' ),
|
||||
[
|
||||
[
|
||||
'woocommerce/product-sale-badge',
|
||||
{
|
||||
align: 'right',
|
||||
style: {
|
||||
spacing: {
|
||||
margin: {
|
||||
top: '4px',
|
||||
right: '4px',
|
||||
bottom: '4px',
|
||||
left: '4px',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'woocommerce/product-gallery-large-image-next-previous',
|
||||
{
|
||||
layout: {
|
||||
type: 'flex',
|
||||
verticalAlignment: 'bottom',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
|
|
@ -37,7 +37,6 @@ export const Edit = ( {
|
|||
const blockProps = useBlockProps( {
|
||||
style: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignItems: getAlignmentStyle(
|
||||
attributes.layout?.verticalAlignment
|
||||
),
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
}
|
||||
|
||||
.wc-block-product-gallery-large-image__inner-blocks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
|
|
|
@ -58,8 +58,10 @@ class ProductSaleBadge extends AbstractBlock {
|
|||
'margin' => true,
|
||||
'padding' => true,
|
||||
'__experimentalSkipSerialization' => true,
|
||||
|
||||
),
|
||||
'__experimentalSelector' => '.wc-block-components-product-sale-badge',
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -106,16 +108,15 @@ class ProductSaleBadge extends AbstractBlock {
|
|||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
|
||||
|
||||
$output = '<div class="wc-block-components-product-sale-badge '
|
||||
. esc_attr( $classes_and_styles['classes'] ) . ' '
|
||||
. esc_attr( $classname ) . '" '
|
||||
. 'style="' . esc_attr( $classes_and_styles['styles'] ) . '"'
|
||||
. '>';
|
||||
$align = isset( $attributes['align'] ) ? $attributes['align'] : '';
|
||||
|
||||
$output = '<div class="wp-block-woocommerce-product-sale-badge ' . esc_attr( $classname ) . '">';
|
||||
$output .= sprintf( '<div class="wc-block-components-product-sale-badge %1$s wc-block-components-product-sale-badge--align-%2$s" style="%3$s">', esc_attr( $classes_and_styles['classes'] ), $align, esc_attr( $classes_and_styles['styles'] ) );
|
||||
$output .= '<span class="wc-block-components-product-sale-badge__text" aria-hidden="true">' . __( 'Sale', 'woo-gutenberg-products-block' ) . '</span>';
|
||||
$output .= '<span class="screen-reader-text">'
|
||||
. __( 'Product on sale', 'woo-gutenberg-products-block' )
|
||||
. '</span>';
|
||||
$output .= '</div>';
|
||||
$output .= '</div></div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { test, expect } from '@woocommerce/e2e-playwright-utils';
|
||||
import { EditorUtils, FrontendUtils } from '@woocommerce/e2e-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
const blockData = {
|
||||
name: 'woocommerce/product-sale-badge',
|
||||
mainClass: '.wp-block-woocommerce-product-sale-badge',
|
||||
selectors: {
|
||||
frontend: {
|
||||
productSaleBadge: '.wc-block-components-product-sale-badge',
|
||||
productSaleBadgeContainer:
|
||||
'.wp-block-woocommerce-product-sale-badge',
|
||||
},
|
||||
editor: {
|
||||
productSaleBadge: '.wc-block-components-product-sale-badge',
|
||||
productSaleBadgeContainer:
|
||||
'.wp-block-woocommerce-product-sale-badge',
|
||||
},
|
||||
},
|
||||
// This margin is applied via Block Styles to the product sale badge. It's necessary to take it into account when calculating the position of the badge. https://github.com/woocommerce/woocommerce-blocks/blob/445b9431ccba460f9badd41d52ed991958524e33/assets/js/blocks/product-gallery/edit.tsx/#L44-L53
|
||||
margin: 4,
|
||||
slug: 'single-product',
|
||||
productPage: '/product/hoodie/',
|
||||
productPageNotOnSale: '/product/album/',
|
||||
};
|
||||
|
||||
const getBoundingClientRect = async ( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend,
|
||||
}: {
|
||||
frontendUtils: FrontendUtils;
|
||||
editorUtils: EditorUtils;
|
||||
isFrontend: boolean;
|
||||
} ) => {
|
||||
const page = isFrontend ? frontendUtils.page : editorUtils.editor.canvas;
|
||||
return {
|
||||
productSaleBadge: await page
|
||||
.locator(
|
||||
blockData.selectors[ isFrontend ? 'frontend' : 'editor' ]
|
||||
.productSaleBadge
|
||||
)
|
||||
.evaluate( ( el ) => el.getBoundingClientRect() ),
|
||||
productSaleBadgeContainer: await page
|
||||
.locator(
|
||||
blockData.selectors[ isFrontend ? 'frontend' : 'editor' ]
|
||||
.productSaleBadgeContainer
|
||||
)
|
||||
.evaluate( ( el ) => el.getBoundingClientRect() ),
|
||||
};
|
||||
};
|
||||
test.describe( `${ blockData.name }`, () => {
|
||||
test.describe( `On the Single Product Template`, () => {
|
||||
test.beforeEach(
|
||||
async ( { requestUtils, admin, editorUtils, editor } ) => {
|
||||
await requestUtils.deleteAllTemplates( 'wp_template' );
|
||||
await requestUtils.deleteAllTemplates( 'wp_template_part' );
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//${ blockData.slug }`,
|
||||
postType: 'wp_template',
|
||||
} );
|
||||
await editorUtils.enterEditMode();
|
||||
await editor.setContent( '' );
|
||||
}
|
||||
);
|
||||
|
||||
test.afterEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.deleteAllTemplates( 'wp_template' );
|
||||
await requestUtils.deleteAllTemplates( 'wp_template_part' );
|
||||
} );
|
||||
|
||||
test( 'should be rendered on the editor side', async ( {
|
||||
editorUtils,
|
||||
editor,
|
||||
} ) => {
|
||||
await editor.insertBlock( {
|
||||
name: 'woocommerce/product-gallery',
|
||||
} );
|
||||
|
||||
const block = await editorUtils.getBlockByName( blockData.name );
|
||||
|
||||
await expect( block ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( 'should be rendered on the frontend side', async ( {
|
||||
frontendUtils,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await editor.insertBlock( {
|
||||
name: 'woocommerce/product-gallery',
|
||||
} );
|
||||
await Promise.all( [
|
||||
editor.saveSiteEditorEntities(),
|
||||
page.waitForResponse( ( response ) =>
|
||||
response.url().includes( 'wp-json/wp/v2/templates/' )
|
||||
),
|
||||
] );
|
||||
|
||||
await page.goto( blockData.productPage, {
|
||||
waitUntil: 'commit',
|
||||
} );
|
||||
|
||||
const block = await frontendUtils.getBlockByName( blockData.name );
|
||||
|
||||
await expect( block ).toBeVisible();
|
||||
} );
|
||||
|
||||
test( `should be not rendered when the product isn't on sale the frontend side`, async ( {
|
||||
frontendUtils,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await editor.insertBlock( {
|
||||
name: 'woocommerce/product-gallery',
|
||||
} );
|
||||
await Promise.all( [
|
||||
editor.saveSiteEditorEntities(),
|
||||
page.waitForResponse( ( response ) =>
|
||||
response.url().includes( 'wp-json/wp/v2/templates/' )
|
||||
),
|
||||
] );
|
||||
|
||||
await page.goto( blockData.productPageNotOnSale, {
|
||||
waitUntil: 'commit',
|
||||
} );
|
||||
|
||||
const block = await frontendUtils.getBlockByName( blockData.name );
|
||||
|
||||
await expect( block ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( 'should be aligned on the left', async ( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await editor.insertBlock( {
|
||||
name: 'woocommerce/product-gallery',
|
||||
} );
|
||||
|
||||
const block = await editorUtils.getBlockByName( blockData.name );
|
||||
|
||||
await block.click();
|
||||
|
||||
await editorUtils.setAlignOption( 'Align Left' );
|
||||
|
||||
const editorBoundingClientRect = await getBoundingClientRect( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend: false,
|
||||
} );
|
||||
|
||||
await expect(
|
||||
editorBoundingClientRect.productSaleBadge.x - blockData.margin
|
||||
).toEqual( editorBoundingClientRect.productSaleBadgeContainer.x );
|
||||
|
||||
await Promise.all( [
|
||||
editor.saveSiteEditorEntities(),
|
||||
page.waitForResponse( ( response ) =>
|
||||
response.url().includes( 'wp-json/wp/v2/templates/' )
|
||||
),
|
||||
] );
|
||||
|
||||
await page.goto( blockData.productPage, {
|
||||
waitUntil: 'commit',
|
||||
} );
|
||||
|
||||
const clientBoundingClientRect = await getBoundingClientRect( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend: true,
|
||||
} );
|
||||
|
||||
await expect(
|
||||
clientBoundingClientRect.productSaleBadge.x - blockData.margin
|
||||
).toEqual( clientBoundingClientRect.productSaleBadgeContainer.x );
|
||||
} );
|
||||
|
||||
test( 'should be aligned on the center', async ( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await editor.insertBlock( {
|
||||
name: 'woocommerce/product-gallery',
|
||||
} );
|
||||
|
||||
const block = await editorUtils.getBlockByName( blockData.name );
|
||||
|
||||
await block.click();
|
||||
|
||||
await editorUtils.setAlignOption( 'Align Center' );
|
||||
|
||||
const editorBoundingClientRect = await getBoundingClientRect( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend: false,
|
||||
} );
|
||||
|
||||
await expect(
|
||||
editorBoundingClientRect.productSaleBadge.right
|
||||
).toBeLessThan(
|
||||
editorBoundingClientRect.productSaleBadgeContainer.right
|
||||
);
|
||||
|
||||
await Promise.all( [
|
||||
editor.saveSiteEditorEntities(),
|
||||
page.waitForResponse( ( response ) =>
|
||||
response.url().includes( 'wp-json/wp/v2/templates/' )
|
||||
),
|
||||
] );
|
||||
|
||||
await page.goto( blockData.productPage, {
|
||||
waitUntil: 'commit',
|
||||
} );
|
||||
|
||||
const clientBoundingClientRect = await getBoundingClientRect( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend: true,
|
||||
} );
|
||||
|
||||
await expect(
|
||||
clientBoundingClientRect.productSaleBadge.right
|
||||
).toBeLessThan(
|
||||
clientBoundingClientRect.productSaleBadgeContainer.right
|
||||
);
|
||||
} );
|
||||
|
||||
test( 'should be aligned on the right by default', async ( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
await editor.insertBlock( {
|
||||
name: 'woocommerce/product-gallery',
|
||||
} );
|
||||
|
||||
const editorBoundingClientRect = await getBoundingClientRect( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend: false,
|
||||
} );
|
||||
|
||||
await expect(
|
||||
editorBoundingClientRect.productSaleBadge.right +
|
||||
blockData.margin
|
||||
).toEqual(
|
||||
editorBoundingClientRect.productSaleBadgeContainer.right
|
||||
);
|
||||
|
||||
await Promise.all( [
|
||||
editor.saveSiteEditorEntities(),
|
||||
page.waitForResponse( ( response ) =>
|
||||
response.url().includes( 'wp-json/wp/v2/templates/' )
|
||||
),
|
||||
] );
|
||||
|
||||
await page.goto( blockData.productPage, {
|
||||
waitUntil: 'commit',
|
||||
} );
|
||||
|
||||
const clientBoundingClientRect = await getBoundingClientRect( {
|
||||
frontendUtils,
|
||||
editorUtils,
|
||||
isFrontend: true,
|
||||
} );
|
||||
|
||||
await expect(
|
||||
clientBoundingClientRect.productSaleBadge.right +
|
||||
blockData.margin
|
||||
).toEqual(
|
||||
clientBoundingClientRect.productSaleBadgeContainer.right
|
||||
);
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -239,4 +239,14 @@ export class EditorUtils {
|
|||
|
||||
await this.page.getByText( option ).click();
|
||||
}
|
||||
|
||||
async setAlignOption(
|
||||
option: 'Align Left' | 'Align Center' | 'Align Right' | 'None'
|
||||
) {
|
||||
const button = this.page.locator( "button[aria-label='Align']" );
|
||||
|
||||
await button.click();
|
||||
|
||||
await this.page.getByText( option ).click();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue