Roo-Code/webview-ui/src/__tests__/FileChangesPanel.spec.tsx
Peter Dave Hello 2541449924
Add OpenAI's GPT-5.3-Codex model support (#11728)
* Add OpenAI's GPT-5.3-Codex model support

Reference:
- https://openai.com/index/introducing-gpt-5-3-codex/
- https://developers.openai.com/api/docs/models/gpt-5.3-codex

* chore: add changeset for GPT-5.3-Codex support

* fix: rename CodeAccordian to CodeAccordion in FileChangesPanel

---------

Co-authored-by: Hannes Rudolph <hrudolph@gmail.com>
Co-authored-by: Robot Tom <bot@example.com>
2026-02-24 14:46:27 -07:00

199 lines
6 KiB
TypeScript

import React from "react"
import { fireEvent, render, screen } from "@/utils/test-utils"
import type { ClineMessage } from "@roo-code/types"
import { TranslationProvider } from "@/i18n/__mocks__/TranslationContext"
import FileChangesPanel from "../components/chat/FileChangesPanel"
const mockPostMessage = vi.fn()
vi.mock("@src/utils/vscode", () => ({
vscode: {
postMessage: (...args: unknown[]) => mockPostMessage(...args),
},
}))
// Mock i18n to return readable header with count
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (key: string, opts?: { count?: number }) => {
if (key === "chat:fileChangesInConversation.header" && opts?.count != null) {
return `${opts.count} file(s) changed in this conversation`
}
return key
},
}),
}))
// Lightweight mock so we don't pull in CodeBlock/DiffView
vi.mock("@src/components/common/CodeAccordion", () => ({
default: ({
path,
isExpanded,
onToggleExpand,
}: {
path?: string
isExpanded: boolean
onToggleExpand: () => void
}) => (
<div data-testid="code-accordian">
<span data-testid="accordian-path">{path}</span>
<button type="button" onClick={onToggleExpand} data-testid="accordian-toggle">
{isExpanded ? "expanded" : "collapsed"}
</button>
</div>
),
}))
function createFileEditMessage(
path: string,
diff: string,
diffStats?: { added: number; removed: number },
): ClineMessage {
return {
type: "ask",
ask: "tool",
ts: Date.now(),
partial: false,
isAnswered: true,
text: JSON.stringify({
tool: "appliedDiff",
path,
diff,
...(diffStats && { diffStats }),
}),
}
}
function renderPanel(messages: ClineMessage[] | undefined) {
return render(
<TranslationProvider>
<FileChangesPanel clineMessages={messages} />
</TranslationProvider>,
)
}
describe("FileChangesPanel", () => {
beforeEach(() => {
vi.clearAllMocks()
})
it("renders nothing when clineMessages is undefined", () => {
const { container } = renderPanel(undefined)
expect(container.firstChild).toBeNull()
})
it("renders nothing when clineMessages is empty", () => {
const { container } = renderPanel([])
expect(container.firstChild).toBeNull()
})
it("renders nothing when there are no file-edit messages", () => {
const messages: ClineMessage[] = [
{
type: "say",
say: "text",
ts: Date.now(),
partial: false,
text: "hello",
},
{
type: "ask",
ask: "tool",
ts: Date.now(),
partial: false,
text: JSON.stringify({ tool: "read_file", path: "x.ts" }),
},
]
const { container } = renderPanel(messages)
expect(container.firstChild).toBeNull()
})
it("renders nothing when file-edit ask tool is not approved (isAnswered false or missing)", () => {
const messages: ClineMessage[] = [
{
type: "ask",
ask: "tool",
ts: Date.now(),
partial: false,
text: JSON.stringify({
tool: "appliedDiff",
path: "src/foo.ts",
diff: "+line",
}),
},
]
const { container } = renderPanel(messages)
expect(container.firstChild).toBeNull()
})
it("renders panel with header when there is one file edit", () => {
const messages = [createFileEditMessage("src/foo.ts", "@@ -1 +1 @@\n+line")]
renderPanel(messages)
expect(screen.getByText("1 file(s) changed in this conversation")).toBeInTheDocument()
// Expand panel so file row is in DOM (CollapsibleContent may not render when closed in some setups)
fireEvent.click(screen.getByText("1 file(s) changed in this conversation").closest("button")!)
expect(screen.getByTestId("accordian-path")).toHaveTextContent("src/foo.ts")
})
it("renders one row per unique path when multiple files edited", () => {
const messages = [createFileEditMessage("src/a.ts", "diff a"), createFileEditMessage("src/b.ts", "diff b")]
renderPanel(messages)
expect(screen.getByText("2 file(s) changed in this conversation")).toBeInTheDocument()
// Expand panel so file rows are rendered
fireEvent.click(screen.getByText("2 file(s) changed in this conversation").closest("button")!)
const paths = screen.getAllByTestId("accordian-path")
expect(paths).toHaveLength(2)
expect(paths.map((el) => el.textContent)).toEqual(expect.arrayContaining(["src/a.ts", "src/b.ts"]))
})
it("collapsed by default: panel trigger shows chevron and expanding reveals file rows", () => {
const messages = [createFileEditMessage("src/foo.ts", "diff")]
renderPanel(messages)
// Header visible
const headerText = screen.getByText("1 file(s) changed in this conversation")
expect(headerText).toBeInTheDocument()
// Trigger is the button that contains the header text
const trigger = headerText.closest("button")
expect(trigger).toBeInTheDocument()
// Expand panel
fireEvent.click(trigger!)
expect(screen.getByTestId("accordian-path")).toHaveTextContent("src/foo.ts")
})
it("toggling a file row expand calls onToggleExpand", () => {
const messages = [createFileEditMessage("src/foo.ts", "diff")]
renderPanel(messages)
// Expand panel first so the file row is rendered
const headerText = screen.getByText("1 file(s) changed in this conversation")
fireEvent.click(headerText.closest("button")!)
const accordianToggle = screen.getByTestId("accordian-toggle")
expect(accordianToggle).toHaveTextContent("collapsed")
fireEvent.click(accordianToggle)
expect(accordianToggle).toHaveTextContent("expanded")
})
it("hides aggregate stats when no diffStats are present", () => {
const messages = [createFileEditMessage("src/a.ts", "diff a"), createFileEditMessage("src/b.ts", "diff b")]
renderPanel(messages)
expect(screen.queryByTestId("total-added")).not.toBeInTheDocument()
expect(screen.queryByTestId("total-removed")).not.toBeInTheDocument()
})
it("shows aggregated + and - totals in the header when diffStats are present", () => {
const messages = [
createFileEditMessage("src/a.ts", "diff a", { added: 3, removed: 1 }),
createFileEditMessage("src/b.ts", "diff b", { added: 2, removed: 5 }),
]
renderPanel(messages)
expect(screen.getByTestId("total-added")).toHaveTextContent("+5")
expect(screen.getByTestId("total-removed")).toHaveTextContent("-6")
})
})