* Add empty block theme to mock E2E tests
* Install empty theme in the test WP instance
This commit is contained in:
Lucio Giannotta 2022-02-21 11:01:42 +01:00 committed by GitHub
parent 71bd69ad07
commit 497820dcb9
15 changed files with 696 additions and 42244 deletions

View File

@ -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

View File

@ -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",

View File

@ -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
);
} );
}

View File

@ -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\\"} /-->"
`;

View File

@ -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,
} );
} );
} );
} );

View File

@ -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 );
}

View File

@ -0,0 +1,3 @@
<!-- wp:site-title /-->
<!-- wp:site-tagline /-->

View File

@ -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 -->

View File

@ -0,0 +1,5 @@
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:post-title /-->
<!-- wp:post-content {"layout":{"inherit":true}} /-->

View File

@ -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' );

View File

@ -0,0 +1,3 @@
<?php
// phpcs:ignoreFile

View File

@ -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.
*/

View File

@ -0,0 +1,23 @@
{
"version": 2,
"settings": {
"color": {
"palette": [
{
"slug": "foreground",
"color": "#3F67C6",
"name": "Foreground"
}
]
}
},
"styles": {
"blocks": {
"core/post-title": {
"typography": {
"fontWeight": "700"
}
}
}
}
}

View File

@ -0,0 +1,10 @@
{
"version": 2,
"settings": {
"appearanceTools": true,
"layout": {
"contentSize": "840px",
"wideSize": "1100px"
}
}
}