Show found desktop updates after manual checks (#1815)
* fix(desktop): report found app updates during manual checks Manual checks could reuse cached update state while the renderer discarded pending update details. Keep found update versions visible while downloads prepare, and reserve install affordances for ready updates. * test(desktop): cover manual update retry after errors * fix(desktop): keep ready updates ready on recheck
This commit is contained in:
@@ -58,6 +58,10 @@ class FakeAppUpdateRuntime implements AppUpdateRuntime {
|
||||
this.configuration?.onUpdateAvailable(info);
|
||||
}
|
||||
|
||||
finishUpdateDownload(info: RuntimeUpdateInfo): void {
|
||||
this.configuration?.onUpdateDownloaded(info);
|
||||
}
|
||||
|
||||
async checkForUpdates(): Promise<{
|
||||
isUpdateAvailable: boolean;
|
||||
updateInfo: RuntimeUpdateInfo;
|
||||
@@ -144,6 +148,37 @@ describe("app update service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("performs a fresh manual check when an update is already cached", async () => {
|
||||
const { runtime, service } = createService({ bucket: async () => 0 });
|
||||
runtime.nextCheck({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
|
||||
await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "automatic",
|
||||
});
|
||||
|
||||
runtime.nextCheck({
|
||||
isUpdateAvailable: true,
|
||||
updateInfo: { ...rolledOutUpdate, version: "1.2.5" },
|
||||
});
|
||||
const result = await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
hasUpdate: true,
|
||||
readyToInstall: false,
|
||||
currentVersion: "1.2.3",
|
||||
latestVersion: "1.2.5",
|
||||
body: null,
|
||||
date: "2026-04-28T00:00:00.000Z",
|
||||
errorMessage: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("trusts the runtime availability decision before comparing versions", async () => {
|
||||
const { runtime, service } = createService({ bucket: async () => 0 });
|
||||
runtime.nextCheck({ isUpdateAvailable: false, updateInfo: rolledOutUpdate });
|
||||
@@ -347,6 +382,70 @@ describe("app update service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("performs a fresh manual check after an update preparation error", async () => {
|
||||
const { runtime, service } = createService();
|
||||
runtime.nextCheck({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
|
||||
await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
runtime.failRuntime(new Error("sha512 checksum mismatch"));
|
||||
|
||||
runtime.nextCheck({
|
||||
isUpdateAvailable: true,
|
||||
updateInfo: { ...rolledOutUpdate, version: "1.2.5" },
|
||||
});
|
||||
const result = await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
hasUpdate: true,
|
||||
readyToInstall: false,
|
||||
currentVersion: "1.2.3",
|
||||
latestVersion: "1.2.5",
|
||||
body: null,
|
||||
date: "2026-04-28T00:00:00.000Z",
|
||||
errorMessage: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a downloaded update ready when a manual check re-announces it", async () => {
|
||||
const { runtime, service } = createService();
|
||||
runtime.nextCheck({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
|
||||
await service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
runtime.finishUpdateDownload(rolledOutUpdate);
|
||||
|
||||
const recheck = runtime.deferNextCheck();
|
||||
const pending = service.checkForAppUpdate({
|
||||
currentVersion: "1.2.3",
|
||||
releaseChannel: "stable",
|
||||
intent: "manual",
|
||||
});
|
||||
runtime.prepareUpdate(rolledOutUpdate);
|
||||
recheck.resolve({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
const result = await pending;
|
||||
|
||||
expect(result).toEqual({
|
||||
hasUpdate: true,
|
||||
readyToInstall: true,
|
||||
currentVersion: "1.2.3",
|
||||
latestVersion: "1.2.4",
|
||||
body: null,
|
||||
date: "2026-04-28T00:00:00.000Z",
|
||||
errorMessage: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns runtime update errors to multiple automatic checks before a manual retry clears them", async () => {
|
||||
const { runtime, service } = createService();
|
||||
runtime.nextCheck({ isUpdateAvailable: true, updateInfo: rolledOutUpdate });
|
||||
|
||||
@@ -163,9 +163,10 @@ export function createAppUpdateService(deps: AppUpdateServiceDeps): AppUpdateSer
|
||||
});
|
||||
},
|
||||
onUpdateAvailable(info) {
|
||||
const alreadyReady = downloadedUpdateVersion === info.version;
|
||||
cachedUpdateInfo = info;
|
||||
downloadedUpdateVersion = null;
|
||||
downloading = true;
|
||||
downloadedUpdateVersion = alreadyReady ? info.version : null;
|
||||
downloading = !alreadyReady;
|
||||
runtimeErrorMessage = null;
|
||||
},
|
||||
onUpdateDownloaded(info) {
|
||||
@@ -212,7 +213,12 @@ export function createAppUpdateService(deps: AppUpdateServiceDeps): AppUpdateSer
|
||||
}
|
||||
|
||||
const cachedVersion = cachedUpdateInfo?.version ?? null;
|
||||
if (!runtimeErrorResult && cachedVersion && cachedVersion !== currentVersion) {
|
||||
if (
|
||||
!runtimeErrorResult &&
|
||||
intent === "automatic" &&
|
||||
cachedVersion &&
|
||||
cachedVersion !== currentVersion
|
||||
) {
|
||||
return buildCheckResult({
|
||||
currentVersion,
|
||||
hasUpdate: true,
|
||||
|
||||
Reference in New Issue
Block a user