11import { afterEach , describe , expect , mock , test } from "bun:test" ;
2- import { cleanup , fireEvent , render } from "@testing-library/react" ;
2+ import { cleanup , fireEvent , render , waitFor } from "@testing-library/react" ;
33import type { UpdateStatus } from "../../shared/rpc-types.ts" ;
44import { setupMockRPC } from "../test-helpers/mock-rpc.ts" ;
55import { UpdateNotification } from "./UpdateNotification.tsx" ;
66
77const VERSION_READY_PATTERN = / v 2 \. 0 \. 0 i s r e a d y / ;
8+ const EXTRACT_FAILED_PATTERN = / E x t r a c t f a i l e d / ;
9+ const UPDATE_FAILED_PATTERN = / U p d a t e f a i l e d / ;
10+ const NETWORK_TIMEOUT_PATTERN = / N e t w o r k t i m e o u t / ;
811
912function defaultProps ( ) {
1013 return {
@@ -68,6 +71,44 @@ describe("UpdateNotification", () => {
6871 expect ( applyUpdate ) . toHaveBeenCalled ( ) ;
6972 } ) ;
7073
74+ test ( "shows Restarting text and disables button while applying" , ( ) => {
75+ const applyUpdate = mock ( ( ) => new Promise < { ok : boolean } > ( ( ) => { } ) ) ; // never resolves
76+ setupMockRPC ( { applyUpdate } ) ;
77+ const props = defaultProps ( ) ;
78+ props . status = { status : "ready" , currentVersion : "1.0.0" , latestVersion : "2.0.0" } ;
79+ const { getByRole } = render ( < UpdateNotification { ...props } /> ) ;
80+ fireEvent . click ( getByRole ( "button" , { name : "Restart to update" } ) ) ;
81+ const button = getByRole ( "button" , { name : "Restarting…" } ) ;
82+ expect ( button ) . toBeDefined ( ) ;
83+ expect ( button . hasAttribute ( "disabled" ) ) . toBe ( true ) ;
84+ } ) ;
85+
86+ test ( "shows error when applyUpdate returns ok: false" , async ( ) => {
87+ const applyUpdate = mock ( ( ) => Promise . resolve ( { ok : false , error : "Extract failed" } ) ) ;
88+ setupMockRPC ( { applyUpdate } ) ;
89+ const props = defaultProps ( ) ;
90+ props . status = { status : "ready" , currentVersion : "1.0.0" , latestVersion : "2.0.0" } ;
91+ const { getByRole, getByText } = render ( < UpdateNotification { ...props } /> ) ;
92+ fireEvent . click ( getByRole ( "button" , { name : "Restart to update" } ) ) ;
93+ await waitFor ( ( ) => {
94+ expect ( getByText ( EXTRACT_FAILED_PATTERN ) ) . toBeDefined ( ) ;
95+ } ) ;
96+ // Button should be re-enabled after error
97+ expect ( getByRole ( "button" , { name : "Restart to update" } ) . hasAttribute ( "disabled" ) ) . toBe ( false ) ;
98+ } ) ;
99+
100+ test ( "shows error when applyUpdate rejects" , async ( ) => {
101+ const applyUpdate = mock ( ( ) => Promise . reject ( new Error ( "RPC error" ) ) ) ;
102+ setupMockRPC ( { applyUpdate } ) ;
103+ const props = defaultProps ( ) ;
104+ props . status = { status : "ready" , currentVersion : "1.0.0" , latestVersion : "2.0.0" } ;
105+ const { getByRole, getByText } = render ( < UpdateNotification { ...props } /> ) ;
106+ fireEvent . click ( getByRole ( "button" , { name : "Restart to update" } ) ) ;
107+ await waitFor ( ( ) => {
108+ expect ( getByText ( UPDATE_FAILED_PATTERN ) ) . toBeDefined ( ) ;
109+ } ) ;
110+ } ) ;
111+
71112 test ( "shows up-to-date message for manual check result" , ( ) => {
72113 const props = defaultProps ( ) ;
73114 props . manualCheckResult = { status : "up-to-date" , currentVersion : "1.0.0" } ;
@@ -83,7 +124,7 @@ describe("UpdateNotification", () => {
83124 error : "Network timeout" ,
84125 } ;
85126 const { getByText } = render ( < UpdateNotification { ...props } /> ) ;
86- expect ( getByText ( / N e t w o r k t i m e o u t / ) ) . toBeDefined ( ) ;
127+ expect ( getByText ( NETWORK_TIMEOUT_PATTERN ) ) . toBeDefined ( ) ;
87128 } ) ;
88129
89130 test ( "calls onDismissManualCheck when dismissing manual check result" , ( ) => {
0 commit comments