Skip to content

Commit b75c793

Browse files
committed
feat: Restore and enhance TerminalRegistry tests with busy flag coverage
- Remove corrupted TerminalRegistry.test.ts.broken file from PR branch - Restore clean TerminalRegistry.test.ts from main branch as base - Add comprehensive test coverage for busy flag management functionality - Include tests for onDidStartTerminalShellExecution and onDidEndTerminalShellExecution events - Ensure proper VSCode API mocking for terminal instances and event emitters - All tests passing, validating both original functionality and new busy flag logic Addresses issue #4319 by ensuring proper test coverage for the terminal busy state fix.
1 parent e0e8434 commit b75c793

File tree

2 files changed

+327
-437
lines changed

2 files changed

+327
-437
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
// npx jest src/integrations/terminal/__tests__/TerminalRegistry.test.ts
2+
3+
import { Terminal } from "../Terminal"
4+
import { TerminalRegistry } from "../TerminalRegistry"
5+
import * as vscode from "vscode"
6+
7+
const PAGER = process.platform === "win32" ? "" : "cat"
8+
9+
// Mock vscode.window.createTerminal
10+
const mockCreateTerminal = jest.fn()
11+
12+
// Event handlers for testing
13+
let mockStartHandler: any = null
14+
let mockEndHandler: any = null
15+
16+
jest.mock("vscode", () => ({
17+
window: {
18+
createTerminal: (...args: any[]) => {
19+
mockCreateTerminal(...args)
20+
return {
21+
name: "Roo Code",
22+
exitStatus: undefined,
23+
dispose: jest.fn(),
24+
show: jest.fn(),
25+
hide: jest.fn(),
26+
sendText: jest.fn(),
27+
}
28+
},
29+
onDidCloseTerminal: jest.fn().mockReturnValue({ dispose: jest.fn() }),
30+
onDidStartTerminalShellExecution: jest.fn().mockImplementation((handler) => {
31+
mockStartHandler = handler
32+
return { dispose: jest.fn() }
33+
}),
34+
onDidEndTerminalShellExecution: jest.fn().mockImplementation((handler) => {
35+
mockEndHandler = handler
36+
return { dispose: jest.fn() }
37+
}),
38+
},
39+
ThemeIcon: jest.fn(),
40+
}))
41+
42+
jest.mock("execa", () => ({
43+
execa: jest.fn(),
44+
}))
45+
46+
describe("TerminalRegistry", () => {
47+
beforeEach(() => {
48+
mockCreateTerminal.mockClear()
49+
50+
// Reset event handlers
51+
mockStartHandler = null
52+
mockEndHandler = null
53+
54+
// Clear terminals array for each test
55+
;(TerminalRegistry as any).terminals = []
56+
;(TerminalRegistry as any).nextTerminalId = 1
57+
;(TerminalRegistry as any).isInitialized = false
58+
})
59+
60+
describe("createTerminal", () => {
61+
it("creates terminal with PAGER set appropriately for platform", () => {
62+
TerminalRegistry.createTerminal("/test/path", "vscode")
63+
64+
expect(mockCreateTerminal).toHaveBeenCalledWith({
65+
cwd: "/test/path",
66+
name: "Roo Code",
67+
iconPath: expect.any(Object),
68+
env: {
69+
PAGER,
70+
VTE_VERSION: "0",
71+
PROMPT_EOL_MARK: "",
72+
},
73+
})
74+
})
75+
76+
it("adds PROMPT_COMMAND when Terminal.getCommandDelay() > 0", () => {
77+
// Set command delay to 50ms for this test
78+
const originalDelay = Terminal.getCommandDelay()
79+
Terminal.setCommandDelay(50)
80+
81+
try {
82+
TerminalRegistry.createTerminal("/test/path", "vscode")
83+
84+
expect(mockCreateTerminal).toHaveBeenCalledWith({
85+
cwd: "/test/path",
86+
name: "Roo Code",
87+
iconPath: expect.any(Object),
88+
env: {
89+
PAGER,
90+
PROMPT_COMMAND: "sleep 0.05",
91+
VTE_VERSION: "0",
92+
PROMPT_EOL_MARK: "",
93+
},
94+
})
95+
} finally {
96+
// Restore original delay
97+
Terminal.setCommandDelay(originalDelay)
98+
}
99+
})
100+
101+
it("adds Oh My Zsh integration env var when enabled", () => {
102+
Terminal.setTerminalZshOhMy(true)
103+
try {
104+
TerminalRegistry.createTerminal("/test/path", "vscode")
105+
106+
expect(mockCreateTerminal).toHaveBeenCalledWith({
107+
cwd: "/test/path",
108+
name: "Roo Code",
109+
iconPath: expect.any(Object),
110+
env: {
111+
PAGER,
112+
VTE_VERSION: "0",
113+
PROMPT_EOL_MARK: "",
114+
ITERM_SHELL_INTEGRATION_INSTALLED: "Yes",
115+
},
116+
})
117+
} finally {
118+
Terminal.setTerminalZshOhMy(false)
119+
}
120+
})
121+
122+
it("adds Powerlevel10k integration env var when enabled", () => {
123+
Terminal.setTerminalZshP10k(true)
124+
try {
125+
TerminalRegistry.createTerminal("/test/path", "vscode")
126+
127+
expect(mockCreateTerminal).toHaveBeenCalledWith({
128+
cwd: "/test/path",
129+
name: "Roo Code",
130+
iconPath: expect.any(Object),
131+
env: {
132+
PAGER,
133+
VTE_VERSION: "0",
134+
PROMPT_EOL_MARK: "",
135+
POWERLEVEL9K_TERM_SHELL_INTEGRATION: "true",
136+
},
137+
})
138+
} finally {
139+
Terminal.setTerminalZshP10k(false)
140+
}
141+
})
142+
})
143+
144+
describe("busy flag management", () => {
145+
let mockVsTerminal: any
146+
147+
beforeEach(() => {
148+
mockVsTerminal = {
149+
name: "Roo Code",
150+
exitStatus: undefined,
151+
dispose: jest.fn(),
152+
show: jest.fn(),
153+
hide: jest.fn(),
154+
sendText: jest.fn(),
155+
}
156+
mockCreateTerminal.mockReturnValue(mockVsTerminal)
157+
})
158+
159+
// Helper function to get the created Roo terminal and its underlying VSCode terminal
160+
const createTerminalAndGetVsTerminal = (path: string = "/test/path") => {
161+
const rooTerminal = TerminalRegistry.createTerminal(path, "vscode")
162+
// Get the actual VSCode terminal that was created and stored
163+
const vsTerminal = (rooTerminal as any).terminal
164+
return { rooTerminal, vsTerminal }
165+
}
166+
167+
it("should initialize terminal with busy = false", () => {
168+
const terminal = TerminalRegistry.createTerminal("/test/path", "vscode")
169+
expect(terminal.busy).toBe(false)
170+
})
171+
172+
it("should set busy = true when shell execution starts", () => {
173+
// Initialize the registry to set up event handlers
174+
TerminalRegistry.initialize()
175+
176+
// Create a terminal and get the actual VSCode terminal
177+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
178+
expect(rooTerminal.busy).toBe(false)
179+
180+
// Simulate shell execution start event
181+
const execution = {
182+
commandLine: { value: "echo test" },
183+
read: jest.fn().mockReturnValue({}),
184+
} as any
185+
186+
if (mockStartHandler) {
187+
mockStartHandler({
188+
terminal: vsTerminal,
189+
execution,
190+
})
191+
}
192+
193+
expect(rooTerminal.busy).toBe(true)
194+
})
195+
196+
it("should set busy = false when shell execution ends for Roo terminals", () => {
197+
// Initialize the registry to set up event handlers
198+
TerminalRegistry.initialize()
199+
200+
// Create a terminal and get the actual VSCode terminal
201+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
202+
rooTerminal.busy = true
203+
204+
// Set up a mock process to simulate running state
205+
const mockProcess = {
206+
command: "echo test",
207+
isHot: false,
208+
hasUnretrievedOutput: () => false,
209+
}
210+
rooTerminal.process = mockProcess as any
211+
212+
// Simulate shell execution end event
213+
const execution = {
214+
commandLine: { value: "echo test" },
215+
} as any
216+
217+
if (mockEndHandler) {
218+
mockEndHandler({
219+
terminal: vsTerminal,
220+
execution,
221+
exitCode: 0,
222+
})
223+
}
224+
225+
expect(rooTerminal.busy).toBe(false)
226+
})
227+
228+
it("should set busy = false when shell execution ends for non-Roo terminals (manual commands)", () => {
229+
// Initialize the registry to set up event handlers
230+
TerminalRegistry.initialize()
231+
232+
// Simulate a shell execution end event for a terminal not in our registry
233+
const unknownVsTerminal = {
234+
name: "Unknown Terminal",
235+
}
236+
237+
const execution = {
238+
commandLine: { value: "sleep 30" },
239+
} as any
240+
241+
// This should not throw an error and should handle the case gracefully
242+
expect(() => {
243+
if (mockEndHandler) {
244+
mockEndHandler({
245+
terminal: unknownVsTerminal,
246+
execution,
247+
exitCode: 0,
248+
})
249+
}
250+
}).not.toThrow()
251+
})
252+
253+
it("should handle busy flag reset when terminal process is not running", () => {
254+
// Initialize the registry to set up event handlers
255+
TerminalRegistry.initialize()
256+
257+
// Create a terminal and get the actual VSCode terminal
258+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
259+
rooTerminal.busy = true
260+
261+
// Ensure terminal.running returns false (no active process)
262+
Object.defineProperty(rooTerminal, "running", {
263+
get: () => false,
264+
configurable: true,
265+
})
266+
267+
// Simulate shell execution end event
268+
const execution = {
269+
commandLine: { value: "echo test" },
270+
} as any
271+
272+
if (mockEndHandler) {
273+
mockEndHandler({
274+
terminal: vsTerminal,
275+
execution,
276+
exitCode: 0,
277+
})
278+
}
279+
280+
// Should reset busy flag even when not running
281+
expect(rooTerminal.busy).toBe(false)
282+
})
283+
284+
it("should maintain busy state during command execution lifecycle", () => {
285+
// Initialize the registry to set up event handlers
286+
TerminalRegistry.initialize()
287+
288+
// Create a terminal and get the actual VSCode terminal
289+
const { rooTerminal, vsTerminal } = createTerminalAndGetVsTerminal()
290+
expect(rooTerminal.busy).toBe(false)
291+
292+
// Start execution
293+
const execution = {
294+
commandLine: { value: "npm test" },
295+
read: jest.fn().mockReturnValue({}),
296+
} as any
297+
298+
if (mockStartHandler) {
299+
mockStartHandler({
300+
terminal: vsTerminal,
301+
execution,
302+
})
303+
}
304+
305+
expect(rooTerminal.busy).toBe(true)
306+
307+
// Set up mock process for running state
308+
const mockProcess = {
309+
command: "npm test",
310+
isHot: true,
311+
hasUnretrievedOutput: () => true,
312+
}
313+
rooTerminal.process = mockProcess as any
314+
315+
// End execution
316+
if (mockEndHandler) {
317+
mockEndHandler({
318+
terminal: vsTerminal,
319+
execution,
320+
exitCode: 0,
321+
})
322+
}
323+
324+
expect(rooTerminal.busy).toBe(false)
325+
})
326+
})
327+
})

0 commit comments

Comments
 (0)