Skip to content

Commit 48d7ef9

Browse files
authored
Merge pull request #6 from RooVetGit/feature/addTerminateTaskButton
2 parents 8150aa8 + aecbde8 commit 48d7ef9

File tree

4 files changed

+175
-3
lines changed

4 files changed

+175
-3
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "roo-cline",
33
"displayName": "Roo Cline",
44
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
5-
"version": "1.0.3",
5+
"version": "1.0.4",
66
"icon": "assets/icons/icon.png",
77
"galleryBanner": {
88
"color": "#617A91",

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
146146
setClineAsk("resume_task")
147147
setEnableButtons(true)
148148
setPrimaryButtonText("Resume Task")
149-
setSecondaryButtonText(undefined)
150-
setDidClickCancel(false) // special case where we reset the cancel button state
149+
setSecondaryButtonText("Terminate")
150+
setDidClickCancel(false) // special case where we reset the cancel button state
151151
break
152152
case "resume_completed_task":
153153
setTextAreaDisabled(false)
@@ -316,6 +316,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
316316
switch (clineAsk) {
317317
case "api_req_failed":
318318
case "mistake_limit_reached":
319+
case "resume_task":
319320
startNewTask()
320321
break
321322
case "command":
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { render, screen, act } from '@testing-library/react'
2+
import userEvent from '@testing-library/user-event'
3+
import { ExtensionStateContextType } from '../../../context/ExtensionStateContext'
4+
import ChatView from '../ChatView'
5+
import { vscode } from '../../../utils/vscode'
6+
import * as ExtensionStateContext from '../../../context/ExtensionStateContext'
7+
8+
// Mock vscode
9+
jest.mock('../../../utils/vscode', () => ({
10+
vscode: {
11+
postMessage: jest.fn()
12+
}
13+
}))
14+
15+
// Mock all components that use problematic dependencies
16+
jest.mock('../../common/CodeBlock', () => ({
17+
__esModule: true,
18+
default: () => <div data-testid="mock-code-block" />
19+
}))
20+
21+
jest.mock('../../common/MarkdownBlock', () => ({
22+
__esModule: true,
23+
default: () => <div data-testid="mock-markdown-block" />
24+
}))
25+
26+
jest.mock('../BrowserSessionRow', () => ({
27+
__esModule: true,
28+
default: () => <div data-testid="mock-browser-session-row" />
29+
}))
30+
31+
// Update ChatRow mock to capture props
32+
let chatRowProps = null
33+
jest.mock('../ChatRow', () => ({
34+
__esModule: true,
35+
default: (props: any) => {
36+
chatRowProps = props
37+
return <div data-testid="mock-chat-row" />
38+
}
39+
}))
40+
41+
42+
// Mock Virtuoso component
43+
jest.mock('react-virtuoso', () => ({
44+
Virtuoso: ({ children }: any) => (
45+
<div data-testid="mock-virtuoso">{children}</div>
46+
)
47+
}))
48+
49+
// Mock VS Code components
50+
jest.mock('@vscode/webview-ui-toolkit/react', () => ({
51+
VSCodeButton: ({ children, onClick }: any) => (
52+
<button onClick={onClick}>{children}</button>
53+
),
54+
VSCodeProgressRing: () => <div data-testid="progress-ring" />
55+
}))
56+
57+
describe('ChatView', () => {
58+
const mockShowHistoryView = jest.fn()
59+
const mockHideAnnouncement = jest.fn()
60+
61+
let mockState: ExtensionStateContextType
62+
63+
beforeEach(() => {
64+
jest.clearAllMocks()
65+
66+
mockState = {
67+
clineMessages: [],
68+
apiConfiguration: {
69+
apiProvider: 'anthropic',
70+
apiModelId: 'claude-3-sonnet'
71+
},
72+
version: '1.0.0',
73+
customInstructions: '',
74+
alwaysAllowReadOnly: true,
75+
alwaysAllowWrite: true,
76+
alwaysAllowExecute: true,
77+
openRouterModels: {},
78+
didHydrateState: true,
79+
showWelcome: false,
80+
theme: 'dark',
81+
filePaths: [],
82+
taskHistory: [],
83+
shouldShowAnnouncement: false,
84+
uriScheme: 'vscode',
85+
86+
setAlwaysAllowReadOnly: jest.fn(),
87+
setAlwaysAllowWrite: jest.fn(),
88+
setCustomInstructions: jest.fn(),
89+
setAlwaysAllowExecute: jest.fn(),
90+
setApiConfiguration: jest.fn(),
91+
setShowAnnouncement: jest.fn()
92+
}
93+
94+
// Mock the useExtensionState hook
95+
jest.spyOn(ExtensionStateContext, 'useExtensionState').mockReturnValue(mockState)
96+
})
97+
98+
const renderChatView = () => {
99+
return render(
100+
<ChatView
101+
isHidden={false}
102+
showAnnouncement={false}
103+
hideAnnouncement={mockHideAnnouncement}
104+
showHistoryView={mockShowHistoryView}
105+
/>
106+
)
107+
}
108+
109+
describe('Streaming State', () => {
110+
it('should show cancel button while streaming and trigger cancel on click', async () => {
111+
mockState.clineMessages = [
112+
{
113+
type: 'say',
114+
say: 'task',
115+
ts: Date.now(),
116+
},
117+
{
118+
type: 'say',
119+
say: 'text',
120+
partial: true,
121+
ts: Date.now(),
122+
}
123+
]
124+
renderChatView()
125+
126+
const cancelButton = screen.getByText('Cancel')
127+
await userEvent.click(cancelButton)
128+
129+
expect(vscode.postMessage).toHaveBeenCalledWith({
130+
type: 'cancelTask'
131+
})
132+
})
133+
134+
it('should show terminate button when task is paused and trigger terminate on click', async () => {
135+
mockState.clineMessages = [
136+
{
137+
type: 'ask',
138+
ask: 'resume_task',
139+
ts: Date.now(),
140+
}
141+
]
142+
renderChatView()
143+
144+
const terminateButton = screen.getByText('Terminate')
145+
await userEvent.click(terminateButton)
146+
147+
expect(vscode.postMessage).toHaveBeenCalledWith({
148+
type: 'clearTask'
149+
})
150+
})
151+
152+
it('should show retry button when API error occurs and trigger retry on click', async () => {
153+
mockState.clineMessages = [
154+
{
155+
type: 'ask',
156+
ask: 'api_req_failed',
157+
ts: Date.now(),
158+
}
159+
]
160+
renderChatView()
161+
162+
const retryButton = screen.getByText('Retry')
163+
await userEvent.click(retryButton)
164+
165+
expect(vscode.postMessage).toHaveBeenCalledWith({
166+
type: 'askResponse',
167+
askResponse: 'yesButtonClicked'
168+
})
169+
})
170+
})
171+
})

0 commit comments

Comments
 (0)