11import { assert , describe , it } from "@effect/vitest" ;
2+ import { HostProcessPlatform } from "@t3tools/shared/hostProcess" ;
3+ import * as Cause from "effect/Cause" ;
24import * as Effect from "effect/Effect" ;
5+ import * as Layer from "effect/Layer" ;
36import type * as Electron from "electron" ;
47import { beforeEach , vi } from "vite-plus/test" ;
58
6- const { appFocusMock, getAllWindowsMock } = vi . hoisted ( ( ) => ( {
7- appFocusMock : vi . fn ( ) ,
8- getAllWindowsMock : vi . fn ( ) ,
9- } ) ) ;
9+ const { appFocusMock, browserWindowMock, getAllWindowsMock, getFocusedWindowMock } = vi . hoisted (
10+ ( ) => ( {
11+ appFocusMock : vi . fn ( ) ,
12+ browserWindowMock : vi . fn ( function BrowserWindowMock ( ) { } ) ,
13+ getAllWindowsMock : vi . fn ( ) ,
14+ getFocusedWindowMock : vi . fn ( ) ,
15+ } ) ,
16+ ) ;
1017
1118vi . mock ( "electron" , ( ) => ( {
1219 app : {
1320 focus : appFocusMock ,
1421 } ,
15- BrowserWindow : {
22+ BrowserWindow : Object . assign ( browserWindowMock , {
1623 getAllWindows : getAllWindowsMock ,
17- } ,
24+ getFocusedWindow : getFocusedWindowMock ,
25+ } ) ,
1826} ) ) ;
1927
2028import * as ElectronWindow from "./ElectronWindow.ts" ;
2129
22- function makeBrowserWindow ( input : { readonly destroyed : boolean } ) {
30+ const TestLayer = ElectronWindow . layer . pipe (
31+ Layer . provide ( Layer . succeed ( HostProcessPlatform , "linux" ) ) ,
32+ ) ;
33+
34+ function makeBrowserWindow ( input : { readonly id : number ; readonly destroyed : boolean } ) {
2335 return {
36+ id : input . id ,
2437 isDestroyed : vi . fn ( ( ) => input . destroyed ) ,
2538 } as unknown as Electron . BrowserWindow ;
2639}
2740
2841describe ( "ElectronWindow" , ( ) => {
2942 beforeEach ( ( ) => {
3043 appFocusMock . mockReset ( ) ;
44+ browserWindowMock . mockReset ( ) ;
3145 getAllWindowsMock . mockReset ( ) ;
46+ getFocusedWindowMock . mockReset ( ) ;
3247 } ) ;
3348
49+ it . effect ( "preserves schema-safe creation context and the Electron cause" , ( ) =>
50+ Effect . gen ( function * ( ) {
51+ const cause = new Error ( "native BrowserWindow construction failed" ) ;
52+ browserWindowMock . mockImplementationOnce ( function BrowserWindowFailure ( ) {
53+ throw cause ;
54+ } ) ;
55+ const options = {
56+ title : "T3 Code" ,
57+ width : 1100 ,
58+ height : 780 ,
59+ minWidth : 840 ,
60+ minHeight : 620 ,
61+ show : false ,
62+ modal : false ,
63+ frame : true ,
64+ transparent : false ,
65+ backgroundColor : "#101010" ,
66+ icon : { } as Electron . NativeImage ,
67+ webPreferences : {
68+ preload : "/tmp/preload.js" ,
69+ partition : "persist:t3code-preview-test" ,
70+ sandbox : true ,
71+ contextIsolation : true ,
72+ nodeIntegration : false ,
73+ webviewTag : true ,
74+ spellcheck : true ,
75+ } ,
76+ } satisfies Electron . BrowserWindowConstructorOptions ;
77+ const electronWindow = yield * ElectronWindow . ElectronWindow ;
78+
79+ const error = yield * electronWindow . create ( options ) . pipe ( Effect . flip ) ;
80+
81+ assert . instanceOf ( error , ElectronWindow . ElectronWindowCreateError ) ;
82+ assert . isTrue ( ElectronWindow . isElectronWindowCreateError ( error ) ) ;
83+ assert . deepEqual ( error . options , {
84+ title : "T3 Code" ,
85+ width : 1100 ,
86+ height : 780 ,
87+ minWidth : 840 ,
88+ minHeight : 620 ,
89+ show : false ,
90+ modal : false ,
91+ frame : true ,
92+ transparent : false ,
93+ backgroundColor : "#101010" ,
94+ webPreferences : {
95+ preload : "/tmp/preload.js" ,
96+ partition : "persist:t3code-preview-test" ,
97+ sandbox : true ,
98+ contextIsolation : true ,
99+ nodeIntegration : false ,
100+ webviewTag : true ,
101+ } ,
102+ } ) ;
103+ assert . isFalse ( "icon" in error . options ) ;
104+ assert . isFalse ( "spellcheck" in error . options . webPreferences ) ;
105+ assert . strictEqual ( error . cause , cause ) ;
106+ assert . equal ( error . message , 'Failed to create Electron BrowserWindow "T3 Code" (1100x780).' ) ;
107+ assert . notInclude ( error . message , cause . message ) ;
108+ assert . deepEqual ( browserWindowMock . mock . calls , [ [ options ] ] ) ;
109+ } ) . pipe ( Effect . provide ( TestLayer ) ) ,
110+ ) ;
111+
34112 it . effect ( "skips windows destroyed before appearance sync runs" , ( ) =>
35113 Effect . gen ( function * ( ) {
36- const liveWindow = makeBrowserWindow ( { destroyed : false } ) ;
37- const destroyedWindow = makeBrowserWindow ( { destroyed : true } ) ;
114+ const liveWindow = makeBrowserWindow ( { id : 1 , destroyed : false } ) ;
115+ const destroyedWindow = makeBrowserWindow ( { id : 2 , destroyed : true } ) ;
38116 getAllWindowsMock . mockReturnValue ( [ destroyedWindow , liveWindow ] ) ;
39117
40118 const syncedWindows : Electron . BrowserWindow [ ] = [ ] ;
@@ -46,6 +124,112 @@ describe("ElectronWindow", () => {
46124 ) ;
47125
48126 assert . deepEqual ( syncedWindows , [ liveWindow ] ) ;
49- } ) . pipe ( Effect . provide ( ElectronWindow . layer ) ) ,
127+ } ) . pipe ( Effect . provide ( TestLayer ) ) ,
128+ ) ;
129+
130+ it . effect ( "preserves window enumeration failures as structured defects" , ( ) =>
131+ Effect . gen ( function * ( ) {
132+ const cause = new Error ( "window enumeration failed" ) ;
133+ getAllWindowsMock . mockImplementationOnce ( ( ) => {
134+ throw cause ;
135+ } ) ;
136+
137+ const electronWindow = yield * ElectronWindow . ElectronWindow ;
138+ const exit = yield * Effect . exit ( electronWindow . currentMainOrFirst ) ;
139+
140+ assert . equal ( exit . _tag , "Failure" ) ;
141+ if ( exit . _tag === "Failure" ) {
142+ const error = Cause . squash ( exit . cause ) ;
143+ assert . instanceOf ( error , ElectronWindow . ElectronWindowOperationError ) ;
144+ assert . equal ( error . operation , "list-windows" ) ;
145+ assert . equal ( error . platform , "linux" ) ;
146+ assert . isNull ( error . windowId ) ;
147+ assert . isNull ( error . channel ) ;
148+ assert . strictEqual ( error . cause , cause ) ;
149+ assert . notInclude ( error . message , cause . message ) ;
150+ }
151+ } ) . pipe ( Effect . provide ( TestLayer ) ) ,
152+ ) ;
153+
154+ it . effect ( "preserves reveal failures with the target window" , ( ) =>
155+ Effect . gen ( function * ( ) {
156+ const cause = new Error ( "window restore failed" ) ;
157+ const window = {
158+ id : 41 ,
159+ isDestroyed : vi . fn ( ( ) => false ) ,
160+ isMinimized : vi . fn ( ( ) => true ) ,
161+ restore : vi . fn ( ( ) => {
162+ throw cause ;
163+ } ) ,
164+ } as unknown as Electron . BrowserWindow ;
165+
166+ const electronWindow = yield * ElectronWindow . ElectronWindow ;
167+ const exit = yield * Effect . exit ( electronWindow . reveal ( window ) ) ;
168+
169+ assert . equal ( exit . _tag , "Failure" ) ;
170+ if ( exit . _tag === "Failure" ) {
171+ const error = Cause . squash ( exit . cause ) ;
172+ assert . instanceOf ( error , ElectronWindow . ElectronWindowOperationError ) ;
173+ assert . equal ( error . operation , "reveal-window" ) ;
174+ assert . equal ( error . windowId , 41 ) ;
175+ assert . isNull ( error . channel ) ;
176+ assert . strictEqual ( error . cause , cause ) ;
177+ }
178+ } ) . pipe ( Effect . provide ( TestLayer ) ) ,
179+ ) ;
180+
181+ it . effect ( "preserves message delivery failures with window and channel context" , ( ) =>
182+ Effect . gen ( function * ( ) {
183+ const cause = new Error ( "renderer send failed" ) ;
184+ const window = {
185+ id : 42 ,
186+ isDestroyed : vi . fn ( ( ) => false ) ,
187+ webContents : {
188+ send : vi . fn ( ( ) => {
189+ throw cause ;
190+ } ) ,
191+ } ,
192+ } as unknown as Electron . BrowserWindow ;
193+ getAllWindowsMock . mockReturnValueOnce ( [ window ] ) ;
194+
195+ const electronWindow = yield * ElectronWindow . ElectronWindow ;
196+ const exit = yield * Effect . exit ( electronWindow . sendAll ( "desktop:update" , { ready : true } ) ) ;
197+
198+ assert . equal ( exit . _tag , "Failure" ) ;
199+ if ( exit . _tag === "Failure" ) {
200+ const error = Cause . squash ( exit . cause ) ;
201+ assert . instanceOf ( error , ElectronWindow . ElectronWindowOperationError ) ;
202+ assert . equal ( error . operation , "send-window-message" ) ;
203+ assert . equal ( error . windowId , 42 ) ;
204+ assert . equal ( error . channel , "desktop:update" ) ;
205+ assert . strictEqual ( error . cause , cause ) ;
206+ }
207+ } ) . pipe ( Effect . provide ( TestLayer ) ) ,
208+ ) ;
209+
210+ it . effect ( "preserves destroy failures with the target window" , ( ) =>
211+ Effect . gen ( function * ( ) {
212+ const cause = new Error ( "window destroy failed" ) ;
213+ const window = {
214+ id : 43 ,
215+ destroy : vi . fn ( ( ) => {
216+ throw cause ;
217+ } ) ,
218+ } as unknown as Electron . BrowserWindow ;
219+ getAllWindowsMock . mockReturnValueOnce ( [ window ] ) ;
220+
221+ const electronWindow = yield * ElectronWindow . ElectronWindow ;
222+ const exit = yield * Effect . exit ( electronWindow . destroyAll ) ;
223+
224+ assert . equal ( exit . _tag , "Failure" ) ;
225+ if ( exit . _tag === "Failure" ) {
226+ const error = Cause . squash ( exit . cause ) ;
227+ assert . instanceOf ( error , ElectronWindow . ElectronWindowOperationError ) ;
228+ assert . equal ( error . operation , "destroy-window" ) ;
229+ assert . equal ( error . windowId , 43 ) ;
230+ assert . isNull ( error . channel ) ;
231+ assert . strictEqual ( error . cause , cause ) ;
232+ }
233+ } ) . pipe ( Effect . provide ( TestLayer ) ) ,
50234 ) ;
51235} ) ;
0 commit comments