diff --git a/src/app/pages/client/DesktopUpdater.test.tsx b/src/app/pages/client/DesktopUpdater.test.tsx index dcf3f8a6d1..e634b850eb 100644 --- a/src/app/pages/client/DesktopUpdater.test.tsx +++ b/src/app/pages/client/DesktopUpdater.test.tsx @@ -2,7 +2,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { useAtomValue, useSetAtom, Provider } from 'jotai'; import { globalBannersAtom } from '$state/globalBanners'; -import { triggerUpdateCheckAtom } from '$state/desktopUpdate'; +import { triggerUpdateCheckAtom, updatePhaseAtom } from '$state/desktopUpdate'; import { DesktopUpdatePill } from '$components/tauri/DesktopUpdatePill'; import { DesktopUpdater } from './DesktopUpdater'; @@ -32,6 +32,7 @@ function BannersProbe() { {banners.map((b) => (
{b.title} + {b.description} @@ -55,6 +56,11 @@ function CheckNowButton() { ); } +function UpdatePhaseProbe() { + const phase = useAtomValue(updatePhaseAtom); + return {phase.type}; +} + function makeUpdate(version: string) { return { version, @@ -166,6 +172,7 @@ describe('DesktopUpdater', () => { + ); @@ -176,10 +183,12 @@ describe('DesktopUpdater', () => { fireEvent.click(screen.getByRole('button', { name: 'Download & Install' })); await waitFor(() => expect(update.downloadAndInstall).toHaveBeenCalledTimes(1)); await waitFor(() => { - expect(screen.getByRole('button', { name: 'Download & Install' })).toBeInTheDocument(); + expect(screen.getByText('Update Failed')).toBeInTheDocument(); }); + expect(screen.getByText('eacces')).toBeInTheDocument(); + expect(screen.getByTestId('update-phase')).toHaveTextContent('ready'); - fireEvent.click(screen.getByRole('button', { name: 'Download & Install' })); + fireEvent.click(screen.getByRole('button', { name: 'Retry' })); await waitFor(() => expect(update.downloadAndInstall).toHaveBeenCalledTimes(2)); await screen.findByTestId('banner-desktop-update-restart'); }); diff --git a/src/app/pages/client/DesktopUpdater.tsx b/src/app/pages/client/DesktopUpdater.tsx index 80a279d9c8..24ca71a5c1 100644 --- a/src/app/pages/client/DesktopUpdater.tsx +++ b/src/app/pages/client/DesktopUpdater.tsx @@ -29,6 +29,7 @@ export function DesktopUpdater() { const [isDownloading, setIsDownloading] = useState(false); const [isInstalled, setIsInstalled] = useState(false); const [isInstalling, setIsInstalling] = useState(false); + const [installError, setInstallError] = useState(null); const [dismissed, setDismissed] = useState(false); const [useCustomTitleBar] = useDesktopSetting('useCustomTitleBar'); const hasUpdateRef = useRef(false); @@ -145,6 +146,7 @@ export function DesktopUpdater() { if (!updateInfo || installStartedRef.current) return; installStartedRef.current = true; try { + setInstallError(null); setIsDownloading(true); setPhase({ type: 'downloading', progress: 0 }); @@ -180,8 +182,10 @@ export function DesktopUpdater() { closePendingUpdate(updateInfo); } catch (err) { log.error('Failed to install update', err); + setInstallError(err instanceof Error ? err.message : String(err)); setIsDownloading(false); setIsInstalling(false); + setPhase({ type: 'ready', version: updateInfo.version }); installStartedRef.current = false; } }, [closePendingUpdate, updateInfo, setPhase]); @@ -223,6 +227,26 @@ export function DesktopUpdater() { }; } + if (installError) { + return { + id: 'desktop-update-ready', + priority: 200, + icon: ArrowUp, + title: 'Update Failed', + description: installError, + primaryAction: { + label: 'Retry', + variant: 'Primary', + onClick: handleInstall, + }, + secondaryAction: { + label: 'Later', + variant: 'Secondary', + onClick: handleDismiss, + }, + }; + } + return { id: 'desktop-update-ready', priority: 200, @@ -251,6 +275,7 @@ export function DesktopUpdater() { isDownloading, isInstalled, isInstalling, + installError, handleInstall, handleRestart, handleDismiss,