1
+ import { type Readable } from 'node:stream' ;
1
2
import path from 'path' ;
2
3
import { setTimeout } from 'timers/promises' ;
4
+ import { on } from 'events' ;
3
5
import { testSuite , expect } from 'manten' ;
4
6
import { createFixture } from 'fs-fixture' ;
5
7
import { tsx } from '../utils/tsx' ;
6
8
9
+ type MaybePromise < T > = T | Promise < T > ;
10
+ const interact = async (
11
+ stdout : Readable ,
12
+ actions : ( ( data : string ) => MaybePromise < boolean | void > ) [ ] ,
13
+ ) => {
14
+ let currentAction = actions . shift ( ) ;
15
+
16
+ const buffers : Buffer [ ] = [ ] ;
17
+ while ( currentAction ) {
18
+ for await ( const [ chunk ] of on ( stdout , 'data' ) ) {
19
+ buffers . push ( chunk ) ;
20
+ if ( await currentAction ( chunk . toString ( ) ) ) {
21
+ currentAction = actions . shift ( ) ;
22
+ break ;
23
+ }
24
+ }
25
+ }
26
+
27
+ return Buffer . concat ( buffers ) . toString ( ) ;
28
+ } ;
29
+
7
30
export default testSuite ( async ( { describe } , fixturePath : string ) => {
8
31
describe ( 'watch' , ( { test, describe } ) => {
9
32
test ( 'require file path' , async ( ) => {
@@ -15,10 +38,17 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
15
38
} ) ;
16
39
17
40
test ( 'watch files for changes' , async ( { onTestFinish } ) => {
41
+ let initialValue = Date . now ( ) ;
18
42
const fixture = await createFixture ( {
19
- 'index.js' : 'console.log(1)' ,
43
+ 'package.json' : JSON . stringify ( {
44
+ type : 'module' ,
45
+ } ) ,
46
+ 'index.js' : `
47
+ import { value } from './value.js';
48
+ console.log(value);
49
+ ` ,
50
+ 'value.js' : `export const value = ${ initialValue } ;` ,
20
51
} ) ;
21
-
22
52
onTestFinish ( async ( ) => await fixture . rm ( ) ) ;
23
53
24
54
const tsxProcess = tsx ( {
@@ -28,21 +58,23 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
28
58
] ,
29
59
} ) ;
30
60
31
- await new Promise < void > ( ( resolve ) => {
32
- async function onStdOut ( data : Buffer ) {
33
- const chunkString = data . toString ( ) ;
61
+ await interact (
62
+ tsxProcess . stdout ! ,
63
+ [
64
+ async ( data ) => {
65
+ if ( data . includes ( `${ initialValue } \n` ) ) {
66
+ initialValue = Date . now ( ) ;
67
+ await fixture . writeFile ( 'value.js' , `export const value = ${ initialValue } ;` ) ;
68
+ return true ;
69
+ }
70
+ } ,
71
+ data => data . includes ( `${ initialValue } \n` ) ,
72
+ ] ,
73
+ ) ;
34
74
35
- if ( chunkString . match ( '1\n' ) ) {
36
- await fixture . writeFile ( 'index.js' , 'console.log(2)' ) ;
37
- } else if ( chunkString . match ( '2\n' ) ) {
38
- tsxProcess . kill ( ) ;
39
- resolve ( ) ;
40
- }
41
- }
75
+ tsxProcess . kill ( ) ;
42
76
43
- tsxProcess . stdout ! . on ( 'data' , onStdOut ) ;
44
- tsxProcess . stderr ! . on ( 'data' , onStdOut ) ;
45
- } ) ;
77
+ await tsxProcess ;
46
78
} , 10_000 ) ;
47
79
48
80
test ( 'suppresses warnings & clear screen' , async ( ) => {
@@ -53,32 +85,24 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
53
85
] ,
54
86
} ) ;
55
87
56
- const stdout = await new Promise < string > ( ( resolve ) => {
57
- let aggregateStdout = '' ;
58
- let hitEnter = false ;
59
-
60
- function onStdOut ( data : Buffer ) {
61
- const chunkString = data . toString ( ) ;
62
- // console.log({ chunkString });
63
-
64
- aggregateStdout += chunkString ;
65
-
66
- if ( chunkString . match ( 'log-argv.ts' ) ) {
67
- if ( hitEnter ) {
68
- tsxProcess . kill ( ) ;
69
- resolve ( aggregateStdout ) ;
70
- } else {
71
- hitEnter = true ;
88
+ await interact (
89
+ tsxProcess . stdout ! ,
90
+ [
91
+ ( data ) => {
92
+ if ( data . includes ( 'log-argv.ts' ) ) {
72
93
tsxProcess . stdin ?. write ( 'enter' ) ;
94
+ return true ;
73
95
}
74
- }
75
- }
76
- tsxProcess . stdout ! . on ( 'data' , onStdOut ) ;
77
- tsxProcess . stderr ! . on ( 'data' , onStdOut ) ;
78
- } ) ;
96
+ } ,
97
+ data => data . includes ( 'log-argv.ts' ) ,
98
+ ] ,
99
+ ) ;
100
+
101
+ tsxProcess . kill ( ) ;
79
102
80
- expect ( stdout ) . not . toMatch ( 'Warning' ) ;
81
- expect ( stdout ) . toMatch ( '\u001Bc' ) ;
103
+ const { all } = await tsxProcess ;
104
+ expect ( all ) . not . toMatch ( 'Warning' ) ;
105
+ expect ( all ) . toMatch ( '\u001Bc' ) ;
82
106
} , 10_000 ) ;
83
107
84
108
test ( 'passes flags' , async ( ) => {
@@ -90,20 +114,15 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
90
114
] ,
91
115
} ) ;
92
116
93
- const stdout = await new Promise < string > ( ( resolve ) => {
94
- tsxProcess . stdout ! . on ( 'data' , ( chunk ) => {
95
- const chunkString = chunk . toString ( ) ;
96
- if ( chunkString . startsWith ( '[' ) ) {
97
- resolve ( chunkString ) ;
98
- }
99
- } ) ;
100
- } ) ;
117
+ await interact (
118
+ tsxProcess . stdout ! ,
119
+ [ data => data . startsWith ( '["' ) ] ,
120
+ ) ;
101
121
102
122
tsxProcess . kill ( ) ;
103
123
104
- expect ( stdout ) . toMatch ( '"--some-flag"' ) ;
105
-
106
- await tsxProcess ;
124
+ const { all } = await tsxProcess ;
125
+ expect ( all ) . toMatch ( '"--some-flag"' ) ;
107
126
} , 10_000 ) ;
108
127
109
128
test ( 'wait for exit' , async ( { onTestFinish } ) => {
@@ -130,42 +149,23 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
130
149
] ,
131
150
} ) ;
132
151
133
- const stdout = await new Promise < string > ( ( resolve ) => {
134
- const buffers : Buffer [ ] = [ ] ;
135
- const waitingOn : [ string , ( ( ) => void ) ] [ ] = [
136
- [ 'start\n' , ( ) => {
137
- tsxProcess . stdin ?. write ( 'enter' ) ;
138
- } ] ,
139
- [ 'end\n' , ( ) => { } ] ,
140
- ] ;
141
-
142
- let currentWaitingOn = waitingOn . shift ( ) ;
143
- async function onStdOut ( data : Buffer ) {
144
- buffers . push ( data ) ;
145
- const chunkString = data . toString ( ) ;
146
-
147
- if ( currentWaitingOn ) {
148
- const [ expected , callback ] = currentWaitingOn ! ;
149
-
150
- // eslint-disable-next-line unicorn/prefer-regexp-test
151
- if ( chunkString . match ( expected ) ) {
152
- callback ( ) ;
153
- currentWaitingOn = waitingOn . shift ( ) ;
154
- if ( ! currentWaitingOn ) {
155
- tsxProcess . kill ( ) ;
156
- resolve ( Buffer . concat ( buffers ) . toString ( ) ) ;
157
- }
152
+ await interact (
153
+ tsxProcess . stdout ! ,
154
+ [
155
+ ( data ) => {
156
+ if ( data . includes ( 'start\n' ) ) {
157
+ tsxProcess . stdin ?. write ( 'enter' ) ;
158
+ return true ;
158
159
}
159
- }
160
- }
161
-
162
- tsxProcess . stdout ! . on ( 'data' , onStdOut ) ;
163
- tsxProcess . stderr ! . on ( 'data' , onStdOut ) ;
164
- } ) ;
160
+ } ,
161
+ data => data . includes ( 'end\n' ) ,
162
+ ] ,
163
+ ) ;
165
164
166
- expect ( stdout ) . toMatch ( / s t a r t [ \s \S ] + e n d / ) ;
165
+ tsxProcess . kill ( ) ;
167
166
168
- await tsxProcess ;
167
+ const { all } = await tsxProcess ;
168
+ expect ( all ) . toMatch ( / s t a r t [ \s \S ] + e n d / ) ;
169
169
} , 10_000 ) ;
170
170
171
171
describe ( 'help' , ( { test } ) => {
@@ -188,20 +188,15 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
188
188
] ,
189
189
} ) ;
190
190
191
- const stdout = await new Promise < string > ( ( resolve ) => {
192
- tsxProcess . stdout ! . on ( 'data' , ( chunk ) => {
193
- const chunkString = chunk . toString ( ) ;
194
- if ( chunkString . startsWith ( '[' ) ) {
195
- resolve ( chunkString ) ;
196
- }
197
- } ) ;
198
- } ) ;
191
+ await interact (
192
+ tsxProcess . stdout ! ,
193
+ [ data => data . startsWith ( '["' ) ] ,
194
+ ) ;
199
195
200
196
tsxProcess . kill ( ) ;
201
197
202
- expect ( stdout ) . toMatch ( '"--help"' ) ;
203
-
204
- await tsxProcess ;
198
+ const { all } = await tsxProcess ;
199
+ expect ( all ) . toMatch ( '"--help"' ) ;
205
200
} , 5000 ) ;
206
201
} ) ;
207
202
@@ -235,28 +230,31 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
235
230
] ,
236
231
} ) ;
237
232
238
- tsxProcess . stdout ! . on ( 'data' , async ( data : Buffer ) => {
239
- const chunkString = data . toString ( ) ;
240
- if ( chunkString === `${ value } ${ value } \n` ) {
241
- value = Date . now ( ) ;
242
- await Promise . all ( [
243
- fixture . writeFile ( fileA , `export default ${ value } ` ) ,
244
- fixture . writeFile ( fileB , `export default ${ value } ` ) ,
245
- ] ) ;
246
-
247
- await setTimeout ( 500 ) ;
248
- await fixture . writeFile ( entryFile , 'console.log("TERMINATE")' ) ;
249
- }
250
-
251
- if ( chunkString === 'TERMINATE\n' ) {
252
- tsxProcess . kill ( ) ;
253
- }
254
- } ) ;
233
+ await interact (
234
+ tsxProcess . stdout ! ,
235
+ [
236
+ async ( data ) => {
237
+ if ( data === `${ value } ${ value } \n` ) {
238
+ value = Date . now ( ) ;
239
+ await Promise . all ( [
240
+ fixture . writeFile ( fileA , `export default ${ value } ` ) ,
241
+ fixture . writeFile ( fileB , `export default ${ value } ` ) ,
242
+ ] ) ;
243
+
244
+ await setTimeout ( 500 ) ;
245
+ await fixture . writeFile ( entryFile , 'console.log("TERMINATE")' ) ;
246
+ return true ;
247
+ }
248
+ } ,
249
+ data => data === 'TERMINATE\n' ,
250
+ ] ,
251
+ ) ;
255
252
256
- const tsxProcessResolved = await tsxProcess ;
253
+ tsxProcess . kill ( ) ;
257
254
258
- expect ( tsxProcessResolved . stdout ) . not . toMatch ( `${ value } ${ value } ` ) ;
259
- expect ( tsxProcessResolved . stderr ) . toBe ( '' ) ;
255
+ const { all, stderr } = await tsxProcess ;
256
+ expect ( all ) . not . toMatch ( `${ value } ${ value } ` ) ;
257
+ expect ( stderr ) . toBe ( '' ) ;
260
258
} , 10_000 ) ;
261
259
} ) ;
262
260
} ) ;
0 commit comments