diff --git a/frontend/src/hooks/useAppUpdateManager.test.tsx b/frontend/src/hooks/useAppUpdateManager.test.tsx index 6146a5da..a08927ae 100644 --- a/frontend/src/hooks/useAppUpdateManager.test.tsx +++ b/frontend/src/hooks/useAppUpdateManager.test.tsx @@ -157,9 +157,46 @@ describe('useAppUpdateManager', () => { expect(backendApp.DownloadUpdate).toHaveBeenCalledTimes(1); expect(backendApp.OpenDownloadedUpdateDirectory).not.toHaveBeenCalled(); + expect(backendApp.InstallUpdateAndRestart).not.toHaveBeenCalled(); expect(hook?.lastUpdateInfo?.downloaded).toBe(true); }); + it('auto-installs a macOS update immediately after download when the backend reports auto relaunch support', async () => { + backendApp.CheckForUpdates.mockResolvedValue({ + success: true, + data: { + hasUpdate: true, + currentVersion: '0.8.1', + latestVersion: '0.8.2', + downloaded: false, + assetSize: 2048, + }, + }); + backendApp.DownloadUpdate.mockResolvedValue({ + success: true, + data: { + platform: 'darwin', + autoRelaunch: true, + downloadPath: '/Users/test/Desktop/GoNavi-0.8.2/GoNavi-0.8.2-MacOS-Arm64.dmg', + }, + }); + backendApp.InstallUpdateAndRestart.mockResolvedValue({ success: true }); + + renderHook(); + + await act(async () => { + await hook?.checkForUpdates(false); + }); + + await act(async () => { + await hook?.downloadUpdate(hook?.lastUpdateInfo!, false); + }); + + expect(backendApp.DownloadUpdate).toHaveBeenCalledTimes(1); + expect(backendApp.InstallUpdateAndRestart).toHaveBeenCalledTimes(1); + expect(backendApp.OpenDownloadedUpdateDirectory).not.toHaveBeenCalled(); + }); + it('switches update channel and re-checks against the selected channel', async () => { backendApp.SetUpdateChannel.mockResolvedValue({ success: true, data: { channel: 'dev' } }); backendApp.CheckForUpdates.mockResolvedValue({ diff --git a/frontend/src/hooks/useAppUpdateManager.ts b/frontend/src/hooks/useAppUpdateManager.ts index 6880d498..f6be441d 100644 --- a/frontend/src/hooks/useAppUpdateManager.ts +++ b/frontend/src/hooks/useAppUpdateManager.ts @@ -63,6 +63,11 @@ const buildUpdateKey = (info: Pick | nu ? `${normalizeUpdateChannel(info.channel)}:${String(info.latestVersion || '').trim()}` : ''; +const shouldAutoInstallDownloadedUpdate = (resultData: UpdateDownloadResultData | null | undefined): boolean => { + const platform = String(resultData?.platform || '').trim().toLowerCase(); + return platform === 'darwin' && resultData?.autoRelaunch !== false; +}; + export const useAppUpdateManager = ({ runtimeBuildType, t, @@ -192,6 +197,21 @@ export const useAppUpdateManager = ({ void message.success({ content: t('app.about.message.download_completed'), duration: 2 }); } setAboutUpdateStatus(formatAboutUpdateStatus({ ...info, channel: normalizeUpdateChannel(info.channel), downloaded: true })); + + if (shouldAutoInstallDownloadedUpdate(resultData)) { + let installRes: any = null; + try { + installRes = await (window as any).go?.app?.App?.InstallUpdateAndRestart?.(); + } catch (error: any) { + installRes = { success: false, message: error?.message || t('common.unknown') }; + } + if (!installRes?.success) { + void message.error(t('app.about.message.install_failed_with_error', { error: installRes?.message || t('common.unknown') })); + return; + } + updateInstallTriggeredVersionRef.current = targetKey || null; + setUpdateDownloadProgress((prev) => ({ ...prev, open: false })); + } } else { setUpdateDownloadProgress((prev) => ({ ...prev,