1
1
import type { ChildProcess } from 'node:child_process'
2
+ import type { TestOptions } from 'vitest'
2
3
import { spawn , spawnSync } from 'node:child_process'
3
4
import { cpSync , rmSync } from 'node:fs'
4
5
import { rm } from 'node:fs/promises'
5
6
import { join , resolve } from 'node:path'
6
- import { fileURLToPath } from 'node:url'
7
7
8
+ import { fileURLToPath } from 'node:url'
8
9
import { getPort , waitForPort } from 'get-port-please'
9
- import { isCI , isLinux , isWindows } from 'std-env'
10
+ import { isCI , isLinux , isMacOS , isWindows } from 'std-env'
10
11
import { WebSocket } from 'undici'
11
- import { afterAll , describe , expect , it , vi } from 'vitest'
12
+ import { it as _it , afterAll , describe , expect , vi } from 'vitest'
12
13
13
14
const playgroundDir = fileURLToPath ( new URL ( '../../../../playground' , import . meta. url ) )
14
15
const nuxiPath = join ( fileURLToPath ( new URL ( '../..' , import . meta. url ) ) , 'bin/nuxi.mjs' )
15
16
16
- const hasBun = spawnSync ( 'bun' , [ '--version' ] , { stdio : 'ignore' } ) . status === 0
17
- const hasDeno = spawnSync ( 'deno' , [ '--version' ] , { stdio : 'ignore' } ) . status === 0
17
+ const runtimes = [ 'bun' , 'node' , 'deno' ] as const
18
18
19
- describe . sequential . each ( [ 'bun' , 'node' , 'deno' ] as const ) ( 'dev server (%s)' , ( runtime ) => {
20
- let server : DevServerInstance
19
+ const platform = {
20
+ windows : isWindows ,
21
+ linux : isLinux ,
22
+ macos : isMacOS ,
23
+ }
21
24
22
- if ( runtime === 'bun' && ! hasBun && ! isCI ) {
23
- console . warn ( 'Not testing locally with bun as it is not installed.' )
24
- it . skip ( 'should pass with bun' )
25
- return
25
+ const runtime = {
26
+ bun : spawnSync ( 'bun' , [ '--version' ] , { stdio : 'ignore' } ) . status === 0 ,
27
+ deno : spawnSync ( 'deno' , [ '--version' ] , { stdio : 'ignore' } ) . status === 0 ,
28
+ node : true ,
29
+ }
30
+
31
+ type SupportStatus = boolean | {
32
+ start : boolean
33
+ fetching : boolean
34
+ websockets : boolean
35
+ websocketClose : boolean
36
+ }
37
+
38
+ const supports : Record < typeof runtimes [ number ] , SupportStatus > = {
39
+ node : true ,
40
+ bun : {
41
+ start : true ,
42
+ fetching : ! platform . windows ,
43
+ websockets : false ,
44
+ websocketClose : false ,
45
+ } ,
46
+ deno : {
47
+ start : ! platform . windows ,
48
+ fetching : ! platform . windows ,
49
+ websockets : platform . linux && ! platform . windows ,
50
+ websocketClose : false ,
51
+ } ,
52
+ }
53
+
54
+ function createIt ( status : SupportStatus ) {
55
+ function it ( description : string , fn : ( ) => Promise < void > ) : void
56
+ function it ( description : string , options : TestOptions , fn : ( ) => Promise < void > ) : void
57
+ function it ( description : string , _options : TestOptions | ( ( ) => Promise < void > ) , _fn ?: ( ) => Promise < void > ) : void {
58
+ const fn = typeof _options === 'function' ? _options : _fn !
59
+ const options = typeof _options === 'function' ? { } : _options
60
+
61
+ if ( status === false ) {
62
+ return _it . fails ( description , options , fn )
63
+ }
64
+ if ( status === true ) {
65
+ return _it ( description , options , fn )
66
+ }
67
+ if ( description . includes ( 'should start dev server' ) ) {
68
+ if ( ! status . start ) {
69
+ return _it . fails ( description , options , fn )
70
+ }
71
+ return _it ( description , options , fn )
72
+ }
73
+ if ( ! status . start ) {
74
+ return _it . todo ( description )
75
+ }
76
+ if ( description . includes ( 'websocket connection close gracefully' ) ) {
77
+ if ( ! status . websocketClose ) {
78
+ return _it . fails ( description , options , fn )
79
+ }
80
+ return _it ( description , options , fn )
81
+ }
82
+ if ( description . includes ( 'websocket' ) ) {
83
+ if ( ! status . websockets ) {
84
+ return _it . fails ( description , options , fn )
85
+ }
86
+ return _it ( description , options , fn )
87
+ }
88
+ // Handle fetching tests (all tests that are not websocket or start tests)
89
+ if ( ! status . fetching ) {
90
+ return _it . fails ( description , options , fn )
91
+ }
92
+ return _it ( description , options , fn )
26
93
}
27
94
28
- if ( runtime === 'deno' && ! hasDeno && ! isCI ) {
29
- console . warn ( 'Not testing locally with deno as it is not installed.' )
30
- it . skip ( 'should pass with deno' )
95
+ return it
96
+ }
97
+
98
+ describe . sequential . each ( runtimes ) ( 'dev server (%s)' , ( runtimeName ) => {
99
+ let server : DevServerInstance
100
+
101
+ if ( ! isCI && ! runtime [ runtimeName ] ) {
102
+ console . warn ( `Not testing locally with ${ runtimeName } as it is not installed.` )
103
+ _it . skip ( `should pass with ${ runtimeName } ` )
31
104
return
32
105
}
33
106
34
- const cwd = resolve ( playgroundDir , `../playground-${ runtime } ` )
107
+ const cwd = resolve ( playgroundDir , `../playground-${ runtimeName } ` )
35
108
36
109
afterAll ( async ( ) => {
37
110
await server ?. close ( )
38
111
await rm ( cwd , { recursive : true , force : true } ) . catch ( ( ) => null )
39
112
} )
40
113
41
- const isWindowsNonDeno = isWindows && runtime === 'deno'
42
- const assertNonDeno = isWindowsNonDeno ? it . fails : it
43
- assertNonDeno ( 'should start dev server' , { timeout : isCI ? 60_000 : 30_000 } , async ( ) => {
114
+ const it = createIt ( supports [ runtimeName ] )
115
+
116
+ it ( 'should start dev server' , { timeout : isCI ? 60_000 : 30_000 } , async ( ) => {
44
117
rmSync ( cwd , { recursive : true , force : true } )
45
118
cpSync ( playgroundDir , cwd , {
46
119
recursive : true ,
47
120
filter : src => ! src . includes ( '.nuxt' ) && ! src . includes ( '.output' ) ,
48
121
} )
49
- server = await startDevServer ( { cwd, runtime } )
122
+ server = await startDevServer ( { cwd, runtime : runtimeName } )
50
123
} )
51
124
52
- if ( isWindowsNonDeno ) {
53
- it . todo ( 'should run rest of tests on windows' )
54
- return
55
- }
56
-
57
- const failsOnlyWithWindowsBun = runtime === 'bun' && isWindows ? it . fails : it
58
- failsOnlyWithWindowsBun ( 'should serve the main page' , async ( ) => {
125
+ it ( 'should serve the main page' , async ( ) => {
59
126
const response = await fetch ( server . url )
60
127
expect ( response . status ) . toBe ( 200 )
61
128
@@ -64,18 +131,18 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
64
131
expect ( html ) . toContain ( '<!DOCTYPE html>' )
65
132
} )
66
133
67
- failsOnlyWithWindowsBun ( 'should serve static assets' , async ( ) => {
134
+ it ( 'should serve static assets' , async ( ) => {
68
135
const response = await fetch ( `${ server . url } /favicon.ico` )
69
136
expect ( response . status ) . toBe ( 200 )
70
137
expect ( response . headers . get ( 'content-type' ) ) . toContain ( 'image/' )
71
138
} )
72
139
73
- failsOnlyWithWindowsBun ( 'should handle API routes' , async ( ) => {
140
+ it ( 'should handle API routes' , async ( ) => {
74
141
const response = await fetch ( `${ server . url } /api/hello` )
75
142
expect ( response . status ) . toBe ( 200 )
76
143
} )
77
144
78
- failsOnlyWithWindowsBun ( 'should handle POST requests' , async ( ) => {
145
+ it ( 'should handle POST requests' , async ( ) => {
79
146
const response = await fetch ( `${ server . url } /api/echo` , {
80
147
method : 'POST' ,
81
148
headers : { 'Content-Type' : 'application/json' } ,
@@ -85,7 +152,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
85
152
expect ( response . status ) . toBe ( 200 )
86
153
} )
87
154
88
- failsOnlyWithWindowsBun ( 'should preserve request headers' , async ( ) => {
155
+ it ( 'should preserve request headers' , async ( ) => {
89
156
const headers = {
90
157
'X-Custom-Header' : 'test-value' ,
91
158
'User-Agent' : 'vitest' ,
@@ -102,7 +169,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
102
169
expect ( res . status ) . toBe ( 200 )
103
170
} )
104
171
105
- failsOnlyWithWindowsBun ( 'should handle concurrent requests' , async ( ) => {
172
+ it ( 'should handle concurrent requests' , async ( ) => {
106
173
const requests = Array . from ( { length : 5 } , ( ) => fetch ( server . url ) )
107
174
const responses = await Promise . all ( requests )
108
175
@@ -112,7 +179,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
112
179
}
113
180
} )
114
181
115
- failsOnlyWithWindowsBun ( 'should handle large request payloads' , async ( ) => {
182
+ it ( 'should handle large request payloads' , async ( ) => {
116
183
const largePayload = { data : 'x' . repeat ( 10_000 ) }
117
184
const response = await fetch ( `${ server . url } /api/echo` , {
118
185
method : 'POST' ,
@@ -125,7 +192,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
125
192
expect ( result . echoed . data ) . toBe ( largePayload . data )
126
193
} )
127
194
128
- failsOnlyWithWindowsBun ( 'should handle different HTTP methods' , async ( ) => {
195
+ it ( 'should handle different HTTP methods' , async ( ) => {
129
196
const methods = [ 'GET' , 'POST' , 'PUT' , 'DELETE' ]
130
197
131
198
for ( const method of methods ) {
@@ -137,9 +204,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
137
204
}
138
205
} )
139
206
140
- // TODO: fix websockets in bun + deno
141
- const failsWithBunOrNonLinuxDeno = runtime === 'bun' || ( runtime === 'deno' && ! isLinux ) ? it . fails : it
142
- failsWithBunOrNonLinuxDeno ( 'should establish websocket connection and handle ping/pong' , async ( ) => {
207
+ it ( 'should establish websocket connection and handle ping/pong' , { timeout : 20_000 } , async ( ) => {
143
208
const wsUrl = `${ server . url . replace ( 'http' , 'ws' ) } /_ws`
144
209
145
210
// Create a promise that resolves when the websocket test is complete
@@ -188,10 +253,9 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
188
253
} )
189
254
190
255
await wsTest
191
- } , 20_000 )
256
+ } )
192
257
193
- // TODO: fix websockets in bun + deno
194
- failsWithBunOrNonLinuxDeno ( 'should handle multiple concurrent websocket connections' , async ( ) => {
258
+ it ( 'should handle multiple concurrent websocket connections' , { timeout : 20_000 } , async ( ) => {
195
259
const wsUrl = `${ server . url . replace ( 'http' , 'ws' ) } /_ws`
196
260
const connectionCount = 3
197
261
@@ -225,10 +289,9 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
225
289
} )
226
290
227
291
await Promise . all ( connectionPromises )
228
- } , 15000 )
292
+ } )
229
293
230
- const failsWithBunOrDeno = runtime === 'bun' || runtime === 'deno' ? it . fails : it
231
- failsWithBunOrDeno ( 'should handle websocket connection close gracefully' , async ( ) => {
294
+ it ( 'should handle websocket connection close gracefully' , { timeout : 10_000 } , async ( ) => {
232
295
const wsUrl = `${ server . url . replace ( 'http' , 'ws' ) } /_ws`
233
296
234
297
const wsTest = new Promise < void > ( ( resolve , reject ) => {
@@ -266,7 +329,7 @@ describe.sequential.each(['bun', 'node', 'deno'] as const)('dev server (%s)', (r
266
329
} )
267
330
268
331
await wsTest
269
- } , 10_000 )
332
+ } )
270
333
} )
271
334
272
335
interface DevServerInstance {
0 commit comments