1
- import { describe , it , expect , vi } from "vitest" ;
1
+ import { describe , it , expect , vi , beforeEach } from "vitest" ;
2
2
import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup" ;
3
- import { render , screen , userEvent } from "../utils/test-utils" ;
3
+ import { render , screen , userEvent , act } from "../utils/test-utils" ;
4
4
import Terminal , { commands } from "../components/Terminal" ;
5
5
6
6
// setup function
@@ -29,91 +29,134 @@ describe("Terminal Component", () => {
29
29
} ) ;
30
30
31
31
it ( "should change input value" , async ( ) => {
32
- await user . type ( terminalInput , "demo" ) ;
32
+ await act ( async ( ) => {
33
+ await user . type ( terminalInput , "demo" ) ;
34
+ } ) ;
33
35
expect ( terminalInput . value ) . toBe ( "demo" ) ;
34
36
} ) ;
35
37
36
38
it ( "should clear input value when click enter" , async ( ) => {
37
- await user . type ( terminalInput , "demo{enter}" ) ;
39
+ await act ( async ( ) => {
40
+ await user . type ( terminalInput , "demo{enter}" ) ;
41
+ } ) ;
38
42
expect ( terminalInput . value ) . toBe ( "" ) ;
39
43
} ) ;
40
44
} ) ;
41
45
42
46
describe ( "Input Commands" , ( ) => {
43
47
it ( "should return 'command not found' when input value is invalid" , async ( ) => {
44
- await user . type ( terminalInput , "demo{enter}" ) ;
48
+ await act ( async ( ) => {
49
+ await user . type ( terminalInput , "demo{enter}" ) ;
50
+ } ) ;
45
51
expect ( screen . getByTestId ( "not-found-0" ) . innerHTML ) . toBe (
46
52
"command not found: demo"
47
53
) ;
48
54
} ) ;
49
55
50
56
it ( "should return 'visitor' when user type 'whoami' cmd" , async ( ) => {
51
- await user . type ( terminalInput , "whoami{enter}" ) ;
57
+ await act ( async ( ) => {
58
+ await user . type ( terminalInput , "whoami{enter}" ) ;
59
+ } ) ;
52
60
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
53
61
"visitor"
54
62
) ;
55
63
} ) ;
56
64
57
- it ( "should return '/home/satnaing' when user type 'pwd' cmd" , async ( ) => {
58
- await user . type ( terminalInput , "pwd{enter}" ) ;
65
+ it ( "should return home directory when user type 'pwd' cmd" , async ( ) => {
66
+ await act ( async ( ) => {
67
+ await user . type ( terminalInput , "pwd{enter}" ) ;
68
+ } ) ;
59
69
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
60
- "/home/satnaing "
70
+ "/home/user "
61
71
) ;
62
72
} ) ;
63
73
64
74
it ( "should display cmd history when user type 'history' cmd" , async ( ) => {
65
- await user . type ( terminalInput , "whoami{enter}" ) ;
66
- await user . type ( terminalInput , "history{enter}" ) ;
75
+ // Type commands one by one
76
+ await act ( async ( ) => {
77
+ await user . type ( terminalInput , "whoami{enter}" ) ;
78
+ } ) ;
79
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
67
80
68
- const commands =
69
- screen . getByTestId ( "latest-output" ) . firstChild ?. childNodes ;
81
+ await act ( async ( ) => {
82
+ await user . type ( terminalInput , "pwd{enter}" ) ;
83
+ } ) ;
84
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
85
+
86
+ await act ( async ( ) => {
87
+ await user . type ( terminalInput , "history{enter}" ) ;
88
+ } ) ;
89
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
70
90
71
- expect ( commands ?. length ) . toBe ( 3 ) ;
91
+ // Get the history output
92
+ const historyOutput = screen . getByTestId ( "history" ) ;
93
+ expect ( historyOutput ) . toBeInTheDocument ( ) ;
94
+
95
+ // Verify the command history
96
+ const commands = historyOutput . querySelectorAll ( "div" ) ;
97
+ expect ( commands . length ) . toBe ( 4 ) ; // welcome (initial), whoami, pwd, history
72
98
73
99
const typedCommands : string [ ] = [ ] ;
74
- commands ? .forEach ( cmd => {
100
+ commands . forEach ( cmd => {
75
101
typedCommands . push ( cmd . textContent || "" ) ;
76
102
} ) ;
77
103
78
- expect ( typedCommands ) . toEqual ( [ "welcome" , "whoami" , "history" ] ) ;
104
+ // Commands appear in the order they were entered
105
+ expect ( typedCommands ) . toEqual ( [ "welcome" , "whoami" , "pwd" , "history" ] ) ;
79
106
} ) ;
80
107
81
108
it ( "should clear everything when user type 'clear' cmd" , async ( ) => {
82
- await user . type ( terminalInput , "clear{enter}" ) ;
109
+ await act ( async ( ) => {
110
+ await user . type ( terminalInput , "clear{enter}" ) ;
111
+ } ) ;
83
112
expect ( screen . getByTestId ( "terminal-wrapper" ) . children . length ) . toBe ( 1 ) ;
84
113
} ) ;
85
114
86
115
it ( "should return `hello world` when user type `echo hello world` cmd" , async ( ) => {
87
- await user . type ( terminalInput , "echo hello world{enter}" ) ;
116
+ await act ( async ( ) => {
117
+ await user . type ( terminalInput , "echo hello world{enter}" ) ;
118
+ } ) ;
88
119
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
89
120
"hello world"
90
121
) ;
91
122
} ) ;
92
123
93
124
it ( "should return `hello world` without quotes when user type `echo 'hello world'` cmd" , async ( ) => {
94
125
// omit single quotes
95
- await user . type ( terminalInput , "echo 'hello world'{enter}" ) ;
126
+ await act ( async ( ) => {
127
+ await user . type ( terminalInput , "echo 'hello world'{enter}" ) ;
128
+ } ) ;
96
129
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
97
130
"hello world"
98
131
) ;
99
132
100
133
// omit double quotes
101
- await user . type ( terminalInput , 'echo "hello world"{enter}' ) ;
134
+ await act ( async ( ) => {
135
+ await user . type ( terminalInput , 'echo "hello world"{enter}' ) ;
136
+ } ) ;
102
137
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
103
138
"hello world"
104
139
) ;
105
140
106
141
// omit backtick
107
- await user . type ( terminalInput , "echo `hello world`{enter}" ) ;
142
+ await act ( async ( ) => {
143
+ await user . type ( terminalInput , "echo `hello world`{enter}" ) ;
144
+ } ) ;
108
145
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
109
146
"hello world"
110
147
) ;
111
148
} ) ;
112
149
113
150
it ( "should render Welcome component when user type 'welcome' cmd" , async ( ) => {
114
- await user . type ( terminalInput , "clear{enter}" ) ;
115
- await user . type ( terminalInput , "welcome{enter}" ) ;
116
- expect ( screen . getByTestId ( "welcome" ) ) . toBeInTheDocument ( ) ;
151
+ // Clear terminal first
152
+ await act ( async ( ) => {
153
+ await user . keyboard ( "{Control>}l{/Control}" ) ;
154
+ } ) ;
155
+ await act ( async ( ) => {
156
+ await user . type ( terminalInput , "welcome{enter}" ) ;
157
+ } ) ;
158
+ const welcomeElements = screen . getAllByTestId ( "welcome" ) ;
159
+ expect ( welcomeElements ) . toHaveLength ( 1 ) ;
117
160
} ) ;
118
161
119
162
const otherCmds = [
@@ -127,7 +170,9 @@ describe("Terminal Component", () => {
127
170
] ;
128
171
otherCmds . forEach ( cmd => {
129
172
it ( `should render ${ cmd } component when user type '${ cmd } ' cmd` , async ( ) => {
130
- await user . type ( terminalInput , `${ cmd } {enter}` ) ;
173
+ await act ( async ( ) => {
174
+ await user . type ( terminalInput , `${ cmd } {enter}` ) ;
175
+ } ) ;
131
176
expect ( screen . getByTestId ( `${ cmd } ` ) ) . toBeInTheDocument ( ) ;
132
177
} ) ;
133
178
} ) ;
@@ -139,32 +184,40 @@ describe("Terminal Component", () => {
139
184
} ) ;
140
185
141
186
it ( "should redirect to portfolio website when user type 'gui' cmd" , async ( ) => {
142
- await user . type ( terminalInput , "gui{enter}" ) ;
187
+ await act ( async ( ) => {
188
+ await user . type ( terminalInput , "gui{enter}" ) ;
189
+ } ) ;
143
190
expect ( window . open ) . toHaveBeenCalled ( ) ;
144
191
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
145
192
""
146
193
) ;
147
194
} ) ;
148
195
149
196
it ( "should open mail app when user type 'email' cmd" , async ( ) => {
150
- await user . type ( terminalInput , "email{enter}" ) ;
197
+ await act ( async ( ) => {
198
+ await user . type ( terminalInput , "email{enter}" ) ;
199
+ } ) ;
151
200
expect ( window . open ) . toHaveBeenCalled ( ) ;
152
201
expect ( screen . getByTestId ( "latest-output" ) . firstChild ?. textContent ) . toBe (
153
-
202
+ "guest "
154
203
) ;
155
204
} ) ;
156
205
157
206
const nums = [ 1 , 2 , 3 , 4 ] ;
158
207
nums . forEach ( num => {
159
208
it ( `should redirect to project URL when user type 'projects go ${ num } ' cmd` , async ( ) => {
160
- await user . type ( terminalInput , `projects go ${ num } {enter}` ) ;
209
+ await act ( async ( ) => {
210
+ await user . type ( terminalInput , `projects go ${ num } {enter}` ) ;
211
+ } ) ;
161
212
expect ( window . open ) . toHaveBeenCalled ( ) ;
162
213
} ) ;
163
214
} ) ;
164
215
165
216
nums . forEach ( num => {
166
217
it ( `should redirect to social media when user type 'socials go ${ num } ' cmd` , async ( ) => {
167
- await user . type ( terminalInput , `socials go ${ num } {enter}` ) ;
218
+ await act ( async ( ) => {
219
+ await user . type ( terminalInput , `socials go ${ num } {enter}` ) ;
220
+ } ) ;
168
221
expect ( window . open ) . toHaveBeenCalled ( ) ;
169
222
} ) ;
170
223
} ) ;
@@ -178,7 +231,9 @@ describe("Terminal Component", () => {
178
231
179
232
usageCmds . forEach ( cmd => {
180
233
it ( `should return usage component for ${ cmd } cmd with invalid arg` , async ( ) => {
181
- await user . type ( terminalInput , `${ cmd } sth{enter}` ) ;
234
+ await act ( async ( ) => {
235
+ await user . type ( terminalInput , `${ cmd } sth{enter}` ) ;
236
+ } ) ;
182
237
expect ( screen . getByTestId ( "usage-output" ) . innerHTML ) . toBe (
183
238
`Usage: ${ cmd } `
184
239
) ;
@@ -187,70 +242,129 @@ describe("Terminal Component", () => {
187
242
188
243
specialUsageCmds . forEach ( cmd => {
189
244
it ( `should return usage component for '${ cmd } ' cmd with invalid arg` , async ( ) => {
190
- await user . type ( terminalInput , `${ cmd } sth{enter}` ) ;
245
+ await act ( async ( ) => {
246
+ await user . type ( terminalInput , `${ cmd } sth{enter}` ) ;
247
+ } ) ;
191
248
expect ( screen . getByTestId ( `${ cmd } -invalid-arg` ) ) . toBeInTheDocument ( ) ;
192
249
} ) ;
193
250
194
251
it ( `should return usage component for '${ cmd } ' cmd with extra args` , async ( ) => {
195
252
const arg = cmd === "themes" ? "set light" : "go 1" ;
196
- await user . type ( terminalInput , `${ cmd } ${ arg } extra-arg{enter}` ) ;
253
+ await act ( async ( ) => {
254
+ await user . type ( terminalInput , `${ cmd } ${ arg } extra-arg{enter}` ) ;
255
+ } ) ;
197
256
expect ( screen . getByTestId ( `${ cmd } -invalid-arg` ) ) . toBeInTheDocument ( ) ;
198
257
} ) ;
199
258
200
259
it ( `should return usage component for '${ cmd } ' cmd with incorrect option` , async ( ) => {
201
260
const arg = cmd === "themes" ? "go light" : "set 4" ;
202
261
window . open = vi . fn ( ) ;
203
262
204
- // firstly run commands correct options
205
- await user . type ( terminalInput , `projects go 4{enter}` ) ;
206
- await user . type ( terminalInput , `socials go 4{enter}` ) ;
207
- await user . type ( terminalInput , `themes set espresso{enter}` ) ;
208
-
209
- // then run cmd with incorrect options
210
- await user . type ( terminalInput , `${ cmd } ${ arg } {enter}` ) ;
211
- expect ( window . open ) . toBeCalledTimes ( 2 ) ;
212
-
213
- // TODO: Test theme change
263
+ await act ( async ( ) => {
264
+ await user . type ( terminalInput , `${ cmd } ${ arg } {enter}` ) ;
265
+ } ) ;
266
+
267
+ if ( cmd === "themes" ) {
268
+ expect ( screen . getByTestId ( "themes-invalid-arg" ) ) . toBeInTheDocument ( ) ;
269
+ } else {
270
+ expect ( window . open ) . not . toHaveBeenCalled ( ) ;
271
+ }
214
272
} ) ;
215
273
} ) ;
216
274
} ) ;
217
275
218
276
describe ( "Keyboard shortcuts" , ( ) => {
219
277
allCmds . forEach ( cmd => {
220
278
it ( `should autocomplete '${ cmd } ' when 'Tab' is pressed` , async ( ) => {
221
- await user . type ( terminalInput , cmd . slice ( 0 , 2 ) ) ;
222
- await user . tab ( ) ;
279
+ await act ( async ( ) => {
280
+ await user . type ( terminalInput , cmd . slice ( 0 , 2 ) ) ;
281
+ await user . tab ( ) ;
282
+ } ) ;
223
283
expect ( terminalInput . value ) . toBe ( cmd ) ;
224
284
} ) ;
225
285
} ) ;
226
286
227
287
allCmds . forEach ( cmd => {
228
288
it ( `should autocomplete '${ cmd } ' when 'Ctrl + i' is pressed` , async ( ) => {
229
- await user . type ( terminalInput , cmd . slice ( 0 , 2 ) ) ;
230
- await user . keyboard ( "{Control>}i{/Control}" ) ;
289
+ await act ( async ( ) => {
290
+ await user . type ( terminalInput , cmd . slice ( 0 , 2 ) ) ;
291
+ await user . keyboard ( "{Control>}i{/Control}" ) ;
292
+ } ) ;
231
293
expect ( terminalInput . value ) . toBe ( cmd ) ;
232
294
} ) ;
233
295
} ) ;
234
296
235
297
it ( "should clear when 'Ctrl + l' is pressed" , async ( ) => {
236
- await user . type ( terminalInput , "history{enter}" ) ;
237
- await user . keyboard ( "{Control>}l{/Control}" ) ;
298
+ await act ( async ( ) => {
299
+ await user . type ( terminalInput , "history{enter}" ) ;
300
+ await user . keyboard ( "{Control>}l{/Control}" ) ;
301
+ } ) ;
238
302
expect ( screen . getByTestId ( "terminal-wrapper" ) . children . length ) . toBe ( 1 ) ;
239
303
} ) ;
240
304
241
305
it ( "should go to previous back and forth when 'Up & Down Arrow' is pressed" , async ( ) => {
242
- await user . type ( terminalInput , "about{enter}" ) ;
243
- await user . type ( terminalInput , "whoami{enter}" ) ;
244
- await user . type ( terminalInput , "pwd{enter}" ) ;
245
- await user . keyboard ( "{arrowup>3}" ) ;
306
+ // Type commands one by one
307
+ await act ( async ( ) => {
308
+ await user . type ( terminalInput , "about{enter}" ) ;
309
+ } ) ;
310
+ await act ( async ( ) => {
311
+ await user . type ( terminalInput , "whoami{enter}" ) ;
312
+ } ) ;
313
+ await act ( async ( ) => {
314
+ await user . type ( terminalInput , "pwd{enter}" ) ;
315
+ } ) ;
316
+
317
+ // Wait for React to process the commands
318
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
319
+
320
+ // Navigate up through history one by one
321
+ await act ( async ( ) => {
322
+ await user . keyboard ( "{ArrowUp}" ) ;
323
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
324
+ } ) ;
325
+ expect ( terminalInput . value ) . toBe ( "pwd" ) ;
326
+
327
+ await act ( async ( ) => {
328
+ await user . keyboard ( "{ArrowUp}" ) ;
329
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
330
+ } ) ;
331
+ expect ( terminalInput . value ) . toBe ( "whoami" ) ;
332
+
333
+ await act ( async ( ) => {
334
+ await user . keyboard ( "{ArrowUp}" ) ;
335
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
336
+ } ) ;
246
337
expect ( terminalInput . value ) . toBe ( "about" ) ;
247
- await user . keyboard ( "{arrowup>2}" ) ;
338
+
339
+ await act ( async ( ) => {
340
+ await user . keyboard ( "{ArrowUp}" ) ;
341
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
342
+ } ) ;
248
343
expect ( terminalInput . value ) . toBe ( "welcome" ) ;
249
- await user . keyboard ( "{arrowdown>2}" ) ;
344
+
345
+ // Navigate back down
346
+ await act ( async ( ) => {
347
+ await user . keyboard ( "{ArrowDown}" ) ;
348
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
349
+ } ) ;
350
+ expect ( terminalInput . value ) . toBe ( "about" ) ;
351
+
352
+ await act ( async ( ) => {
353
+ await user . keyboard ( "{ArrowDown}" ) ;
354
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
355
+ } ) ;
250
356
expect ( terminalInput . value ) . toBe ( "whoami" ) ;
251
- await user . keyboard ( "{arrowdown}" ) ;
357
+
358
+ await act ( async ( ) => {
359
+ await user . keyboard ( "{ArrowDown}" ) ;
360
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
361
+ } ) ;
252
362
expect ( terminalInput . value ) . toBe ( "pwd" ) ;
253
- await user . keyboard ( "{arrowdown}" ) ;
363
+
364
+ await act ( async ( ) => {
365
+ await user . keyboard ( "{ArrowDown}" ) ;
366
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
367
+ } ) ;
254
368
expect ( terminalInput . value ) . toBe ( "" ) ;
255
369
} ) ;
256
370
} ) ;
0 commit comments