From 29c5dd5fb055c365699b2b2b95266d9cc4c01f7e Mon Sep 17 00:00:00 2001 From: ArkaPrabhaChowdhury Date: Thu, 23 Jul 2026 15:16:24 +0530 Subject: [PATCH 1/3] Administration: Handle AJAX failures for plugin and theme actions --- src/js/_enqueues/wp/updates.js | 92 +++++++++++++++++++++--------- tests/qunit/wp-admin/js/updates.js | 91 +++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 26 deletions(-) diff --git a/src/js/_enqueues/wp/updates.js b/src/js/_enqueues/wp/updates.js index ef4b47e66093e..5b80c6f5c61e2 100644 --- a/src/js/_enqueues/wp/updates.js +++ b/src/js/_enqueues/wp/updates.js @@ -1330,13 +1330,23 @@ * decorated with an abort() method. */ wp.updates.deletePlugin = function( args ) { - var $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' ); + var errorCallback, + $link = $( '[data-plugin="' + args.plugin + '"]' ).find( '.row-actions a.delete' ); args = _.extend( { success: wp.updates.deletePluginSuccess, error: wp.updates.deletePluginError }, args ); + errorCallback = args.error; + if ( errorCallback ) { + args.error = function() { + $link.text( $link.data( 'originaltext' ) ); + + errorCallback.apply( this, arguments ); + }; + } + if ( $link.html() !== __( 'Deleting...' ) ) { $link .data( 'originaltext', $link.html() ) @@ -1488,12 +1498,7 @@ * @param {string} response.errorMessage The error that occurred. */ wp.updates.deletePluginError = function( response ) { - var $plugin, $pluginUpdateRow, - pluginUpdateRow = wp.template( 'item-update-row' ), - noticeContent = wp.updates.adminNotice( { - className: 'update-message notice-error notice-alt', - message: response.errorMessage - } ); + var $deleteLink, $plugin, $pluginUpdateRow, noticeContent, pluginUpdateRow; if ( response.plugin ) { $plugin = $( 'tr.inactive[data-plugin="' + response.plugin + '"]' ); @@ -1503,6 +1508,9 @@ $pluginUpdateRow = $plugin.siblings( '[data-slug="' + response.slug + '"]' ); } + $deleteLink = $plugin.find( '.row-actions a.delete' ); + $deleteLink.text( $deleteLink.data( 'originaltext' ) ); + if ( ! wp.updates.isValidResponse( response, 'delete' ) ) { return; } @@ -1511,6 +1519,12 @@ return; } + pluginUpdateRow = wp.template( 'item-update-row' ); + noticeContent = wp.updates.adminNotice( { + className: 'update-message notice-error notice-alt', + message: response.errorMessage + } ); + // Add a plugin update row if it doesn't exist yet. if ( ! $pluginUpdateRow.length ) { $plugin.addClass( 'update' ).after( @@ -1908,7 +1922,7 @@ * decorated with an abort() method. */ wp.updates.deleteTheme = function( args ) { - var $button; + var $button, errorCallback; if ( 'themes' === pagenow ) { $button = $( '.theme-actions .delete-theme' ); @@ -1921,6 +1935,17 @@ error: wp.updates.deleteThemeError }, args ); + errorCallback = args.error; + if ( errorCallback ) { + args.error = function() { + if ( $button ) { + $button.html( $button.data( 'originaltext' ) ); + } + + errorCallback.apply( this, arguments ); + }; + } + if ( $button && $button.html() !== __( 'Deleting...' ) ) { $button .data( 'originaltext', $button.html() ) @@ -2034,24 +2059,37 @@ * @param {string} response.errorMessage The error that occurred. */ wp.updates.deleteThemeError = function( response ) { - var $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ), - $button = $( '.theme-actions .delete-theme' ), - updateRow = wp.template( 'item-update-row' ), - $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ), - errorMessage = sprintf( - /* translators: %s: Error string for a failed deletion. */ - __( 'Deletion failed: %s' ), - response.errorMessage - ), - $message = wp.updates.adminNotice( { - className: 'update-message notice-error notice-alt', - message: errorMessage - } ); + var $button, $message, errorMessage, updateRow, + $themeRow = $( 'tr.inactive[data-slug="' + response.slug + '"]' ), + $updateRow = $themeRow.siblings( '#' + response.slug + '-update' ); + + if ( 'themes-network' === pagenow ) { + $button = $themeRow.find( '.row-actions a.delete' ); + } else { + $button = $( '.theme-actions .delete-theme' ); + } + + $button.html( $button.data( 'originaltext' ) ); + + if ( ! wp.updates.isValidResponse( response, 'delete' ) ) { + return; + } if ( wp.updates.maybeHandleCredentialError( response, 'delete-theme' ) ) { return; } + updateRow = wp.template( 'item-update-row' ); + errorMessage = sprintf( + /* translators: %s: Error string for a failed deletion. */ + __( 'Deletion failed: %s' ), + response.errorMessage + ); + $message = wp.updates.adminNotice( { + className: 'update-message notice-error notice-alt', + message: errorMessage + } ); + if ( 'themes-network' === pagenow ) { if ( ! $updateRow.length ) { $themeRow.addClass( 'update' ).after( @@ -2070,8 +2108,6 @@ $( '.theme-info .theme-description' ).before( $message ); } - $button.html( $button.data( 'originaltext' ) ); - wp.a11y.speak( errorMessage, 'assertive' ); $document.trigger( 'wp-theme-delete-error', response ); @@ -3418,7 +3454,7 @@ if ( response.data && response.data.error ) { errorMessage = response.data.error; } else { - errorMessage = __( 'The request could not be completed.' ); + errorMessage = __( 'The request could not be completed. Please try again.' ); } $parent.find( '.notice.notice-error' ).removeClass( 'hidden' ).find( 'p' ).text( errorMessage ); @@ -3482,11 +3518,15 @@ $parent.find( '.notice.notice-error' ) .removeClass( 'hidden' ) .find( 'p' ) - .text( __( 'The request could not be completed.' ) ); + .text( __( 'The request could not be completed. Please try again.' ) ); - wp.a11y.speak( __( 'The request could not be completed.' ), 'assertive' ); + wp.a11y.speak( __( 'The request could not be completed. Please try again.' ), 'assertive' ); } ) .always( function() { + if ( action === $toggler.attr( 'data-wp-action' ) ) { + $label.text( 'enable' === action ? __( 'Enable auto-updates' ) : __( 'Disable auto-updates' ) ); + } + $toggler.removeAttr( 'data-doing-ajax' ).find( '.dashicons-update' ).addClass( 'hidden' ); } ); } diff --git a/tests/qunit/wp-admin/js/updates.js b/tests/qunit/wp-admin/js/updates.js index 9d3948811abfd..315e0c0e380b6 100644 --- a/tests/qunit/wp-admin/js/updates.js +++ b/tests/qunit/wp-admin/js/updates.js @@ -123,6 +123,24 @@ jQuery( function( $ ) { assert.equal( jQuery.ajax.getCall( 0 ).args[0].data.slug, 'jetpack' ); } ); + /** + * @ticket 55368 + */ + QUnit.test( 'A failed plugin deletion should restore the delete link', function( assert ) { + var $pluginRow = $( + '' + + '' + + '
' + ).appendTo( '#qunit-fixture' ).find( 'tr' ); + + sinon.stub( wp.ajax, 'send' ).returns( jQuery.Deferred().promise() ); + + wp.updates.deletePlugin( { slug: 'jetpack', plugin: 'jetpack/jetpack.php' } ); + wp.ajax.send.firstCall.args[ 0 ].error( 'Briefly unavailable for scheduled maintenance.' ); + + assert.strictEqual( $pluginRow.find( 'a.delete' ).text(), 'Delete', 'The delete link text is restored.' ); + } ); + // QUnit.test( 'A successful update changes the message?', function( assert ) {} ); // QUnit.test( 'A failed update changes the message?', function( assert ) {} ); @@ -174,6 +192,79 @@ jQuery( function( $ ) { assert.equal( jQuery.ajax.getCall( 0 ).args[0].data.slug, 'twentyeleven' ); } ); + /** + * @ticket 55368 + */ + QUnit.test( 'A failed theme deletion should restore the delete button', function( assert ) { + var $button = $( '
' ) + .appendTo( '#qunit-fixture' ) + .find( '.delete-theme' ); + + sinon.stub( wp.ajax, 'send' ).returns( jQuery.Deferred().promise() ); + + wp.updates.deleteTheme( { slug: 'twentyeleven' } ); + wp.ajax.send.firstCall.args[ 0 ].error( 'Briefly unavailable for scheduled maintenance.' ); + + assert.strictEqual( $button.text(), 'Delete', 'The delete button text is restored.' ); + } ); + // QUnit.test( 'A successful update changes the message?', function( assert ) {} ); // QUnit.test( 'A failed update changes the message?', function( assert ) {} ); + + QUnit.module( 'wp.updates.autoUpdates', { + beforeEach: function() { + this.oldPagenow = window.pagenow; + window.pagenow = 'plugins'; + this.request = jQuery.Deferred(); + sinon.stub( jQuery, 'post' ).returns( this.request.promise() ); + this.$column = $( + '
' + + '' + + '' + + '
' + ).appendTo( '#qunit-fixture' ); + }, + afterEach: function() { + window.pagenow = this.oldPagenow; + } + } ); + + /** + * @ticket 55368 + */ + QUnit.test( 'A failed auto-update request should restore the toggle label', function( assert ) { + var $toggler = this.$column.find( '.toggle-auto-update' ); + + $toggler.trigger( 'click' ); + assert.strictEqual( $toggler.find( '.label' ).text(), 'Enabling...', 'The pending label is shown.' ); + + this.request.reject(); + + assert.strictEqual( $toggler.find( '.label' ).text(), 'Enable auto-updates', 'The toggle label is restored.' ); + assert.strictEqual( + this.$column.find( '.notice-error p' ).text(), + 'The request could not be completed. Please try again.', + 'The error message suggests retrying the request.' + ); + } ); + + /** + * @ticket 55368 + */ + QUnit.test( 'An invalid auto-update response should restore the toggle label', function( assert ) { + var $toggler = this.$column.find( '.toggle-auto-update' ); + + $toggler.trigger( 'click' ); + this.request.resolve( 'Briefly unavailable for scheduled maintenance.' ); + + assert.strictEqual( $toggler.find( '.label' ).text(), 'Enable auto-updates', 'The toggle label is restored.' ); + assert.strictEqual( + this.$column.find( '.notice-error p' ).text(), + 'The request could not be completed. Please try again.', + 'The error message suggests retrying the request.' + ); + } ); }); From adc3160f52abe02a523a7c085cf8c35aff9c428b Mon Sep 17 00:00:00 2001 From: ArkaPrabhaChowdhury <92904175+ArkaPrabhaChowdhury@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:33:34 +0530 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/js/_enqueues/wp/updates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/_enqueues/wp/updates.js b/src/js/_enqueues/wp/updates.js index 5b80c6f5c61e2..2d6b5acf27505 100644 --- a/src/js/_enqueues/wp/updates.js +++ b/src/js/_enqueues/wp/updates.js @@ -1341,7 +1341,7 @@ errorCallback = args.error; if ( errorCallback ) { args.error = function() { - $link.text( $link.data( 'originaltext' ) ); + $link.html( $link.data( 'originaltext' ) ); errorCallback.apply( this, arguments ); }; From 2ee8409332a0b76c29446834fa56adcc48679683 Mon Sep 17 00:00:00 2001 From: ArkaPrabhaChowdhury <92904175+ArkaPrabhaChowdhury@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:40:00 +0530 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/qunit/wp-admin/js/updates.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/qunit/wp-admin/js/updates.js b/tests/qunit/wp-admin/js/updates.js index 315e0c0e380b6..cf47e66bb0f3c 100644 --- a/tests/qunit/wp-admin/js/updates.js +++ b/tests/qunit/wp-admin/js/updates.js @@ -200,12 +200,14 @@ jQuery( function( $ ) { .appendTo( '#qunit-fixture' ) .find( '.delete-theme' ); - sinon.stub( wp.ajax, 'send' ).returns( jQuery.Deferred().promise() ); + var sendStub = sinon.stub( wp.ajax, 'send' ).returns( jQuery.Deferred().promise() ); wp.updates.deleteTheme( { slug: 'twentyeleven' } ); wp.ajax.send.firstCall.args[ 0 ].error( 'Briefly unavailable for scheduled maintenance.' ); assert.strictEqual( $button.text(), 'Delete', 'The delete button text is restored.' ); + + sendStub.restore(); } ); // QUnit.test( 'A successful update changes the message?', function( assert ) {} );