@@ -2,15 +2,19 @@ import type { Listener } from '../../src/dev/listen'
22
33import { networkInterfaces } from 'node:os'
44
5- import { afterEach , describe , expect , it , vi } from 'vitest'
5+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
66
7- import { copyURL , getNetworkAddresses , listen , resolveOpenCommand } from '../../src/dev/listen'
7+ import { copyURL , getNetworkAddresses , listen , openBrowser , resolveOpenCommand } from '../../src/dev/listen'
88
99const writeText = vi . hoisted ( ( ) => vi . fn ( ) )
1010
1111vi . mock ( 'tinyclip' , ( ) => ( { writeText } ) )
1212
13- const spawn = vi . hoisted ( ( ) => vi . fn ( ( _command : string , _args : string [ ] ) => ( { on : ( ) => ( { unref : ( ) => { } } ) } ) ) )
13+ const spawn = vi . hoisted ( ( ) => vi . fn ( ( _command : string , _args : string [ ] ) => ( {
14+ once : ( ) => { } ,
15+ off : ( ) => { } ,
16+ unref : ( ) => { } ,
17+ } ) ) )
1418
1519vi . mock ( 'node:child_process' , ( ) => ( { spawn } ) )
1620
@@ -21,6 +25,20 @@ vi.mock('node:os', () => ({
2125
2226const mocked = vi . mocked ( networkInterfaces )
2327
28+ const realPlatform = process . platform
29+ const realEnv = { ...process . env }
30+
31+ /** Pretend to run on `platform`, with only the display variables in `env` set. */
32+ function stubEnvironment ( platform : NodeJS . Platform , env : NodeJS . ProcessEnv = { } ) {
33+ Object . defineProperty ( process , 'platform' , { value : platform , configurable : true } )
34+ process . env = { ...realEnv , DISPLAY : undefined , WAYLAND_DISPLAY : undefined , WSL_DISTRO_NAME : undefined , ...env }
35+ }
36+
37+ function restoreEnvironment ( ) {
38+ Object . defineProperty ( process , 'platform' , { value : realPlatform , configurable : true } )
39+ process . env = { ...realEnv }
40+ }
41+
2442describe ( 'getNetworkAddresses' , ( ) => {
2543 it ( 'should return external IPv4 addresses' , ( ) => {
2644 mocked . mockReturnValue ( {
@@ -76,7 +94,11 @@ describe('resolveOpenCommand', () => {
7694describe ( 'listen' , ( ) => {
7795 const listeners : Listener [ ] = [ ]
7896
97+ // `openBrowser` refuses to spawn a launcher without a graphical session.
98+ beforeEach ( ( ) => stubEnvironment ( realPlatform , { DISPLAY : ':0' } ) )
99+
79100 afterEach ( async ( ) => {
101+ restoreEnvironment ( )
80102 await Promise . all ( listeners . splice ( 0 ) . map ( listener => listener . close ( ) ) )
81103 } )
82104
@@ -124,48 +146,62 @@ describe('listen', () => {
124146} )
125147
126148describe ( 'copyURL' , ( ) => {
127- const platform = process . platform
128- const env = { ...process . env }
129-
130149 afterEach ( ( ) => {
131- Object . defineProperty ( process , 'platform' , { value : platform , configurable : true } )
132- process . env = { ...env }
150+ restoreEnvironment ( )
133151 vi . clearAllMocks ( )
134152 } )
135153
136- function stubPlatform ( value : NodeJS . Platform , overrides : NodeJS . ProcessEnv = { } ) {
137- Object . defineProperty ( process , 'platform' , { value, configurable : true } )
138- process . env = { ...env , DISPLAY : undefined , WAYLAND_DISPLAY : undefined , WSL_DISTRO_NAME : undefined , ...overrides }
139- }
140-
141154 it ( 'should skip copying without a display server on linux' , async ( ) => {
142- stubPlatform ( 'linux' )
155+ stubEnvironment ( 'linux' )
143156
144157 await copyURL ( 'http://localhost:3000/' )
145158
146159 expect ( writeText ) . not . toHaveBeenCalled ( )
147160 } )
148161
149162 it ( 'should copy when a display server is available' , async ( ) => {
150- stubPlatform ( 'linux' , { DISPLAY : ':0' } )
163+ stubEnvironment ( 'linux' , { DISPLAY : ':0' } )
151164
152165 await copyURL ( 'http://localhost:3000/' )
153166
154167 expect ( writeText ) . toHaveBeenCalledWith ( 'http://localhost:3000/' )
155168 } )
156169
157170 it ( 'should copy on platforms that do not need a display server' , async ( ) => {
158- stubPlatform ( 'darwin' )
171+ stubEnvironment ( 'darwin' )
159172
160173 await copyURL ( 'http://localhost:3000/' )
161174
162175 expect ( writeText ) . toHaveBeenCalledWith ( 'http://localhost:3000/' )
163176 } )
164177
165178 it ( 'should warn rather than throw when copying fails' , async ( ) => {
166- stubPlatform ( 'darwin' )
179+ stubEnvironment ( 'darwin' )
167180 writeText . mockRejectedValueOnce ( new Error ( 'no clipboard tool found' ) )
168181
169182 await expect ( copyURL ( 'http://localhost:3000/' ) ) . resolves . toBeUndefined ( )
170183 } )
171184} )
185+
186+ describe ( 'openBrowser' , ( ) => {
187+ afterEach ( ( ) => {
188+ restoreEnvironment ( )
189+ vi . clearAllMocks ( )
190+ } )
191+
192+ it ( 'should not spawn a launcher without a display server' , ( ) => {
193+ stubEnvironment ( 'linux' )
194+
195+ openBrowser ( 'http://localhost:3000/' )
196+
197+ expect ( spawn ) . not . toHaveBeenCalled ( )
198+ } )
199+
200+ it ( 'should spawn a launcher when a display server is available' , ( ) => {
201+ stubEnvironment ( 'linux' , { DISPLAY : ':0' } )
202+
203+ openBrowser ( 'http://localhost:3000/' )
204+
205+ expect ( spawn ) . toHaveBeenCalledWith ( 'xdg-open' , [ 'http://localhost:3000/' ] , expect . anything ( ) )
206+ } )
207+ } )
0 commit comments