diff --git a/projects/plugins/jetpack/changelog/fix-ai-sidebar-flaky-big-sky b/projects/plugins/jetpack/changelog/fix-ai-sidebar-flaky-big-sky new file mode 100644 index 000000000000..7906e4e904a5 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-ai-sidebar-flaky-big-sky @@ -0,0 +1,3 @@ +Significance: patch +Type: other +Comment: Stabilize AI Sidebar PHPUnit test isolation. diff --git a/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php b/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php index 3584417c8f52..f89b7e61d781 100644 --- a/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php +++ b/projects/plugins/jetpack/tests/php/extensions/plugins/ai-sidebar/Jetpack_AI_Sidebar_Test.php @@ -7,6 +7,7 @@ use Automattic\Jetpack\Extensions\AiAssistantPlugin; use Automattic\Jetpack\Extensions\AiAssistantPlugin\Jetpack_AI_Sidebar; +use Automattic\Jetpack\Status\Cache as Status_Cache; require_once JETPACK__PLUGIN_DIR . '/extensions/plugins/ai-assistant-plugin/ai-sidebar/class-jetpack-ai-sidebar.php'; @@ -37,20 +38,34 @@ class Jetpack_AI_Sidebar_Test extends WP_UnitTestCase { */ private $saved_screen; + /** + * Saved current user ID. + * + * @var int + */ + private $saved_current_user_id; + /** * Set up before each test. */ public function set_up() { parent::set_up(); + add_filter( 'jetpack_offline_mode', '__return_false' ); + update_option( 'jetpack_offline_mode', '0' ); + Status_Cache::clear(); delete_transient( AiAssistantPlugin\AM_ASSET_TRANSIENT ); delete_transient( AiAssistantPlugin\AM_ASSET_DC_TRANSIENT ); delete_transient( AiAssistantPlugin\AI_SIDEBAR_ASSET_TRANSIENT ); - $this->saved_wp_scripts = $GLOBALS['wp_scripts'] ?? null; - $this->saved_wp_styles = $GLOBALS['wp_styles'] ?? null; - $GLOBALS['wp_scripts'] = new WP_Scripts(); - $GLOBALS['wp_styles'] = new WP_Styles(); - $this->saved_screen = $GLOBALS['current_screen'] ?? null; + $this->saved_wp_scripts = $GLOBALS['wp_scripts'] ?? null; + $this->saved_wp_styles = $GLOBALS['wp_styles'] ?? null; + $GLOBALS['wp_scripts'] = new WP_Scripts(); + $GLOBALS['wp_styles'] = new WP_Styles(); + $this->saved_screen = $GLOBALS['current_screen'] ?? null; + $this->saved_current_user_id = get_current_user_id(); $this->simulate_connected_owner(); + // Ensure Big Sky is disabled by default so tests aren't affected by the + // Big_Sky class persisting across tests once it is declared. + update_option( 'big_sky_enable', '0' ); } /** @@ -64,7 +79,12 @@ public function tear_down() { remove_all_filters( 'agents_manager_agent_providers' ); remove_all_filters( 'pre_http_request' ); remove_all_filters( 'jetpack_ai_enabled' ); + remove_all_filters( 'jetpack_offline_mode' ); + Status_Cache::clear(); ( new \Automattic\Jetpack\Connection\Manager( 'jetpack' ) )->reset_connection_status(); + delete_option( 'jetpack_offline_mode' ); + delete_option( 'big_sky_enable' ); + wp_set_current_user( $this->saved_current_user_id ); $GLOBALS['current_screen'] = $this->saved_screen; $GLOBALS['wp_scripts'] = $this->saved_wp_scripts; $GLOBALS['wp_styles'] = $this->saved_wp_styles; @@ -78,6 +98,7 @@ private function simulate_connected_owner() { $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); \Jetpack_Options::update_option( 'master_user', $user_id ); \Jetpack_Options::update_option( 'user_tokens', array( $user_id => 'token.secret.' . $user_id ) ); + wp_set_current_user( $user_id ); ( new \Automattic\Jetpack\Connection\Manager( 'jetpack' ) )->reset_connection_status(); } @@ -96,6 +117,16 @@ private function enable_sidebar() { add_filter( 'jetpack_ai_sidebar_enabled', '__return_true' ); } + /** + * Simulate the Big_Sky class existing. + */ + private function simulate_big_sky_class() { + if ( ! class_exists( 'Big_Sky' ) ) { + // phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound, Generic.Classes.DuplicateClassName.Found + eval( 'class Big_Sky {}' ); // @codingStandardsIgnoreLine — minimal stub for unit test isolation. + } + } + /** * Cache AI sidebar asset data so register_provider succeeds. * @@ -109,6 +140,7 @@ private function cache_sidebar_asset_data( $data = null ) { ); } set_transient( AiAssistantPlugin\AI_SIDEBAR_ASSET_TRANSIENT, $data, HOUR_IN_SECONDS ); + $this->mock_asset_request( 'jetpack-ai-sidebar.asset.json', $data ); } /** @@ -128,6 +160,38 @@ private function cache_am_asset_data( $variant = 'gutenberg', $data = null ) { ? AiAssistantPlugin\AM_ASSET_DC_TRANSIENT : AiAssistantPlugin\AM_ASSET_TRANSIENT; set_transient( $transient, $data, HOUR_IN_SECONDS ); + $this->mock_asset_request( "agents-manager-{$variant}.asset.json", $data ); + } + + /** + * Mock a CDN asset manifest response for tests that run with SCRIPT_DEBUG. + * + * @param string $filename Asset manifest filename. + * @param array $data Asset data. + */ + private function mock_asset_request( $filename, $data ) { + add_filter( + 'pre_http_request', + static function ( $preempt, $parsed_args, $url ) use ( $filename, $data ) { + if ( ! is_string( $url ) || substr( $url, -strlen( $filename ) ) !== $filename ) { + return $preempt; + } + + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => wp_json_encode( $data, JSON_HEX_TAG | JSON_HEX_AMP ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + ); + }, + 10, + 3 + ); } // ────────────────────────────────────────────────── @@ -195,6 +259,20 @@ public function test_maybe_enqueue_am_enqueues_in_block_editor() { $this->assertTrue( wp_script_is( 'agents-manager', 'enqueued' ) ); } + /** + * Test that maybe_enqueue_am enqueues when Big Sky exists but is disabled. + */ + public function test_maybe_enqueue_am_enqueues_when_big_sky_is_disabled() { + $this->simulate_big_sky_class(); + update_option( 'big_sky_enable', '0' ); + $this->set_block_editor_screen(); + $this->cache_am_asset_data(); + + Jetpack_AI_Sidebar::maybe_enqueue_am(); + + $this->assertTrue( wp_script_is( 'agents-manager', 'enqueued' ) ); + } + /** * Test that maybe_enqueue_am skips when AM is already loaded. */ @@ -332,6 +410,10 @@ public function test_register_provider_preserves_existing_providers() { * Test that AI sidebar asset data is cached and used when enqueueing. */ public function test_sidebar_asset_data_is_cached() { + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + $this->markTestSkipped( 'Asset manifest transients are bypassed when SCRIPT_DEBUG is enabled.' ); + } + $this->set_block_editor_screen(); $this->cache_sidebar_asset_data( array(