Repository navigation
RTC: fix interaction with autosave that can cause posts and revisions to get lost even when a single user is editing a post #77865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dmsnell
merged 13 commits into
WordPress:trunk
from
danluu:fix/rtc-autodraft-autosave-loss-pr
May 5, 2026
Merged
Changes from 11 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
8f8c725
Add RTC auto-draft autosave REST tests
danluu 300363a
Add browser test for RTC auto-draft autosave loss
danluu 7c4e3ac
Promote RTC auto-drafts on autosave
danluu 761b7a8
Add backport reference
dmsnell 56810f5
Fix RTC auto-draft autosave e2e expectations
danluu b677576
Fix autosave e2e REST query handling
danluu 9562595
Merge remote-tracking branch 'origin/trunk' into fix/rtc-autodraft-au…
dmsnell 9ee5b38
Update docs and rename variables to clarify logic.
dmsnell 9fb36f8
Revise comment change
dmsnell 8a78d45
Extract condition to give it a name showing its purpose.
dmsnell 7f4e2d9
Merge remote-tracking branch 'origin/trunk' into fix/rtc-autodraft-au…
dmsnell 98a4234
Add RTC auto-draft collaborator autosave test
danluu 163ab5e
Allow RTC collaborators to promote auto-drafts
danluu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| https://github.com/WordPress/wordpress-develop/pull/11691 | ||
|
|
||
| * https://github.com/WordPress/gutenberg/pull/77865 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| <?php | ||
| /** | ||
| * Tests for the collaboration autosaves REST controller override. | ||
| * | ||
| * @package gutenberg | ||
| * @subpackage Collaboration | ||
| * | ||
| * @group collaboration | ||
| * @group restapi | ||
| */ | ||
| class Tests_Collaboration_RestAutosavesController extends WP_UnitTestCase { | ||
|
|
||
| protected static int $author_id; | ||
|
|
||
| public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { | ||
| self::$author_id = $factory->user->create( array( 'role' => 'author' ) ); | ||
| } | ||
|
|
||
| public static function wpTearDownAfterClass() { | ||
| self::delete_user( self::$author_id ); | ||
| delete_option( 'wp_collaboration_enabled' ); | ||
| } | ||
|
|
||
| public function set_up() { | ||
| parent::set_up(); | ||
| wp_set_current_user( self::$author_id ); | ||
| } | ||
|
|
||
| /** | ||
| * Creates an empty auto-draft post. | ||
| * | ||
| * @return int Post ID. | ||
| */ | ||
| private function create_auto_draft(): int { | ||
| return self::factory()->post->create( | ||
| array( | ||
| 'post_author' => self::$author_id, | ||
| 'post_content' => '', | ||
| 'post_status' => 'auto-draft', | ||
| 'post_title' => 'Auto Draft', | ||
| 'post_type' => 'post', | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Dispatches an autosave request for a post. | ||
| * | ||
| * @param int $post_id Post ID. | ||
| * @param string $title Autosaved post title. | ||
| * @param string $content Autosaved post content. | ||
| * @return WP_REST_Response Autosave response. | ||
| */ | ||
| private function dispatch_autosave( int $post_id, string $title, string $content ): WP_REST_Response { | ||
| $request = new WP_REST_Request( 'POST', "/wp/v2/posts/{$post_id}/autosaves" ); | ||
| $request->set_param( 'title', $title ); | ||
| $request->set_param( 'content', $content ); | ||
| $request->set_param( 'status', 'draft' ); | ||
|
|
||
| return rest_get_server()->dispatch( $request ); | ||
| } | ||
|
|
||
| public function test_auto_draft_autosave_promotes_parent_post_when_collaboration_is_disabled() { | ||
| update_option( 'wp_collaboration_enabled', 0 ); | ||
|
|
||
| $post_id = $this->create_auto_draft(); | ||
| $title = 'No RTC autosaved title'; | ||
| $content = '<!-- wp:paragraph --><p>No RTC autosaved content</p><!-- /wp:paragraph -->'; | ||
|
|
||
| $response = $this->dispatch_autosave( $post_id, $title, $content ); | ||
|
|
||
| $this->assertSame( 200, $response->get_status() ); | ||
| $post = get_post( $post_id ); | ||
| $this->assertSame( 'draft', $post->post_status ); | ||
| $this->assertSame( $title, $post->post_title ); | ||
| $this->assertSame( $content, $post->post_content ); | ||
| } | ||
|
|
||
| public function test_auto_draft_autosave_promotes_parent_post_when_collaboration_is_enabled() { | ||
| update_option( 'wp_collaboration_enabled', 1 ); | ||
|
|
||
| $post_id = $this->create_auto_draft(); | ||
| $title = 'RTC autosaved title'; | ||
| $content = '<!-- wp:paragraph --><p>RTC autosaved content</p><!-- /wp:paragraph -->'; | ||
|
|
||
| $response = $this->dispatch_autosave( $post_id, $title, $content ); | ||
|
|
||
| $this->assertSame( 200, $response->get_status() ); | ||
| $post = get_post( $post_id ); | ||
| $this->assertSame( 'draft', $post->post_status ); | ||
| $this->assertSame( $title, $post->post_title ); | ||
| $this->assertSame( $content, $post->post_content ); | ||
| } | ||
| } |
133 changes: 133 additions & 0 deletions
133
test/e2e/specs/editor/collaboration/collaboration-autodraft-autosave-loss.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import type { Page, Response } from '@playwright/test'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { test, expect } from './fixtures'; | ||
|
|
||
| type RestPost = { | ||
| id: number; | ||
| status: string; | ||
| title?: { raw?: string; rendered?: string } | string; | ||
| content?: { raw?: string; rendered?: string } | string; | ||
| }; | ||
|
|
||
| function rawField( field: RestPost[ 'title' ] ): string { | ||
| if ( ! field ) { | ||
| return ''; | ||
| } | ||
|
|
||
| return typeof field === 'string' | ||
| ? field | ||
| : field.raw ?? field.rendered ?? ''; | ||
| } | ||
|
|
||
| async function getCurrentPostId( page: Page ): Promise< number > { | ||
| return page.evaluate( () => | ||
| ( window as any ).wp.data.select( 'core/editor' ).getCurrentPostId() | ||
| ); | ||
| } | ||
|
|
||
| function isAutosaveResponse( response: Response, postId: number ): boolean { | ||
| if ( response.request().method() !== 'POST' ) { | ||
| return false; | ||
| } | ||
|
|
||
| const url = new URL( response.url() ); | ||
| const route = `/wp/v2/posts/${ postId }/autosaves`; | ||
|
|
||
| return ( | ||
| url.pathname.includes( `/wp-json${ route }` ) || | ||
| url.searchParams.get( 'rest_route' ) === route | ||
| ); | ||
| } | ||
|
|
||
| async function useFastAutosaveInterval( page: Page ): Promise< void > { | ||
| await page.evaluate( () => { | ||
| const editorStore = ( window as any ).wp.data.select( 'core/editor' ); | ||
| const settings = editorStore.getEditorSettings(); | ||
|
|
||
| ( window as any ).wp.data | ||
| .dispatch( 'core/editor' ) | ||
| .updateEditorSettings( { | ||
| ...settings, | ||
| autosaveInterval: 1, | ||
| } ); | ||
| } ); | ||
| } | ||
|
|
||
| test.describe( 'Collaboration - auto-draft autosave retention', () => { | ||
| test( 'keeps a new post discoverable after the editor automatically autosaves it', async ( { | ||
| admin, | ||
| collaborationUtils, | ||
| editor, | ||
| page, | ||
| requestUtils, | ||
| } ) => { | ||
| test.setTimeout( 120_000 ); | ||
|
|
||
| const title = 'RTC automatic auto-draft autosave title'; | ||
| const marker = 'rtc-automatic-auto-draft-autosave-content'; | ||
|
|
||
| await admin.visitAdminPage( 'post-new.php' ); | ||
| await editor.setPreferences( 'core/edit-post', { | ||
| welcomeGuide: false, | ||
| fullscreenMode: false, | ||
| } ); | ||
| await collaborationUtils.waitForEntityReady( page, { | ||
| requireCollaboration: false, | ||
| timeout: 30_000, | ||
| } ); | ||
| await collaborationUtils.waitForCollaborationReady( page, { | ||
| timeout: 30_000, | ||
| } ); | ||
|
|
||
| await editor.canvas | ||
| .getByRole( 'textbox', { name: 'Add title' } ) | ||
| .fill( title ); | ||
| await editor.canvas | ||
| .getByRole( 'button', { name: 'Add default block' } ) | ||
| .click(); | ||
| await page.keyboard.type( marker ); | ||
|
|
||
| const postId = await getCurrentPostId( page ); | ||
| const autosaveRequest = page.waitForResponse( | ||
| ( response ) => isAutosaveResponse( response, postId ), | ||
| { timeout: 30_000 } | ||
| ); | ||
| await useFastAutosaveInterval( page ); | ||
|
|
||
| await autosaveRequest; | ||
| await page.waitForFunction( | ||
| () => | ||
| ! ( window as any ).wp.data | ||
| .select( 'core/editor' ) | ||
| .isAutosavingPost(), | ||
| undefined, | ||
| { timeout: 30_000 } | ||
| ); | ||
|
|
||
| const post = await requestUtils.rest< RestPost >( { | ||
| path: `/wp/v2/posts/${ postId }`, | ||
| params: { | ||
| context: 'edit', | ||
| }, | ||
| } ); | ||
| const drafts = await requestUtils.rest< RestPost[] >( { | ||
| path: '/wp/v2/posts', | ||
| params: { | ||
| context: 'edit', | ||
| status: 'draft', | ||
| search: title, | ||
| }, | ||
| } ); | ||
|
|
||
| expect( post.status ).toBe( 'draft' ); | ||
| expect( rawField( post.title ) ).toContain( title ); | ||
| expect( rawField( post.content ) ).toContain( marker ); | ||
| expect( drafts.map( ( draft ) => draft.id ) ).toContain( postId ); | ||
| } ); | ||
| } ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.