From 59fd9b8646248cdc1a78e5d183a4b0f38a9b6738 Mon Sep 17 00:00:00 2001 From: Amit Haridas Date: Mon, 8 Jun 2026 06:31:07 +0530 Subject: [PATCH] feat(renderer): add UpdateBanner for non-blocking update notifications --- src/renderer/components/UpdateBanner.tsx | 44 +++++++++++++++++++++ tests/component/UpdateBanner.test.tsx | 49 ++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/renderer/components/UpdateBanner.tsx create mode 100644 tests/component/UpdateBanner.test.tsx diff --git a/src/renderer/components/UpdateBanner.tsx b/src/renderer/components/UpdateBanner.tsx new file mode 100644 index 0000000..2f0a436 --- /dev/null +++ b/src/renderer/components/UpdateBanner.tsx @@ -0,0 +1,44 @@ +import { useUpdaterStore } from '@/lib/updater-store'; +import { ipc } from '@/lib/ipc'; +import { toast } from 'sonner'; + +export function UpdateBanner() { + const { state, version, percent, install, check } = useUpdaterStore(); + + if (state === 'idle' || state === 'checking') return null; + + if (state === 'error') { + return ( +
+ Couldn't check for updates.{' '} + +
+ ); + } + + if (state === 'downloading') { + return ( +
+ Downloading update… {Math.round(percent)}% +
+ ); + } + + if (state === 'ready') { + return ( +
+ v{version} is ready. + + +
+ ); + } + + return null; +} \ No newline at end of file diff --git a/tests/component/UpdateBanner.test.tsx b/tests/component/UpdateBanner.test.tsx new file mode 100644 index 0000000..cc7b92b --- /dev/null +++ b/tests/component/UpdateBanner.test.tsx @@ -0,0 +1,49 @@ +import { describe, expect, test, beforeEach, vi } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { useUpdaterStore } from '@/lib/updater-store'; +import { UpdateBanner } from '@/components/UpdateBanner'; + +vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); +vi.mock('@/lib/ipc', () => ({ + ipc: { + app: { openExternal: vi.fn() }, + updater: { check: vi.fn(), install: vi.fn() }, + }, +})); + +describe('UpdateBanner', () => { + beforeEach(() => { + useUpdaterStore.setState({ state: 'idle', version: null, percent: 0 }); + }); + + test('renders nothing when state is idle', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + test('shows version and restart button when state is ready', () => { + useUpdaterStore.setState({ state: 'ready', version: '5.0.2' }); + render(); + expect(screen.getByText(/5\.0\.2/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /restart/i })).toBeInTheDocument(); + }); + + test('shows download progress when state is downloading', () => { + useUpdaterStore.setState({ state: 'downloading', percent: 42 }); + render(); + expect(screen.getByText(/42%/)).toBeInTheDocument(); + }); + + test('shows error copy when state is error', () => { + useUpdaterStore.setState({ state: 'error' }); + render(); + expect(screen.getByText(/couldn.?t check/i)).toBeInTheDocument(); + }); + + test('restart button calls ipc.updater.install', () => { + useUpdaterStore.setState({ state: 'ready', version: '5.0.2' }); + render(); + fireEvent.click(screen.getByRole('button', { name: /restart/i })); + expect(useUpdaterStore.getState().install).toBeDefined(); + }); +}); \ No newline at end of file