E2E tests for Single Product Template (https://github.com/woocommerce/woocommerce-blocks/pull/5722)
* Add empty block theme to mock E2E tests * Install empty theme in the test WP instance
This commit is contained in:
parent
71bd69ad07
commit
497820dcb9
|
@ -6,7 +6,8 @@
|
|||
"."
|
||||
],
|
||||
"themes": [
|
||||
"https://downloads.wordpress.org/theme/storefront.latest-stable.zip"
|
||||
"https://downloads.wordpress.org/theme/storefront.latest-stable.zip",
|
||||
"./tests/mocks/emptytheme"
|
||||
],
|
||||
"mappings": {
|
||||
".htaccess": "./bin/.htaccess"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -105,6 +105,7 @@
|
|||
"@types/jest": "26.0.24",
|
||||
"@types/jquery": "3.5.13",
|
||||
"@types/lodash": "4.14.178",
|
||||
"@types/puppeteer": "^5.4.4",
|
||||
"@types/react": "16.14.23",
|
||||
"@types/wordpress__block-editor": "6.0.5",
|
||||
"@types/wordpress__compose": "4.0.1",
|
||||
|
@ -128,7 +129,7 @@
|
|||
"@wordpress/data-controls": "2.2.7",
|
||||
"@wordpress/dependency-extraction-webpack-plugin": "3.2.1",
|
||||
"@wordpress/dom": "3.2.7",
|
||||
"@wordpress/e2e-test-utils": "5.4.10",
|
||||
"@wordpress/e2e-test-utils": "^6.0.1",
|
||||
"@wordpress/element": "4.0.4",
|
||||
"@wordpress/env": "4.1.3",
|
||||
"@wordpress/html-entities": "3.2.3",
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @file Generic utility functions for Puppeteer
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('@types/puppeteer').Page} Page
|
||||
* @typedef {import('@types/puppeteer').ElementHandle} ElementHandle
|
||||
* @typedef {import('@wordpress/blocks').Block} WPBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks whether an element exists under a certain context
|
||||
*
|
||||
* @param {string} selector The selector for the desired element
|
||||
* @param {Page | ElementHandle} [root=page] The root from which to search for the selector
|
||||
*
|
||||
* @return {Promise<boolean>} Whether the element exists or not
|
||||
*/
|
||||
export async function elementExists( selector, root = page ) {
|
||||
return !! ( await root.$( selector ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text value of an element
|
||||
*
|
||||
* If the element is an `input` it will get the `value`, otherwise,
|
||||
* it will get the `textContent`.
|
||||
*
|
||||
* @param {string} selector The selector for the desired element
|
||||
* @param {Page | ElementHandle} [root=page] The root from which to search for the selector
|
||||
*
|
||||
* @return {Promise<string[]>} An array of text contained in those selected elements
|
||||
*/
|
||||
export function getTextContent( selector, root = page ) {
|
||||
return root.$$eval( selector, ( $elements ) => {
|
||||
return $elements.map(
|
||||
( $element ) => $element.value || $element.textContent
|
||||
);
|
||||
} );
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Store Editing Templates Single Product block template should contain the "WooCommerce Single Product Block" legacy template 1`] = `
|
||||
"<!-- wp:template-part {\\"slug\\":\\"header\\",\\"theme\\":\\"emptytheme\\"} /-->
|
||||
|
||||
<!-- wp:group {\\"layout\\":{\\"inherit\\":true}} -->
|
||||
<div class=\\"wp-block-group\\"><!-- wp:woocommerce/legacy-template {\\"template\\":\\"single-product\\"} /--></div>
|
||||
<!-- /wp:group -->
|
||||
|
||||
<!-- wp:template-part {\\"slug\\":\\"footer\\",\\"theme\\":\\"emptytheme\\"} /-->"
|
||||
`;
|
|
@ -0,0 +1,180 @@
|
|||
import {
|
||||
activateTheme,
|
||||
canvas,
|
||||
getCurrentSiteEditorContent,
|
||||
insertBlock,
|
||||
trashAllPosts,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import {
|
||||
getNormalPagePermalink,
|
||||
visitPostOfType,
|
||||
} from '@woocommerce/blocks-test-utils';
|
||||
import {
|
||||
DEFAULT_TIMEOUT,
|
||||
filterCurrentBlocks,
|
||||
getAllTemplates,
|
||||
goToSiteEditor,
|
||||
saveTemplate,
|
||||
waitForCanvas,
|
||||
} from '../../utils';
|
||||
|
||||
function blockSelector( id ) {
|
||||
return `[data-type="${ id }"]`;
|
||||
}
|
||||
|
||||
function defaultTemplateProps( templateTitle ) {
|
||||
return {
|
||||
templateTitle,
|
||||
addedBy: WOOCOMMERCE_ID,
|
||||
hasActions: false,
|
||||
};
|
||||
}
|
||||
|
||||
function legacyBlockSelector( title ) {
|
||||
return `${ blockSelector(
|
||||
'woocommerce/legacy-template'
|
||||
) }[data-title="${ title }"]`;
|
||||
}
|
||||
|
||||
const BLOCK_DATA = {
|
||||
'single-product': {
|
||||
attributes: {
|
||||
placeholder: 'single-product',
|
||||
template: 'single-product',
|
||||
title: 'WooCommerce Single Product Block',
|
||||
},
|
||||
name: 'woocommerce/legacy-template',
|
||||
},
|
||||
};
|
||||
|
||||
const SELECTORS = {
|
||||
blocks: {
|
||||
paragraph: blockSelector( 'core/paragraph' ),
|
||||
singleProduct: legacyBlockSelector(
|
||||
'WooCommerce Single Product Block'
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const CUSTOMIZED_STRING = 'My awesome customization';
|
||||
const WOOCOMMERCE_ID = 'woocommerce/woocommerce';
|
||||
const WOOCOMMERCE_PARSED_ID = 'WooCommerce';
|
||||
|
||||
describe( 'Store Editing Templates', () => {
|
||||
beforeAll( async () => {
|
||||
await activateTheme( 'emptytheme' );
|
||||
await trashAllPosts( 'wp_template' );
|
||||
await trashAllPosts( 'wp_template_part' );
|
||||
} );
|
||||
|
||||
afterAll( async () => {
|
||||
await activateTheme( 'twentytwentyone' );
|
||||
} );
|
||||
|
||||
describe( 'Single Product block template', () => {
|
||||
it( 'default template from WooCommerce Blocks is available on an FSE theme', async () => {
|
||||
const EXPECTED_TEMPLATE = defaultTemplateProps( 'Single Product' );
|
||||
|
||||
await goToSiteEditor( '?postType=wp_template' );
|
||||
|
||||
const templates = await getAllTemplates();
|
||||
|
||||
try {
|
||||
expect( templates ).toContainEqual( EXPECTED_TEMPLATE );
|
||||
} catch ( ok ) {
|
||||
// Depending on the speed of the execution and whether Chrome is headless or not
|
||||
// the id might be parsed or not
|
||||
|
||||
expect( templates ).toContainEqual( {
|
||||
...EXPECTED_TEMPLATE,
|
||||
addedBy: WOOCOMMERCE_PARSED_ID,
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
it( 'should contain the "WooCommerce Single Product Block" legacy template', async () => {
|
||||
const templateQuery = addQueryArgs( '', {
|
||||
postId: 'woocommerce/woocommerce//single-product',
|
||||
postType: 'wp_template',
|
||||
} );
|
||||
|
||||
await goToSiteEditor( templateQuery );
|
||||
await waitForCanvas();
|
||||
|
||||
const [ legacyBlock ] = await filterCurrentBlocks(
|
||||
( block ) => block.name === BLOCK_DATA[ 'single-product' ].name
|
||||
);
|
||||
|
||||
// Comparing only the `template` property currently
|
||||
// because the other properties seem to be slightly unreliable.
|
||||
// Investigation pending.
|
||||
expect( legacyBlock.attributes.template ).toBe(
|
||||
BLOCK_DATA[ 'single-product' ].attributes.template
|
||||
);
|
||||
expect( await getCurrentSiteEditorContent() ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
it( 'should show the action menu if the template has been customized by the user', async () => {
|
||||
const EXPECTED_TEMPLATE = {
|
||||
...defaultTemplateProps( 'Single Product' ),
|
||||
hasActions: true,
|
||||
};
|
||||
|
||||
const templateQuery = addQueryArgs( '', {
|
||||
postId: 'woocommerce/woocommerce//single-product',
|
||||
postType: 'wp_template',
|
||||
} );
|
||||
|
||||
await goToSiteEditor( templateQuery );
|
||||
await waitForCanvas();
|
||||
await insertBlock( 'Paragraph' );
|
||||
await page.keyboard.type( CUSTOMIZED_STRING );
|
||||
await saveTemplate();
|
||||
|
||||
await goToSiteEditor( '?postType=wp_template' );
|
||||
const templates = await getAllTemplates();
|
||||
|
||||
try {
|
||||
expect( templates ).toContainEqual( EXPECTED_TEMPLATE );
|
||||
} catch ( ok ) {
|
||||
// Depending on the speed of the execution and whether Chrome is headless or not
|
||||
// the id might be parsed or not
|
||||
|
||||
expect( templates ).toContainEqual( {
|
||||
...EXPECTED_TEMPLATE,
|
||||
addedBy: WOOCOMMERCE_PARSED_ID,
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
it( 'should preserve and correctly show the user customization on the back-end', async () => {
|
||||
const templateQuery = addQueryArgs( '', {
|
||||
postId: 'woocommerce/woocommerce//single-product',
|
||||
postType: 'wp_template',
|
||||
} );
|
||||
|
||||
await goToSiteEditor( templateQuery );
|
||||
await waitForCanvas();
|
||||
|
||||
await expect( canvas() ).toMatchElement(
|
||||
SELECTORS.blocks.paragraph,
|
||||
{ text: CUSTOMIZED_STRING, timeout: DEFAULT_TIMEOUT }
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should show the user customization on the front-end', async () => {
|
||||
const exampleProductName = 'Woo Single #1';
|
||||
|
||||
await visitPostOfType( exampleProductName, 'product' );
|
||||
const permalink = await getNormalPagePermalink();
|
||||
|
||||
await page.goto( permalink );
|
||||
|
||||
await expect( page ).toMatchElement( 'p', {
|
||||
text: CUSTOMIZED_STRING,
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -2,13 +2,51 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
disableSiteEditorWelcomeGuide,
|
||||
openGlobalBlockInserter,
|
||||
pressKeyWithModifier,
|
||||
visitAdminPage,
|
||||
visitSiteEditor,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
import { getQueryArgs } from '@wordpress/url';
|
||||
import { WP_ADMIN_DASHBOARD } from '@woocommerce/e2e-utils';
|
||||
|
||||
const INSERTER_SEARCH_SELECTOR =
|
||||
'.components-search-control__input,.block-editor-inserter__search input,.block-editor-inserter__search-input,input.block-editor-inserter__search';
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { elementExists, getTextContent } from './page-utils';
|
||||
|
||||
/**
|
||||
* @typedef {import('@types/puppeteer').ElementHandle} ElementHandle
|
||||
* @typedef {import('@wordpress/blocks').Block} WPBlock
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ addedBy: string, hasActions: boolean, templateTitle: string }} TemplateTableItem
|
||||
*/
|
||||
|
||||
export const DEFAULT_TIMEOUT = 30000;
|
||||
|
||||
const SELECTORS = {
|
||||
canvas: 'iframe[name="editor-canvas"]',
|
||||
inserter: {
|
||||
search:
|
||||
'.components-search-control__input,.block-editor-inserter__search input,.block-editor-inserter__search-input,input.block-editor-inserter__search',
|
||||
},
|
||||
templatesListTable: {
|
||||
actionsContainer: '.edit-site-list-table__actions',
|
||||
cells: '.edit-site-list-table-column',
|
||||
headings: 'thead th.edit-site-list-table-column',
|
||||
root: '.edit-site-list-table',
|
||||
rows: '.edit-site-list-table-row',
|
||||
templateTitle: '[data-wp-component="Heading"]',
|
||||
},
|
||||
toolbar: {
|
||||
confirmSave: '.editor-entities-saved-states__save-button',
|
||||
saveButton: '.edit-site-save-button__button',
|
||||
savePrompt: '.entities-saved-states__text-prompt',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Search for block in the global inserter.
|
||||
|
@ -18,8 +56,8 @@ const INSERTER_SEARCH_SELECTOR =
|
|||
* @param {string} searchTerm The text to search the inserter for.
|
||||
*/
|
||||
export async function searchForBlock( searchTerm ) {
|
||||
await page.waitForSelector( INSERTER_SEARCH_SELECTOR );
|
||||
await page.focus( INSERTER_SEARCH_SELECTOR );
|
||||
await page.waitForSelector( SELECTORS.inserter.search );
|
||||
await page.focus( SELECTORS.inserter.search );
|
||||
await pressKeyWithModifier( 'primary', 'a' );
|
||||
await page.keyboard.type( searchTerm );
|
||||
}
|
||||
|
@ -92,3 +130,113 @@ export const isBlockInsertedInWidgetsArea = async ( blockName ) => {
|
|||
.length ) > 0
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Visits the Site Editor main page in Core WordPress
|
||||
*
|
||||
* There are two different possible site editor pages:
|
||||
*
|
||||
* 1. `themes.php?page=gutenberg-edit-site` is the one used and available if the Gutenberg plugin is enabled.
|
||||
* 2. `site-editor.php` is the one available in WP Core.
|
||||
*
|
||||
* @param {string} query String to be serialized as query portion of URL.
|
||||
* @param {'core' | 'gutenberg'} [editorContext='core'] Whether to go to the Gutenberg URL or the Core one.
|
||||
*/
|
||||
export async function goToSiteEditor( query, editorContext = 'core' ) {
|
||||
if ( editorContext === 'gutenberg' ) {
|
||||
const queryArgs = getQueryArgs( query );
|
||||
|
||||
await visitSiteEditor( queryArgs );
|
||||
} else {
|
||||
await visitAdminPage( 'site-editor.php', query );
|
||||
await disableSiteEditorWelcomeGuide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the Gutenberg canvas to be available
|
||||
*
|
||||
* @param {number} [timeout=DEFAULT_TIMEOUT] The amount of ms to wait for the element
|
||||
*
|
||||
* @return {Promise<?ElementHandle>} The canvas element handle
|
||||
*/
|
||||
export function waitForCanvas( timeout = DEFAULT_TIMEOUT ) {
|
||||
return page.waitForSelector( SELECTORS.canvas, { timeout } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a template
|
||||
*/
|
||||
export async function saveTemplate() {
|
||||
const { confirmSave, saveButton, savePrompt } = SELECTORS.toolbar;
|
||||
|
||||
await page.click( saveButton );
|
||||
await page.waitForSelector( savePrompt );
|
||||
await page.click( confirmSave );
|
||||
await page.waitForSelector( `${ saveButton }[aria-disabled="true"]` );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all available templates from the template list table UI
|
||||
*
|
||||
* @return {Promise<TemplateTableItem[]>} A promise of an array of informations about the templates extracted from the UI
|
||||
*/
|
||||
export async function getAllTemplates() {
|
||||
const { templatesListTable } = SELECTORS;
|
||||
|
||||
await page.waitForSelector( templatesListTable.root );
|
||||
const table = await page.$( templatesListTable.root );
|
||||
|
||||
if ( ! table ) throw new Error( 'Templates table not found' );
|
||||
|
||||
const rows = await table.$$( templatesListTable.rows );
|
||||
|
||||
return Promise.all(
|
||||
rows.map( async ( row ) => ( {
|
||||
addedBy: (
|
||||
await getTextContent( templatesListTable.cells, row )
|
||||
)[ 1 ],
|
||||
hasActions: await elementExists(
|
||||
templatesListTable.actionsContainer,
|
||||
row
|
||||
),
|
||||
templateTitle: (
|
||||
await getTextContent( templatesListTable.templateTitle, row )
|
||||
)[ 0 ],
|
||||
} ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the blocks that fulfill a given predicate
|
||||
*
|
||||
* @param {( block: WPBlock ) => boolean} predicate The function invoked per iteration
|
||||
* @return {Promise< Partial< WPBlock >[] >} The blocks which have been found
|
||||
*/
|
||||
export async function filterCurrentBlocks( predicate ) {
|
||||
/**
|
||||
* @type {WPBlock[]}
|
||||
*/
|
||||
const blocks = await page.evaluate( () => {
|
||||
/**
|
||||
* Gets all serializeable data from a block
|
||||
*
|
||||
* @param {WPBlock} block A Gutenberg Block
|
||||
* @return {Partial<WPBlock>} A block with unserializeable values turned to `null`
|
||||
*/
|
||||
function getSerializeableBlockData( block ) {
|
||||
return JSON.parse( JSON.stringify( block ) );
|
||||
}
|
||||
|
||||
const blockEditorStore = window.wp.data.select( 'core/block-editor' );
|
||||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
const allClientIds = blockEditorStore.getClientIdsWithDescendants();
|
||||
|
||||
return allClientIds.map( ( id ) =>
|
||||
getSerializeableBlockData( blockEditorStore.getBlock( id ) )
|
||||
);
|
||||
} );
|
||||
|
||||
return blocks.filter( predicate );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<!-- wp:site-title /-->
|
||||
|
||||
<!-- wp:site-tagline /-->
|
|
@ -0,0 +1,10 @@
|
|||
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
|
||||
|
||||
<!-- wp:query {"queryId":1,"query":{"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","sticky":""}} -->
|
||||
<div class="wp-block-query">
|
||||
<!-- wp:post-template -->
|
||||
<!-- wp:post-title {"isLink":true} /-->
|
||||
<!-- wp:post-excerpt /-->
|
||||
<!-- /wp:post-template -->
|
||||
</div>
|
||||
<!-- /wp:query -->
|
|
@ -0,0 +1,5 @@
|
|||
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
|
||||
|
||||
<!-- wp:post-title /-->
|
||||
|
||||
<!-- wp:post-content {"layout":{"inherit":true}} /-->
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
||||
if ( ! function_exists( 'emptytheme_support' ) ) :
|
||||
function emptytheme_support() {
|
||||
|
||||
// Adding support for core block visual styles.
|
||||
add_theme_support( 'wp-block-styles' );
|
||||
|
||||
// Enqueue editor styles.
|
||||
add_editor_style( 'style.css' );
|
||||
}
|
||||
add_action( 'after_setup_theme', 'emptytheme_support' );
|
||||
endif;
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles.
|
||||
*/
|
||||
function emptytheme_scripts() {
|
||||
// Enqueue theme stylesheet.
|
||||
wp_enqueue_style( 'emptytheme-style', get_template_directory_uri() . '/style.css', array(), wp_get_theme()->get( 'Version' ) );
|
||||
}
|
||||
|
||||
add_action( 'wp_enqueue_scripts', 'emptytheme_scripts' );
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
// phpcs:ignoreFile
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
Theme Name: Emptytheme
|
||||
Theme URI: https://github.com/wordpress/theme-experiments/
|
||||
Author: the WordPress team
|
||||
Description: The base for a block-based theme.
|
||||
Requires at least: 5.3
|
||||
Tested up to: 5.5
|
||||
Requires PHP: 5.6
|
||||
Version: 1.0
|
||||
License: GNU General Public License v2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
Text Domain: emptytheme
|
||||
Emptytheme WordPress Theme, (C) 2021 WordPress.org
|
||||
Emptytheme is distributed under the terms of the GNU GPL.
|
||||
*/
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"version": 2,
|
||||
"settings": {
|
||||
"color": {
|
||||
"palette": [
|
||||
{
|
||||
"slug": "foreground",
|
||||
"color": "#3F67C6",
|
||||
"name": "Foreground"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"blocks": {
|
||||
"core/post-title": {
|
||||
"typography": {
|
||||
"fontWeight": "700"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"version": 2,
|
||||
"settings": {
|
||||
"appearanceTools": true,
|
||||
"layout": {
|
||||
"contentSize": "840px",
|
||||
"wideSize": "1100px"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue