1- import { beforeEach , describe , expect , it , vi } from "vitest" ;
1+ import { afterEach , beforeEach , describe , expect , it , vi } from "vitest" ;
22import { AppLifecycleService } from "./service.js" ;
33
4- const { mockApp, mockContainer, mockTrackAppEvent, mockShutdownPostHog } =
5- vi . hoisted ( ( ) => ( {
6- mockApp : {
7- exit : vi . fn ( ) ,
8- } ,
9- mockContainer : {
10- unbindAll : vi . fn ( ( ) => Promise . resolve ( ) ) ,
11- } ,
12- mockTrackAppEvent : vi . fn ( ) ,
13- mockShutdownPostHog : vi . fn ( ( ) => Promise . resolve ( ) ) ,
14- } ) ) ;
4+ const {
5+ mockApp,
6+ mockContainer,
7+ mockTrackAppEvent,
8+ mockShutdownPostHog,
9+ mockProcessExit,
10+ } = vi . hoisted ( ( ) => ( {
11+ mockApp : {
12+ exit : vi . fn ( ) ,
13+ } ,
14+ mockContainer : {
15+ unbindAll : vi . fn ( ( ) => Promise . resolve ( ) ) ,
16+ } ,
17+ mockTrackAppEvent : vi . fn ( ) ,
18+ mockShutdownPostHog : vi . fn ( ( ) => Promise . resolve ( ) ) ,
19+ mockProcessExit : vi . fn ( ) as unknown as ( code ?: number ) => never ,
20+ } ) ) ;
1521
1622vi . mock ( "electron" , ( ) => ( {
1723 app : mockApp ,
@@ -45,12 +51,20 @@ vi.mock("../../../types/analytics.js", () => ({
4551
4652describe ( "AppLifecycleService" , ( ) => {
4753 let service : AppLifecycleService ;
54+ const originalProcessExit = process . exit ;
4855
4956 beforeEach ( ( ) => {
5057 vi . clearAllMocks ( ) ;
58+ vi . useFakeTimers ( ) ;
59+ process . exit = mockProcessExit ;
5160 service = new AppLifecycleService ( ) ;
5261 } ) ;
5362
63+ afterEach ( ( ) => {
64+ vi . useRealTimers ( ) ;
65+ process . exit = originalProcessExit ;
66+ } ) ;
67+
5468 describe ( "isQuittingForUpdate" , ( ) => {
5569 it ( "returns false by default" , ( ) => {
5670 expect ( service . isQuittingForUpdate ) . toBe ( false ) ;
@@ -62,19 +76,38 @@ describe("AppLifecycleService", () => {
6276 } ) ;
6377 } ) ;
6478
79+ describe ( "isShuttingDown" , ( ) => {
80+ it ( "returns false by default" , ( ) => {
81+ expect ( service . isShuttingDown ) . toBe ( false ) ;
82+ } ) ;
83+
84+ it ( "returns true after shutdown is called" , async ( ) => {
85+ const shutdownPromise = service . shutdown ( ) ;
86+ expect ( service . isShuttingDown ) . toBe ( true ) ;
87+ await vi . runAllTimersAsync ( ) ;
88+ await shutdownPromise ;
89+ } ) ;
90+ } ) ;
91+
6592 describe ( "shutdown" , ( ) => {
6693 it ( "unbinds all container services" , async ( ) => {
67- await service . shutdown ( ) ;
94+ const promise = service . shutdown ( ) ;
95+ await vi . runAllTimersAsync ( ) ;
96+ await promise ;
6897 expect ( mockContainer . unbindAll ) . toHaveBeenCalled ( ) ;
6998 } ) ;
7099
71100 it ( "tracks app quit event" , async ( ) => {
72- await service . shutdown ( ) ;
101+ const promise = service . shutdown ( ) ;
102+ await vi . runAllTimersAsync ( ) ;
103+ await promise ;
73104 expect ( mockTrackAppEvent ) . toHaveBeenCalledWith ( "app_quit" ) ;
74105 } ) ;
75106
76107 it ( "shuts down PostHog" , async ( ) => {
77- await service . shutdown ( ) ;
108+ const promise = service . shutdown ( ) ;
109+ await vi . runAllTimersAsync ( ) ;
110+ await promise ;
78111 expect ( mockShutdownPostHog ) . toHaveBeenCalled ( ) ;
79112 } ) ;
80113
@@ -91,7 +124,9 @@ describe("AppLifecycleService", () => {
91124 callOrder . push ( "shutdownPostHog" ) ;
92125 } ) ;
93126
94- await service . shutdown ( ) ;
127+ const promise = service . shutdown ( ) ;
128+ await vi . runAllTimersAsync ( ) ;
129+ await promise ;
95130
96131 expect ( callOrder ) . toEqual ( [
97132 "unbindAll" ,
@@ -103,7 +138,9 @@ describe("AppLifecycleService", () => {
103138 it ( "continues shutdown if container unbind fails" , async ( ) => {
104139 mockContainer . unbindAll . mockRejectedValue ( new Error ( "unbind failed" ) ) ;
105140
106- await service . shutdown ( ) ;
141+ const promise = service . shutdown ( ) ;
142+ await vi . runAllTimersAsync ( ) ;
143+ await promise ;
107144
108145 expect ( mockTrackAppEvent ) . toHaveBeenCalled ( ) ;
109146 expect ( mockShutdownPostHog ) . toHaveBeenCalled ( ) ;
@@ -112,7 +149,28 @@ describe("AppLifecycleService", () => {
112149 it ( "continues shutdown if PostHog shutdown fails" , async ( ) => {
113150 mockShutdownPostHog . mockRejectedValue ( new Error ( "posthog failed" ) ) ;
114151
115- await expect ( service . shutdown ( ) ) . resolves . toBeUndefined ( ) ;
152+ const promise = service . shutdown ( ) ;
153+ await vi . runAllTimersAsync ( ) ;
154+ await expect ( promise ) . resolves . toBeUndefined ( ) ;
155+ } ) ;
156+
157+ it ( "force-exits on re-entrant shutdown call" , async ( ) => {
158+ const promise = service . shutdown ( ) ;
159+ service . shutdown ( ) ;
160+ expect ( mockProcessExit ) . toHaveBeenCalledWith ( 1 ) ;
161+ await vi . runAllTimersAsync ( ) ;
162+ await promise ;
163+ } ) ;
164+
165+ it ( "force-exits when shutdown times out" , async ( ) => {
166+ mockContainer . unbindAll . mockReturnValue ( new Promise ( ( ) => { } ) ) ;
167+
168+ const promise = service . shutdown ( ) ;
169+
170+ await vi . advanceTimersByTimeAsync ( 3000 ) ;
171+ await promise ;
172+
173+ expect ( mockProcessExit ) . toHaveBeenCalledWith ( 1 ) ;
116174 } ) ;
117175 } ) ;
118176
@@ -127,14 +185,18 @@ describe("AppLifecycleService", () => {
127185 callOrder . push ( "exit" ) ;
128186 } ) ;
129187
130- await service . shutdownAndExit ( ) ;
188+ const promise = service . shutdownAndExit ( ) ;
189+ await vi . runAllTimersAsync ( ) ;
190+ await promise ;
131191
132192 expect ( callOrder [ 0 ] ) . toBe ( "unbindAll" ) ;
133193 expect ( callOrder [ callOrder . length - 1 ] ) . toBe ( "exit" ) ;
134194 } ) ;
135195
136196 it ( "exits with code 0" , async ( ) => {
137- await service . shutdownAndExit ( ) ;
197+ const promise = service . shutdownAndExit ( ) ;
198+ await vi . runAllTimersAsync ( ) ;
199+ await promise ;
138200 expect ( mockApp . exit ) . toHaveBeenCalledWith ( 0 ) ;
139201 } ) ;
140202 } ) ;
0 commit comments