From 12d4c2cb11024e243bf7b5af3b6bdf3dac65fd6f Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 16 Jun 2026 23:57:34 +0200 Subject: [PATCH 1/4] Prevent creation of empty draft from Quick Draft dashboard widget. --- src/wp-admin/post.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/wp-admin/post.php b/src/wp-admin/post.php index dd7bad1bb3830..95266cc88d3ed 100644 --- a/src/wp-admin/post.php +++ b/src/wp-admin/post.php @@ -96,11 +96,19 @@ $_POST['comment_status'] = get_default_comment_status( $post->post_type ); $_POST['ping_status'] = get_default_comment_status( $post->post_type, 'pingback' ); - // Wrap Quick Draft content in the Paragraph block. - if ( ! str_contains( $_POST['content'], '' ) ) { + $quickdraft_post_title = trim( $_POST['post_title'] ); + $quickdraft_post_content = trim( $_POST['content'] ); + + if ( empty( $quickdraft_post_title ) && empty( $quickdraft_post_content ) ) { + return wp_dashboard_quick_press( __( 'Cannot create a draft post with empty title and content.' ) ); + } + + // Wrap Quick Draft content in a Paragraph block. + if ( ! empty( $quickdraft_post_content ) && ! str_contains( $quickdraft_post_content, '' ) ) { + // Note that `edit_post()` reads from the $_POST superglobal by reference. $_POST['content'] = sprintf( '%s', - str_replace( array( "\r\n", "\r", "\n" ), '
', $_POST['content'] ) + str_replace( array( "\r\n", "\r", "\n" ), '
', $quickdraft_post_content ) ); } From e28d63af23bd64c3397a48b6af14a53ee0bd904e Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 17 Jun 2026 00:04:16 +0200 Subject: [PATCH 2/4] Only wrap Quick Draft content in a Paragraph block if the block editor is in use. --- src/wp-admin/post.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wp-admin/post.php b/src/wp-admin/post.php index 95266cc88d3ed..8341ac969559b 100644 --- a/src/wp-admin/post.php +++ b/src/wp-admin/post.php @@ -104,7 +104,11 @@ } // Wrap Quick Draft content in a Paragraph block. - if ( ! empty( $quickdraft_post_content ) && ! str_contains( $quickdraft_post_content, '' ) ) { + if ( + use_block_editor_for_post_type( 'post' ) && + ! empty( $quickdraft_post_content ) && + ! str_contains( $quickdraft_post_content, '' ) + ) { // Note that `edit_post()` reads from the $_POST superglobal by reference. $_POST['content'] = sprintf( '%s', From f5f4d5ece11772b8f252d21d89726d71b92fbdd9 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 17 Jun 2026 08:34:25 +0200 Subject: [PATCH 3/4] Make Quick Draft notice an ARIA alert. --- src/wp-admin/includes/dashboard.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 778e3de40326b..f82ef53309aaf 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -586,6 +586,9 @@ function wp_dashboard_quick_press( $error_msg = false ) { $error_msg, array( 'additional_classes' => array( 'error' ), + 'attributes' => array( + 'role' => 'alert', + ), ) ); } From b790734a49588cffe38fb040de374d0d3bc7adf9 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 17 Jun 2026 12:15:05 +0200 Subject: [PATCH 4/4] Add E2E tests for the Quick Draft. --- src/wp-admin/css/dashboard.css | 2 +- src/wp-admin/includes/dashboard.php | 2 +- tests/e2e/specs/dashboard.test.js | 129 ++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 19 deletions(-) diff --git a/src/wp-admin/css/dashboard.css b/src/wp-admin/css/dashboard.css index ab73f828f7067..fc5d83bef6dc8 100644 --- a/src/wp-admin/css/dashboard.css +++ b/src/wp-admin/css/dashboard.css @@ -854,7 +854,7 @@ body #dashboard-widgets .postbox form .submit { color: #646970; } -#dashboard_quick_press .drafts p { +#dashboard_quick_press .drafts .draft-content { margin: 0; word-wrap: break-word; } diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index f82ef53309aaf..761e85f640ccd 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -691,7 +691,7 @@ function wp_dashboard_recent_drafts( $drafts = false ) { $the_content = wp_trim_words( $draft->post_content, $draft_length ); if ( $the_content ) { - echo '

' . $the_content . '

'; + echo '

' . $the_content . '

'; } echo "\n"; } diff --git a/tests/e2e/specs/dashboard.test.js b/tests/e2e/specs/dashboard.test.js index 90459ac83ae6f..09508f6622299 100644 --- a/tests/e2e/specs/dashboard.test.js +++ b/tests/e2e/specs/dashboard.test.js @@ -8,13 +8,13 @@ test.describe( 'Quick Draft', () => { await requestUtils.deleteAllPosts(); } ); - test( 'Allows draft to be created with Title and Content', async ( { + test( 'should allow Quick Draft to be created with Title and Content', async ( { admin, page } ) => { await admin.visitAdminPage( '/' ); - // Wait for Quick Draft title field to appear. + // Wait for the Quick Draft title field to appear. const draftTitleField = page.locator( '#quick-press' ).getByRole( 'textbox', { name: 'Title' } ); @@ -22,36 +22,51 @@ test.describe( 'Quick Draft', () => { await expect( draftTitleField ).toBeVisible(); // Focus and fill in a title. - await draftTitleField.fill( 'Test Draft Title' ); + await draftTitleField.fill( 'Quick Draft test title' ); - // Navigate to content field and type in some content - await page.keyboard.press( 'Tab' ); - await page.keyboard.type( 'Test Draft Content' ); + // Wait for the Quick Draft content textarea to appear. + const quickDraftContentTextarea = page.locator( + '#quick-press' + ).getByRole( 'textbox', { name: 'Content' } ); + + await expect( quickDraftContentTextarea ).toBeVisible(); + + // Focus and fill in some content. + await quickDraftContentTextarea.fill( 'Quick Draft test content' ); + + // Wait for the Save Draft button to appear and click it. + const saveDraftButton = page.locator( + '#quick-press' + ).getByRole( 'button', { name: 'Save Draft' } ); - // Navigate to Save Draft button and press it. - await page.keyboard.press( 'Tab' ); - await page.keyboard.press( 'Enter' ); + await expect( saveDraftButton ).toBeVisible(); + await saveDraftButton.click(); - // Check that new draft appears in Your Recent Drafts section + // Check that the new draft title appears in the 'Your Recent Drafts' section. await expect( page.locator( '.drafts .draft-title' ).first().getByRole( 'link' ) - ).toHaveText( 'Test Draft Title' ); + ).toHaveText( 'Quick Draft test title' ); + + // Check that the new draft content appears in the 'Your Recent Drafts' section. + await expect( + page.locator( '.drafts .draft-content' ).first() + ).toHaveText( 'Quick Draft test content' ); - // Check that new draft appears in Posts page + // Check that the new draft appears in the Posts page. await admin.visitAdminPage( '/edit.php' ); await expect( page.locator( '.type-post.status-draft .title' ).first() - ).toContainText( 'Test Draft Title' ); + ).toContainText( 'Quick Draft test title' ); } ); - test( 'Allows draft to be created without Title or Content', async ( { + test( 'should prevent Quick Draft from being created without Title or Content', async ( { admin, page } ) => { await admin.visitAdminPage( '/' ); - // Wait for Save Draft button to appear and click it + // Wait for the Save Draft button to appear and click it. const saveDraftButton = page.locator( '#quick-press' ).getByRole( 'button', { name: 'Save Draft' } ); @@ -59,12 +74,92 @@ test.describe( 'Quick Draft', () => { await expect( saveDraftButton ).toBeVisible(); await saveDraftButton.click(); - // Check that new draft appears in Your Recent Drafts section + // Check that an admin notice with ARIA role 'alert' appears. + await expect( + page.locator( '#quick-press' ).getByRole( 'alert' ) + ).toHaveText( 'Cannot create a draft post with empty title and content.' ); + + // Check that no new draft appears in the Posts page. + await admin.visitAdminPage( '/edit.php' ); + + await expect( + page.locator( '#the-list .no-items .colspanchange' ) + ).toContainText( 'No posts found.' ); + } ); + + test( 'should allow Quick Draft to be created with only the Title', async ( { + admin, + page + } ) => { + await admin.visitAdminPage( '/' ); + + // Wait for the Quick Draft title field to appear. + const quickDraftTitleField = page.locator( + '#quick-press' + ).getByRole( 'textbox', { name: 'Title' } ); + + await expect( quickDraftTitleField ).toBeVisible(); + + // Focus and fill in a title. + await quickDraftTitleField.fill( 'Quick Draft test title' ); + + // Wait for the Save Draft button to appear and click it. + const saveDraftButton = page.locator( + '#quick-press' + ).getByRole( 'button', { name: 'Save Draft' } ); + + await expect( saveDraftButton ).toBeVisible(); + await saveDraftButton.click(); + + // Check that the new draft title appears in the 'Your Recent Drafts' section. + await expect( + page.locator( '.drafts .draft-title' ).first().getByRole( 'link' ) + ).toHaveText( 'Quick Draft test title' ); + + // Check that the new draft appears in the Posts page. + await admin.visitAdminPage( '/edit.php' ); + + await expect( + page.locator( '.type-post.status-draft .title' ).first() + ).toContainText( 'Quick Draft test title' ); + } ); + + test( 'should allow Quick Draft to be created with only the Content', async ( { + admin, + page + } ) => { + await admin.visitAdminPage( '/' ); + + // Wait for the Quick Draft content textarea to appear. + const quickDraftContentTextarea = page.locator( + '#quick-press' + ).getByRole( 'textbox', { name: 'Content' } ); + + await expect( quickDraftContentTextarea ).toBeVisible(); + + // Focus and fill in some content. + await quickDraftContentTextarea.fill( 'Quick Draft test content' ); + + // Wait for the Save Draft button to appear and click it. + const saveDraftButton = page.locator( + '#quick-press' + ).getByRole( 'button', { name: 'Save Draft' } ); + + await expect( saveDraftButton ).toBeVisible(); + await saveDraftButton.click(); + + // Check that the new draft title appears in the 'Your Recent Drafts' section. + // This test relies on Twenty Twenty-One being the active theme. + // Twenty Twenty-One alters the default post title from "(no title)" to "Untitled". await expect( page.locator( '.drafts .draft-title' ).first().getByRole( 'link' ) ).toHaveText( 'Untitled' ); - // Check that new draft appears in Posts page + await expect( + page.locator( '.drafts .draft-content' ).first() + ).toHaveText( 'Quick Draft test content' ); + + // Check that the new draft appears in the Posts page. await admin.visitAdminPage( '/edit.php' ); await expect(