mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
fix(desktop): render v2 patch metadata (#42716)
This commit is contained in:
@@ -3,7 +3,43 @@ import { patchFiles } from "./apply-patch-file"
|
||||
import { text } from "./session-diff"
|
||||
|
||||
describe("apply patch file", () => {
|
||||
test("parses patch metadata from the server", () => {
|
||||
test("parses v2 patch metadata", () => {
|
||||
const file = patchFiles([
|
||||
{
|
||||
file: "a.ts",
|
||||
status: "modified",
|
||||
patch:
|
||||
"Index: a.ts\n===================================================================\n--- a.ts\n+++ a.ts\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
},
|
||||
])[0]
|
||||
|
||||
expect(file).toBeDefined()
|
||||
expect(file?.filePath).toBe("a.ts")
|
||||
expect(file?.relativePath).toBe("a.ts")
|
||||
expect(file?.type).toBe("update")
|
||||
expect(file?.view.fileDiff.name).toBe("a.ts")
|
||||
expect(file?.view.fileDiff.isPartial).toBe(true)
|
||||
expect(text(file.view, "deletions")).toBe("one\ntwo\n")
|
||||
expect(text(file.view, "additions")).toBe("one\nthree\n")
|
||||
})
|
||||
|
||||
test("maps all v2 patch statuses", () => {
|
||||
expect(
|
||||
patchFiles([
|
||||
{ file: "added.ts", status: "added", patch: "+one", additions: 1, deletions: 0 },
|
||||
{ file: "deleted.ts", status: "deleted", patch: "-one", additions: 0, deletions: 1 },
|
||||
{ file: "modified.ts", status: "modified", patch: "-one\n+two", additions: 1, deletions: 1 },
|
||||
]).map((file) => ({ file: file.filePath, type: file.type })),
|
||||
).toEqual([
|
||||
{ file: "added.ts", type: "add" },
|
||||
{ file: "deleted.ts", type: "delete" },
|
||||
{ file: "modified.ts", type: "update" },
|
||||
])
|
||||
})
|
||||
|
||||
test("parses legacy patch metadata", () => {
|
||||
const file = patchFiles([
|
||||
{
|
||||
filePath: "/tmp/a.ts",
|
||||
@@ -19,8 +55,8 @@ describe("apply patch file", () => {
|
||||
expect(file).toBeDefined()
|
||||
expect(file?.view.fileDiff.name).toBe("a.ts")
|
||||
expect(file?.view.fileDiff.isPartial).toBe(false)
|
||||
expect(text(file!.view, "deletions")).toBe("one\ntwo\n")
|
||||
expect(text(file!.view, "additions")).toBe("one\nthree\n")
|
||||
expect(text(file.view, "deletions")).toBe("one\ntwo\n")
|
||||
expect(text(file.view, "additions")).toBe("one\nthree\n")
|
||||
})
|
||||
|
||||
test("keeps legacy before and after payloads working", () => {
|
||||
@@ -37,7 +73,7 @@ describe("apply patch file", () => {
|
||||
])[0]
|
||||
|
||||
expect(file).toBeDefined()
|
||||
expect(text(file!.view, "deletions")).toBe("one\n")
|
||||
expect(text(file!.view, "additions")).toBe("two\n")
|
||||
expect(text(file.view, "deletions")).toBe("one\n")
|
||||
expect(text(file.view, "additions")).toBe("two\n")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,9 +3,11 @@ import { normalize, type ViewDiff } from "./session-diff"
|
||||
type Kind = "add" | "update" | "delete" | "move"
|
||||
|
||||
type Raw = {
|
||||
file?: string
|
||||
filePath?: string
|
||||
relativePath?: string
|
||||
type?: Kind
|
||||
status?: "added" | "deleted" | "modified"
|
||||
patch?: string
|
||||
diff?: string
|
||||
before?: string
|
||||
@@ -27,6 +29,10 @@ export type ApplyPatchFile = {
|
||||
|
||||
function kind(value: unknown) {
|
||||
if (value === "add" || value === "update" || value === "delete" || value === "move") return value
|
||||
if (value === "added") return "add"
|
||||
if (value === "deleted") return "delete"
|
||||
if (value === "modified") return "update"
|
||||
return undefined
|
||||
}
|
||||
|
||||
function status(type: Kind): "added" | "deleted" | "modified" {
|
||||
@@ -36,18 +42,19 @@ function status(type: Kind): "added" | "deleted" | "modified" {
|
||||
}
|
||||
|
||||
export function patchFile(raw: unknown): ApplyPatchFile | undefined {
|
||||
if (!raw || typeof raw !== "object") return
|
||||
if (!raw || typeof raw !== "object") return undefined
|
||||
|
||||
const value = raw as Raw
|
||||
const type = kind(value.type)
|
||||
const filePath = typeof value.filePath === "string" ? value.filePath : undefined
|
||||
const type = kind(value.type) ?? kind(value.status)
|
||||
const filePath =
|
||||
typeof value.filePath === "string" ? value.filePath : typeof value.file === "string" ? value.file : undefined
|
||||
const relativePath = typeof value.relativePath === "string" ? value.relativePath : filePath
|
||||
const patch = typeof value.patch === "string" ? value.patch : typeof value.diff === "string" ? value.diff : undefined
|
||||
const before = typeof value.before === "string" ? value.before : undefined
|
||||
const after = typeof value.after === "string" ? value.after : undefined
|
||||
|
||||
if (!type || !filePath || !relativePath) return
|
||||
if (!patch && before === undefined && after === undefined) return
|
||||
if (!type || !filePath || !relativePath) return undefined
|
||||
if (!patch && before === undefined && after === undefined) return undefined
|
||||
|
||||
const additions = typeof value.additions === "number" ? value.additions : 0
|
||||
const deletions = typeof value.deletions === "number" ? value.deletions : 0
|
||||
|
||||
@@ -26,6 +26,21 @@ describe("partDefaultOpen", () => {
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
test("collapses v2 patches containing only deleted files when enabled", () => {
|
||||
expect(
|
||||
partDefaultOpen(
|
||||
tool("patch", {
|
||||
files: [
|
||||
{ file: "one.ts", status: "deleted" },
|
||||
{ file: "two.ts", status: "deleted" },
|
||||
],
|
||||
}),
|
||||
false,
|
||||
true,
|
||||
),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
test("keeps mixed patches expanded when enabled", () => {
|
||||
expect(
|
||||
partDefaultOpen(
|
||||
@@ -41,6 +56,21 @@ describe("partDefaultOpen", () => {
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("keeps mixed v2 patches expanded when enabled", () => {
|
||||
expect(
|
||||
partDefaultOpen(
|
||||
tool("patch", {
|
||||
files: [
|
||||
{ file: "one.ts", status: "deleted" },
|
||||
{ file: "two.ts", status: "modified" },
|
||||
],
|
||||
}),
|
||||
false,
|
||||
true,
|
||||
),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test("preserves shell defaults", () => {
|
||||
expect(partDefaultOpen(tool("shell", {}), true, false)).toBe(true)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,12 @@ function deletionOnly(part: ToolPart) {
|
||||
|
||||
const files = metadata.files
|
||||
if (Array.isArray(files) && files.length > 0) {
|
||||
return files.every((file) => !!file && typeof file === "object" && "type" in file && file.type === "delete")
|
||||
return files.every(
|
||||
(file) =>
|
||||
!!file &&
|
||||
typeof file === "object" &&
|
||||
(("type" in file && file.type === "delete") || ("status" in file && file.status === "deleted")),
|
||||
)
|
||||
}
|
||||
|
||||
const filediff = metadata.filediff
|
||||
@@ -16,11 +21,12 @@ function deletionOnly(part: ToolPart) {
|
||||
return filediff.additions === 0 && typeof filediff.deletions === "number" && filediff.deletions > 0
|
||||
}
|
||||
|
||||
export function partDefaultOpen(part: PartType, shell = false, edit = false) {
|
||||
if (part.type !== "tool") return
|
||||
export function partDefaultOpen(part: PartType, shell = false, edit = false): boolean | undefined {
|
||||
if (part.type !== "tool") return undefined
|
||||
if (part.tool === "bash" || part.tool === "shell") return shell
|
||||
if (part.tool === "edit" || part.tool === "write" || part.tool === "patch" || part.tool === "apply_patch") {
|
||||
if (!edit) return false
|
||||
return !deletionOnly(part)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user