From fcebc079b7b0bad516203d2610b8366d969ed282 Mon Sep 17 00:00:00 2001 From: ramon Date: Wed, 26 Jun 2024 16:56:22 +1000 Subject: [PATCH] Adding some explanatory comments Add rudimentary E2E test covering block style partials, applying them, updating them and viewing styles revisions. --- .../src/hooks/block-style-variation.js | 4 + .../block-style-variations.spec.js | 303 ++++++++++++++++++ .../block-templates/singular.html | 2 + .../styles/block-style-variation-a.json | 20 ++ .../styles/block-style-variation-b.json | 20 ++ .../styles/block-style-variation-c.json | 20 ++ 6 files changed, 369 insertions(+) create mode 100644 test/e2e/specs/site-editor/block-style-variations.spec.js create mode 100644 test/gutenberg-test-themes/style-variations/block-templates/singular.html create mode 100644 test/gutenberg-test-themes/style-variations/styles/block-style-variation-a.json create mode 100644 test/gutenberg-test-themes/style-variations/styles/block-style-variation-b.json create mode 100644 test/gutenberg-test-themes/style-variations/styles/block-style-variation-c.json diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index 1b4cc3ad5afe48..f00c96d640a575 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -60,6 +60,7 @@ function getVariationNameFromClass( className, registeredStyles = [] ) { return null; } +// A helper component to apply a style override using the useStyleOverride hook. function OverrideStyles( { override } ) { useStyleOverride( override ); } @@ -69,6 +70,9 @@ function OverrideStyles( { override } ) { * based on an incoming theme config. If a matching style is found in the config, * a new override is created and returned. The overrides can be used in conjunction with * useStyleOverride to apply the new styles to the editor. + * NOTE: This component is required to load global styles revisions config. Style variation overrides are + * generated using the canvas's current global styles (see useBlockStyleVariation()). This component is, + * however, due to be refactored and therefore it's use elsewhere is not recommended. * * @param {Object} props Props. * @param {Object} props.config A global styles object, containing settings and styles. diff --git a/test/e2e/specs/site-editor/block-style-variations.spec.js b/test/e2e/specs/site-editor/block-style-variations.spec.js new file mode 100644 index 00000000000000..88539c844a6a89 --- /dev/null +++ b/test/e2e/specs/site-editor/block-style-variations.spec.js @@ -0,0 +1,303 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); +const TEST_PAGE_TITLE = 'Test Page for Block Style Variations'; +test.use( { + siteEditorBlockStyleVariations: async ( { page, editor }, use ) => { + await use( new SiteEditorBlockStyleVariations( { page, editor } ) ); + }, +} ); + +test.describe( 'Block Style Variations', () => { + let stylesPostId; + test.beforeAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.activateTheme( + 'gutenberg-test-themes/style-variations' + ), + requestUtils.deleteAllPages(), + ] ); + stylesPostId = await requestUtils.getCurrentThemeGlobalStylesPostId(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await Promise.all( [ + requestUtils.activateTheme( 'twentytwentyone' ), + requestUtils.deleteAllPages(), + ] ); + } ); + + test.beforeEach( async ( { requestUtils, admin } ) => { + await Promise.all( [ + requestUtils.deleteAllPages(), + admin.visitSiteEditor(), + ] ); + } ); + + test( 'apply block styles variations to nested blocks', async ( { + editor, + page, + } ) => { + await draftNewPage( page ); + await addPageContent( editor, page ); + const firstGroup = editor.canvas + .locator( '[data-type="core/group"]' ) + .first(); + const secondGroup = editor.canvas + .locator( '[data-type="core/group"]' ) + .nth( 1 ); + const thirdGroup = editor.canvas + .locator( '[data-type="core/group"]' ) + .nth( 2 ); + + // Apply a block style to the parent Group block. + await editor.selectBlocks( firstGroup ); + await editor.openDocumentSettingsSidebar(); + await page.getByRole( 'tab', { name: 'Styles' } ).click(); + await page + .getByRole( 'button', { name: 'Block Style Variation A' } ) + .click( { force: true } ); + + // Check parent styles. + await expect( firstGroup ).toHaveCSS( 'border-style', 'dotted' ); + await expect( firstGroup ).toHaveCSS( 'border-width', '1px' ); + + // Check nested child and grandchild group block inherited styles. + await expect( secondGroup ).toHaveCSS( 'border-style', 'solid' ); + await expect( secondGroup ).toHaveCSS( 'border-width', '11px' ); + await expect( thirdGroup ).toHaveCSS( 'border-style', 'solid' ); + await expect( thirdGroup ).toHaveCSS( 'border-width', '11px' ); + + // Apply a block style to the first, nested Group block. + await editor.selectBlocks( secondGroup ); + await page.getByRole( 'tab', { name: 'Styles' } ).click(); + await page + .getByRole( 'button', { name: 'Block Style Variation B' } ) + .click( { force: true } ); + + // Check nested child styles. + await expect( secondGroup ).toHaveCSS( 'border-style', 'dashed' ); + await expect( secondGroup ).toHaveCSS( 'border-width', '2px' ); + + // Check nested grandchild group block inherited styles. + await expect( thirdGroup ).toHaveCSS( 'border-style', 'groove' ); + await expect( thirdGroup ).toHaveCSS( 'border-width', '22px' ); + } ); + + test( 'update block style variations in global styles and check revisions match styles', async ( { + editor, + page, + siteEditorBlockStyleVariations, + } ) => { + await draftNewPage( page ); + await addPageContent( editor, page ); + const firstGroup = editor.canvas + .locator( '[data-type="core/group"]' ) + .first(); + const secondGroup = editor.canvas + .locator( '[data-type="core/group"]' ) + .nth( 1 ); + const thirdGroup = editor.canvas + .locator( '[data-type="core/group"]' ) + .nth( 2 ); + + // Apply a block style to the parent Group block. + await editor.selectBlocks( firstGroup ); + await editor.openDocumentSettingsSidebar(); + await page.getByRole( 'tab', { name: 'Styles' } ).click(); + await page + .getByRole( 'button', { name: 'Block Style Variation A' } ) + .click( { force: true } ); + + // Apply a block style to the first, nested Group block. + await editor.selectBlocks( secondGroup ); + await page.getByRole( 'tab', { name: 'Styles' } ).click(); + await page + .getByRole( 'button', { name: 'Block Style Variation B' } ) + .click( { force: true } ); + + // Update user global styles with new block style variation values. + await siteEditorBlockStyleVariations.saveRevision( stylesPostId, { + blocks: { + 'core/group': { + variations: { + 'block-style-variation-a': { + border: { + width: '3px', + style: 'outset', + }, + }, + 'block-style-variation-b': { + border: { + width: '4px', + style: 'double', + }, + }, + }, + }, + }, + } ); + + // Check parent styles. + await expect( firstGroup ).toHaveCSS( 'border-style', 'outset' ); + await expect( firstGroup ).toHaveCSS( 'border-width', '3px' ); + + // Check nested child styles. + await expect( secondGroup ).toHaveCSS( 'border-style', 'double' ); + await expect( secondGroup ).toHaveCSS( 'border-width', '4px' ); + + // Check nested grandchild group block inherited styles. + await expect( thirdGroup ).toHaveCSS( 'border-style', 'groove' ); + await expect( thirdGroup ).toHaveCSS( 'border-width', '22px' ); + + // The initial revision styles should match the editor canvas. + await siteEditorBlockStyleVariations.openStylesPanel(); + const revisionsButton = page.getByRole( 'button', { + name: 'Revisions', + exact: true, + } ); + await revisionsButton.click(); + await expect( + page.locator( 'iframe[name="revisions"]' ) + ).toBeVisible(); + const revisionIframe = page.frameLocator( '[name="revisions"]' ); + + const revisionFirstGroup = revisionIframe + .getByRole( 'document', { + name: 'Block: Content', + } ) + .locator( '[data-type="core/group"]' ) + .first(); + const revisionSecondGroup = revisionIframe + .getByRole( 'document', { + name: 'Block: Content', + } ) + .locator( '[data-type="core/group"]' ) + .nth( 1 ); + const revisionThirdGroup = revisionIframe + .getByRole( 'document', { + name: 'Block: Content', + } ) + .locator( '[data-type="core/group"]' ) + .nth( 2 ); + + // Check parent styles. + await expect( revisionFirstGroup ).toHaveCSS( + 'border-style', + 'outset' + ); + await expect( revisionFirstGroup ).toHaveCSS( 'border-width', '3px' ); + + // Check nested child styles. + await expect( revisionSecondGroup ).toHaveCSS( + 'border-style', + 'double' + ); + await expect( revisionSecondGroup ).toHaveCSS( 'border-width', '4px' ); + + // Check nested grandchild group block inherited styles. + await expect( revisionThirdGroup ).toHaveCSS( + 'border-style', + 'groove' + ); + await expect( revisionThirdGroup ).toHaveCSS( 'border-width', '22px' ); + } ); +} ); + +class SiteEditorBlockStyleVariations { + constructor( { page, editor } ) { + this.page = page; + this.editor = editor; + } + + async openStylesPanel() { + await this.page + .getByRole( 'region', { name: 'Editor top bar' } ) + .getByRole( 'button', { name: 'Styles' } ) + .click(); + } + + async saveRevision( stylesPostId, styles = {}, settings = {} ) { + await this.page.evaluate( + async ( [ _stylesPostId, _styles, _settings ] ) => { + window.wp.data + .dispatch( 'core' ) + .editEntityRecord( 'root', 'globalStyles', _stylesPostId, { + id: _stylesPostId, + settings: _settings, + styles: _styles, + } ); + }, + [ stylesPostId, styles, settings ] + ); + await this.editor.saveSiteEditorEntities(); + } +} + +async function draftNewPage( page ) { + await page.getByRole( 'button', { name: 'Pages' } ).click(); + await page.getByRole( 'button', { name: 'Add new page' } ).click(); + await page + .locator( 'role=dialog[name="Draft a new page"i]' ) + .locator( 'role=textbox[name="Page title"i]' ) + .fill( TEST_PAGE_TITLE ); + await page.keyboard.press( 'Enter' ); + await expect( + page.locator( + `role=button[name="Dismiss this notice"i] >> text='"${ TEST_PAGE_TITLE }" successfully created.'` + ) + ).toBeVisible(); +} + +// Create a Group block with 2 nested Group blocks. +async function addPageContent( editor, page ) { + const inserterButton = page.locator( + 'role=button[name="Toggle block inserter"i]' + ); + await inserterButton.click(); + await page.type( + 'role=searchbox[name="Search for blocks and patterns"i]', + 'Group' + ); + await page.click( + 'role=listbox[name="Blocks"i] >> role=option[name="Group"i]' + ); + await editor.canvas + .locator( 'role=button[name="Group: Gather blocks in a container."i]' ) + .click(); + await editor.canvas.locator( 'role=button[name="Add block"i]' ).click(); + await page.click( + 'role=listbox[name="Blocks"i] >> role=option[name="Paragraph"i]' + ); + await page.keyboard.type( 'Parent Group Block with a Paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '/group' ); + await expect( + page.locator( 'role=option[name="Group"i][selected]' ) + ).toBeVisible(); + await page.keyboard.press( 'Enter' ); + await editor.canvas + .locator( 'role=button[name="Group: Gather blocks in a container."i]' ) + .click(); + await editor.canvas.locator( 'role=button[name="Add block"i]' ).click(); + await page.click( + 'role=listbox[name="Blocks"i] >> role=option[name="Paragraph"i]' + ); + await page.keyboard.type( 'Child Group Block with a Paragraph' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '/group' ); + await expect( + page.locator( 'role=option[name="Group"i][selected]' ) + ).toBeVisible(); + await page.keyboard.press( 'Enter' ); + await editor.canvas + .locator( 'role=button[name="Group: Gather blocks in a container."i]' ) + .click(); + await editor.canvas.locator( 'role=button[name="Add block"i]' ).click(); + await page.click( + 'role=listbox[name="Blocks"i] >> role=option[name="Paragraph"i]' + ); + await page.keyboard.type( 'Grandchild Group Block with a Paragraph' ); + await page.getByRole( 'button', { name: 'Publish', exact: true } ).click(); +} diff --git a/test/gutenberg-test-themes/style-variations/block-templates/singular.html b/test/gutenberg-test-themes/style-variations/block-templates/singular.html new file mode 100644 index 00000000000000..cd05d5fe917fea --- /dev/null +++ b/test/gutenberg-test-themes/style-variations/block-templates/singular.html @@ -0,0 +1,2 @@ + + diff --git a/test/gutenberg-test-themes/style-variations/styles/block-style-variation-a.json b/test/gutenberg-test-themes/style-variations/styles/block-style-variation-a.json new file mode 100644 index 00000000000000..92a11d953cdb5c --- /dev/null +++ b/test/gutenberg-test-themes/style-variations/styles/block-style-variation-a.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "title": "Block Style Variation A", + "slug": "block-style-variation-a", + "blockTypes": [ "core/group" ], + "styles": { + "border": { + "width": "1px", + "style": "dotted" + }, + "blocks": { + "core/group": { + "border": { + "width": "11px", + "style": "solid" + } + } + } + } +} diff --git a/test/gutenberg-test-themes/style-variations/styles/block-style-variation-b.json b/test/gutenberg-test-themes/style-variations/styles/block-style-variation-b.json new file mode 100644 index 00000000000000..79644689d18402 --- /dev/null +++ b/test/gutenberg-test-themes/style-variations/styles/block-style-variation-b.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "title": "Block Style Variation B", + "slug": "block-style-variation-b", + "blockTypes": [ "core/group" ], + "styles": { + "border": { + "width": "2px", + "style": "dashed" + }, + "blocks": { + "core/group": { + "border": { + "width": "22px", + "style": "groove" + } + } + } + } +} diff --git a/test/gutenberg-test-themes/style-variations/styles/block-style-variation-c.json b/test/gutenberg-test-themes/style-variations/styles/block-style-variation-c.json new file mode 100644 index 00000000000000..835bb595ece632 --- /dev/null +++ b/test/gutenberg-test-themes/style-variations/styles/block-style-variation-c.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "title": "Block Style Variation C", + "slug": "block-style-variation-c", + "blockTypes": [ "core/group" ], + "styles": { + "color": { + "background": "darkslateblue", + "text": "aliceblue" + }, + "blocks": { + "core/group": { + "color": { + "background": "slateblue", + "text": "linen" + } + } + } + } +}