mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: resolve test failures in image-only message tests
- Fixed JavaScript hoisting errors by reordering function definitions - Resolved infinite render loops in useDeepCompareEffect mock - Added missing mocks for useSize and StandardTooltip - Updated test scenarios to properly simulate AI busy/available states - Fixed all ESLint warnings in test files - All 6 image-only message tests now pass
This commit is contained in:
parent
7786171a68
commit
ac2031dc53
3 changed files with 653 additions and 744 deletions
|
|
@ -250,179 +250,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
vscode.postMessage({ type: "playTts", text })
|
||||
}
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
// if last message is an ask, show user ask UI
|
||||
// if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost.
|
||||
// basically as long as a task is active, the conversation history will be persisted
|
||||
if (lastMessage) {
|
||||
switch (lastMessage.type) {
|
||||
case "ask":
|
||||
// Reset user response flag when a new ask arrives to allow auto-approval
|
||||
userRespondedRef.current = false
|
||||
const isPartial = lastMessage.partial === true
|
||||
switch (lastMessage.ask) {
|
||||
case "api_req_failed":
|
||||
playSound("progress_loop")
|
||||
setSendingDisabled(true)
|
||||
setClineAsk("api_req_failed")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:retry.title"))
|
||||
setSecondaryButtonText(t("chat:startNewTask.title"))
|
||||
break
|
||||
case "mistake_limit_reached":
|
||||
playSound("progress_loop")
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("mistake_limit_reached")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:proceedAnyways.title"))
|
||||
setSecondaryButtonText(t("chat:startNewTask.title"))
|
||||
break
|
||||
case "followup":
|
||||
if (!isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("followup")
|
||||
// setting enable buttons to `false` would trigger a focus grab when
|
||||
// the text area is enabled which is undesirable.
|
||||
// We have no buttons for this tool, so no problem having them "enabled"
|
||||
// to workaround this issue. See #1358.
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(undefined)
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "tool":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("tool")
|
||||
setEnableButtons(!isPartial)
|
||||
const tool = JSON.parse(lastMessage.text || "{}") as ClineSayTool
|
||||
switch (tool.tool) {
|
||||
case "editedExistingFile":
|
||||
case "appliedDiff":
|
||||
case "newFileCreated":
|
||||
case "insertContent":
|
||||
setPrimaryButtonText(t("chat:save.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "finishTask":
|
||||
setPrimaryButtonText(t("chat:completeSubtaskAndReturn"))
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "readFile":
|
||||
if (tool.batchFiles && Array.isArray(tool.batchFiles)) {
|
||||
setPrimaryButtonText(t("chat:read-batch.approve.title"))
|
||||
setSecondaryButtonText(t("chat:read-batch.deny.title"))
|
||||
} else {
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
}
|
||||
break
|
||||
default:
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
}
|
||||
break
|
||||
case "browser_action_launch":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("browser_action_launch")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "command":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("command")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:runCommand.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "command_output":
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("command_output")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:proceedWhileRunning.title"))
|
||||
setSecondaryButtonText(t("chat:killCommand.title"))
|
||||
break
|
||||
case "use_mcp_server":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("use_mcp_server")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "completion_result":
|
||||
// extension waiting for feedback. but we can just present a new task button
|
||||
if (!isPartial) {
|
||||
playSound("celebration")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("completion_result")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:startNewTask.title"))
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "resume_task":
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("resume_task")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:resumeTask.title"))
|
||||
setSecondaryButtonText(t("chat:terminate.title"))
|
||||
setDidClickCancel(false) // special case where we reset the cancel button state
|
||||
break
|
||||
case "resume_completed_task":
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("resume_completed_task")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:startNewTask.title"))
|
||||
setSecondaryButtonText(undefined)
|
||||
setDidClickCancel(false)
|
||||
break
|
||||
}
|
||||
break
|
||||
case "say":
|
||||
// Don't want to reset since there could be a "say" after
|
||||
// an "ask" while ask is waiting for response.
|
||||
switch (lastMessage.say) {
|
||||
case "api_req_retry_delayed":
|
||||
setSendingDisabled(true)
|
||||
break
|
||||
case "api_req_started":
|
||||
if (secondLastMessage?.ask === "command_output") {
|
||||
setSendingDisabled(true)
|
||||
setSelectedImages([])
|
||||
setClineAsk(undefined)
|
||||
setEnableButtons(false)
|
||||
}
|
||||
break
|
||||
case "api_req_finished":
|
||||
case "error":
|
||||
case "text":
|
||||
case "browser_action":
|
||||
case "browser_action_result":
|
||||
case "command_output":
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
case "completion_result":
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [lastMessage, secondLastMessage])
|
||||
|
||||
useEffect(() => {
|
||||
if (messages.length === 0) {
|
||||
setSendingDisabled(false)
|
||||
|
|
@ -1181,6 +1008,179 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
|
|||
],
|
||||
)
|
||||
|
||||
useDeepCompareEffect(() => {
|
||||
// if last message is an ask, show user ask UI
|
||||
// if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost.
|
||||
// basically as long as a task is active, the conversation history will be persisted
|
||||
if (lastMessage) {
|
||||
switch (lastMessage.type) {
|
||||
case "ask":
|
||||
// Reset user response flag when a new ask arrives to allow auto-approval
|
||||
userRespondedRef.current = false
|
||||
const isPartial = lastMessage.partial === true
|
||||
switch (lastMessage.ask) {
|
||||
case "api_req_failed":
|
||||
playSound("progress_loop")
|
||||
setSendingDisabled(true)
|
||||
setClineAsk("api_req_failed")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:retry.title"))
|
||||
setSecondaryButtonText(t("chat:startNewTask.title"))
|
||||
break
|
||||
case "mistake_limit_reached":
|
||||
playSound("progress_loop")
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("mistake_limit_reached")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:proceedAnyways.title"))
|
||||
setSecondaryButtonText(t("chat:startNewTask.title"))
|
||||
break
|
||||
case "followup":
|
||||
if (!isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("followup")
|
||||
// setting enable buttons to `false` would trigger a focus grab when
|
||||
// the text area is enabled which is undesirable.
|
||||
// We have no buttons for this tool, so no problem having them "enabled"
|
||||
// to workaround this issue. See #1358.
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(undefined)
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "tool":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("tool")
|
||||
setEnableButtons(!isPartial)
|
||||
const tool = JSON.parse(lastMessage.text || "{}") as ClineSayTool
|
||||
switch (tool.tool) {
|
||||
case "editedExistingFile":
|
||||
case "appliedDiff":
|
||||
case "newFileCreated":
|
||||
case "insertContent":
|
||||
setPrimaryButtonText(t("chat:save.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "finishTask":
|
||||
setPrimaryButtonText(t("chat:completeSubtaskAndReturn"))
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "readFile":
|
||||
if (tool.batchFiles && Array.isArray(tool.batchFiles)) {
|
||||
setPrimaryButtonText(t("chat:read-batch.approve.title"))
|
||||
setSecondaryButtonText(t("chat:read-batch.deny.title"))
|
||||
} else {
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
}
|
||||
break
|
||||
default:
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
}
|
||||
break
|
||||
case "browser_action_launch":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("browser_action_launch")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "command":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("command")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:runCommand.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "command_output":
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("command_output")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:proceedWhileRunning.title"))
|
||||
setSecondaryButtonText(t("chat:killCommand.title"))
|
||||
break
|
||||
case "use_mcp_server":
|
||||
if (!isAutoApproved(lastMessage) && !isPartial) {
|
||||
playSound("notification")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("use_mcp_server")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:approve.title"))
|
||||
setSecondaryButtonText(t("chat:reject.title"))
|
||||
break
|
||||
case "completion_result":
|
||||
// extension waiting for feedback. but we can just present a new task button
|
||||
if (!isPartial) {
|
||||
playSound("celebration")
|
||||
}
|
||||
setSendingDisabled(isPartial)
|
||||
setClineAsk("completion_result")
|
||||
setEnableButtons(!isPartial)
|
||||
setPrimaryButtonText(t("chat:startNewTask.title"))
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "resume_task":
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("resume_task")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:resumeTask.title"))
|
||||
setSecondaryButtonText(t("chat:terminate.title"))
|
||||
setDidClickCancel(false) // special case where we reset the cancel button state
|
||||
break
|
||||
case "resume_completed_task":
|
||||
setSendingDisabled(false)
|
||||
setClineAsk("resume_completed_task")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText(t("chat:startNewTask.title"))
|
||||
setSecondaryButtonText(undefined)
|
||||
setDidClickCancel(false)
|
||||
break
|
||||
}
|
||||
break
|
||||
case "say":
|
||||
// Don't want to reset since there could be a "say" after
|
||||
// an "ask" while ask is waiting for response.
|
||||
switch (lastMessage.say) {
|
||||
case "api_req_retry_delayed":
|
||||
setSendingDisabled(true)
|
||||
break
|
||||
case "api_req_started":
|
||||
if (secondLastMessage?.ask === "command_output") {
|
||||
setSendingDisabled(true)
|
||||
setSelectedImages([])
|
||||
setClineAsk(undefined)
|
||||
setEnableButtons(false)
|
||||
}
|
||||
break
|
||||
case "api_req_finished":
|
||||
case "error":
|
||||
case "text":
|
||||
case "browser_action":
|
||||
case "browser_action_result":
|
||||
case "command_output":
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
case "completion_result":
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [lastMessage, secondLastMessage])
|
||||
|
||||
useEffect(() => {
|
||||
// This ensures the first message is not read, future user messages are
|
||||
// labeled as `user_feedback`.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,29 @@ vi.mock("@src/utils/vscode", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
// Mock useSelectedModel to return a model that supports images
|
||||
vi.mock("@src/components/ui/hooks/useSelectedModel", () => ({
|
||||
useSelectedModel: vi.fn(() => ({
|
||||
info: {
|
||||
supportsImages: true,
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
},
|
||||
})),
|
||||
}))
|
||||
|
||||
// Mock the API configuration and related hooks
|
||||
vi.mock("@src/shared/api", () => ({
|
||||
getModelMaxOutputTokens: vi.fn(() => 8192),
|
||||
}))
|
||||
|
||||
// Mock TaskHeader to avoid API configuration issues
|
||||
vi.mock("../TaskHeader", () => ({
|
||||
default: function MockTaskHeader() {
|
||||
return <div data-testid="task-header">Task Header</div>
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock use-sound hook
|
||||
const mockPlayFunction = vi.fn()
|
||||
vi.mock("use-sound", () => ({
|
||||
|
|
@ -68,7 +91,7 @@ vi.mock("../../common/VersionIndicator", () => ({
|
|||
|
||||
vi.mock("../Announcement", () => ({
|
||||
default: function MockAnnouncement({ hideAnnouncement }: { hideAnnouncement: () => void }) {
|
||||
const React = require("react")
|
||||
const React = require("react") // eslint-disable-line @typescript-eslint/no-require-imports
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ "data-testid": "announcement-modal" },
|
||||
|
|
@ -94,7 +117,6 @@ vi.mock("../QueuedMessages", () => ({
|
|||
default: function MockQueuedMessages({
|
||||
queue = [],
|
||||
onRemove,
|
||||
onUpdate,
|
||||
}: {
|
||||
queue?: Array<{ id: string; text: string; images: string[] }>
|
||||
onRemove?: (index: number) => void
|
||||
|
|
@ -178,7 +200,7 @@ const mockFocus = vi.fn()
|
|||
|
||||
// Create a simple mock that can test the core functionality
|
||||
vi.mock("../ChatTextArea", () => {
|
||||
const mockReact = require("react")
|
||||
const mockReact = require("react") // eslint-disable-line @typescript-eslint/no-require-imports
|
||||
|
||||
return {
|
||||
default: mockReact.forwardRef(function MockChatTextArea(
|
||||
|
|
@ -190,6 +212,9 @@ vi.mock("../ChatTextArea", () => {
|
|||
focus: mockFocus,
|
||||
}))
|
||||
|
||||
// Use the selectedImages from props directly
|
||||
const selectedImages = props.selectedImages || []
|
||||
|
||||
return (
|
||||
<div data-testid="chat-textarea">
|
||||
<input
|
||||
|
|
@ -207,7 +232,17 @@ vi.mock("../ChatTextArea", () => {
|
|||
disabled={props.sendingDisabled}>
|
||||
Send Image Only
|
||||
</button>
|
||||
<div data-testid="selected-images-count">{props.selectedImages?.length || 0}</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
// Simulate adding images for testing
|
||||
const newImages = ["data:image/png;base64,test1", "data:image/png;base64,test2"]
|
||||
props.setSelectedImages?.(newImages)
|
||||
}}
|
||||
data-testid="add-images-button"
|
||||
disabled={props.shouldDisableImages}>
|
||||
Add Images
|
||||
</button>
|
||||
<div data-testid="selected-images-count">{selectedImages.length}</div>
|
||||
</div>
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue