mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: reset didClickCancel state when streaming ends to prevent re-rendering
- Added logic to reset didClickCancel to false when streaming transitions from true to false - This prevents the chat UI from re-rendering unexpectedly when canceling OpenAI Compatible API requests - Added comprehensive tests to verify the cancel button state is properly managed Fixes #6726
This commit is contained in:
parent
ea79dfeb66
commit
3f04e1eb17
2 changed files with 177 additions and 0 deletions
|
|
@ -1208,6 +1208,11 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
}
|
||||
}
|
||||
|
||||
// Reset didClickCancel when streaming ends
|
||||
if (wasStreaming && !isStreaming) {
|
||||
setDidClickCancel(false)
|
||||
}
|
||||
|
||||
// Update previous value.
|
||||
setWasStreaming(isStreaming)
|
||||
}, [isStreaming, lastMessage, wasStreaming, isAutoApproved, messages.length])
|
||||
|
|
|
|||
|
|
@ -1493,3 +1493,175 @@ describe("ChatView - Message Queueing Tests", () => {
|
|||
expect(input.getAttribute("data-sending-disabled")).toBe("false")
|
||||
})
|
||||
})
|
||||
|
||||
describe("ChatView - Cancel Button State Tests", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(vscode.postMessage).mockClear()
|
||||
})
|
||||
|
||||
it("resets didClickCancel state when streaming ends", async () => {
|
||||
const { getByText, queryByText } = renderChatView()
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Add a streaming API request
|
||||
mockPostMessage({
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
ts: Date.now() - 1000,
|
||||
text: JSON.stringify({ model: "openai-compatible" }),
|
||||
partial: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Wait for cancel button to appear
|
||||
await waitFor(() => {
|
||||
expect(getByText("chat:cancel.title")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Click cancel button
|
||||
const cancelButton = getByText("chat:cancel.title")
|
||||
act(() => {
|
||||
cancelButton.click()
|
||||
})
|
||||
|
||||
// Verify cancel task was sent
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "cancelTask" })
|
||||
|
||||
// Clear the mock to check for subsequent calls
|
||||
vi.mocked(vscode.postMessage).mockClear()
|
||||
|
||||
// Simulate streaming ending by updating messages with completed API request
|
||||
mockPostMessage({
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
ts: Date.now() - 1000,
|
||||
text: JSON.stringify({
|
||||
model: "openai-compatible",
|
||||
cost: 0.01, // Cost indicates request finished
|
||||
cancelReason: "User cancelled",
|
||||
}),
|
||||
partial: false,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Wait for UI to update
|
||||
await waitFor(() => {
|
||||
// Cancel button should no longer be visible when not streaming
|
||||
expect(queryByText("chat:cancel.title")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Add a new streaming request to verify didClickCancel was reset
|
||||
mockPostMessage({
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
ts: Date.now() - 1000,
|
||||
text: JSON.stringify({
|
||||
model: "openai-compatible",
|
||||
cost: 0.01,
|
||||
cancelReason: "User cancelled",
|
||||
}),
|
||||
partial: false,
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
ts: Date.now(),
|
||||
text: JSON.stringify({ model: "openai-compatible" }),
|
||||
partial: true, // New streaming request
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Wait for cancel button to appear again
|
||||
await waitFor(() => {
|
||||
expect(getByText("chat:cancel.title")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// The cancel button should be enabled
|
||||
// This verifies that didClickCancel was properly reset
|
||||
const newCancelButton = getByText("chat:cancel.title")
|
||||
// The button should not be disabled
|
||||
expect(newCancelButton).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it("maintains correct button opacity based on streaming and didClickCancel state", async () => {
|
||||
const { container } = renderChatView()
|
||||
|
||||
// First hydrate state with initial task
|
||||
mockPostMessage({
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Add a streaming API request
|
||||
mockPostMessage({
|
||||
clineMessages: [
|
||||
{
|
||||
type: "say",
|
||||
say: "task",
|
||||
ts: Date.now() - 2000,
|
||||
text: "Initial task",
|
||||
},
|
||||
{
|
||||
type: "say",
|
||||
say: "api_req_started",
|
||||
ts: Date.now() - 1000,
|
||||
text: JSON.stringify({ model: "openai-compatible" }),
|
||||
partial: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Wait for button container to appear
|
||||
await waitFor(() => {
|
||||
const buttonContainer = container.querySelector('[class*="opacity-"]')
|
||||
expect(buttonContainer).toBeInTheDocument()
|
||||
// Should have opacity-100 when streaming and cancel not clicked
|
||||
expect(buttonContainer?.className).toContain("opacity-100")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue