Show the Enable auto-updates toggle for the self-updater
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m7s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Build Plugin Zip (pull_request) Skipped

WordPress only renders the "Enable auto-updates" toggle for a plugin that
appears in the update_plugins transient's response or no_update list, which
is what sets core's update-supported flag. UpdateChecker only populated the
response side (when a newer release existed), so between releases the plugin
was absent from the transient and the toggle never showed.

provideUpdate() now returns a no_update payload (installed version, empty
package) whenever no newer release is offered — including when the release
lookup fails — so the plugin stays in the transient and the toggle appears.
The response path (one-click and unattended updates) is unchanged.

Bumps to 1.1.1 so the fix ships to installed sites via the self-updater.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-24 08:46:31 -03:00
co-authored by Claude Opus 4.8
parent 37ec8a3315
commit 51dd032668
4 changed files with 68 additions and 22 deletions
+32 -10
View File
@@ -32,6 +32,22 @@ class UpdateCheckerTest extends TestCase
return ['name' => $name, 'browser_download_url' => self::PACKAGE_URL];
}
/**
* The payload provideUpdate() returns when no newer release is offered.
* Core files this under the transient's `no_update` list, which is what
* makes the "Enable auto-updates" toggle appear. USC_VERSION is 1.0.0 in
* the test bootstrap.
*/
private function noUpdatePayload(): array
{
return [
'slug' => 'unsupervised-schedular',
'version' => '1.0.0',
'url' => UpdateChecker::REPO_URL,
'package' => '',
];
}
public function testRegisterHooksHostnameFilter(): void
{
Filters\expectAdded('update_plugins_git.unsupervised.ca')->once();
@@ -109,7 +125,7 @@ class UpdateCheckerTest extends TestCase
self::assertFalse($result);
}
public function testNoUpdateWhenReleaseIsNotNewer(): void
public function testNoUpdatePayloadWhenReleaseIsNotNewer(): void
{
Functions\when('plugin_basename')->justReturn(self::PLUGIN_FILE);
Functions\when('get_transient')->justReturn(false);
@@ -118,7 +134,9 @@ class UpdateCheckerTest extends TestCase
$result = (new UpdateChecker())->provideUpdate(false, [], self::PLUGIN_FILE);
self::assertFalse($result);
// Current version → core files this under `no_update` so the
// auto-update toggle stays visible; no package to install.
self::assertSame($this->noUpdatePayload(), $result);
}
public function testUsesCachedReleaseWithoutHittingApi(): void
@@ -134,7 +152,7 @@ class UpdateCheckerTest extends TestCase
self::assertSame('2.0.0', $result['version']);
}
public function testApiFailureIsCachedAndReturnsUpdateUnchanged(): void
public function testApiFailureIsCachedAndStillReportsUpdateSupport(): void
{
Functions\when('plugin_basename')->justReturn(self::PLUGIN_FILE);
Functions\when('get_transient')->justReturn(false);
@@ -150,10 +168,12 @@ class UpdateCheckerTest extends TestCase
$result = (new UpdateChecker())->provideUpdate(false, [], self::PLUGIN_FILE);
self::assertFalse($result);
// Even with the lookup failed we still return the `no_update` payload,
// so the auto-update toggle does not flicker away during a Gitea blip.
self::assertSame($this->noUpdatePayload(), $result);
}
public function testNon200ResponseReturnsUpdateUnchanged(): void
public function testNon200ResponseReturnsNoUpdatePayload(): void
{
Functions\when('plugin_basename')->justReturn(self::PLUGIN_FILE);
Functions\when('get_transient')->justReturn(false);
@@ -162,7 +182,7 @@ class UpdateCheckerTest extends TestCase
$result = (new UpdateChecker())->provideUpdate(false, [], self::PLUGIN_FILE);
self::assertFalse($result);
self::assertSame($this->noUpdatePayload(), $result);
}
public function testPicksFirstZipAssetAndSkipsOthers(): void
@@ -181,7 +201,7 @@ class UpdateCheckerTest extends TestCase
self::assertSame(self::PACKAGE_URL, $result['package']);
}
public function testReleaseWithoutZipAssetOffersNoUpdate(): void
public function testReleaseWithoutZipAssetOffersNoUpdatePayload(): void
{
Functions\when('plugin_basename')->justReturn(self::PLUGIN_FILE);
Functions\when('get_transient')->justReturn(false);
@@ -192,10 +212,12 @@ class UpdateCheckerTest extends TestCase
$result = (new UpdateChecker())->provideUpdate(false, [], self::PLUGIN_FILE);
self::assertFalse($result);
// No installable package means no update to offer, but we still keep
// the plugin in `no_update` so the toggle shows.
self::assertSame($this->noUpdatePayload(), $result);
}
public function testMalformedApiBodyOffersNoUpdate(): void
public function testMalformedApiBodyOffersNoUpdatePayload(): void
{
Functions\when('plugin_basename')->justReturn(self::PLUGIN_FILE);
Functions\when('get_transient')->justReturn(false);
@@ -207,6 +229,6 @@ class UpdateCheckerTest extends TestCase
$result = (new UpdateChecker())->provideUpdate(false, [], self::PLUGIN_FILE);
self::assertFalse($result);
self::assertSame($this->noUpdatePayload(), $result);
}
}