diff --git a/packages/e2e-tests/specs/editor/various/post-editor-template-mode.test.js b/packages/e2e-tests/specs/editor/various/post-editor-template-mode.test.js index 89daeceeb07b2b..ded00f8e1d77ab 100644 --- a/packages/e2e-tests/specs/editor/various/post-editor-template-mode.test.js +++ b/packages/e2e-tests/specs/editor/various/post-editor-template-mode.test.js @@ -11,6 +11,7 @@ import { activatePlugin, deactivatePlugin, deleteAllTemplates, + setBrowserViewport, } from '@wordpress/e2e-test-utils'; const openSidebarPanelWithTitle = async ( title ) => { @@ -56,7 +57,7 @@ const switchToTemplateMode = async () => { // Check that we switched properly to edit mode. await page.waitForXPath( - '//*[contains(@class, "components-snackbar")]/*[text()="Editing template. Changes made here affect all posts and pages that use the template."]' + '//*[text()="Editing template. Changes made here affect all posts and pages that use the template."]' ); const title = await page.$eval( '.edit-post-template-top-area', @@ -119,7 +120,6 @@ describe( 'Post Editor Template mode', () => { // there's a template resolution bug forcing us to do so. await saveDraft(); await page.reload(); - await switchToTemplateMode(); // Edit the template @@ -197,3 +197,180 @@ describe( 'Post Editor Template mode', () => { expect( content ).toMatchSnapshot(); } ); } ); + +describe( 'Delete Post Template Confirmation Dialog', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-block-templates' ); + await deleteAllTemplates( 'wp_template' ); + await deleteAllTemplates( 'wp_template_part' ); + await activateTheme( 'twentytwentyone' ); + await createNewPost(); + // Create a random post. + await page.type( '.editor-post-title__input', 'Just an FSE Post' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Hello World' ); + + // Save the post + // Saving shouldn't be necessary but unfortunately, + // there's a template resolution bug forcing us to do so. + await saveDraft(); + await page.reload(); + // Unselect the blocks. + await page.evaluate( () => { + wp.data.dispatch( 'core/block-editor' ).clearSelectedBlock(); + } ); + } ); + + afterAll( async () => { + await activateTheme( 'twentytwentyone' ); + await deactivatePlugin( 'gutenberg-test-block-templates' ); + } ); + + [ 'large', 'small' ].forEach( ( viewport ) => { + it( `should retain template if deletion is canceled when the viewport is ${ viewport }`, async () => { + await setBrowserViewport( viewport ); + + const isWelcomeGuideActive = await page.evaluate( () => + wp.data + .select( 'core/edit-post' ) + .isFeatureActive( 'welcomeGuide' ) + ); + if ( isWelcomeGuideActive === true ) { + await page.evaluate( () => + wp.data + .dispatch( 'core/edit-post' ) + .toggleFeature( 'welcomeGuide' ) + ); + await page.reload(); + await page.waitForSelector( '.edit-post-layout' ); + } + if ( viewport === 'small' ) { + await page.waitForXPath( '//button[@aria-label="Settings"]' ); + await openDocumentSettingsSidebar(); + } + const templateTitle = `${ viewport } Viewport Deletion Test`; + + await createNewTemplate( templateTitle ); + // Edit the template + if ( viewport === 'small' ) { + await page.waitForXPath( `//h2[text()="${ templateTitle }"]` ); + const closeDocumentSettingsButton = await page.waitForXPath( + '//button[@aria-label="Close settings"]' + ); + await closeDocumentSettingsButton.click(); + } + await insertBlock( 'Paragraph' ); + await page.keyboard.type( + 'Just a random paragraph added to the template' + ); + + // Save changes + const publishButton = await page.waitForXPath( + `//button[contains(text(), 'Publish')]` + ); + await publishButton.click(); + const saveButton = await page.waitForXPath( + `//div[contains(@class, "entities-saved-states__panel-header")]/button[contains(text(), 'Save')]` + ); + await saveButton.click(); + // Avoid publishing the post + // Select the cancel button via parent div's class, because the text `Cancel` is used on another button as well + const cancelButton = await page.waitForXPath( + `//div[contains(@class,"editor-post-publish-panel__header-cancel-button")]/button[not(@disabled)]` + ); + await cancelButton.click(); + + const templateDropdown = await page.waitForXPath( + `//button[contains(text(), '${ templateTitle }')]` + ); + await templateDropdown.click(); + const deleteTemplateButton = await page.waitForXPath( + '//button[@role="menuitem"][@aria-label="Delete template"]' + ); + await deleteTemplateButton.click(); + + await page.waitForXPath( + `//*[text()="Are you sure you want to delete the ${ templateTitle } template? It may be used by other pages or posts."]` + ); + const dialogCancelButton = await page.waitForXPath( + '//*[@role="dialog"][not(@id="wp-link-wrap")]//button[text()="Cancel"]' + ); + await dialogCancelButton.click(); + + const exitTemplateModeButton = await page.waitForXPath( + '//button[text()="Back"]' + ); + await exitTemplateModeButton.click(); + + await page.waitForXPath( + '//button[@aria-label="Settings"][@aria-expanded="false"]' + ); + await openDocumentSettingsSidebar(); + + const element = await page.waitForXPath( + '//h2/button[contains(text(), "Template")]/../..//select' + ); + const value = await element.getProperty( 'value' ); + const currentTemplateSlug = await value.jsonValue(); + + expect( currentTemplateSlug ).toBe( + `wp-custom-template-${ viewport }-viewport-deletion-test` + ); + } ); + + it( `should delete template if deletion is confirmed when the viewport is ${ viewport }`, async () => { + const templateTitle = `${ viewport } Viewport Deletion Test`; + + await setBrowserViewport( viewport ); + + await switchToTemplateMode(); + if ( viewport === 'small' ) { + const closeDocumentSettingsButton = await page.waitForXPath( + '//div[contains(@class,"interface-complementary-area-header__small")]/button[@aria-label="Close settings"]' + ); + await closeDocumentSettingsButton.click(); + } + + const templateDropdown = await page.waitForXPath( + `//button[contains(text(), '${ templateTitle }')]` + ); + await templateDropdown.click(); + + const deleteTemplateButton = await page.waitForXPath( + '//button[@role="menuitem"][@aria-label="Delete template"]' + ); + await deleteTemplateButton.click(); + + await page.waitForXPath( + `//*[text()="Are you sure you want to delete the ${ templateTitle } template? It may be used by other pages or posts."]` + ); + const dialogConfirmButton = await page.waitForXPath( + '//*[@role="dialog"][not(@id="wp-link-wrap")]//button[text()="OK"]' + ); + + await dialogConfirmButton.click(); + + // Saving isn't technically necessary, but for themes without any specified templates, + // the removal of the Templates dropdown is delayed. A save and reload allows for this + // delay and prevents flakiness + await saveDraft(); + await page.reload(); + + const optionElementHandlers = await page.$x( + '//h2/button[contains(text(), "Template")]/../..//select/option' + ); + const availableTemplates = []; + for ( const elem of optionElementHandlers ) { + const elemName = await elem.getProperty( 'textContent' ); + const templateName = await elemName.jsonValue(); + availableTemplates.push( templateName ); + } + + expect( + availableTemplates.includes( + `${ viewport } Viewport Deletion Test` + ) + ).toBe( false ); + } ); + } ); +} ); diff --git a/packages/edit-post/src/components/header/template-title/delete-template.js b/packages/edit-post/src/components/header/template-title/delete-template.js index 22609a23eec600..3d09c4e34caf34 100644 --- a/packages/edit-post/src/components/header/template-title/delete-template.js +++ b/packages/edit-post/src/components/header/template-title/delete-template.js @@ -7,11 +7,16 @@ import { pickBy } from 'lodash'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { MenuGroup, MenuItem } from '@wordpress/components'; +import { + MenuGroup, + MenuItem, + __experimentalConfirmDialog as ConfirmDialog, +} from '@wordpress/components'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; import { store as coreStore } from '@wordpress/core-data'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -33,6 +38,7 @@ export default function DeleteTemplate() { template: _isEditing ? getEditedPostTemplate() : null, }; }, [] ); + const [ showConfirmDialog, setShowConfirmDialog ] = useState( false ); if ( ! template || ! template.wp_id ) { return null; @@ -42,53 +48,58 @@ export default function DeleteTemplate() { templateTitle = template.title; } + const onDelete = () => { + clearSelectedBlock(); + setIsEditingTemplate( false ); + setShowConfirmDialog( false ); + + editPost( { + template: '', + } ); + const settings = getEditorSettings(); + const newAvailableTemplates = pickBy( + settings.availableTemplates, + ( _title, id ) => { + return id !== template.slug; + } + ); + updateEditorSettings( { + ...settings, + availableTemplates: newAvailableTemplates, + } ); + deleteEntityRecord( 'postType', 'wp_template', template.id ); + }; + return ( - { - if ( - // eslint-disable-next-line no-alert - window.confirm( - sprintf( - /* translators: %s: template name */ - __( - 'Are you sure you want to delete the %s template? It may be used by other pages or posts.' - ), - templateTitle - ) - ) - ) { - clearSelectedBlock(); - setIsEditingTemplate( false ); - - editPost( { - template: '', - } ); - const settings = getEditorSettings(); - const newAvailableTemplates = pickBy( - settings.availableTemplates, - ( _title, id ) => { - return id !== template.slug; - } - ); - updateEditorSettings( { - ...settings, - availableTemplates: newAvailableTemplates, - } ); - deleteEntityRecord( - 'postType', - 'wp_template', - template.id - ); - } - } } - > - { __( 'Delete template' ) } - + <> + { + setShowConfirmDialog( true ); + } } + > + { __( 'Delete template' ) } + + { + setShowConfirmDialog( false ); + } } + > + { sprintf( + /* translators: %s: template name */ + __( + 'Are you sure you want to delete the %s template? It may be used by other pages or posts.' + ), + templateTitle + ) } + + ); }