diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index ab001b41ff793e..1109056e7e5d56 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -1,5 +1,6 @@ name: Bug report description: Report a bug with the WordPress block editor or Gutenberg plugin +labels: ['[Type] Bug'] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md index cfae99f42ff9ea..66bd0943c31b45 100644 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -1,6 +1,7 @@ --- name: Feature request about: Propose an idea for a feature or an enhancement +labels: "[Type] Enhancement" --- diff --git a/.github/ISSUE_TEMPLATE/New_release.md b/.github/ISSUE_TEMPLATE/New_release.md index d6732a659731f6..629a4dafa5ba56 100644 --- a/.github/ISSUE_TEMPLATE/New_release.md +++ b/.github/ISSUE_TEMPLATE/New_release.md @@ -1,6 +1,7 @@ --- name: Gutenberg Release about: A checklist for the Gutenberg plugin release process +labels: Gutenberg Plugin, [Type] Project Management --- This issue is to provide visibility on the progress of the release process of Gutenberg VERSION_NUMBER and to centralize any conversations about it. The ultimate goal of this issue is to keep the reference of the steps, resources, work, and conversations about this release so it can be helpful for the next contributors releasing a new Gutenberg version. diff --git a/docs/getting-started/devenv/get-started-with-create-block.md b/docs/getting-started/devenv/get-started-with-create-block.md index 8b3a7b5867476f..a01c08a4ce2f44 100644 --- a/docs/getting-started/devenv/get-started-with-create-block.md +++ b/docs/getting-started/devenv/get-started-with-create-block.md @@ -57,7 +57,7 @@ See the `wp-scripts` [package documentation](https://developer.wordpress.org/blo ### Interactive mode -For developers who prefer a more guided experience, the `create-block package` provides an interactive mode. Instead of manually specifying all options upfront, like the `slug` in the above example, this mode will prompt you for inputs step-by-step. +For developers who prefer a more guided experience, the `create-block` package provides an interactive mode. Instead of manually specifying all options upfront, like the `slug` in the above example, this mode will prompt you for inputs step-by-step. To use this mode, run the command: diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 375f39a7cc3c81..4957f79fa0d481 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -69,17 +69,17 @@ function ListViewBlock( { const blockTitle = blockInformation?.name || blockInformation?.title || __( 'Untitled' ); - const block = useSelect( - ( select ) => select( blockEditorStore ).getBlock( clientId ), - [ clientId ] - ); - const blockName = useSelect( - ( select ) => select( blockEditorStore ).getBlockName( clientId ), - [ clientId ] - ); - const blockEditingMode = useSelect( - ( select ) => - select( blockEditorStore ).getBlockEditingMode( clientId ), + const { block, blockName, blockEditingMode } = useSelect( + ( select ) => { + const { getBlock, getBlockName, getBlockEditingMode } = + select( blockEditorStore ); + + return { + block: getBlock( clientId ), + blockName: getBlockName( clientId ), + blockEditingMode: getBlockEditingMode( clientId ), + }; + }, [ clientId ] ); diff --git a/packages/block-library/src/form/index.js b/packages/block-library/src/form/index.js index a67fc67ca06f0c..1e45c642b6d48e 100644 --- a/packages/block-library/src/form/index.js +++ b/packages/block-library/src/form/index.js @@ -27,7 +27,7 @@ export const init = () => { const DISALLOWED_PARENTS = [ 'core/form' ]; addFilter( 'blockEditor.__unstableCanInsertBlockType', - 'removeTemplatePartsFromPostTemplates', + 'core/block-library/preventInsertingFormIntoAnotherForm', ( canInsert, blockType, diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 934682ed91b7de..e1721928362149 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -62,6 +62,13 @@ figure.wp-block-image:not(.wp-block) { left: 50%; transform: translate(-50%, -50%); } + + // When the Image block is linked, + // it's wrapped with a disabled tag. + // Restore cursor style so it doesn't appear 'clickable'. + > a { + cursor: default; + } } // This is necessary for the editor resize handles to accurately work on a non-floated, non-resized, small image. diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index c465677a986e05..acefd5714bbd47 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -242,10 +242,9 @@ class="lightbox-trigger" data-wp-on--click="actions.core.image.showLightbox" data-wp-style--right="context.core.image.imageButtonRight" data-wp-style--top="context.core.image.imageButtonTop" - style="background: #000" > -
-
-

-
-
-" -`; - -exports[`RenderAppender prop of InnerBlocks Users can dynamically customize the appender 1`] = ` -" -
-
-

-
- - - -
-
-" -`; diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/meta-attribute-block.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/meta-attribute-block.test.js.snap deleted file mode 100644 index 268c8b45d059f2..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/meta-attribute-block.test.js.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Block with a meta attribute Early Registration Should persist the meta attribute properly 1`] = `""`; - -exports[`Block with a meta attribute Early Registration Should persist the meta attribute properly in a different post type 1`] = `""`; - -exports[`Block with a meta attribute Late Registration Should persist the meta attribute properly 1`] = `""`; - -exports[`Block with a meta attribute Late Registration Should persist the meta attribute properly in a different post type 1`] = `""`; diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js deleted file mode 100644 index 1322713b033e24..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-render-appender.test.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - getAllBlockInserterItemTitles, - getEditedPostContent, - insertBlock, - closeGlobalBlockInserter, -} from '@wordpress/e2e-test-utils'; - -const INSERTER_RESULTS_SELECTOR = - '.block-editor-inserter__quick-inserter-results'; -const QUOTE_INSERT_BUTTON_SELECTOR = '//button[.="Quote"]'; -const APPENDER_SELECTOR = '.my-custom-awesome-appender'; -const DYNAMIC_APPENDER_SELECTOR = 'my-dynamic-blocks-appender'; - -describe( 'RenderAppender prop of InnerBlocks', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-innerblocks-render-appender' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-innerblocks-render-appender' ); - } ); - - it( 'Users can customize the appender and can still insert blocks using exposed components', async () => { - // Insert the InnerBlocks renderAppender block. - await insertBlock( 'InnerBlocks renderAppender' ); - await closeGlobalBlockInserter(); - // Wait for the custom block appender to appear. - await page.waitForSelector( APPENDER_SELECTOR ); - // Verify if the custom block appender text is the expected one. - expect( - await page.evaluate( - ( el ) => el.innerText, - await page.$( `${ APPENDER_SELECTOR } > span` ) - ) - ).toEqual( 'My custom awesome appender' ); - - // Open the inserter of our custom block appender and expand all the categories. - await page.click( - `${ APPENDER_SELECTOR } .block-editor-button-block-appender` - ); - // Verify if the blocks the custom inserter is rendering are the expected ones. - expect( await getAllBlockInserterItemTitles() ).toEqual( [ - 'Quote', - 'Video', - ] ); - - // Find the quote block insert button option within the inserter popover. - const inserterPopover = await page.$( INSERTER_RESULTS_SELECTOR ); - const quoteButton = ( - await inserterPopover.$x( QUOTE_INSERT_BUTTON_SELECTOR ) - )[ 0 ]; - - // Insert a quote block. - await quoteButton.click(); - // Verify if the post content is the expected one e.g: the quote was inserted. - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'Users can dynamically customize the appender', async () => { - // Insert the InnerBlocks renderAppender dynamic block. - await insertBlock( 'InnerBlocks renderAppender dynamic' ); - await closeGlobalBlockInserter(); - - // Wait for the custom dynamic block appender to appear. - await page.waitForSelector( '.' + DYNAMIC_APPENDER_SELECTOR ); - - // Verify if the custom block appender text is the expected one. - await page.waitForXPath( - `//*[contains(@class, "${ DYNAMIC_APPENDER_SELECTOR }")]/span[contains(@class, "empty-blocks-appender")][contains(text(), "Empty Blocks Appender")]` - ); - - // Open the inserter of our custom block appender and expand all the categories. - const blockAppenderButtonSelector = `.${ DYNAMIC_APPENDER_SELECTOR } .block-editor-button-block-appender`; - await page.click( blockAppenderButtonSelector ); - - // Verify if the blocks the custom inserter is rendering are the expected ones. - expect( await getAllBlockInserterItemTitles() ).toEqual( [ - 'Quote', - 'Video', - ] ); - - // Find the quote block insert button option within the inserter popover. - const inserterPopover = await page.$( INSERTER_RESULTS_SELECTOR ); - const quoteButton = ( - await inserterPopover.$x( QUOTE_INSERT_BUTTON_SELECTOR ) - )[ 0 ]; - - // Insert a quote block. - await quoteButton.click(); - - // Select the quote block. - await page.keyboard.press( 'ArrowDown' ); - - // Verify if the custom block appender text changed as expected. - await page.waitForXPath( - `//*[contains(@class, "${ DYNAMIC_APPENDER_SELECTOR }")]/span[contains(@class, "single-blocks-appender")][contains(text(), "Single Blocks Appender")]` - ); - - // Verify that the custom appender button is still being rendered. - expect( await page.$( blockAppenderButtonSelector ) ).toBeTruthy(); - - // Insert a video block. - await insertBlock( 'Video' ); - - // Verify if the custom block appender text changed as expected. - await page.waitForXPath( - `//*[contains(@class, "${ DYNAMIC_APPENDER_SELECTOR }")]/span[contains(@class, "multiple-blocks-appender")][contains(text(), "Multiple Blocks Appender")]` - ); - - // Verify that the custom appender button is now not being rendered. - expect( await page.$( blockAppenderButtonSelector ) ).toBeFalsy(); - - // Verify that final block markup is the expected one. - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js deleted file mode 100644 index a95d11b4f7dadb..00000000000000 --- a/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, - getEditedPostContent, - insertBlock, - saveDraft, - pressKeyTimes, -} from '@wordpress/e2e-test-utils'; - -describe( 'Block with a meta attribute', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-meta-attribute-block' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-meta-attribute-block' ); - } ); - - describe.each( [ [ 'Early Registration' ], [ 'Late Registration' ] ] )( - '%s', - ( variant ) => { - it( 'Should persist the meta attribute properly', async () => { - await insertBlock( `Test Meta Attribute Block (${ variant })` ); - await page.keyboard.type( 'Value' ); - - // Regression Test: Previously the caret would wrongly reset to the end - // of any input for meta-sourced attributes, due to syncing behavior of - // meta attribute updates. - // - // See: https://github.com/WordPress/gutenberg/issues/15739 - await pressKeyTimes( 'ArrowLeft', 5 ); - await page.keyboard.type( 'Meta ' ); - - await saveDraft(); - await page.reload(); - await page.waitForSelector( '.edit-post-layout' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - const persistedValue = await page.evaluate( - () => document.querySelector( '.my-meta-input' ).value - ); - expect( persistedValue ).toBe( 'Meta Value' ); - } ); - - it( 'Should use the same value in all the blocks', async () => { - await insertBlock( `Test Meta Attribute Block (${ variant })` ); - await insertBlock( `Test Meta Attribute Block (${ variant })` ); - await insertBlock( `Test Meta Attribute Block (${ variant })` ); - await page.keyboard.type( 'Meta Value' ); - - const inputs = await page.$$( '.my-meta-input' ); - await Promise.all( - inputs.map( async ( input ) => { - // Clicking the input selects the block, - // and selecting the block enables the sync data mode - // as otherwise the asynchronous re-rendering of unselected blocks - // may cause the input to have not yet been updated for the other blocks. - await input.click(); - const inputValue = await input.getProperty( 'value' ); - expect( await inputValue.jsonValue() ).toBe( - 'Meta Value' - ); - } ) - ); - } ); - - it( 'Should persist the meta attribute properly in a different post type', async () => { - await createNewPost( { postType: 'page' } ); - await insertBlock( `Test Meta Attribute Block (${ variant })` ); - await page.keyboard.type( 'Value' ); - - // Regression Test: Previously the caret would wrongly reset to the end - // of any input for meta-sourced attributes, due to syncing behavior of - // meta attribute updates. - // - // See: https://github.com/WordPress/gutenberg/issues/15739 - await pressKeyTimes( 'ArrowLeft', 5 ); - await page.keyboard.type( 'Meta ' ); - - await saveDraft(); - await page.reload(); - await page.waitForSelector( '.edit-post-layout' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - const persistedValue = await page.evaluate( - () => document.querySelector( '.my-meta-input' ).value - ); - expect( persistedValue ).toBe( 'Meta Value' ); - } ); - } - ); -} ); diff --git a/packages/edit-site/src/components/dataviews/dataviews.js b/packages/edit-site/src/components/dataviews/dataviews.js index 81a1224b4c9fec..cc983f25e9e719 100644 --- a/packages/edit-site/src/components/dataviews/dataviews.js +++ b/packages/edit-site/src/components/dataviews/dataviews.js @@ -16,6 +16,22 @@ import ViewActions from './view-actions'; import Filters from './filters'; import Search from './search'; import { ViewGrid } from './view-grid'; +import { ViewSideBySide } from './view-side-by-side'; + +// To do: convert to view type registry. +export const viewTypeSupportsMap = { + list: {}, + grid: {}, + 'side-by-side': { + preview: true, + }, +}; + +const viewTypeMap = { + list: ViewList, + grid: ViewGrid, + 'side-by-side': ViewSideBySide, +}; export default function DataViews( { view, @@ -28,7 +44,7 @@ export default function DataViews( { isLoading = false, paginationInfo, } ) { - const ViewComponent = view.type === 'list' ? ViewList : ViewGrid; + const ViewComponent = viewTypeMap[ view.type ]; const _fields = useMemo( () => { return fields.map( ( field ) => ( { ...field, diff --git a/packages/edit-site/src/components/dataviews/in-filter.js b/packages/edit-site/src/components/dataviews/in-filter.js index e1f3dcca4c8c92..826e94de652de3 100644 --- a/packages/edit-site/src/components/dataviews/in-filter.js +++ b/packages/edit-site/src/components/dataviews/in-filter.js @@ -9,9 +9,14 @@ import { const OPERATOR_IN = 'in'; export default ( { filter, view, onChangeView } ) => { - const activeValue = view.filters.find( + const valueFound = view.filters.find( ( f ) => f.field === filter.id && f.operator === OPERATOR_IN - )?.value; + ); + + const activeValue = + ! valueFound || ! valueFound.hasOwnProperty( 'value' ) + ? '' + : valueFound.value; return ( ; +} diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss index 11c7bdeeaf2a19..088217776b4d29 100644 --- a/packages/edit-site/src/components/layout/style.scss +++ b/packages/edit-site/src/components/layout/style.scss @@ -157,11 +157,16 @@ } // This shouldn't be necessary (we should have a way to say that a skeletton is relative -.edit-site-layout__canvas .interface-interface-skeleton { +.edit-site-layout__canvas .interface-interface-skeleton, +.edit-site-page-pages-preview .interface-interface-skeleton { position: relative !important; min-height: 100% !important; } +.edit-site-page-pages-preview { + height: 100%; +} + .edit-site-layout__view-mode-toggle.components-button { position: relative; color: $white; diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 7076794f27a7ee..529b22f01ca530 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -6,19 +6,20 @@ import { __experimentalVStack as VStack, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useEntityRecords } from '@wordpress/core-data'; +import { useEntityRecords, store as coreStore } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; import { dateI18n, getDate, getSettings } from '@wordpress/date'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import Page from '../page'; import Link from '../routes/link'; -import { DataViews } from '../dataviews'; -import { default as DEFAULT_VIEWS } from './default-views'; +import { DataViews, viewTypeSupportsMap } from '../dataviews'; +import { default as DEFAULT_VIEWS } from '../sidebar-dataviews/default-views'; import { useTrashPostAction, usePermanentlyDeletePostAction, @@ -27,6 +28,7 @@ import { viewPostAction, useEditPostAction, } from '../actions'; +import SideEditor from './side-editor'; import Media from '../media'; import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); @@ -43,19 +45,69 @@ const defaultConfigPerViewType = { // The reason for that is to match the default statuses coming from the endpoint (entity request). export const DEFAULT_STATUSES = 'draft,future,pending,private,publish'; // All statuses but 'trash'. -export default function PagePages() { +function useView( type ) { const { - params: { path, activeView = 'all' }, + params: { activeView = 'all', isCustom = 'false' }, } = useLocation(); - const initialView = DEFAULT_VIEWS.find( - ( { slug } ) => slug === activeView - ).view; - const [ view, setView ] = useState( initialView ); + const selectedDefaultView = + isCustom === 'false' && + DEFAULT_VIEWS[ type ].find( ( { slug } ) => slug === activeView )?.view; + const [ view, setView ] = useState( selectedDefaultView ); + useEffect( () => { - setView( - DEFAULT_VIEWS.find( ( { slug } ) => slug === activeView ).view + if ( selectedDefaultView ) { + setView( selectedDefaultView ); + } + }, [ selectedDefaultView ] ); + const editedViewRecord = useSelect( + ( select ) => { + if ( isCustom !== 'true' ) { + return; + } + const { getEditedEntityRecord } = select( coreStore ); + const dataviewRecord = getEditedEntityRecord( + 'postType', + 'wp_dataviews', + Number( activeView ) + ); + return dataviewRecord; + }, + [ activeView, isCustom ] + ); + const { editEntityRecord } = useDispatch( coreStore ); + + const customView = useMemo( () => { + return ( + editedViewRecord?.content && JSON.parse( editedViewRecord?.content ) ); - }, [ path, activeView ] ); + }, [ editedViewRecord?.content ] ); + const setCustomView = useCallback( + ( viewToSet ) => { + editEntityRecord( + 'postType', + 'wp_dataviews', + editedViewRecord?.id, + { + content: JSON.stringify( viewToSet ), + } + ); + }, + [ editEntityRecord, editedViewRecord?.id ] + ); + + if ( isCustom === 'false' ) { + return [ view, setView ]; + } else if ( isCustom === 'true' && customView ) { + return [ customView, setCustomView ]; + } + // Loading state where no the view was not found on custom views or default views. + return [ DEFAULT_VIEWS[ type ][ 0 ].view, setView ]; +} + +export default function PagePages() { + const postType = 'page'; + const [ view, setView ] = useView( postType ); + const [ selection, setSelection ] = useState( [] ); const { records: statuses, isResolving: isLoadingStatus } = useEntityRecords( 'root', 'status' ); const defaultStatuses = useMemo( () => { @@ -99,7 +151,7 @@ export default function PagePages() { isResolving: isLoadingPages, totalItems, totalPages, - } = useEntityRecords( 'postType', 'page', queryArgs ); + } = useEntityRecords( 'postType', postType, queryArgs ); const { records: authors, isResolving: isLoadingAuthors } = useEntityRecords( 'root', 'user' ); @@ -136,7 +188,7 @@ export default function PagePages() { header: __( 'Title' ), id: 'title', getValue: ( { item } ) => item.title?.rendered || item.slug, - render: ( { item } ) => { + render: ( { item, view: { type } } ) => { return ( @@ -146,6 +198,14 @@ export default function PagePages() { postType: item.type, canvas: 'edit', } } + onClick={ ( event ) => { + if ( + viewTypeSupportsMap[ type ].preview + ) { + event.preventDefault(); + setSelection( [ item.id ] ); + } + } } > { decodeEntities( item.title?.rendered || item.slug @@ -250,18 +310,45 @@ export default function PagePages() { // TODO: we need to handle properly `data={ data || EMPTY_ARRAY }` for when `isLoading`. return ( - - - + <> + + + + { viewTypeSupportsMap[ view.type ].preview && ( + +
+ { selection.length === 1 && ( + + ) } + { selection.length !== 1 && ( +
+

{ __( 'Select a page to preview' ) }

+
+ ) } +
+
+ ) } + ); } diff --git a/packages/edit-site/src/components/page-pages/side-editor.js b/packages/edit-site/src/components/page-pages/side-editor.js new file mode 100644 index 00000000000000..fca561cf9f4d5d --- /dev/null +++ b/packages/edit-site/src/components/page-pages/side-editor.js @@ -0,0 +1,14 @@ +/** + * Internal dependencies + */ +import Editor from '../editor'; +import { useInitEditedEntity } from '../sync-state-with-url/use-init-edited-entity-from-url'; + +export default function SideEditor( { postType, postId } ) { + useInitEditedEntity( { + postId, + postType, + } ); + + return ; +} diff --git a/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js b/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js new file mode 100644 index 00000000000000..220634c54e09b5 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-dataviews/add-new-view.js @@ -0,0 +1,142 @@ +/** + * WordPress dependencies + */ +import { + Modal, + TextControl, + __experimentalHStack as HStack, + __experimentalVStack as VStack, + Button, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useDispatch, resolveSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { useState } from '@wordpress/element'; +import { plus } from '@wordpress/icons'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import SidebarNavigationItem from '../sidebar-navigation-item'; +import DEFAULT_VIEWS from './default-views'; +import { unlock } from '../../lock-unlock'; + +const { useHistory, useLocation } = unlock( routerPrivateApis ); + +function AddNewItemModalContent( { type, setIsAdding } ) { + const { + params: { path }, + } = useLocation(); + const history = useHistory(); + const { saveEntityRecord } = useDispatch( coreStore ); + const [ title, setTitle ] = useState( '' ); + const [ isSaving, setIsSaving ] = useState( false ); + return ( +
{ + event.preventDefault(); + setIsSaving( true ); + const { getEntityRecords } = resolveSelect( coreStore ); + let dataViewTaxonomyId; + const dataViewTypeRecords = await getEntityRecords( + 'taxonomy', + 'wp_dataviews_type', + { slug: type } + ); + if ( dataViewTypeRecords && dataViewTypeRecords.length > 0 ) { + dataViewTaxonomyId = dataViewTypeRecords[ 0 ].id; + } else { + const record = await saveEntityRecord( + 'taxonomy', + 'wp_dataviews_type', + { name: type } + ); + if ( record && record.id ) { + dataViewTaxonomyId = record.id; + } + } + const savedRecord = await saveEntityRecord( + 'postType', + 'wp_dataviews', + { + title, + status: 'publish', + wp_dataviews_type: dataViewTaxonomyId, + content: JSON.stringify( + DEFAULT_VIEWS[ type ][ 0 ].view + ), + } + ); + history.push( { + path, + activeView: savedRecord.id, + isCustom: 'true', + } ); + setIsSaving( false ); + setIsAdding( false ); + } } + > + + + + + + + + +
+ ); +} + +export default function AddNewItem( { type } ) { + const [ isAdding, setIsAdding ] = useState( false ); + return ( + <> + { + setIsAdding( true ); + } } + className="dataviews__siderbar-content-add-new-item" + > + { __( 'New view' ) } + + { isAdding && ( + { + setIsAdding( false ); + } } + overlayClassName="" + > + + + ) } + + ); +} diff --git a/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js new file mode 100644 index 00000000000000..f9b0cddb7d8e1e --- /dev/null +++ b/packages/edit-site/src/components/sidebar-dataviews/custom-dataviews-list.js @@ -0,0 +1,89 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; +import { useMemo } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DataViewItem from './dataview-item'; +import AddNewItem from './add-new-view'; + +const EMPTY_ARRAY = []; + +function CustomDataViewItem( { dataviewId, isActive } ) { + const { dataview } = useSelect( + ( select ) => { + const { getEditedEntityRecord } = select( coreStore ); + return { + dataview: getEditedEntityRecord( + 'postType', + 'wp_dataviews', + dataviewId + ), + }; + }, + [ dataviewId ] + ); + const type = useMemo( () => { + const viewContent = JSON.parse( dataview.content ); + return viewContent.type; + }, [ dataview.content ] ); + return ( + + ); +} + +export function useCustomDataViews( type ) { + const customDataViews = useSelect( ( select ) => { + const { getEntityRecords } = select( coreStore ); + const dataViewTypeRecords = getEntityRecords( + 'taxonomy', + 'wp_dataviews_type', + { slug: type } + ); + if ( ! dataViewTypeRecords || dataViewTypeRecords.length === 0 ) { + return EMPTY_ARRAY; + } + const dataViews = getEntityRecords( 'postType', 'wp_dataviews', { + wp_dataviews_type: dataViewTypeRecords[ 0 ].id, + orderby: 'date', + order: 'asc', + } ); + if ( ! dataViews ) { + return EMPTY_ARRAY; + } + return dataViews; + } ); + return customDataViews; +} + +export default function CustomDataViewsList( { type, activeView, isCustom } ) { + const customDataViews = useCustomDataViews( type ); + return ( + + { customDataViews.map( ( customViewRecord ) => { + return ( + + ); + } ) } + + + ); +} diff --git a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js new file mode 100644 index 00000000000000..45e3a9d50f3f6f --- /dev/null +++ b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { page, columns } from '@wordpress/icons'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { useLink } from '../routes/link'; +import SidebarNavigationItem from '../sidebar-navigation-item'; +import { unlock } from '../../lock-unlock'; +const { useLocation } = unlock( routerPrivateApis ); + +function getDataViewIcon( type ) { + const icons = { list: page, grid: columns }; + return icons[ type ]; +} + +export default function DataViewItem( { + title, + slug, + customViewId, + type, + icon, + isActive, + isCustom, +} ) { + const { + params: { path }, + } = useLocation(); + + const iconToUse = icon || getDataViewIcon( type ); + + const linkInfo = useLink( { + path, + activeView: isCustom === 'true' ? customViewId : slug, + isCustom, + } ); + return ( + + { title } + + ); +} diff --git a/packages/edit-site/src/components/page-pages/default-views.js b/packages/edit-site/src/components/sidebar-dataviews/default-views.js similarity index 51% rename from packages/edit-site/src/components/page-pages/default-views.js rename to packages/edit-site/src/components/sidebar-dataviews/default-views.js index fda1ef789e3c84..3a2f5991bdd015 100644 --- a/packages/edit-site/src/components/page-pages/default-views.js +++ b/packages/edit-site/src/components/sidebar-dataviews/default-views.js @@ -21,29 +21,35 @@ const DEFAULT_PAGE_BASE = { layout: {}, }; -const DEFAULT_VIEWS = [ - { - title: __( 'All' ), - slug: 'all', - view: DEFAULT_PAGE_BASE, - }, - { - title: __( 'Drafts' ), - slug: 'drafts', - view: { - ...DEFAULT_PAGE_BASE, - filters: [ { field: 'status', operator: 'in', value: 'draft' } ], +const DEFAULT_VIEWS = { + page: [ + { + title: __( 'All' ), + slug: 'all', + view: DEFAULT_PAGE_BASE, }, - }, - { - title: __( 'Trash' ), - slug: 'trash', - icon: trash, - view: { - ...DEFAULT_PAGE_BASE, - filters: [ { field: 'status', operator: 'in', value: 'trash' } ], + { + title: __( 'Drafts' ), + slug: 'drafts', + view: { + ...DEFAULT_PAGE_BASE, + filters: [ + { field: 'status', operator: 'in', value: 'draft' }, + ], + }, }, - }, -]; + { + title: __( 'Trash' ), + slug: 'trash', + icon: trash, + view: { + ...DEFAULT_PAGE_BASE, + filters: [ + { field: 'status', operator: 'in', value: 'trash' }, + ], + }, + }, + ], +}; export default DEFAULT_VIEWS; diff --git a/packages/edit-site/src/components/sidebar-dataviews/index.js b/packages/edit-site/src/components/sidebar-dataviews/index.js index 7704c0637b4904..9e4534ab342745 100644 --- a/packages/edit-site/src/components/sidebar-dataviews/index.js +++ b/packages/edit-site/src/components/sidebar-dataviews/index.js @@ -2,65 +2,56 @@ * WordPress dependencies */ import { __experimentalItemGroup as ItemGroup } from '@wordpress/components'; -import { page, columns } from '@wordpress/icons'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; /** * Internal dependencies */ -import { useLink } from '../routes/link'; -import { default as DEFAULT_VIEWS } from '../page-pages/default-views'; + +import { default as DEFAULT_VIEWS } from './default-views'; import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); -import SidebarNavigationItem from '../sidebar-navigation-item'; - -function getDataViewIcon( dataview ) { - const icons = { list: page, grid: columns }; - return icons[ dataview.view.type ]; -} - -function DataViewItem( { dataview, isActive, icon } ) { - const { - params: { path }, - } = useLocation(); - - const _icon = icon || getDataViewIcon( dataview ); +import DataViewItem from './dataview-item'; +import CustomDataViewsList from './custom-dataviews-list'; - const linkInfo = useLink( { - path, - activeView: dataview.slug, - } ); - return ( - - { dataview.title } - - ); -} +const PATH_TO_TYPE = { + '/pages': 'page', +}; export default function DataViewsSidebarContent() { const { - params: { path, activeView = 'all' }, + params: { path, activeView = 'all', isCustom = 'false' }, } = useLocation(); - if ( ! path || path !== '/pages' ) { + if ( ! path || ! PATH_TO_TYPE[ path ] ) { return null; } + const type = PATH_TO_TYPE[ path ]; return ( - - { DEFAULT_VIEWS.map( ( dataview ) => { - return ( - - ); - } ) } - + <> + + { DEFAULT_VIEWS[ type ].map( ( dataview ) => { + return ( + + ); + } ) } + + + ); } diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index b178ce501301ef..f94428e6bf0881 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -20,8 +20,7 @@ import { const { useLocation } = unlock( routerPrivateApis ); -export default function useInitEditedEntityFromURL() { - const { params: { postId, postType } = {} } = useLocation(); +export function useInitEditedEntity( { postId, postType } ) { const { isRequestingSite, homepageId, url } = useSelect( ( select ) => { const { getSite, getUnstableBase } = select( coreDataStore ); const siteData = getSite(); @@ -92,3 +91,8 @@ export default function useInitEditedEntityFromURL() { setNavigationMenu, ] ); } + +export default function useInitEditedEntityFromURL() { + const { params = {} } = useLocation(); + return useInitEditedEntity( params ); +} diff --git a/test/e2e/specs/editor/blocks/query.spec.js b/test/e2e/specs/editor/blocks/query.spec.js index 79b15222630efa..dbefb66e353bc7 100644 --- a/test/e2e/specs/editor/blocks/query.spec.js +++ b/test/e2e/specs/editor/blocks/query.spec.js @@ -21,7 +21,10 @@ test.describe( 'Query block', () => { } ); test.beforeEach( async ( { admin } ) => { - await admin.createNewPost( { postType: 'page', title: 'Query Page' } ); + await admin.createNewPost( { + postType: 'page', + title: 'Query Page', + } ); } ); test.afterEach( async ( { requestUtils } ) => { diff --git a/test/e2e/specs/editor/local/demo.spec.js b/test/e2e/specs/editor/local/demo.spec.js index acfee9296e2324..2782461812ea84 100644 --- a/test/e2e/specs/editor/local/demo.spec.js +++ b/test/e2e/specs/editor/local/demo.spec.js @@ -7,21 +7,12 @@ test.describe( 'New editor state', () => { test( 'content should load, making the post dirty', async ( { page, admin, + editor, } ) => { await admin.visitAdminPage( 'post-new.php', 'gutenberg-demo' ); - await page.waitForFunction( () => { - if ( ! window?.wp?.data?.dispatch ) { - return false; - } - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuide', false ); - - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'fullscreenMode', false ); - - return true; + await editor.setPreferences( 'core/edit-site', { + welcomeGuide: false, + fullscreenMode: false, } ); const isDirty = await page.evaluate( () => { diff --git a/test/e2e/specs/editor/plugins/inner-blocks-render-appender.spec.js b/test/e2e/specs/editor/plugins/inner-blocks-render-appender.spec.js new file mode 100644 index 00000000000000..9886bd00fe2b66 --- /dev/null +++ b/test/e2e/specs/editor/plugins/inner-blocks-render-appender.spec.js @@ -0,0 +1,129 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'RenderAppender prop of InnerBlocks', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'gutenberg-test-innerblocks-render-appender' + ); + } ); + + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-innerblocks-render-appender' + ); + } ); + + test( 'Users can customize the appender and can still insert blocks using exposed components', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'test/inner-blocks-render-appender', + } ); + + const customAppender = page.locator( '.my-custom-awesome-appender' ); + + // Verify if the custom block appender text is the expected one. + await expect( customAppender ).toContainText( + 'My custom awesome appender' + ); + + // Open the inserter of our custom block appender. + await customAppender + .getByRole( 'button', { name: 'Add block' } ) + .click(); + + // Verify if the blocks the custom inserter is rendering are the expected ones. + const blockListBox = page.getByRole( 'listbox', { name: 'Blocks' } ); + await expect( blockListBox.getByRole( 'option' ) ).toHaveText( [ + 'Quote', + 'Video', + ] ); + + // Insert a quote block. + await blockListBox.getByRole( 'option', { name: 'Quote' } ).click(); + + // Verify if the post content is the expected one. + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'test/inner-blocks-render-appender', + innerBlocks: [ + { + name: 'core/quote', + }, + ], + }, + ] ); + } ); + + test( 'Users can dynamically customize the appender', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'test/inner-blocks-render-appender-dynamic', + } ); + + const dynamimcAppender = page.locator( '.my-dynamic-blocks-appender' ); + const addBlockBtn = dynamimcAppender.getByRole( 'button', { + name: 'Add block', + } ); + + // Verify if the custom block appender text is the expected one. + await expect( dynamimcAppender ).toContainText( + 'Empty Blocks Appender' + ); + + // Open the inserter of our custom block appender. + await addBlockBtn.click(); + + // Verify if the blocks the custom inserter is rendering are the expected ones. + const blockListBox = page.getByRole( 'listbox', { name: 'Blocks' } ); + await expect( blockListBox.getByRole( 'option' ) ).toHaveText( [ + 'Quote', + 'Video', + ] ); + + // Insert a quote block. + await blockListBox.getByRole( 'option', { name: 'Quote' } ).click(); + + // Verify if the custom block appender text changed as expected. + await expect( + dynamimcAppender.getByText( 'Single Blocks Appender' ) + ).toBeVisible(); + + // Insert a video block. + await addBlockBtn.click(); + await blockListBox.getByRole( 'option', { name: 'Video' } ).click(); + + // Verify if the custom block appender text changed as expected. + await expect( + dynamimcAppender.getByText( 'Multiple Blocks Appender' ) + ).toBeVisible(); + + // Verify that the custom appender button is now not being rendered. + await expect( addBlockBtn ).toBeHidden(); + + // Verify if the post content is the expected one. + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'test/inner-blocks-render-appender-dynamic', + innerBlocks: [ + { + name: 'core/quote', + }, + { + name: 'core/video', + }, + ], + }, + ] ); + } ); +} ); diff --git a/test/e2e/specs/editor/plugins/meta-attribute-block.spec.js b/test/e2e/specs/editor/plugins/meta-attribute-block.spec.js new file mode 100644 index 00000000000000..f11d59b39dc6d8 --- /dev/null +++ b/test/e2e/specs/editor/plugins/meta-attribute-block.spec.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +const VARIATIONS = [ + [ 'Early Registration', 'test/test-meta-attribute-block-early' ], + [ 'Late Registration', 'test/test-meta-attribute-block-late' ], +]; + +test.describe( 'Block with a meta attribute', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'gutenberg-test-meta-attribute-block' + ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-meta-attribute-block' + ); + } ); + + for ( const [ title, blockName ] of VARIATIONS ) { + test.describe( title, () => { + test( 'Should persist the meta attribute properly', async ( { + admin, + editor, + page, + pageUtils, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockName } ); + await page.keyboard.type( 'Value' ); + + // Regression Test: Previously the caret would wrongly reset to the end + // of any input for meta-sourced attributes, due to syncing behavior of + // meta attribute updates. + // + // See: https://github.com/WordPress/gutenberg/issues/15739 + await pageUtils.pressKeys( 'ArrowLeft', { times: 5 } ); + await page.keyboard.type( 'Meta ' ); + + await editor.saveDraft(); + await page.reload(); + + const block = page.getByRole( 'document', { + name: `Block: Test Meta Attribute Block (${ title })`, + } ); + await expect( block ).toBeVisible(); + await expect( block.locator( '.my-meta-input' ) ).toHaveValue( + 'Meta Value' + ); + } ); + + test( 'Should use the same value in all the blocks', async ( { + admin, + editor, + page, + } ) => { + await admin.createNewPost(); + await editor.insertBlock( { name: blockName } ); + await editor.insertBlock( { name: blockName } ); + await editor.insertBlock( { name: blockName } ); + await page.keyboard.type( 'Meta Value' ); + + const inputs = await page.locator( '.my-meta-input' ).all(); + for ( const input of inputs ) { + await expect( input ).toHaveValue( 'Meta Value' ); + } + } ); + + test( 'Should persist the meta attribute properly in a different post type', async ( { + admin, + editor, + page, + pageUtils, + } ) => { + await admin.createNewPost( { postType: 'page' } ); + await editor.insertBlock( { name: blockName } ); + await page.keyboard.type( 'Value' ); + + // Regression Test: Previously the caret would wrongly reset to the end + // of any input for meta-sourced attributes, due to syncing behavior of + // meta attribute updates. + // + // See: https://github.com/WordPress/gutenberg/issues/15739 + await pageUtils.pressKeys( 'ArrowLeft', { times: 5 } ); + await page.keyboard.type( 'Meta ' ); + + await editor.saveDraft(); + await page.reload(); + + const block = page.getByRole( 'document', { + name: `Block: Test Meta Attribute Block (${ title })`, + } ); + await expect( block ).toBeVisible(); + await expect( block.locator( '.my-meta-input' ) ).toHaveValue( + 'Meta Value' + ); + } ); + } ); + } +} ); diff --git a/test/e2e/specs/editor/various/block-renaming.spec.js b/test/e2e/specs/editor/various/block-renaming.spec.js index 4150be64bd33d6..f8d9548fbe8667 100644 --- a/test/e2e/specs/editor/various/block-renaming.spec.js +++ b/test/e2e/specs/editor/various/block-renaming.spec.js @@ -15,10 +15,8 @@ test.describe( 'Block Renaming', () => { pageUtils, } ) => { // Turn on block list view by default. - await page.evaluate( () => { - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'showListViewByDefault', true ); + await editor.setPreferences( 'core/edit-site', { + showListViewByDefault: true, } ); const listView = page.getByRole( 'treegrid', { diff --git a/test/e2e/specs/editor/various/post-editor-template-mode.spec.js b/test/e2e/specs/editor/various/post-editor-template-mode.spec.js index 6f8bb5596bf2a5..b9bfbf9dd6b05e 100644 --- a/test/e2e/specs/editor/various/post-editor-template-mode.spec.js +++ b/test/e2e/specs/editor/various/post-editor-template-mode.spec.js @@ -121,10 +121,8 @@ class PostEditorTemplateMode { async disableTemplateWelcomeGuide() { // Turn off the welcome guide. - await this.page.evaluate( () => { - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuideTemplate', false ); + await this.editor.setPreferences( 'core/edit-post', { + welcomeGuideTemplate: false, } ); } diff --git a/test/e2e/specs/editor/various/preview.spec.js b/test/e2e/specs/editor/various/preview.spec.js index ea920270e093e8..8a4ee5a6bd81d2 100644 --- a/test/e2e/specs/editor/various/preview.spec.js +++ b/test/e2e/specs/editor/various/preview.spec.js @@ -295,7 +295,10 @@ test.describe( 'Preview with private custom post type', () => { admin, page, } ) => { - await admin.createNewPost( { postType: 'not_public', title: 'aaaaa' } ); + await admin.createNewPost( { + postType: 'not_public', + title: 'aaaaa', + } ); // Open the view menu. await page.click( 'role=button[name="Preview"i]' ); diff --git a/test/e2e/specs/editor/various/switch-to-draft.spec.js b/test/e2e/specs/editor/various/switch-to-draft.spec.js index 59837a3d3c7657..5cfeda60a2d185 100644 --- a/test/e2e/specs/editor/various/switch-to-draft.spec.js +++ b/test/e2e/specs/editor/various/switch-to-draft.spec.js @@ -139,20 +139,7 @@ class SwitchToDraftUtils { id = page.id; } - await this.#admin.visitAdminPage( - 'post.php', - `post=${ id }&action=edit` - ); - - // Disable welcome guide and full screen mode. - await this.#page.evaluate( () => { - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'welcomeGuide', false ); - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-post', 'fullscreenMode', false ); - } ); + await this.#admin.editPost( id ); }; getPostStatus = async () => { diff --git a/test/e2e/specs/site-editor/list-view.spec.js b/test/e2e/specs/site-editor/list-view.spec.js index ed64574168bd02..74d3559d4e3d27 100644 --- a/test/e2e/specs/site-editor/list-view.spec.js +++ b/test/e2e/specs/site-editor/list-view.spec.js @@ -23,16 +23,15 @@ test.describe( 'Site Editor List View', () => { test( 'should open by default when preference is enabled', async ( { page, + editor, } ) => { await expect( page.locator( 'role=region[name="List View"i]' ) ).toBeHidden(); // Turn on block list view by default. - await page.evaluate( () => { - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'showListViewByDefault', true ); + await editor.setPreferences( 'core/edit-site', { + showListViewByDefault: true, } ); await page.reload(); @@ -42,10 +41,8 @@ test.describe( 'Site Editor List View', () => { ).toBeVisible(); // The preferences cleanup. - await page.evaluate( () => { - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'showListViewByDefault', false ); + await editor.setPreferences( 'core/edit-site', { + showListViewByDefault: false, } ); } ); diff --git a/test/e2e/specs/widgets/customizing-widgets.spec.js b/test/e2e/specs/widgets/customizing-widgets.spec.js index 6a6a51cae26860..34400ea00976e2 100644 --- a/test/e2e/specs/widgets/customizing-widgets.spec.js +++ b/test/e2e/specs/widgets/customizing-widgets.spec.js @@ -12,11 +12,17 @@ const { * @typedef {import('@playwright/test').FrameLocator} FrameLocator * @typedef {import('@wordpress/e2e-test-utils-playwright').PageUtils} PageUtils * @typedef {import('@wordpress/e2e-test-utils-playwright').RequestUtils} RequestUtils + * @typedef {import('@wordpress/e2e-test-utils-playwright').Editor} Editor */ test.use( { - widgetsCustomizerPage: async ( { admin, page, pageUtils }, use ) => { - await use( new WidgetsCustomizerPage( { admin, page, pageUtils } ) ); + widgetsCustomizerPage: async ( + { admin, page, pageUtils, editor }, + use + ) => { + await use( + new WidgetsCustomizerPage( { admin, page, pageUtils, editor } ) + ); }, } ); @@ -613,11 +619,13 @@ class WidgetsCustomizerPage { * @param {Admin} config.admin * @param {Page} config.page * @param {PageUtils} config.pageUtils + * @param {Editor} config.editor */ - constructor( { admin, page, pageUtils } ) { + constructor( { admin, page, pageUtils, editor } ) { this.admin = admin; this.page = page; this.pageUtils = pageUtils; + this.editor = editor; /** @type {FrameLocator} */ this.previewFrame = this.page @@ -631,10 +639,8 @@ class WidgetsCustomizerPage { await this.admin.visitAdminPage( 'customize.php' ); // Disable welcome guide. - await this.page.evaluate( () => { - window.wp.data - .dispatch( 'core/preferences' ) - .set( 'core/customize-widgets', 'welcomeGuide', false ); + await this.editor.setPreferences( 'core/customize-widgets', { + welcomeGuide: false, } ); } diff --git a/test/performance/fixtures/perf-utils.ts b/test/performance/fixtures/perf-utils.ts index dcd9579364e10b..d17eec2c935b1b 100644 --- a/test/performance/fixtures/perf-utils.ts +++ b/test/performance/fixtures/perf-utils.ts @@ -59,7 +59,9 @@ export class PerfUtils { this.page.getByRole( 'button', { name: 'Saved' } ) ).toBeDisabled(); - return this.page.url(); + const postId = new URL( this.page.url() ).searchParams.get( 'post' ); + + return postId; } /** diff --git a/test/performance/specs/post-editor.spec.js b/test/performance/specs/post-editor.spec.js index da20e3c3e667b5..d5ff40570afd78 100644 --- a/test/performance/specs/post-editor.spec.js +++ b/test/performance/specs/post-editor.spec.js @@ -48,12 +48,12 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Loading', () => { - let draftURL = null; + let draftId = null; test( 'Setup the test post', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.loadBlocksForLargePost(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); const samples = 10; @@ -61,12 +61,12 @@ test.describe( 'Post Editor Performance', () => { const iterations = samples + throwaway; for ( let i = 1; i <= iterations; i++ ) { test( `Run the test (${ i } of ${ iterations })`, async ( { - page, + admin, perfUtils, metrics, } ) => { // Open the test draft. - await page.goto( draftURL ); + await admin.editPost( draftId ); const canvas = await perfUtils.getCanvas(); // Wait for the first block. @@ -92,17 +92,17 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Typing', () => { - let draftURL = null; + let draftId = null; test( 'Setup the test post', async ( { admin, perfUtils, editor } ) => { await admin.createNewPost(); await perfUtils.loadBlocksForLargePost(); await editor.insertBlock( { name: 'core/paragraph' } ); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { - await page.goto( draftURL ); + test( 'Run the test', async ( { admin, perfUtils, metrics } ) => { + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const canvas = await perfUtils.getCanvas(); @@ -145,16 +145,16 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Typing within containers', () => { - let draftURL = null; + let draftId = null; test( 'Set up the test post', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.loadBlocksForSmallPostWithContainers(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { - await page.goto( draftURL ); + test( 'Run the test', async ( { admin, perfUtils, metrics } ) => { + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const canvas = await perfUtils.getCanvas(); @@ -201,16 +201,16 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Selecting blocks', () => { - let draftURL = null; + let draftId = null; test( 'Set up the test post', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.load1000Paragraphs(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { - await page.goto( draftURL ); + test( 'Run the test', async ( { admin, page, perfUtils, metrics } ) => { + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const canvas = await perfUtils.getCanvas(); @@ -251,16 +251,16 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Opening persistent List View', () => { - let draftURL = null; + let draftId = null; test( 'Set up the test page', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.load1000Paragraphs(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { - await page.goto( draftURL ); + test( 'Run the test', async ( { page, admin, perfUtils, metrics } ) => { + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const listViewToggle = page.getByRole( 'button', { @@ -301,17 +301,17 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Opening Inserter', () => { - let draftURL = null; + let draftId = null; test( 'Set up the test page', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.load1000Paragraphs(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { + test( 'Run the test', async ( { page, admin, perfUtils, metrics } ) => { // Go to the test page. - await page.goto( draftURL ); + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const globalInserterToggle = page.getByRole( 'button', { name: 'Toggle block inserter', @@ -357,17 +357,17 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Searching Inserter', () => { - let draftURL = null; + let draftId = null; test( 'Set up the test page', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.load1000Paragraphs(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { + test( 'Run the test', async ( { page, admin, perfUtils, metrics } ) => { // Go to the test page. - await page.goto( draftURL ); + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const globalInserterToggle = page.getByRole( 'button', { name: 'Toggle block inserter', @@ -413,17 +413,17 @@ test.describe( 'Post Editor Performance', () => { } ); test.describe( 'Hovering Inserter items', () => { - let draftURL = null; + let draftId = null; test( 'Set up the test page', async ( { admin, perfUtils } ) => { await admin.createNewPost(); await perfUtils.load1000Paragraphs(); - draftURL = await perfUtils.saveDraft(); + draftId = await perfUtils.saveDraft(); } ); - test( 'Run the test', async ( { page, perfUtils, metrics } ) => { + test( 'Run the test', async ( { page, admin, perfUtils, metrics } ) => { // Go to the test page. - await page.goto( draftURL ); + await admin.editPost( draftId ); await perfUtils.disableAutosave(); const globalInserterToggle = page.getByRole( 'button', {