* WIP Product Gallery: Add the Thumbnails block

* Product Gallery Thumbnails: Add block settings

* Add template for the Product Gallery block

* Add template for the Product Gallery block. Add the rest of the files.

* Product Gallery Thumbnails: Add context settings sharing between the Product Gallery and Thumbnails block.

* Product Gallery Thumbnails: Add UI functionality and frontend functionality. Add settings for the Thumbnails in both places - Product Gallery and the Thumbnails block.

* Product Gallery Thumbnails: Move the static template ouside of the component

* Make sure the context is set before accesing the array values

* Product Gallery Thumbnails: Move the setGroupAttributes() function outside of the component

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Update the Features Flags and Experimental Interfaces doc

* Product Gallery Thumbnails: Fix TS error

* Product Gallery Thumbnails: Remove unused stylesheet

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Remove unused context and fix the thumbnails bottom position styling on the frontend.

* Product Gallery Thumbnails: Allow the user to move the horizontal thumbnails above the large image and don't overwrite that automatically

* E2E: Add tests for the Product Gallery Thumbnails block

* Product Gallery Thumbnails: Add code comments and remove the incorrect conditional check when moving thumbnails up and down

* Product Gallery Thumbnails: Add failure handling

* Product Gallery Thumbnails: Fix the eslint dependency error

* Product Gallery Thumbnails: Add inner blocks to the sideEffects array

* Product Gallery Thumbnails: Refactor Product Gallery edit code and move the logic to a utils file

* Product Gallery Thumbnails: Update the utils file

* Product Gallery Thumbnails: Update the utils file. Fix comment indentation

* Product Gallery Thumbnails: Fix failing tests

* Revert unrelated package.json changes

* Product Gallery Thumbnails: Further package.json reverts

* Product Gallery Thumbnails: Rename the test screenshots

* Product Gallery Thumbnails: Fix undefined variable html when only 1 product image is set

* Product Gallery: Rename clientId to productGalleryClientId

* Product Gallery Thumbnails: Combine the useEffect code having the same dependencies

* Product Gallery Thumbnails: Combine all useEffect code together

* Product Gallery Thumbnails: Add a ThumbnailsPosition enum

* Product Gallery Thumbnails: Update the thumbnailsPosition to an enum

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Fix TS errors

* Product Gallery Thumbnails: Add missing dependency

* Product Gallery Thumbnails: Uppercase the enum and fix the thumbnails position bug when initially adding the Product Gallery block

* Product Gallery Thumbnails: Fix E2E tests

* Product Gallery Thumbnails: Remove unused function from frontend utils

* Product Gallery Thumbnails: Remove unused screenshots and config amendment

* Product Gallery Thumbnails: Add check for the order of block on the frontend

* Product Gallery: Add crop, zoom and full-screen settings

* Product Gallery Thumbnails: Replace ts-ignore with ts-expect-error

* Product Gallery Thumbnails: Replace ts-ignore with ts-expect-error

* Product Gallery Thumbnails: Revert back to ts-ignore

* Revert "Product Gallery: Add crop, zoom and full-screen settings"

This reverts commit 840654197619e2611029b81990493387ae0b543d.

* Product Gallery: Add crop, zoom and full-screen settings

* Product Gallery: Remove the redundant React Fragment

* Product Gallery E2E: Simplify and combine the tests
This commit is contained in:
Daniel Dudzic 2023-08-04 13:07:31 +02:00 committed by GitHub
parent e016b169c3
commit c411137ba4
6 changed files with 518 additions and 3 deletions

View File

@ -11,6 +11,7 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup node version and npm cache
@ -58,7 +59,11 @@ jobs:
- name: Run Playwright tests
run: npm run test:e2e-pw
- uses: actions/upload-artifact@v3.1.2
- uses: actions/upload-artifact@v3
if: ${{ failure() }}
with:
name: playwright-report
path: playwright-report
path: artifacts/test-results
if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn`

View File

@ -27,7 +27,10 @@
"./assets/js/blocks/filter-wrapper/register-components.ts",
"./assets/js/blocks/product-query/variations/**.tsx",
"./assets/js/blocks/product-query/index.tsx",
"./assets/js/blocks/product-query/inspector-controls.tsx"
"./assets/js/blocks/product-query/inspector-controls.tsx",
"./assets/js/blocks/product-gallery/**.tsx",
"./assets/js/blocks/product-gallery/inner-blocks/**/index.tsx",
"./assets/js/templates/revert-button/index.tsx"
],
"repository": {
"type": "git",

View File

@ -0,0 +1,375 @@
/**
* External dependencies
*/
import { test, expect } from '@woocommerce/e2e-playwright-utils';
/**
* Internal dependencies
*/
import { addBlock } from './utils';
const blockData = {
name: 'woocommerce/product-gallery-thumbnails',
mainClass: '.wp-block-woocommerce-product-gallery-thumbnails',
selectors: {
frontend: {},
editor: {
thumbnails: '.wp-block-woocommerce-product-gallery-thumbnails',
noThumbnailsOption: 'button[data-value=off]',
leftPositionThumbnailsOption: 'button[data-value=left]',
bottomPositionThumbnailsOption: 'button[data-value=bottom]',
rightPositionThumbnailsOption: 'button[data-value=right]',
},
},
slug: 'single-product',
productPage: '/product/v-neck-t-shirt/',
};
test.describe( `${ blockData.name }`, () => {
test.beforeEach( async ( { requestUtils, admin, editorUtils } ) => {
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();
} );
test( 'Renders Product Gallery Thumbnails block on the editor and frontend side', async ( {
page,
editor,
editorUtils,
frontendUtils,
} ) => {
await editor.insertBlock( {
name: 'woocommerce/product-gallery',
} );
const block = await editorUtils.getBlockByName( blockData.name );
await expect( block ).toBeVisible();
await Promise.all( [
editor.saveSiteEditorEntities(),
page.waitForResponse( ( response ) =>
response.url().includes( 'wp-json/wp/v2/templates/' )
),
] );
await page.goto( blockData.productPage, {
waitUntil: 'networkidle',
} );
const blockFrontend = await frontendUtils.getBlockByName(
'woocommerce/product-gallery'
);
await expect( blockFrontend ).toBeVisible();
} );
test.describe( `${ blockData.name } Settings`, () => {
test( 'Hide correctly the thumbnails', async ( {
page,
editor,
editorUtils,
admin,
} ) => {
await addBlock( admin, editor, editorUtils );
await (
await editorUtils.getBlockByName( blockData.name )
).click();
await editor.openDocumentSettingsSidebar();
await page
.locator( blockData.selectors.editor.noThumbnailsOption )
.click();
const isVisible = await page
.locator( blockData.selectors.editor.thumbnails )
.isVisible();
expect( isVisible ).toBe( false );
await editor.saveSiteEditorEntities();
await page.goto( blockData.productPage, {
waitUntil: 'networkidle',
} );
} );
// We can test the left position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-nowrap".
// - The Thumbnails block has a lower index than the Large Image block.
test( 'Position thumbnails on the left of the large image', async ( {
page,
editor,
editorUtils,
frontendUtils,
} ) => {
// Currently we are adding the block under the legacy Product Image Gallery block, but in the future we have to add replace the product gallery block with this block.
const parentBlock = await editorUtils.getBlockByName(
'woocommerce/product-image-gallery'
);
const clientId =
( await parentBlock.getAttribute( 'data-block' ) ) ?? '';
const parentClientId =
( await editorUtils.getBlockRootClientId( clientId ) ) ?? '';
await editor.selectBlocks( parentBlock );
await editorUtils.insertBlock(
{ name: 'woocommerce/product-gallery' },
undefined,
parentClientId
);
await (
await editorUtils.getBlockByName( blockData.name )
).click();
await editor.openDocumentSettingsSidebar();
await page
.locator(
blockData.selectors.editor.leftPositionThumbnailsOption
)
.click();
await page.waitForTimeout( 500 );
const groupBlock = await editorUtils.getBlockByTypeWithParent(
'core/group',
'woocommerce/product-gallery'
);
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsBlockEarlier =
await editorUtils.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'woocommerce/product-gallery-large-image'
);
expect( isThumbnailsBlockEarlier ).toBe( true );
await Promise.all( [
editor.saveSiteEditorEntities(),
page.waitForResponse( ( response ) =>
response.url().includes( 'wp-json/wp/v2/templates/' )
),
] );
await page.goto( blockData.productPage, {
waitUntil: 'networkidle',
} );
const groupBlockFrontend =
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
);
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThan(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails',
'woocommerce/product-gallery-large-image'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( true );
} );
// We can test the bottom position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-vertical".
// - The Thumbnails block has a higher index than the Large Image block.
test( 'Position thumbnails on the bottom of the large image', async ( {
page,
editor,
editorUtils,
frontendUtils,
} ) => {
// Currently we are adding the block under the legacy Product Image Gallery block, but in the future we have to add replace the product gallery block with this block.
const parentBlock = await editorUtils.getBlockByName(
'woocommerce/product-image-gallery'
);
const clientId =
( await parentBlock.getAttribute( 'data-block' ) ) ?? '';
const parentClientId =
( await editorUtils.getBlockRootClientId( clientId ) ) ?? '';
await editor.selectBlocks( parentBlock );
await editorUtils.insertBlock(
{ name: 'woocommerce/product-gallery' },
undefined,
parentClientId
);
await (
await editorUtils.getBlockByName( blockData.name )
).click();
await editor.openDocumentSettingsSidebar();
await page
.locator(
blockData.selectors.editor.bottomPositionThumbnailsOption
)
.click();
await page.waitForTimeout( 500 );
const groupBlock = await editorUtils.getBlockByTypeWithParent(
'core/group',
'woocommerce/product-gallery'
);
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-vertical' );
const isThumbnailsBlockEarlier =
await editorUtils.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'woocommerce/product-gallery-large-image'
);
expect( isThumbnailsBlockEarlier ).toBe( false );
await Promise.all( [
editor.saveSiteEditorEntities(),
page.waitForResponse( ( response ) =>
response.url().includes( 'wp-json/wp/v2/templates/' )
),
] );
await page.goto( blockData.productPage, {
waitUntil: 'networkidle',
} );
const groupBlockFrontend =
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
);
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain(
'is-vertical'
);
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThan(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails',
'woocommerce/product-gallery-large-image'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( false );
} );
// We can test the right position of thumbnails by cross-checking:
// - The Gallery block has the classes "is-layout-flex" and "is-nowrap".
// - The Thumbnails block has a higher index than the Large Image block.
test( 'Position thumbnails on the right of the large image', async ( {
page,
editor,
editorUtils,
frontendUtils,
} ) => {
// Currently we are adding the block under the legacy Product Image Gallery block, but in the future we have to add replace the product gallery block with this block.
const parentBlock = await editorUtils.getBlockByName(
'woocommerce/product-image-gallery'
);
const clientId =
( await parentBlock.getAttribute( 'data-block' ) ) ?? '';
const parentClientId =
( await editorUtils.getBlockRootClientId( clientId ) ) ?? '';
await editor.selectBlocks( parentBlock );
await editorUtils.insertBlock(
{ name: 'woocommerce/product-gallery' },
undefined,
parentClientId
);
await (
await editorUtils.getBlockByName( blockData.name )
).click();
await editor.openDocumentSettingsSidebar();
await page
.locator(
blockData.selectors.editor.rightPositionThumbnailsOption
)
.click();
await page.waitForTimeout( 500 );
const groupBlock = await editorUtils.getBlockByTypeWithParent(
'core/group',
'woocommerce/product-gallery'
);
const groupBlockClassAttribute = await groupBlock.getAttribute(
'class'
);
expect( groupBlockClassAttribute ).toContain( 'is-layout-flex' );
expect( groupBlockClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsBlockEarlier =
await editorUtils.isBlockEarlierThan(
groupBlock,
'woocommerce/product-gallery-thumbnails',
'woocommerce/product-gallery-large-image'
);
expect( isThumbnailsBlockEarlier ).toBe( false );
await Promise.all( [
editor.saveSiteEditorEntities(),
page.waitForResponse( ( response ) =>
response.url().includes( 'wp-json/wp/v2/templates/' )
),
] );
await page.goto( blockData.productPage, {
waitUntil: 'networkidle',
} );
const groupBlockFrontend =
await frontendUtils.getBlockByClassWithParent(
'wp-block-group',
'woocommerce/product-gallery'
);
const groupBlockFrontendClassAttribute =
await groupBlockFrontend.getAttribute( 'class' );
expect( groupBlockFrontendClassAttribute ).toContain(
'is-layout-flex'
);
expect( groupBlockFrontendClassAttribute ).toContain( 'is-nowrap' );
const isThumbnailsFrontendBlockEarlier =
await frontendUtils.isBlockEarlierThan(
groupBlockFrontend,
'woocommerce/product-gallery-thumbnails',
'woocommerce/product-gallery-large-image'
);
expect( isThumbnailsFrontendBlockEarlier ).toBe( false );
} );
} );
} );

View File

@ -0,0 +1,26 @@
/**
* External dependencies
*/
import { EditorUtils } from '@woocommerce/e2e-utils';
import { Admin, Editor } from '@wordpress/e2e-test-utils-playwright';
// Define a utility function to add the "woocommerce/product-gallery" block to the editor
export const addBlock = async (
admin: Admin,
editor: Editor,
editorUtils: EditorUtils
) => {
// Visit the site editor for the specific product page
await admin.visitSiteEditor( {
postId: `woocommerce/woocommerce//single-product`,
postType: 'wp_template',
} );
// Enter the edit mode
await editorUtils.enterEditMode();
// Insert the "woocommerce/product-gallery" block
await editor.insertBlock( {
name: 'woocommerce/product-gallery',
} );
};

View File

@ -17,6 +17,15 @@ export class EditorUtils {
return this.editor.canvas.locator( `[data-type="${ name }"]` );
}
async getBlockByTypeWithParent( name: string, parentName: string ) {
const parentBlock = await this.getBlockByName( parentName );
if ( ! parentBlock ) {
throw new Error( `Parent block "${ parentName }" not found.` );
}
const block = await parentBlock.locator( `[data-type="${ name }"]` );
return block;
}
// todo: Make a PR to @wordpress/e2e-test-utils-playwright to add this method.
/**
* Inserts a block after a given client ID.
@ -71,4 +80,48 @@ export class EditorUtils {
);
await this.editor.canvas.click( 'body' );
}
async isBlockEarlierThan< T >(
containerBlock: T,
firstBlock: string,
secondBlock: string
) {
const container =
containerBlock instanceof Function
? await containerBlock()
: containerBlock;
if ( ! container ) {
throw new Error( 'Container block not found.' );
}
const childBlocks = container.locator( ':scope > .wp-block' );
let firstBlockIndex = -1;
let secondBlockIndex = -1;
for ( let i = 0; i < ( await childBlocks.count() ); i++ ) {
const blockName = await childBlocks
.nth( i )
.getAttribute( 'data-type' );
if ( blockName === firstBlock ) {
firstBlockIndex = i;
}
if ( blockName === secondBlock ) {
secondBlockIndex = i;
}
if ( firstBlockIndex !== -1 && secondBlockIndex !== -1 ) {
break;
}
}
if ( firstBlockIndex === -1 || secondBlockIndex === -1 ) {
throw new Error( 'Both blocks must exist within the editor' );
}
return firstBlockIndex < secondBlockIndex;
}
}

View File

@ -18,6 +18,15 @@ export class FrontendUtils {
return this.page.locator( `[data-block-name="${ name }"]` );
}
async getBlockByClassWithParent( blockClass: string, parentName: string ) {
const parentBlock = await this.getBlockByName( parentName );
if ( ! parentBlock ) {
throw new Error( `Parent block "${ parentName }" not found.` );
}
const block = await parentBlock.locator( `.${ blockClass }` );
return block;
}
async addToCart() {
await this.page.click( 'text=Add to cart' );
await this.page.waitForLoadState( 'networkidle' );
@ -28,4 +37,48 @@ export class FrontendUtils {
waitUntil: 'networkidle',
} );
}
async isBlockEarlierThan< T >(
containerBlock: T,
firstBlock: string,
secondBlock: string
) {
const container =
containerBlock instanceof Function
? await containerBlock()
: containerBlock;
if ( ! container ) {
throw new Error( 'Container block not found.' );
}
const childBlocks = container.locator( '[data-block-name]' );
let firstBlockIndex = -1;
let secondBlockIndex = -1;
for ( let i = 0; i < ( await childBlocks.count() ); i++ ) {
const blockName = await childBlocks
.nth( i )
.getAttribute( 'data-block-name' );
if ( blockName === firstBlock ) {
firstBlockIndex = i;
}
if ( blockName === secondBlock ) {
secondBlockIndex = i;
}
if ( firstBlockIndex !== -1 && secondBlockIndex !== -1 ) {
break;
}
}
if ( firstBlockIndex === -1 || secondBlockIndex === -1 ) {
throw new Error( 'Both blocks must exist within the editor' );
}
return firstBlockIndex < secondBlockIndex;
}
}