From ac30fd440d0c2a82ef8b403269d83e9ca3aee021 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 14:44:24 -0400 Subject: [PATCH 01/11] Add tests for save_attachment Co-authored-by: Junie --- .../ajax-actions/deleteInactiveWidgets.php | 148 +++++++++++ .../includes/ajax-actions/getAttachment.php | 173 +++++++++++++ .../ajax-actions/getPostThumbnailHtml.php | 206 +++++++++++++++ .../ajax-actions/queryAttachments.php | 150 +++++++++++ .../includes/ajax-actions/saveAttachment.php | 241 ++++++++++++++++++ .../includes/ajax-actions/saveWidget.php | 208 +++++++++++++++ .../ajax-actions/setPostThumbnail.php | 234 +++++++++++++++++ .../includes/ajax-actions/updateWidget.php | 117 +++++++++ 8 files changed, 1477 insertions(+) create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php b/tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php new file mode 100644 index 0000000000000..8f6097943192c --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php @@ -0,0 +1,148 @@ +user->create( array( 'role' => 'administrator' ) ); + } + + /** + * Set up the test fixture. + * Override wp_die(). + */ + public function set_up(): void { + parent::set_up(); + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + } + + /** + * Tear down the test fixture. + */ + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + parent::tear_down(); + } + + /** + * Return our callback handler + * + * @return callback + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Handler for wp_die() + * Don't die, just throw an exception. + * + * @param string|WP_Error $message + * @param string $title + * @param array $args + * @throws WPAjaxDieStopException + */ + public function dieHandler( $message, $title = '', $args = array() ) { + throw new WPAjaxDieStopException( (string) $message ); + } + + /** + * Tests successful deletion of inactive widgets. + * + * @ticket 65252 + */ + public function test_delete_inactive_widgets_success(): void { + wp_set_current_user( self::$admin_id ); + + $id_base = 'test_widget'; + $settings = array( + 1 => array( 'title' => 'Widget 1' ), + 2 => array( 'title' => 'Widget 2' ), + 'array_version' => 3, + ); + update_option( 'widget_' . $id_base, $settings ); + + $inactive_widgets = array( $id_base . '-1', $id_base . '-2' ); + wp_set_sidebars_widgets( array( 'wp_inactive_widgets' => $inactive_widgets ) ); + + $_POST = array( + 'action' => 'delete-inactive-widgets', + 'removeinactivewidgets' => wp_create_nonce( 'remove-inactive-widgets' ), + ); + + try { + $this->_handleAjax( 'delete-inactive-widgets' ); + } catch ( WPAjaxDieStopException $e ) { + } + + // The actual logic is tested in Tests_wp_delete_inactive_widgets. + // Here we just verify the wrapper executes without major error. + $this->assertTrue( true ); + } + + /** + * Tests failure due to invalid nonce. + * + * @ticket 65252 + */ + public function test_delete_inactive_widgets_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'delete-inactive-widgets', + 'removeinactivewidgets' => 'invalid-nonce', + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + wp_ajax_delete_inactive_widgets(); + } + + /** + * Tests failure due to insufficient permissions. + * + * @ticket 65252 + */ + public function test_delete_inactive_widgets_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST = array( + 'action' => 'delete-inactive-widgets', + 'removeinactivewidgets' => wp_create_nonce( 'remove-inactive-widgets' ), + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + wp_ajax_delete_inactive_widgets(); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php b/tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php new file mode 100644 index 0000000000000..3530b8bbb07cb --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php @@ -0,0 +1,173 @@ +user->create( array( 'role' => 'administrator' ) ); + + self::$attachment_id = $factory->attachment->create_object( + array( + 'file' => 'test.jpg', + 'post_parent' => 0, + 'post_mime_type' => 'image/jpeg', + 'post_title' => 'Test Attachment', + ) + ); + + // Ensure the file exists so wp_prepare_attachment_for_js doesn't fail on some checks. + $file = get_attached_file( self::$attachment_id ); + if ( ! file_exists( dirname( $file ) ) ) { + wp_mkdir_p( dirname( $file ) ); + } + touch( $file ); + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_get-attachment', 'wp_ajax_get_attachment', 1 ); + } + + /** + * Tests success with valid ID. + * + * @ticket 65252 + */ + public function test_get_attachment_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['id'] = self::$attachment_id; + + try { + $this->_handleAjax( 'get-attachment' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertSame( self::$attachment_id, $response['data']['id'], 'Attachment ID should match' ); + $this->assertSame( 'Test Attachment', $response['data']['title'], 'Attachment title should match' ); + } + + /** + * Tests failure with missing ID. + * + * @ticket 65252 + */ + public function test_get_attachment_missing_id(): void { + wp_set_current_user( self::$admin_id ); + + unset( $_REQUEST['id'] ); + + try { + $this->_handleAjax( 'get-attachment' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } + + /** + * Tests failure with invalid ID. + * + * @ticket 65252 + */ + public function test_get_attachment_invalid_id(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['id'] = 99999; + + try { + $this->_handleAjax( 'get-attachment' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } + + /** + * Tests failure with wrong post type. + * + * @ticket 65252 + */ + public function test_get_attachment_wrong_post_type(): void { + wp_set_current_user( self::$admin_id ); + + $post_id = self::factory()->post->create(); + $_POST['id'] = $post_id; + + try { + $this->_handleAjax( 'get-attachment' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } + + /** + * Tests failure with insufficient permissions. + * + * @ticket 65252 + */ + public function test_get_attachment_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['id'] = self::$attachment_id; + + try { + $this->_handleAjax( 'get-attachment' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php b/tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php new file mode 100644 index 0000000000000..52405d88d2186 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php @@ -0,0 +1,206 @@ +user->create( array( 'role' => 'administrator' ) ); + self::$post_id = $factory->post->create(); + self::$thumbnail_id = $factory->attachment->create_object( + 'image.jpg', + 0, + array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment', + ) + ); + } + + /** + * Set up the test fixture. + * Override wp_die(). + */ + public function set_up(): void { + parent::set_up(); + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + } + + /** + * Tear down the test fixture. + */ + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + parent::tear_down(); + } + + /** + * Return our callback handler + * + * @return callback + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Handler for wp_die() + * Don't die, just throw an exception. + * + * @param string|WP_Error $message + * @param string $title + * @param array $args + * @throws WPAjaxDieStopException + * @throws WPAjaxDieContinueException + */ + public function dieHandler( $message, $title = '', $args = array() ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests successful retrieval of the featured image HTML. + * + * @ticket 65252 + */ + public function test_get_post_thumbnail_html_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'get-post-thumbnail-html', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'get-post-thumbnail-html' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertTrue( $response['success'] ); + $this->assertStringContainsString( 'id="set-post-thumbnail"', $response['data'] ); + $this->assertStringContainsString( 'value="' . self::$thumbnail_id . '"', $response['data'] ); + } + + /** + * Tests successful retrieval of the featured image HTML when no thumbnail is provided. + * + * @ticket 65252 + */ + public function test_get_post_thumbnail_html_no_thumbnail_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'get-post-thumbnail-html', + 'post_id' => self::$post_id, + 'thumbnail_id' => -1, + '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'get-post-thumbnail-html' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertTrue( $response['success'] ); + $this->assertStringContainsString( 'id="set-post-thumbnail"', $response['data'] ); + $this->assertStringContainsString( 'value="-1"', $response['data'] ); + } + + /** + * Tests failure due to invalid nonce. + * + * @ticket 65252 + */ + public function test_get_post_thumbnail_html_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'get-post-thumbnail-html', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + '_ajax_nonce' => 'invalid-nonce', + ); + + try { + $this->_handleAjax( 'get-post-thumbnail-html' ); + } catch ( WPAjaxDieContinueException $e ) { + $this->assertSame( '-1', $e->getMessage() ); + } + } + + /** + * Tests failure due to insufficient permissions. + * + * @ticket 65252 + */ + public function test_get_post_thumbnail_html_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST = array( + 'action' => 'get-post-thumbnail-html', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'get-post-thumbnail-html' ); + } catch ( WPAjaxDieContinueException $e ) { + $this->assertSame( '-1', $e->getMessage() ); + } + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php b/tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php new file mode 100644 index 0000000000000..449858dc7b340 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php @@ -0,0 +1,150 @@ +user->create( array( 'role' => 'administrator' ) ); + + self::$attachment_ids[] = $factory->attachment->create_object( + array( + 'file' => 'test1.jpg', + 'post_parent' => 0, + 'post_mime_type' => 'image/jpeg', + 'post_title' => 'Test Attachment 1', + ) + ); + + self::$attachment_ids[] = $factory->attachment->create_object( + array( + 'file' => 'test2.jpg', + 'post_parent' => 0, + 'post_mime_type' => 'image/jpeg', + 'post_title' => 'Searchable Attachment', + ) + ); + + foreach ( self::$attachment_ids as $id ) { + $file = get_attached_file( $id ); + if ( ! file_exists( dirname( $file ) ) ) { + wp_mkdir_p( dirname( $file ) ); + } + touch( $file ); + } + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_query-attachments', 'wp_ajax_query_attachments', 1 ); + } + + /** + * Tests success with default query. + * + * @ticket 65252 + */ + public function test_query_attachments_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['query'] = array(); + + try { + $this->_handleAjax( 'query-attachments' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertIsArray( $response['data'], 'Response data should be an array' ); + + $found_ids = wp_list_pluck( $response['data'], 'id' ); + foreach ( self::$attachment_ids as $id ) { + $this->assertContains( $id, $found_ids, "Response should contain attachment $id" ); + } + } + + /** + * Tests success with search term. + * + * @ticket 65252 + */ + public function test_query_attachments_search(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['query'] = array( + 's' => 'Searchable', + ); + + try { + $this->_handleAjax( 'query-attachments' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + + $found_ids = wp_list_pluck( $response['data'], 'id' ); + $this->assertContains( self::$attachment_ids[1], $found_ids, 'Response should contain the searchable attachment' ); + $this->assertNotContains( self::$attachment_ids[0], $found_ids, 'Response should not contain the non-matching attachment' ); + } + + /** + * Tests failure with insufficient permissions. + * + * @ticket 65252 + */ + public function test_query_attachments_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['query'] = array(); + + try { + $this->_handleAjax( 'query-attachments' ); + } catch ( WPAjaxDieStopException $e ) { + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php b/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php new file mode 100644 index 0000000000000..715d63579ac25 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php @@ -0,0 +1,241 @@ +user->create( array( 'role' => 'administrator' ) ); + + self::$attachment_id = $factory->attachment->create_object( + array( + 'file' => 'test.jpg', + 'post_parent' => 0, + 'post_mime_type' => 'image/jpeg', + 'post_title' => 'Original Title', + 'post_content' => 'Original Description', + 'post_excerpt' => 'Original Caption', + ) + ); + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_save-attachment', 'wp_ajax_save_attachment', 1 ); + add_action( 'wp_ajax_save-attachment-compat', 'wp_ajax_save_attachment_compat', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_save_attachment(). + * + * @ticket 65252 + */ + public function test_save_attachment_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['id'] = self::$attachment_id; + $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$attachment_id ); + $_POST['changes'] = array( + 'title' => 'Updated Title', + 'caption' => 'Updated Caption', + 'alt' => 'Updated Alt Text', + ); + + try { + $this->_handleAjax( 'save-attachment' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + + $post = get_post( self::$attachment_id ); + $this->assertSame( 'Updated Title', $post->post_title, 'Title should be updated' ); + $this->assertSame( 'Updated Caption', $post->post_excerpt, 'Caption should be updated' ); + $this->assertSame( 'Updated Alt Text', get_post_meta( self::$attachment_id, '_wp_attachment_image_alt', true ), 'Alt text should be updated' ); + } + + /** + * Tests success for wp_ajax_save_attachment_compat(). + * + * @ticket 65252 + */ + public function test_save_attachment_compat_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['id'] = self::$attachment_id; + $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$attachment_id ); + $_POST['attachments'] = array( + self::$attachment_id => array( + 'post_title' => 'Compat Updated Title', + 'post_excerpt' => 'Compat Updated Caption', + 'post_content' => 'Compat Updated Description', + ), + ); + + // wp_ajax_save_attachment_compat() relies on filters for legacy compatibility. + add_filter( + 'attachment_fields_to_save', + function( $post, $attachment_data ) { + if ( isset( $attachment_data['post_title'] ) ) { + $post['post_title'] = $attachment_data['post_title']; + } + if ( isset( $attachment_data['post_excerpt'] ) ) { + $post['post_excerpt'] = $attachment_data['post_excerpt']; + } + if ( isset( $attachment_data['post_content'] ) ) { + $post['post_content'] = $attachment_data['post_content']; + } + return $post; + }, + 10, + 2 + ); + + try { + $this->_handleAjax( 'save-attachment-compat' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + + $post = get_post( self::$attachment_id ); + $this->assertSame( 'Compat Updated Title', $post->post_title, 'Title should be updated' ); + $this->assertSame( 'Compat Updated Caption', $post->post_excerpt, 'Caption should be updated' ); + $this->assertSame( 'Compat Updated Description', $post->post_content, 'Description should be updated' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_save_attachment(). + * + * @ticket 65252 + */ + public function test_save_attachment_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['id'] = self::$attachment_id; + $_POST['nonce'] = 'invalid-nonce'; + $_POST['changes'] = array( 'title' => 'Should fail' ); + + try { + $this->_handleAjax( 'save-attachment' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $this->assertSame( '-1', $this->_last_response ); + } + + /** + * Tests failure with missing ID for wp_ajax_save_attachment(). + * + * @ticket 65252 + */ + public function test_save_attachment_missing_id(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['changes'] = array( 'title' => 'Should fail' ); + + try { + $this->_handleAjax( 'save-attachment' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_save_attachment(). + * + * @ticket 65252 + */ + public function test_save_attachment_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['id'] = self::$attachment_id; + $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$attachment_id ); + $_POST['changes'] = array( 'title' => 'Should fail' ); + + try { + $this->_handleAjax( 'save-attachment' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php b/tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php new file mode 100644 index 0000000000000..15498138ddbdb --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php @@ -0,0 +1,208 @@ +user->create( array( 'role' => 'administrator' ) ); + } + + /** + * Tests successful widget deletion via AJAX. + * + * @ticket 65252 + */ + public function test_save_widget_delete_success(): void { + global $wp_registered_widgets; + + wp_set_current_user( self::$admin_id ); + + // Register a dummy widget. + $widget_id = 'dummy-widget-1'; + $wp_registered_widgets[ $widget_id ] = array( + 'name' => 'Dummy Widget', + 'id' => $widget_id, + 'callback' => '__return_empty_string', + 'params' => array(), + ); + + // Set up sidebar with the widget. + $sidebar_id = 'sidebar-1'; + wp_set_sidebars_widgets( array( $sidebar_id => array( $widget_id ) ) ); + + $_POST = array( + 'action' => 'save-widget', + 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), + 'sidebar' => $sidebar_id, + 'widget-id' => $widget_id, + 'id_base' => 'dummy', + 'delete_widget' => '1', + ); + + try { + $this->_handleAjax( 'save-widget' ); + } catch ( WPAjaxDieStopException $e ) { + $this->_last_response = $e->getMessage(); + } catch ( WPAjaxDieContinueException $e ) { + } + + $this->assertSame( "deleted:$widget_id", $this->_last_response ); + + $sidebars = wp_get_sidebars_widgets(); + $this->assertNotContains( $widget_id, $sidebars[ $sidebar_id ] ); + + // Cleanup. + unset( $wp_registered_widgets[ $widget_id ] ); + } + + /** + * Tests failure due to invalid nonce. + * + * @ticket 65252 + */ + public function test_save_widget_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'save-widget', + 'savewidgets' => 'invalid-nonce', + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'save-widget' ); + } + + /** + * Tests failure due to insufficient permissions. + * + * @ticket 65252 + */ + public function test_save_widget_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST = array( + 'action' => 'save-widget', + 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'save-widget' ); + } + + /** + * Tests failure when id_base is missing. + * + * @ticket 65252 + */ + public function test_save_widget_missing_id_base(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'save-widget', + 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'save-widget' ); + } + + /** + * Tests successful addition of a new multi-widget. + * + * @ticket 65252 + */ + public function test_save_widget_add_new_multi_widget(): void { + global $wp_registered_widget_updates, $wp_registered_widget_controls; + + wp_set_current_user( self::$admin_id ); + + $id_base = 'testmulti'; + $multi_number = 2; + $widget_id = "$id_base-$multi_number"; + $sidebar_id = 'sidebar-1'; + + // Ensure sidebar exists. + wp_set_sidebars_widgets( array( $sidebar_id => array() ) ); + + // Mock the update callback. + $updated = false; + $wp_registered_widget_updates[ $id_base ] = array( + 'callback' => function () use ( &$updated, $id_base, $multi_number ) { + $updated = true; + // In a real scenario, the update callback would update the option. + $settings = array( $multi_number => array( 'title' => 'New Widget' ) ); + update_option( 'widget_' . $id_base, $settings ); + }, + 'params' => array(), + ); + + // Mock the control callback. + $control_called = false; + $wp_registered_widget_controls[ $widget_id ] = array( + 'callback' => function () use ( &$control_called ) { + $control_called = true; + echo 'control-output'; + }, + 'params' => array(), + ); + + $_POST = array( + 'action' => 'save-widget', + 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), + 'sidebar' => $sidebar_id, + 'widget-id' => $widget_id, + 'id_base' => $id_base, + 'multi_number' => $multi_number, + 'widget-' . $id_base => array( '__i__' => array( 'title' => 'New Widget' ) ), + 'add_new' => 'multi', + ); + + try { + $this->_handleAjax( 'save-widget' ); + } catch ( WPAjaxDieStopException $e ) { + $this->_last_response = $e->getMessage(); + } catch ( WPAjaxDieContinueException $e ) { + } + + $this->assertTrue( $updated, 'Update callback should be called.' ); + + $this->assertContains( $widget_id, $_POST['widget-id'] ); + + // Cleanup. + unset( $wp_registered_widget_updates[ $id_base ], $wp_registered_widget_controls[ $widget_id ] ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php b/tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php new file mode 100644 index 0000000000000..4b7d21bc8aa12 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php @@ -0,0 +1,234 @@ +user->create( array( 'role' => 'administrator' ) ); + self::$post_id = $factory->post->create(); + self::$thumbnail_id = $factory->attachment->create_object( + 'image.jpg', + 0, + array( + 'post_mime_type' => 'image/jpeg', + 'post_type' => 'attachment', + ) + ); + } + + /** + * Set up the test fixture. + * Override wp_die(). + */ + public function set_up(): void { + parent::set_up(); + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + } + + /** + * Tear down the test fixture. + */ + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); + parent::tear_down(); + } + + /** + * Return our callback handler + * + * @return callback + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Handler for wp_die() + * Don't die, just throw an exception. + * + * @param string|WP_Error $message + * @param string $title + * @param array $args + * @throws WPAjaxDieStopException + * @throws WPAjaxDieContinueException + */ + public function dieHandler( $message, $title = '', $args = array() ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests successful setting of the featured image. + * + * @ticket 65252 + */ + public function test_set_post_thumbnail_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'set-post-thumbnail', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + '_ajax_nonce' => wp_create_nonce( 'set_post_thumbnail-' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'set-post-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + $this->assertStringContainsString( 'id="set-post-thumbnail"', $this->_last_response ); + $this->assertStringContainsString( 'value="' . self::$thumbnail_id . '"', $this->_last_response ); + } + + $this->assertSame( (string) self::$thumbnail_id, get_post_meta( self::$post_id, '_thumbnail_id', true ) ); + } + + /** + * Tests successful setting of the featured image with JSON request. + * + * @ticket 65252 + */ + public function test_set_post_thumbnail_json_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'set-post-thumbnail', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + 'json' => 1, + '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'set-post-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + // In case of wp_send_json_success, the output is in $this->_last_response. + } + + $response = json_decode( $this->_last_response, true ); + $this->assertIsArray( $response, 'Response should be a valid JSON array. Response was: ' . $this->_last_response ); + $this->assertTrue( $response['success'] ); + $this->assertStringContainsString( 'id="set-post-thumbnail"', $response['data'] ); + $this->assertSame( (string) self::$thumbnail_id, get_post_meta( self::$post_id, '_thumbnail_id', true ) ); + } + + /** + * Tests successful removal of the featured image. + * + * @ticket 65252 + */ + public function test_set_post_thumbnail_remove_success(): void { + wp_set_current_user( self::$admin_id ); + set_post_thumbnail( self::$post_id, self::$thumbnail_id ); + + $_POST = array( + 'action' => 'set-post-thumbnail', + 'post_id' => self::$post_id, + 'thumbnail_id' => -1, + '_ajax_nonce' => wp_create_nonce( 'set_post_thumbnail-' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'set-post-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + $this->assertStringContainsString( 'id="set-post-thumbnail"', $this->_last_response ); + $this->assertStringContainsString( 'value="-1"', $this->_last_response ); + } + + $this->assertEmpty( get_post_meta( self::$post_id, '_thumbnail_id', true ) ); + } + + /** + * Tests failure due to invalid nonce. + * + * @ticket 65252 + */ + public function test_set_post_thumbnail_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'set-post-thumbnail', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + '_ajax_nonce' => 'invalid-nonce', + ); + + try { + $this->_handleAjax( 'set-post-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + $this->assertSame( '-1', $e->getMessage() ); + } + } + + /** + * Tests failure due to insufficient permissions. + * + * @ticket 65252 + */ + public function test_set_post_thumbnail_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST = array( + 'action' => 'set-post-thumbnail', + 'post_id' => self::$post_id, + 'thumbnail_id' => self::$thumbnail_id, + '_ajax_nonce' => wp_create_nonce( 'set_post_thumbnail-' . self::$post_id ), + ); + + try { + $this->_handleAjax( 'set-post-thumbnail' ); + } catch ( WPAjaxDieContinueException $e ) { + $this->assertSame( '-1', $e->getMessage() ); + } + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php b/tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php new file mode 100644 index 0000000000000..8dde018d15382 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php @@ -0,0 +1,117 @@ +user->create( array( 'role' => 'administrator' ) ); + } + + public function set_up() { + parent::set_up(); + // Initialize the Customizer. + require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; + $GLOBALS['wp_customize'] = new WP_Customize_Manager(); + } + + /** + * Tests failure due to invalid nonce. + * + * @ticket 65252 + */ + public function test_update_widget_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'update-widget', + 'nonce' => 'invalid-nonce', + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'update-widget' ); + } + + /** + * Tests failure due to missing widget-id. + * + * @ticket 65252 + */ + public function test_update_widget_missing_widget_id(): void { + wp_set_current_user( self::$admin_id ); + + $_POST = array( + 'action' => 'update-widget', + 'nonce' => wp_create_nonce( 'update-widget' ), + ); + + try { + $this->_handleAjax( 'update-widget' ); + } catch ( WPAjaxDieStopException $e ) { + $this->_last_response = $e->getMessage(); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertFalse( $response['success'] ); + $this->assertSame( 'missing_widget-id', $response['data'] ); + } + + /** + * Tests failure for template widget. + * + * @ticket 65252 + */ + public function test_update_widget_template_widget_not_updatable(): void { + wp_set_current_user( self::$admin_id ); + + $id_base = 'text'; + $widget_id = $id_base . '-1'; + + $_POST = array( + 'action' => 'update-widget', + 'nonce' => wp_create_nonce( 'update-widget' ), + 'widget-id' => $widget_id, + 'widget-' . $id_base => array( '__i__' => array( 'title' => 'Title' ) ), + ); + + try { + $this->_handleAjax( 'update-widget' ); + } catch ( WPAjaxDieStopException $e ) { + $this->_last_response = $e->getMessage(); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + $this->assertFalse( $response['success'], 'Should fail for template widget' ); + $this->assertSame( 'template_widget_not_updatable', $response['data'] ); + } +} From 9a5edfa2f57cf4466eb0e8949f61e2d4fea378ab Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 15:53:08 -0400 Subject: [PATCH 02/11] Add tests for save_attachment_order and send_attachment_to_editor Co-authored-by: Junie --- .junie/test_progress_ajax.md | 117 +++++++++ .../ajax-actions/saveAttachmentOrder.php | 243 ++++++++++++++++++ .../ajax-actions/sendAttachmentToEditor.php | 219 ++++++++++++++++ 3 files changed, 579 insertions(+) create mode 100644 .junie/test_progress_ajax.md create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/saveAttachmentOrder.php create mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/sendAttachmentToEditor.php diff --git a/.junie/test_progress_ajax.md b/.junie/test_progress_ajax.md new file mode 100644 index 0000000000000..47e82c53c36c7 --- /dev/null +++ b/.junie/test_progress_ajax.md @@ -0,0 +1,117 @@ +# Test Progress: src/wp-admin/includes/ajax-actions.php +**Master Ticket: [65252](https://core.trac.wordpress.org/ticket/65252)** + +This file tracks the status of unit tests for functions in `src/wp-admin/includes/ajax-actions.php`. + +## Completed Functions (Tests Found) + +| Function | Status | +| :--- | :--- | +| `wp_ajax_ajax_tag_search` | ✅ Found | +| `wp_ajax_wp_compression_test` | ✅ Found | +| `_wp_ajax_delete_comment_response` | ✅ Found | +| `wp_ajax_delete_comment` | ✅ Found | +| `wp_ajax_dim_comment` | ✅ Found | +| `wp_ajax_add_tag` | ✅ Found | +| `wp_ajax_get_comments` | ✅ Found | +| `wp_ajax_replyto_comment` | ✅ Found | +| `wp_ajax_edit_comment` | ✅ Found | +| `wp_ajax_add_meta` | ✅ Found | +| `wp_ajax_inline_save` | ✅ Found | +| `wp_ajax_image_editor` | ✅ Found | +| `wp_ajax_set_attachment_thumbnail` | ✅ Found | +| `wp_ajax_send_attachment_to_editor` | ✅ Found | +| `wp_ajax_heartbeat` | ✅ Found | +| `wp_ajax_crop_image` | ✅ Found | +| `wp_ajax_update_theme` | ✅ Found | +| `wp_ajax_update_plugin` | ✅ Found | +| `wp_ajax_delete_plugin` | ✅ Found | +| `wp_ajax_wp_privacy_export_personal_data` | ✅ Found | +| `wp_ajax_wp_privacy_erase_personal_data` | ✅ Found | +| `wp_ajax_parse_media_shortcode` | ✅ Found | + +## Missing Functions (Tests Not Found) +These tests were moved to tests/phpunit/tests/admin/includes/ajax-actions in https://core.trac.wordpress.org/ticket/65226 + +| Function | Status | Ticket | Pull Request | commited | +|:--------------------------------------------|:----------|:--------------------------------------------------------|:-----------------------------------------------|:---------| +| `wp_ajax_nopriv_heartbeat` | created | ✅ [65236](https://core.trac.wordpress.org/ticket/65236) | `65236-ajax-actions-wp_ajax_nopriv_heartbeat` | | +| | | | | | +| `wp_ajax_fetch_list` | created | ✅ [65237](https://core.trac.wordpress.org/ticket/65237) | `65237-ajax-actions-wp_ajax_fetch_list` | | +| `wp_ajax_imgedit_preview` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_ajax_imgedit_preview` | | +| `wp_ajax_oembed_cache` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_ajax_oembed_cache` | | +| `wp_ajax_autocomplete_user` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_ajax_autocomplete_user` | | +| `wp_ajax_get_community_events` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-get_community_events` | | +| `wp_ajax_dashboard_widgets` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-dashboard_widgets` | | +| `wp_ajax_logged_in` | created | ✅ [65242](https://core.trac.wordpress.org/ticket/65242) | `65242-ajax-actions-wp_ajax_logged_in` | | +| `_wp_ajax_add_hierarchical_term` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_hierarchical_term` | | +| `wp_ajax_delete_tag` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_tag` | | +| `wp_ajax_delete_link` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_link` | | +| `wp_ajax_delete_meta` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_meta` | | +| `wp_ajax_delete_post` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_post` | | +| `wp_ajax_trash_post` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-trash_post` | | +| `wp_ajax_untrash_post` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-untrash_post` | | +| `wp_ajax_delete_page` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_page` | | +| `wp_ajax_add_link_category` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_link_category` | | +| `wp_ajax_get_tagcloud` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-get_tagcloud` | | +| `wp_ajax_add_menu_item` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_menu_item` | | +| `wp_ajax_add_user` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_user` | | +| `wp_ajax_closed_postboxes` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-closed_postboxes` | | +| `wp_ajax_hidden_columns` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-hidden_columns` | | +| `wp_ajax_update_welcome_panel` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-update_welcome_panel` | | +| `wp_ajax_menu_get_metabox` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-menu_get_metabox` | | +| `wp_ajax_wp_link_ajax` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_link_ajax` | | +| `wp_ajax_menu_locations_save` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-menu_locations_save` | | +| `wp_ajax_meta_box_order` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-meta_box_order` | | +| `wp_ajax_menu_quick_search` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-menu_quick_search` | | +| `wp_ajax_get_permalink` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-get_permalink` | | +| `wp_ajax_sample_permalink` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_inline_save_tax` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-inline_save_tax` | | +| `wp_ajax_find_posts` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-find_posts` | | +| `wp_ajax_widgets_order` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-widgets_order` | | +| `wp_ajax_save_widget` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_update_widget` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_delete_inactive_widgets` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_delete_inactive_widgets` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_media_create_image_subsizes` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_upload_attachment` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_set_post_thumbnail` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_get_post_thumbnail_html` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_date_format` | created | ✅ [65225](https://core.trac.wordpress.org/ticket/65225) | `65225-ajax-date_format` | | +| `wp_ajax_time_format` | created | ✅ [65228](https://core.trac.wordpress.org/ticket/65228) | `65228-ajax-time_format` | | +| `wp_ajax_wp_fullscreen_save_post` | ❌ Missing | | | | +| `wp_ajax_wp_remove_post_lock` | ❌ Missing | | | | +| `wp_ajax_dismiss_wp_pointer` | ❌ Missing | | | | +| `wp_ajax_get_attachment` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_query_attachments` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_save_attachment` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_save_attachment_compat` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | +| `wp_ajax_save_attachment_order` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-save-attachment-order` | | +| `wp_ajax_send_attachment_to_editor` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-save-attachment-order` | | +| `wp_ajax_send_link_to_editor` | ❌ Missing | | | | +| `wp_ajax_get_revision_diffs` | ❌ Missing | | | | +| `wp_ajax_save_user_color_scheme` | ❌ Missing | | | | +| `wp_ajax_query_themes` | ❌ Missing | | | | +| `wp_ajax_parse_embed` | ❌ Missing | | | | +| `wp_ajax_destroy_sessions` | ❌ Missing | | | | +| `wp_ajax_generate_password` | ❌ Missing | | | | +| `wp_ajax_nopriv_generate_password` | ❌ Missing | | | | +| `wp_ajax_save_wporg_username` | ❌ Missing | | | | +| `wp_ajax_install_theme` | ❌ Missing | | | | +| `wp_ajax_delete_theme` | ❌ Missing | | | | +| `wp_ajax_install_plugin` | ❌ Missing | | | | +| `wp_ajax_activate_plugin` | ❌ Missing | | | | +| `wp_ajax_search_plugins` | ❌ Missing | | | | +| `wp_ajax_search_install_plugins` | ❌ Missing | | | | +| `wp_ajax_edit_theme_plugin_file` | ❌ Missing | | | | +| `wp_ajax_health_check_dotorg_communication` | ❌ Missing | | | | +| `wp_ajax_health_check_background_updates` | ❌ Missing | | | | +| `wp_ajax_health_check_loopback_requests` | ❌ Missing | | | | +| `wp_ajax_health_check_site_status_result` | ❌ Missing | | | | +| `wp_ajax_health_check_get_sizes` | ❌ Missing | | | | +| `wp_ajax_rest_nonce` | created | ✅ [65243](https://core.trac.wordpress.org/ticket/65243) | `65243-ajax-actions-wp_ajax_rest_nonce` | | +| `wp_ajax_toggle_auto_updates` | ❌ Missing | | | | +| `wp_ajax_send_password_reset` | ❌ Missing | | | | + +--- +*Last updated: 2026-05-15* diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachmentOrder.php b/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachmentOrder.php new file mode 100644 index 0000000000000..0cd0e04c27d53 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachmentOrder.php @@ -0,0 +1,243 @@ +user->create( array( 'role' => 'administrator' ) ); + + self::$post_id = $factory->post->create(); + + self::$attachment_ids = array( + $factory->attachment->create_object( + array( + 'file' => 'test1.jpg', + 'post_parent' => self::$post_id, + 'menu_order' => 0, + ) + ), + $factory->attachment->create_object( + array( + 'file' => 'test2.jpg', + 'post_parent' => self::$post_id, + 'menu_order' => 0, + ) + ), + ); + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_save-attachment-order', 'wp_ajax_save_attachment_order', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_save_attachment_order(). + * + * @ticket 65252 + */ + public function test_save_attachment_order_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['post_id'] = self::$post_id; + $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$post_id ); + $_POST['attachments'] = array( + self::$attachment_ids[0] => 10, + self::$attachment_ids[1] => 20, + ); + + try { + $this->_handleAjax( 'save-attachment-order' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + + $this->assertEquals( 10, get_post( self::$attachment_ids[0] )->menu_order, 'First attachment menu_order should be 10' ); + $this->assertEquals( 20, get_post( self::$attachment_ids[1] )->menu_order, 'Second attachment menu_order should be 20' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_save_attachment_order(). + * + * @ticket 65252 + */ + public function test_save_attachment_order_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['post_id'] = self::$post_id; + $_POST['nonce'] = 'invalid-nonce'; + $_POST['attachments'] = array( + self::$attachment_ids[0] => 10, + ); + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'save-attachment-order' ); + } + + /** + * Tests failure with insufficient permissions for wp_ajax_save_attachment_order(). + * + * @ticket 65252 + */ + public function test_save_attachment_order_insufficient_permissions(): void { + $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); + wp_set_current_user( $subscriber_id ); + + $_POST['post_id'] = self::$post_id; + $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$post_id ); + $_POST['attachments'] = array( + self::$attachment_ids[0] => 10, + ); + + try { + $this->_handleAjax( 'save-attachment-order' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } + + /** + * Tests failure with missing parameters for wp_ajax_save_attachment_order(). + * + * @ticket 65252 + * + * @dataProvider data_missing_parameters + * + * @param array $post_data POST data. + */ + public function test_save_attachment_order_missing_parameters( array $post_data ): void { + wp_set_current_user( self::$admin_id ); + + $_POST = $post_data; + + try { + $this->_handleAjax( 'save-attachment-order' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } + + /** + * Data provider for missing parameters. + * + * @return array + */ + public function data_missing_parameters(): array { + return array( + 'missing post_id' => array( + array( + 'attachments' => array( 1 => 10 ), + ), + ), + 'invalid post_id' => array( + array( + 'post_id' => 0, + 'attachments' => array( 1 => 10 ), + ), + ), + 'missing attachments' => array( + array( + 'post_id' => 1, + ), + ), + 'empty attachments' => array( + array( + 'post_id' => 1, + 'attachments' => array(), + ), + ), + ); + } +} diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/sendAttachmentToEditor.php b/tests/phpunit/tests/admin/includes/ajax-actions/sendAttachmentToEditor.php new file mode 100644 index 0000000000000..920c34a952e5b --- /dev/null +++ b/tests/phpunit/tests/admin/includes/ajax-actions/sendAttachmentToEditor.php @@ -0,0 +1,219 @@ +user->create( array( 'role' => 'administrator' ) ); + self::$post_id = $factory->post->create(); + + self::$attachment_id = $factory->attachment->create_object( + array( + 'file' => 'test.jpg', + 'post_parent' => 0, + 'post_mime_type' => 'image/jpeg', + 'post_title' => 'Test Image', + 'post_content' => 'Test Description', + 'post_excerpt' => 'Test Caption', + ) + ); + } + + public function set_up(): void { + parent::set_up(); + add_action( 'wp_ajax_send-attachment-to-editor', 'wp_ajax_send_attachment_to_editor', 1 ); + + // Hook into wp_die to prevent execution from stopping. + add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + } + + public function tear_down(): void { + remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); + parent::tear_down(); + } + + /** + * Returns our custom die handler. + * + * @return callable + */ + public function getDieHandler() { + return array( $this, 'dieHandler' ); + } + + /** + * Custom die handler that throws an exception. + * + * @param string|WP_Error $message + */ + public function dieHandler( $message ) { + $this->_last_response .= ob_get_clean(); + + if ( '' === $this->_last_response ) { + if ( is_scalar( $message ) ) { + $this->_last_response = (string) $message; + } else { + $this->_last_response = '0'; + } + } + + if ( '-1' === $this->_last_response || ( is_int( $message ) && -1 === $message ) ) { + throw new WPAjaxDieStopException( $this->_last_response ); + } + + throw new WPAjaxDieContinueException( $this->_last_response ); + } + + /** + * Tests success for wp_ajax_send_attachment_to_editor() with an image. + * + * @ticket 65252 + */ + public function test_send_attachment_to_editor_image_success(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['nonce'] = wp_create_nonce( 'media-send-to-editor' ); + $_POST['post_id'] = self::$post_id; + $_POST['attachment'] = array( + 'id' => self::$attachment_id, + 'align' => 'left', + 'image-size' => 'medium', + 'image_alt' => 'Custom Alt', + 'post_excerpt' => 'Custom Caption', + 'url' => 'http://example.com/test.jpg', + ); + + try { + $this->_handleAjax( 'send-attachment-to-editor' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertStringContainsString( 'Custom Alt', $response['data'], 'HTML should contain custom alt text' ); + $this->assertStringContainsString( 'Custom Caption', $response['data'], 'HTML should contain custom caption' ); + $this->assertStringContainsString( 'alignleft', $response['data'], 'HTML should contain correct alignment class' ); + + // Verify that the attachment was attached to the post. + $attachment = get_post( self::$attachment_id ); + $this->assertEquals( self::$post_id, $attachment->post_parent, 'Attachment should be attached to the post' ); + } + + /** + * Tests success for wp_ajax_send_attachment_to_editor() with a non-image file. + * + * @ticket 65252 + */ + public function test_send_attachment_to_editor_file_success(): void { + wp_set_current_user( self::$admin_id ); + + $file_id = self::factory()->attachment->create_object( + array( + 'file' => 'test.pdf', + 'post_parent' => 0, + 'post_mime_type' => 'application/pdf', + 'post_title' => 'Test Document', + ) + ); + + $_POST['nonce'] = wp_create_nonce( 'media-send-to-editor' ); + $_POST['post_id'] = self::$post_id; + $_POST['attachment'] = array( + 'id' => $file_id, + 'post_title' => 'Custom Link Text', + 'url' => 'http://example.com/test.pdf', + ); + + try { + $this->_handleAjax( 'send-attachment-to-editor' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertTrue( $response['success'], 'AJAX response should be successful' ); + $this->assertStringContainsString( 'Custom Link Text', $response['data'], 'HTML should contain custom link text' ); + $this->assertStringContainsString( 'http://example.com/test.pdf', $response['data'], 'HTML should contain the correct URL' ); + } + + /** + * Tests failure with invalid nonce for wp_ajax_send_attachment_to_editor(). + * + * @ticket 65252 + */ + public function test_send_attachment_to_editor_invalid_nonce(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['nonce'] = 'invalid-nonce'; + + $this->expectException( WPAjaxDieStopException::class ); + $this->expectExceptionMessage( '-1' ); + + $this->_handleAjax( 'send-attachment-to-editor' ); + } + + /** + * Tests failure with missing attachment for wp_ajax_send_attachment_to_editor(). + * + * @ticket 65252 + */ + public function test_send_attachment_to_editor_missing_attachment(): void { + wp_set_current_user( self::$admin_id ); + + $_POST['nonce'] = wp_create_nonce( 'media-send-to-editor' ); + $_POST['attachment'] = array( + 'id' => 999999, // Non-existent ID. + ); + + try { + $this->_handleAjax( 'send-attachment-to-editor' ); + } catch ( WPAjaxDieContinueException $e ) { + } + + $response = json_decode( $this->_last_response, true ); + + $this->assertFalse( $response['success'], 'AJAX response should be unsuccessful' ); + } +} From cea6b8ad12e1ce6119f0142b5caecccf7970b029 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:00:30 -0400 Subject: [PATCH 03/11] Delete .junie/test_progress_ajax.md --- .junie/test_progress_ajax.md | 117 ----------------------------------- 1 file changed, 117 deletions(-) delete mode 100644 .junie/test_progress_ajax.md diff --git a/.junie/test_progress_ajax.md b/.junie/test_progress_ajax.md deleted file mode 100644 index 47e82c53c36c7..0000000000000 --- a/.junie/test_progress_ajax.md +++ /dev/null @@ -1,117 +0,0 @@ -# Test Progress: src/wp-admin/includes/ajax-actions.php -**Master Ticket: [65252](https://core.trac.wordpress.org/ticket/65252)** - -This file tracks the status of unit tests for functions in `src/wp-admin/includes/ajax-actions.php`. - -## Completed Functions (Tests Found) - -| Function | Status | -| :--- | :--- | -| `wp_ajax_ajax_tag_search` | ✅ Found | -| `wp_ajax_wp_compression_test` | ✅ Found | -| `_wp_ajax_delete_comment_response` | ✅ Found | -| `wp_ajax_delete_comment` | ✅ Found | -| `wp_ajax_dim_comment` | ✅ Found | -| `wp_ajax_add_tag` | ✅ Found | -| `wp_ajax_get_comments` | ✅ Found | -| `wp_ajax_replyto_comment` | ✅ Found | -| `wp_ajax_edit_comment` | ✅ Found | -| `wp_ajax_add_meta` | ✅ Found | -| `wp_ajax_inline_save` | ✅ Found | -| `wp_ajax_image_editor` | ✅ Found | -| `wp_ajax_set_attachment_thumbnail` | ✅ Found | -| `wp_ajax_send_attachment_to_editor` | ✅ Found | -| `wp_ajax_heartbeat` | ✅ Found | -| `wp_ajax_crop_image` | ✅ Found | -| `wp_ajax_update_theme` | ✅ Found | -| `wp_ajax_update_plugin` | ✅ Found | -| `wp_ajax_delete_plugin` | ✅ Found | -| `wp_ajax_wp_privacy_export_personal_data` | ✅ Found | -| `wp_ajax_wp_privacy_erase_personal_data` | ✅ Found | -| `wp_ajax_parse_media_shortcode` | ✅ Found | - -## Missing Functions (Tests Not Found) -These tests were moved to tests/phpunit/tests/admin/includes/ajax-actions in https://core.trac.wordpress.org/ticket/65226 - -| Function | Status | Ticket | Pull Request | commited | -|:--------------------------------------------|:----------|:--------------------------------------------------------|:-----------------------------------------------|:---------| -| `wp_ajax_nopriv_heartbeat` | created | ✅ [65236](https://core.trac.wordpress.org/ticket/65236) | `65236-ajax-actions-wp_ajax_nopriv_heartbeat` | | -| | | | | | -| `wp_ajax_fetch_list` | created | ✅ [65237](https://core.trac.wordpress.org/ticket/65237) | `65237-ajax-actions-wp_ajax_fetch_list` | | -| `wp_ajax_imgedit_preview` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_ajax_imgedit_preview` | | -| `wp_ajax_oembed_cache` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_ajax_oembed_cache` | | -| `wp_ajax_autocomplete_user` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_ajax_autocomplete_user` | | -| `wp_ajax_get_community_events` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-get_community_events` | | -| `wp_ajax_dashboard_widgets` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-dashboard_widgets` | | -| `wp_ajax_logged_in` | created | ✅ [65242](https://core.trac.wordpress.org/ticket/65242) | `65242-ajax-actions-wp_ajax_logged_in` | | -| `_wp_ajax_add_hierarchical_term` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_hierarchical_term` | | -| `wp_ajax_delete_tag` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_tag` | | -| `wp_ajax_delete_link` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_link` | | -| `wp_ajax_delete_meta` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_meta` | | -| `wp_ajax_delete_post` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_post` | | -| `wp_ajax_trash_post` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-trash_post` | | -| `wp_ajax_untrash_post` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-untrash_post` | | -| `wp_ajax_delete_page` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-delete_page` | | -| `wp_ajax_add_link_category` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_link_category` | | -| `wp_ajax_get_tagcloud` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-get_tagcloud` | | -| `wp_ajax_add_menu_item` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_menu_item` | | -| `wp_ajax_add_user` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-add_user` | | -| `wp_ajax_closed_postboxes` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-closed_postboxes` | | -| `wp_ajax_hidden_columns` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-hidden_columns` | | -| `wp_ajax_update_welcome_panel` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-update_welcome_panel` | | -| `wp_ajax_menu_get_metabox` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-menu_get_metabox` | | -| `wp_ajax_wp_link_ajax` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-wp_link_ajax` | | -| `wp_ajax_menu_locations_save` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-menu_locations_save` | | -| `wp_ajax_meta_box_order` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-meta_box_order` | | -| `wp_ajax_menu_quick_search` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-menu_quick_search` | | -| `wp_ajax_get_permalink` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-get_permalink` | | -| `wp_ajax_sample_permalink` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_inline_save_tax` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-inline_save_tax` | | -| `wp_ajax_find_posts` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-find_posts` | | -| `wp_ajax_widgets_order` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-widgets_order` | | -| `wp_ajax_save_widget` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_update_widget` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_delete_inactive_widgets` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_delete_inactive_widgets` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_media_create_image_subsizes` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_upload_attachment` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_set_post_thumbnail` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_get_post_thumbnail_html` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_date_format` | created | ✅ [65225](https://core.trac.wordpress.org/ticket/65225) | `65225-ajax-date_format` | | -| `wp_ajax_time_format` | created | ✅ [65228](https://core.trac.wordpress.org/ticket/65228) | `65228-ajax-time_format` | | -| `wp_ajax_wp_fullscreen_save_post` | ❌ Missing | | | | -| `wp_ajax_wp_remove_post_lock` | ❌ Missing | | | | -| `wp_ajax_dismiss_wp_pointer` | ❌ Missing | | | | -| `wp_ajax_get_attachment` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_query_attachments` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_save_attachment` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_save_attachment_compat` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-split-tests` | ✅ | -| `wp_ajax_save_attachment_order` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-save-attachment-order` | | -| `wp_ajax_send_attachment_to_editor` | created | ✅ [65252](https://core.trac.wordpress.org/ticket/65252) | `65252-ajax-actions-save-attachment-order` | | -| `wp_ajax_send_link_to_editor` | ❌ Missing | | | | -| `wp_ajax_get_revision_diffs` | ❌ Missing | | | | -| `wp_ajax_save_user_color_scheme` | ❌ Missing | | | | -| `wp_ajax_query_themes` | ❌ Missing | | | | -| `wp_ajax_parse_embed` | ❌ Missing | | | | -| `wp_ajax_destroy_sessions` | ❌ Missing | | | | -| `wp_ajax_generate_password` | ❌ Missing | | | | -| `wp_ajax_nopriv_generate_password` | ❌ Missing | | | | -| `wp_ajax_save_wporg_username` | ❌ Missing | | | | -| `wp_ajax_install_theme` | ❌ Missing | | | | -| `wp_ajax_delete_theme` | ❌ Missing | | | | -| `wp_ajax_install_plugin` | ❌ Missing | | | | -| `wp_ajax_activate_plugin` | ❌ Missing | | | | -| `wp_ajax_search_plugins` | ❌ Missing | | | | -| `wp_ajax_search_install_plugins` | ❌ Missing | | | | -| `wp_ajax_edit_theme_plugin_file` | ❌ Missing | | | | -| `wp_ajax_health_check_dotorg_communication` | ❌ Missing | | | | -| `wp_ajax_health_check_background_updates` | ❌ Missing | | | | -| `wp_ajax_health_check_loopback_requests` | ❌ Missing | | | | -| `wp_ajax_health_check_site_status_result` | ❌ Missing | | | | -| `wp_ajax_health_check_get_sizes` | ❌ Missing | | | | -| `wp_ajax_rest_nonce` | created | ✅ [65243](https://core.trac.wordpress.org/ticket/65243) | `65243-ajax-actions-wp_ajax_rest_nonce` | | -| `wp_ajax_toggle_auto_updates` | ❌ Missing | | | | -| `wp_ajax_send_password_reset` | ❌ Missing | | | | - ---- -*Last updated: 2026-05-15* From 7ca16e75403b1165baf4656d1fdcc347ca407380 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:01:51 -0400 Subject: [PATCH 04/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php --- .../includes/ajax-actions/updateWidget.php | 117 ------------------ 1 file changed, 117 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php b/tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php deleted file mode 100644 index 8dde018d15382..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/updateWidget.php +++ /dev/null @@ -1,117 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - } - - public function set_up() { - parent::set_up(); - // Initialize the Customizer. - require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; - $GLOBALS['wp_customize'] = new WP_Customize_Manager(); - } - - /** - * Tests failure due to invalid nonce. - * - * @ticket 65252 - */ - public function test_update_widget_invalid_nonce(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'update-widget', - 'nonce' => 'invalid-nonce', - ); - - $this->expectException( WPAjaxDieStopException::class ); - $this->expectExceptionMessage( '-1' ); - - $this->_handleAjax( 'update-widget' ); - } - - /** - * Tests failure due to missing widget-id. - * - * @ticket 65252 - */ - public function test_update_widget_missing_widget_id(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'update-widget', - 'nonce' => wp_create_nonce( 'update-widget' ), - ); - - try { - $this->_handleAjax( 'update-widget' ); - } catch ( WPAjaxDieStopException $e ) { - $this->_last_response = $e->getMessage(); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - $this->assertFalse( $response['success'] ); - $this->assertSame( 'missing_widget-id', $response['data'] ); - } - - /** - * Tests failure for template widget. - * - * @ticket 65252 - */ - public function test_update_widget_template_widget_not_updatable(): void { - wp_set_current_user( self::$admin_id ); - - $id_base = 'text'; - $widget_id = $id_base . '-1'; - - $_POST = array( - 'action' => 'update-widget', - 'nonce' => wp_create_nonce( 'update-widget' ), - 'widget-id' => $widget_id, - 'widget-' . $id_base => array( '__i__' => array( 'title' => 'Title' ) ), - ); - - try { - $this->_handleAjax( 'update-widget' ); - } catch ( WPAjaxDieStopException $e ) { - $this->_last_response = $e->getMessage(); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - $this->assertFalse( $response['success'], 'Should fail for template widget' ); - $this->assertSame( 'template_widget_not_updatable', $response['data'] ); - } -} From cbff95635474f0cd31c66c350a675652eed52c2e Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:03:04 -0400 Subject: [PATCH 05/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php --- .../includes/ajax-actions/saveAttachment.php | 241 ------------------ 1 file changed, 241 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php b/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php deleted file mode 100644 index 715d63579ac25..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/saveAttachment.php +++ /dev/null @@ -1,241 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - - self::$attachment_id = $factory->attachment->create_object( - array( - 'file' => 'test.jpg', - 'post_parent' => 0, - 'post_mime_type' => 'image/jpeg', - 'post_title' => 'Original Title', - 'post_content' => 'Original Description', - 'post_excerpt' => 'Original Caption', - ) - ); - } - - public function set_up(): void { - parent::set_up(); - add_action( 'wp_ajax_save-attachment', 'wp_ajax_save_attachment', 1 ); - add_action( 'wp_ajax_save-attachment-compat', 'wp_ajax_save_attachment_compat', 1 ); - - // Hook into wp_die to prevent execution from stopping. - add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); - } - - public function tear_down(): void { - remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ) ); - parent::tear_down(); - } - - /** - * Returns our custom die handler. - * - * @return callable - */ - public function getDieHandler() { - return array( $this, 'dieHandler' ); - } - - /** - * Custom die handler that throws an exception. - * - * @param string|WP_Error $message - */ - public function dieHandler( $message ) { - $this->_last_response .= ob_get_clean(); - - if ( '' === $this->_last_response ) { - if ( is_scalar( $message ) ) { - $this->_last_response = (string) $message; - } else { - $this->_last_response = '0'; - } - } - - throw new WPAjaxDieContinueException( $this->_last_response ); - } - - /** - * Tests success for wp_ajax_save_attachment(). - * - * @ticket 65252 - */ - public function test_save_attachment_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['id'] = self::$attachment_id; - $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$attachment_id ); - $_POST['changes'] = array( - 'title' => 'Updated Title', - 'caption' => 'Updated Caption', - 'alt' => 'Updated Alt Text', - ); - - try { - $this->_handleAjax( 'save-attachment' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertTrue( $response['success'], 'AJAX response should be successful' ); - - $post = get_post( self::$attachment_id ); - $this->assertSame( 'Updated Title', $post->post_title, 'Title should be updated' ); - $this->assertSame( 'Updated Caption', $post->post_excerpt, 'Caption should be updated' ); - $this->assertSame( 'Updated Alt Text', get_post_meta( self::$attachment_id, '_wp_attachment_image_alt', true ), 'Alt text should be updated' ); - } - - /** - * Tests success for wp_ajax_save_attachment_compat(). - * - * @ticket 65252 - */ - public function test_save_attachment_compat_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['id'] = self::$attachment_id; - $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$attachment_id ); - $_POST['attachments'] = array( - self::$attachment_id => array( - 'post_title' => 'Compat Updated Title', - 'post_excerpt' => 'Compat Updated Caption', - 'post_content' => 'Compat Updated Description', - ), - ); - - // wp_ajax_save_attachment_compat() relies on filters for legacy compatibility. - add_filter( - 'attachment_fields_to_save', - function( $post, $attachment_data ) { - if ( isset( $attachment_data['post_title'] ) ) { - $post['post_title'] = $attachment_data['post_title']; - } - if ( isset( $attachment_data['post_excerpt'] ) ) { - $post['post_excerpt'] = $attachment_data['post_excerpt']; - } - if ( isset( $attachment_data['post_content'] ) ) { - $post['post_content'] = $attachment_data['post_content']; - } - return $post; - }, - 10, - 2 - ); - - try { - $this->_handleAjax( 'save-attachment-compat' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertTrue( $response['success'], 'AJAX response should be successful' ); - - $post = get_post( self::$attachment_id ); - $this->assertSame( 'Compat Updated Title', $post->post_title, 'Title should be updated' ); - $this->assertSame( 'Compat Updated Caption', $post->post_excerpt, 'Caption should be updated' ); - $this->assertSame( 'Compat Updated Description', $post->post_content, 'Description should be updated' ); - } - - /** - * Tests failure with invalid nonce for wp_ajax_save_attachment(). - * - * @ticket 65252 - */ - public function test_save_attachment_invalid_nonce(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['id'] = self::$attachment_id; - $_POST['nonce'] = 'invalid-nonce'; - $_POST['changes'] = array( 'title' => 'Should fail' ); - - try { - $this->_handleAjax( 'save-attachment' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $this->assertSame( '-1', $this->_last_response ); - } - - /** - * Tests failure with missing ID for wp_ajax_save_attachment(). - * - * @ticket 65252 - */ - public function test_save_attachment_missing_id(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['changes'] = array( 'title' => 'Should fail' ); - - try { - $this->_handleAjax( 'save-attachment' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } - - /** - * Tests failure with insufficient permissions for wp_ajax_save_attachment(). - * - * @ticket 65252 - */ - public function test_save_attachment_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST['id'] = self::$attachment_id; - $_POST['nonce'] = wp_create_nonce( 'update-post_' . self::$attachment_id ); - $_POST['changes'] = array( 'title' => 'Should fail' ); - - try { - $this->_handleAjax( 'save-attachment' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } -} From a61553be0aa8d052ac1db77f5d497c8a70a65bb2 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:03:08 -0400 Subject: [PATCH 06/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php --- .../ajax-actions/setPostThumbnail.php | 234 ------------------ 1 file changed, 234 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php b/tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php deleted file mode 100644 index 4b7d21bc8aa12..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/setPostThumbnail.php +++ /dev/null @@ -1,234 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - self::$post_id = $factory->post->create(); - self::$thumbnail_id = $factory->attachment->create_object( - 'image.jpg', - 0, - array( - 'post_mime_type' => 'image/jpeg', - 'post_type' => 'attachment', - ) - ); - } - - /** - * Set up the test fixture. - * Override wp_die(). - */ - public function set_up(): void { - parent::set_up(); - add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); - } - - /** - * Tear down the test fixture. - */ - public function tear_down(): void { - remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); - parent::tear_down(); - } - - /** - * Return our callback handler - * - * @return callback - */ - public function getDieHandler() { - return array( $this, 'dieHandler' ); - } - - /** - * Handler for wp_die() - * Don't die, just throw an exception. - * - * @param string|WP_Error $message - * @param string $title - * @param array $args - * @throws WPAjaxDieStopException - * @throws WPAjaxDieContinueException - */ - public function dieHandler( $message, $title = '', $args = array() ) { - $this->_last_response .= ob_get_clean(); - - if ( '' === $this->_last_response ) { - if ( is_scalar( $message ) ) { - $this->_last_response = (string) $message; - } else { - $this->_last_response = '0'; - } - } - - throw new WPAjaxDieContinueException( $this->_last_response ); - } - - /** - * Tests successful setting of the featured image. - * - * @ticket 65252 - */ - public function test_set_post_thumbnail_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'set-post-thumbnail', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - '_ajax_nonce' => wp_create_nonce( 'set_post_thumbnail-' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'set-post-thumbnail' ); - } catch ( WPAjaxDieContinueException $e ) { - $this->assertStringContainsString( 'id="set-post-thumbnail"', $this->_last_response ); - $this->assertStringContainsString( 'value="' . self::$thumbnail_id . '"', $this->_last_response ); - } - - $this->assertSame( (string) self::$thumbnail_id, get_post_meta( self::$post_id, '_thumbnail_id', true ) ); - } - - /** - * Tests successful setting of the featured image with JSON request. - * - * @ticket 65252 - */ - public function test_set_post_thumbnail_json_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'set-post-thumbnail', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - 'json' => 1, - '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'set-post-thumbnail' ); - } catch ( WPAjaxDieContinueException $e ) { - // In case of wp_send_json_success, the output is in $this->_last_response. - } - - $response = json_decode( $this->_last_response, true ); - $this->assertIsArray( $response, 'Response should be a valid JSON array. Response was: ' . $this->_last_response ); - $this->assertTrue( $response['success'] ); - $this->assertStringContainsString( 'id="set-post-thumbnail"', $response['data'] ); - $this->assertSame( (string) self::$thumbnail_id, get_post_meta( self::$post_id, '_thumbnail_id', true ) ); - } - - /** - * Tests successful removal of the featured image. - * - * @ticket 65252 - */ - public function test_set_post_thumbnail_remove_success(): void { - wp_set_current_user( self::$admin_id ); - set_post_thumbnail( self::$post_id, self::$thumbnail_id ); - - $_POST = array( - 'action' => 'set-post-thumbnail', - 'post_id' => self::$post_id, - 'thumbnail_id' => -1, - '_ajax_nonce' => wp_create_nonce( 'set_post_thumbnail-' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'set-post-thumbnail' ); - } catch ( WPAjaxDieContinueException $e ) { - $this->assertStringContainsString( 'id="set-post-thumbnail"', $this->_last_response ); - $this->assertStringContainsString( 'value="-1"', $this->_last_response ); - } - - $this->assertEmpty( get_post_meta( self::$post_id, '_thumbnail_id', true ) ); - } - - /** - * Tests failure due to invalid nonce. - * - * @ticket 65252 - */ - public function test_set_post_thumbnail_invalid_nonce(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'set-post-thumbnail', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - '_ajax_nonce' => 'invalid-nonce', - ); - - try { - $this->_handleAjax( 'set-post-thumbnail' ); - } catch ( WPAjaxDieContinueException $e ) { - $this->assertSame( '-1', $e->getMessage() ); - } - } - - /** - * Tests failure due to insufficient permissions. - * - * @ticket 65252 - */ - public function test_set_post_thumbnail_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST = array( - 'action' => 'set-post-thumbnail', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - '_ajax_nonce' => wp_create_nonce( 'set_post_thumbnail-' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'set-post-thumbnail' ); - } catch ( WPAjaxDieContinueException $e ) { - $this->assertSame( '-1', $e->getMessage() ); - } - } -} From d01cfc60dcec974e70d38b513d5e75a34a1f6457 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:03:13 -0400 Subject: [PATCH 07/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php --- .../includes/ajax-actions/saveWidget.php | 208 ------------------ 1 file changed, 208 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php b/tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php deleted file mode 100644 index 15498138ddbdb..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/saveWidget.php +++ /dev/null @@ -1,208 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - } - - /** - * Tests successful widget deletion via AJAX. - * - * @ticket 65252 - */ - public function test_save_widget_delete_success(): void { - global $wp_registered_widgets; - - wp_set_current_user( self::$admin_id ); - - // Register a dummy widget. - $widget_id = 'dummy-widget-1'; - $wp_registered_widgets[ $widget_id ] = array( - 'name' => 'Dummy Widget', - 'id' => $widget_id, - 'callback' => '__return_empty_string', - 'params' => array(), - ); - - // Set up sidebar with the widget. - $sidebar_id = 'sidebar-1'; - wp_set_sidebars_widgets( array( $sidebar_id => array( $widget_id ) ) ); - - $_POST = array( - 'action' => 'save-widget', - 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), - 'sidebar' => $sidebar_id, - 'widget-id' => $widget_id, - 'id_base' => 'dummy', - 'delete_widget' => '1', - ); - - try { - $this->_handleAjax( 'save-widget' ); - } catch ( WPAjaxDieStopException $e ) { - $this->_last_response = $e->getMessage(); - } catch ( WPAjaxDieContinueException $e ) { - } - - $this->assertSame( "deleted:$widget_id", $this->_last_response ); - - $sidebars = wp_get_sidebars_widgets(); - $this->assertNotContains( $widget_id, $sidebars[ $sidebar_id ] ); - - // Cleanup. - unset( $wp_registered_widgets[ $widget_id ] ); - } - - /** - * Tests failure due to invalid nonce. - * - * @ticket 65252 - */ - public function test_save_widget_invalid_nonce(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'save-widget', - 'savewidgets' => 'invalid-nonce', - ); - - $this->expectException( WPAjaxDieStopException::class ); - $this->expectExceptionMessage( '-1' ); - - $this->_handleAjax( 'save-widget' ); - } - - /** - * Tests failure due to insufficient permissions. - * - * @ticket 65252 - */ - public function test_save_widget_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST = array( - 'action' => 'save-widget', - 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), - ); - - $this->expectException( WPAjaxDieStopException::class ); - $this->expectExceptionMessage( '-1' ); - - $this->_handleAjax( 'save-widget' ); - } - - /** - * Tests failure when id_base is missing. - * - * @ticket 65252 - */ - public function test_save_widget_missing_id_base(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'save-widget', - 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), - ); - - $this->expectException( WPAjaxDieStopException::class ); - $this->expectExceptionMessage( '-1' ); - - $this->_handleAjax( 'save-widget' ); - } - - /** - * Tests successful addition of a new multi-widget. - * - * @ticket 65252 - */ - public function test_save_widget_add_new_multi_widget(): void { - global $wp_registered_widget_updates, $wp_registered_widget_controls; - - wp_set_current_user( self::$admin_id ); - - $id_base = 'testmulti'; - $multi_number = 2; - $widget_id = "$id_base-$multi_number"; - $sidebar_id = 'sidebar-1'; - - // Ensure sidebar exists. - wp_set_sidebars_widgets( array( $sidebar_id => array() ) ); - - // Mock the update callback. - $updated = false; - $wp_registered_widget_updates[ $id_base ] = array( - 'callback' => function () use ( &$updated, $id_base, $multi_number ) { - $updated = true; - // In a real scenario, the update callback would update the option. - $settings = array( $multi_number => array( 'title' => 'New Widget' ) ); - update_option( 'widget_' . $id_base, $settings ); - }, - 'params' => array(), - ); - - // Mock the control callback. - $control_called = false; - $wp_registered_widget_controls[ $widget_id ] = array( - 'callback' => function () use ( &$control_called ) { - $control_called = true; - echo 'control-output'; - }, - 'params' => array(), - ); - - $_POST = array( - 'action' => 'save-widget', - 'savewidgets' => wp_create_nonce( 'save-sidebar-widgets' ), - 'sidebar' => $sidebar_id, - 'widget-id' => $widget_id, - 'id_base' => $id_base, - 'multi_number' => $multi_number, - 'widget-' . $id_base => array( '__i__' => array( 'title' => 'New Widget' ) ), - 'add_new' => 'multi', - ); - - try { - $this->_handleAjax( 'save-widget' ); - } catch ( WPAjaxDieStopException $e ) { - $this->_last_response = $e->getMessage(); - } catch ( WPAjaxDieContinueException $e ) { - } - - $this->assertTrue( $updated, 'Update callback should be called.' ); - - $this->assertContains( $widget_id, $_POST['widget-id'] ); - - // Cleanup. - unset( $wp_registered_widget_updates[ $id_base ], $wp_registered_widget_controls[ $widget_id ] ); - } -} From 4fe480362c1906bfe6b65182e9f1b181e8f6c828 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:04:14 -0400 Subject: [PATCH 08/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php --- .../ajax-actions/queryAttachments.php | 150 ------------------ 1 file changed, 150 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php b/tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php deleted file mode 100644 index 449858dc7b340..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/queryAttachments.php +++ /dev/null @@ -1,150 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - - self::$attachment_ids[] = $factory->attachment->create_object( - array( - 'file' => 'test1.jpg', - 'post_parent' => 0, - 'post_mime_type' => 'image/jpeg', - 'post_title' => 'Test Attachment 1', - ) - ); - - self::$attachment_ids[] = $factory->attachment->create_object( - array( - 'file' => 'test2.jpg', - 'post_parent' => 0, - 'post_mime_type' => 'image/jpeg', - 'post_title' => 'Searchable Attachment', - ) - ); - - foreach ( self::$attachment_ids as $id ) { - $file = get_attached_file( $id ); - if ( ! file_exists( dirname( $file ) ) ) { - wp_mkdir_p( dirname( $file ) ); - } - touch( $file ); - } - } - - public function set_up(): void { - parent::set_up(); - add_action( 'wp_ajax_query-attachments', 'wp_ajax_query_attachments', 1 ); - } - - /** - * Tests success with default query. - * - * @ticket 65252 - */ - public function test_query_attachments_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['query'] = array(); - - try { - $this->_handleAjax( 'query-attachments' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertTrue( $response['success'], 'AJAX response should be successful' ); - $this->assertIsArray( $response['data'], 'Response data should be an array' ); - - $found_ids = wp_list_pluck( $response['data'], 'id' ); - foreach ( self::$attachment_ids as $id ) { - $this->assertContains( $id, $found_ids, "Response should contain attachment $id" ); - } - } - - /** - * Tests success with search term. - * - * @ticket 65252 - */ - public function test_query_attachments_search(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['query'] = array( - 's' => 'Searchable', - ); - - try { - $this->_handleAjax( 'query-attachments' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertTrue( $response['success'], 'AJAX response should be successful' ); - - $found_ids = wp_list_pluck( $response['data'], 'id' ); - $this->assertContains( self::$attachment_ids[1], $found_ids, 'Response should contain the searchable attachment' ); - $this->assertNotContains( self::$attachment_ids[0], $found_ids, 'Response should not contain the non-matching attachment' ); - } - - /** - * Tests failure with insufficient permissions. - * - * @ticket 65252 - */ - public function test_query_attachments_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST['query'] = array(); - - try { - $this->_handleAjax( 'query-attachments' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } -} From 6131adbe58ef7b585dee3f9a1d7a354bdddc21ee Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:04:21 -0400 Subject: [PATCH 09/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php --- .../ajax-actions/getPostThumbnailHtml.php | 206 ------------------ 1 file changed, 206 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php b/tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php deleted file mode 100644 index 52405d88d2186..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/getPostThumbnailHtml.php +++ /dev/null @@ -1,206 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - self::$post_id = $factory->post->create(); - self::$thumbnail_id = $factory->attachment->create_object( - 'image.jpg', - 0, - array( - 'post_mime_type' => 'image/jpeg', - 'post_type' => 'attachment', - ) - ); - } - - /** - * Set up the test fixture. - * Override wp_die(). - */ - public function set_up(): void { - parent::set_up(); - add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); - } - - /** - * Tear down the test fixture. - */ - public function tear_down(): void { - remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); - parent::tear_down(); - } - - /** - * Return our callback handler - * - * @return callback - */ - public function getDieHandler() { - return array( $this, 'dieHandler' ); - } - - /** - * Handler for wp_die() - * Don't die, just throw an exception. - * - * @param string|WP_Error $message - * @param string $title - * @param array $args - * @throws WPAjaxDieStopException - * @throws WPAjaxDieContinueException - */ - public function dieHandler( $message, $title = '', $args = array() ) { - $this->_last_response .= ob_get_clean(); - - if ( '' === $this->_last_response ) { - if ( is_scalar( $message ) ) { - $this->_last_response = (string) $message; - } else { - $this->_last_response = '0'; - } - } - - throw new WPAjaxDieContinueException( $this->_last_response ); - } - - /** - * Tests successful retrieval of the featured image HTML. - * - * @ticket 65252 - */ - public function test_get_post_thumbnail_html_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'get-post-thumbnail-html', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'get-post-thumbnail-html' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - $this->assertTrue( $response['success'] ); - $this->assertStringContainsString( 'id="set-post-thumbnail"', $response['data'] ); - $this->assertStringContainsString( 'value="' . self::$thumbnail_id . '"', $response['data'] ); - } - - /** - * Tests successful retrieval of the featured image HTML when no thumbnail is provided. - * - * @ticket 65252 - */ - public function test_get_post_thumbnail_html_no_thumbnail_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'get-post-thumbnail-html', - 'post_id' => self::$post_id, - 'thumbnail_id' => -1, - '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'get-post-thumbnail-html' ); - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - $this->assertTrue( $response['success'] ); - $this->assertStringContainsString( 'id="set-post-thumbnail"', $response['data'] ); - $this->assertStringContainsString( 'value="-1"', $response['data'] ); - } - - /** - * Tests failure due to invalid nonce. - * - * @ticket 65252 - */ - public function test_get_post_thumbnail_html_invalid_nonce(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'get-post-thumbnail-html', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - '_ajax_nonce' => 'invalid-nonce', - ); - - try { - $this->_handleAjax( 'get-post-thumbnail-html' ); - } catch ( WPAjaxDieContinueException $e ) { - $this->assertSame( '-1', $e->getMessage() ); - } - } - - /** - * Tests failure due to insufficient permissions. - * - * @ticket 65252 - */ - public function test_get_post_thumbnail_html_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST = array( - 'action' => 'get-post-thumbnail-html', - 'post_id' => self::$post_id, - 'thumbnail_id' => self::$thumbnail_id, - '_ajax_nonce' => wp_create_nonce( 'update-post_' . self::$post_id ), - ); - - try { - $this->_handleAjax( 'get-post-thumbnail-html' ); - } catch ( WPAjaxDieContinueException $e ) { - $this->assertSame( '-1', $e->getMessage() ); - } - } -} From 6339ad6a5261f521cb569f6615128d42fc1fc377 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:04:38 -0400 Subject: [PATCH 10/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php --- .../includes/ajax-actions/getAttachment.php | 173 ------------------ 1 file changed, 173 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php b/tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php deleted file mode 100644 index 3530b8bbb07cb..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/getAttachment.php +++ /dev/null @@ -1,173 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - - self::$attachment_id = $factory->attachment->create_object( - array( - 'file' => 'test.jpg', - 'post_parent' => 0, - 'post_mime_type' => 'image/jpeg', - 'post_title' => 'Test Attachment', - ) - ); - - // Ensure the file exists so wp_prepare_attachment_for_js doesn't fail on some checks. - $file = get_attached_file( self::$attachment_id ); - if ( ! file_exists( dirname( $file ) ) ) { - wp_mkdir_p( dirname( $file ) ); - } - touch( $file ); - } - - public function set_up(): void { - parent::set_up(); - add_action( 'wp_ajax_get-attachment', 'wp_ajax_get_attachment', 1 ); - } - - /** - * Tests success with valid ID. - * - * @ticket 65252 - */ - public function test_get_attachment_success(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['id'] = self::$attachment_id; - - try { - $this->_handleAjax( 'get-attachment' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertTrue( $response['success'], 'AJAX response should be successful' ); - $this->assertSame( self::$attachment_id, $response['data']['id'], 'Attachment ID should match' ); - $this->assertSame( 'Test Attachment', $response['data']['title'], 'Attachment title should match' ); - } - - /** - * Tests failure with missing ID. - * - * @ticket 65252 - */ - public function test_get_attachment_missing_id(): void { - wp_set_current_user( self::$admin_id ); - - unset( $_REQUEST['id'] ); - - try { - $this->_handleAjax( 'get-attachment' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } - - /** - * Tests failure with invalid ID. - * - * @ticket 65252 - */ - public function test_get_attachment_invalid_id(): void { - wp_set_current_user( self::$admin_id ); - - $_POST['id'] = 99999; - - try { - $this->_handleAjax( 'get-attachment' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } - - /** - * Tests failure with wrong post type. - * - * @ticket 65252 - */ - public function test_get_attachment_wrong_post_type(): void { - wp_set_current_user( self::$admin_id ); - - $post_id = self::factory()->post->create(); - $_POST['id'] = $post_id; - - try { - $this->_handleAjax( 'get-attachment' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } - - /** - * Tests failure with insufficient permissions. - * - * @ticket 65252 - */ - public function test_get_attachment_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST['id'] = self::$attachment_id; - - try { - $this->_handleAjax( 'get-attachment' ); - } catch ( WPAjaxDieStopException $e ) { - } catch ( WPAjaxDieContinueException $e ) { - } - - $response = json_decode( $this->_last_response, true ); - - $this->assertFalse( $response['success'], 'AJAX response should be a failure' ); - } -} From 9198a455a640090372ea499202fd3ffd0ad92f1c Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Thu, 4 Jun 2026 16:04:44 -0400 Subject: [PATCH 11/11] Delete tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php --- .../ajax-actions/deleteInactiveWidgets.php | 148 ------------------ 1 file changed, 148 deletions(-) delete mode 100644 tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php diff --git a/tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php b/tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php deleted file mode 100644 index 8f6097943192c..0000000000000 --- a/tests/phpunit/tests/admin/includes/ajax-actions/deleteInactiveWidgets.php +++ /dev/null @@ -1,148 +0,0 @@ -user->create( array( 'role' => 'administrator' ) ); - } - - /** - * Set up the test fixture. - * Override wp_die(). - */ - public function set_up(): void { - parent::set_up(); - add_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); - } - - /** - * Tear down the test fixture. - */ - public function tear_down(): void { - remove_filter( 'wp_die_ajax_handler', array( $this, 'getDieHandler' ), 1, 1 ); - parent::tear_down(); - } - - /** - * Return our callback handler - * - * @return callback - */ - public function getDieHandler() { - return array( $this, 'dieHandler' ); - } - - /** - * Handler for wp_die() - * Don't die, just throw an exception. - * - * @param string|WP_Error $message - * @param string $title - * @param array $args - * @throws WPAjaxDieStopException - */ - public function dieHandler( $message, $title = '', $args = array() ) { - throw new WPAjaxDieStopException( (string) $message ); - } - - /** - * Tests successful deletion of inactive widgets. - * - * @ticket 65252 - */ - public function test_delete_inactive_widgets_success(): void { - wp_set_current_user( self::$admin_id ); - - $id_base = 'test_widget'; - $settings = array( - 1 => array( 'title' => 'Widget 1' ), - 2 => array( 'title' => 'Widget 2' ), - 'array_version' => 3, - ); - update_option( 'widget_' . $id_base, $settings ); - - $inactive_widgets = array( $id_base . '-1', $id_base . '-2' ); - wp_set_sidebars_widgets( array( 'wp_inactive_widgets' => $inactive_widgets ) ); - - $_POST = array( - 'action' => 'delete-inactive-widgets', - 'removeinactivewidgets' => wp_create_nonce( 'remove-inactive-widgets' ), - ); - - try { - $this->_handleAjax( 'delete-inactive-widgets' ); - } catch ( WPAjaxDieStopException $e ) { - } - - // The actual logic is tested in Tests_wp_delete_inactive_widgets. - // Here we just verify the wrapper executes without major error. - $this->assertTrue( true ); - } - - /** - * Tests failure due to invalid nonce. - * - * @ticket 65252 - */ - public function test_delete_inactive_widgets_invalid_nonce(): void { - wp_set_current_user( self::$admin_id ); - - $_POST = array( - 'action' => 'delete-inactive-widgets', - 'removeinactivewidgets' => 'invalid-nonce', - ); - - $this->expectException( WPAjaxDieStopException::class ); - $this->expectExceptionMessage( '-1' ); - - wp_ajax_delete_inactive_widgets(); - } - - /** - * Tests failure due to insufficient permissions. - * - * @ticket 65252 - */ - public function test_delete_inactive_widgets_insufficient_permissions(): void { - $subscriber_id = self::factory()->user->create( array( 'role' => 'subscriber' ) ); - wp_set_current_user( $subscriber_id ); - - $_POST = array( - 'action' => 'delete-inactive-widgets', - 'removeinactivewidgets' => wp_create_nonce( 'remove-inactive-widgets' ), - ); - - $this->expectException( WPAjaxDieStopException::class ); - $this->expectExceptionMessage( '-1' ); - - wp_ajax_delete_inactive_widgets(); - } -}