diff --git a/tests/acceptance/PostLock.spec.ts b/tests/acceptance/PostLock.spec.ts new file mode 100644 index 0000000..5abb21a --- /dev/null +++ b/tests/acceptance/PostLock.spec.ts @@ -0,0 +1,42 @@ +import { test, expect } from './utils/test-setup'; + +test.describe( 'Post Lock', () => { + let sharedPostId: string; + let editorId: string; + + test.beforeAll( async ( { globalUtils } ) => { + await globalUtils.installWordPress(); + sharedPostId = globalUtils.runWPCLICommand( 'post create --post_title="Test Post" --post_status=publish --porcelain' ); + editorId = globalUtils.runWPCLICommand( 'user create editor editor@example.com --role=editor --user_pass=password --porcelain' ); + } ); + + test( 'Switch back from a post editing screen releases the post lock', { + annotation: { + type: 'user-story', + description: 'As a user who has switched accounts to edit a post, I want to switch back without seeing the post locked modal, in order to continue editing' + } + }, async ( { + page, + admin, + userSwitching, + globalUtils, + } ) => { + await userSwitching.loginViaPage( 'admin', 'password' ); + + // Simulate a fresh lock left behind by the editor user. + const setStaleLock = () => { + const now = Math.floor( Date.now() / 1000 ); + globalUtils.runWPCLICommand( `post meta update ${sharedPostId} _edit_lock "${now}:${editorId}"` ); + }; + + // Without the after-switch-back query args, the lock modal should be visible. + setStaleLock(); + await admin.visitAdminPage( 'post.php', `post=${sharedPostId}&action=edit` ); + await expect( page.getByText( 'This post is already being edited' ) ).toBeVisible(); + + // With the after-switch-back query args, the lock modal should not be visible. + setStaleLock(); + await admin.visitAdminPage( 'post.php', `post=${sharedPostId}&action=edit&user_switched=true&switched_back=true` ); + await expect( page.getByText( 'This post is already being edited' ) ).not.toBeVisible(); + } ); +} ); diff --git a/tests/integration/PostLockTest.php b/tests/integration/PostLockTest.php new file mode 100644 index 0000000..9862fc4 --- /dev/null +++ b/tests/integration/PostLockTest.php @@ -0,0 +1,57 @@ + 'true', + 'action' => 'edit', + ]; + + self::assertSame( 0, apply_filters( 'wp_check_post_lock_window', 150 ) ); + } + + public function testPostLockWindowIsUnchangedWithoutSwitchedBack(): void { + $_GET = [ + 'action' => 'edit', + ]; + + self::assertSame( 150, apply_filters( 'wp_check_post_lock_window', 150 ) ); + } + + public function testPostLockWindowIsUnchangedWhenActionIsNotEdit(): void { + $_GET = [ + 'switched_back' => 'true', + ]; + + self::assertSame( 150, apply_filters( 'wp_check_post_lock_window', 150 ) ); + } + + public function testStalePostLockIsTreatedAsExpiredAfterSwitchBack(): void { + $current = self::$testers['admin']; + $previous = self::$users['editor']; + + $post = self::factory()->post->create_and_get(); + self::assertInstanceOf( 'WP_Post', $post ); + + wp_set_current_user( $current->ID ); + + // Simulate a fresh lock left behind by the user being switched away from. + update_post_meta( $post->ID, '_edit_lock', time() . ':' . $previous->ID ); + + // Without the after-switch-back query args, the lock is considered active. + $_GET = []; + self::assertSame( $previous->ID, wp_check_post_lock( $post->ID ) ); + + // With the after-switch-back query args, the lock is treated as expired. + $_GET = [ + 'switched_back' => 'true', + 'action' => 'edit', + ]; + self::assertFalse( wp_check_post_lock( $post->ID ) ); + } +} diff --git a/user-switching.php b/user-switching.php index 225822f..99f869d 100644 --- a/user-switching.php +++ b/user-switching.php @@ -73,6 +73,7 @@ public function init_hooks(): void { add_filter( 'ms_user_row_actions', [ $this, 'filter_user_row_actions' ], 10, 2 ); add_filter( 'login_message', [ $this, 'filter_login_message' ], 1 ); add_filter( 'removable_query_args', [ $this, 'filter_removable_query_args' ] ); + add_filter( 'wp_check_post_lock_window', [ $this, 'filter_post_lock_window_after_switch_back' ] ); add_action( 'wp_meta', [ $this, 'action_wp_meta' ] ); add_filter( 'plugin_row_meta', [ $this, 'filter_plugin_row_meta' ], 10, 2 ); add_action( 'wp_footer', [ $this, 'action_wp_footer' ] ); @@ -975,6 +976,24 @@ public function filter_removable_query_args( array $args ): array { ] ); } + /** + * Treats the post lock as expired on the post-edit screen immediately after switching back. + * + * @param int $window The post lock window duration in seconds. + * @return int The filtered post lock window duration in seconds. + */ + public function filter_post_lock_window_after_switch_back( int $window ): int { + if ( empty( $_GET['switched_back'] ) ) { + return $window; + } + + if ( ( $_GET['action'] ?? '' ) !== 'edit' ) { + return $window; + } + + return 0; + } + /** * Returns the switch to or switch back URL for a given user. *