diff --git a/ui/src/components/agents/utils/diffUtils.test.ts b/ui/src/components/agents/utils/diffUtils.test.ts new file mode 100644 index 00000000..86430873 --- /dev/null +++ b/ui/src/components/agents/utils/diffUtils.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest" + +import { fileContentsCacheKey } from "./diffUtils" + +describe("fileContentsCacheKey", () => { + it("builds a stable key from string contents", () => { + const key = fileContentsCacheKey("src/a.ts", "new", "hello") + expect(key.startsWith("src/a.ts:new:5:")).toBe(true) + expect(fileContentsCacheKey("src/a.ts", "new", "hello")).toBe(key) + }) + + it("treats null contents as empty instead of crashing", () => { + // Binary/oversized/added/removed blobs arrive as null from the backend; the + // reviews page still computes this key in an unconditional useMemo. + expect(() => fileContentsCacheKey("bin.png", "old", null)).not.toThrow() + expect(fileContentsCacheKey("bin.png", "old", null)).toBe( + fileContentsCacheKey("bin.png", "old", "") + ) + }) + + it("treats undefined contents as empty", () => { + expect(() => + fileContentsCacheKey("bin.png", "new", undefined) + ).not.toThrow() + expect(fileContentsCacheKey("bin.png", "new", undefined)).toBe( + fileContentsCacheKey("bin.png", "new", "") + ) + }) +}) diff --git a/ui/src/components/agents/utils/diffUtils.ts b/ui/src/components/agents/utils/diffUtils.ts index 5c9c8fd0..c502f86e 100644 --- a/ui/src/components/agents/utils/diffUtils.ts +++ b/ui/src/components/agents/utils/diffUtils.ts @@ -137,13 +137,16 @@ function hashFileContents(contents: string): string { } // Stable per-file content key so the worker pool dedupes highlight work across -// re-renders instead of re-tokenizing identical content. +// re-renders instead of re-tokenizing identical content. Added/removed/binary/ +// oversized blobs arrive as null (see pr_diff.py); coerce to "" so the key never +// dereferences null — these files don't render a diff, so the exact key is moot. export function fileContentsCacheKey( path: string, side: "old" | "new", - contents: string + contents: string | null | undefined ): string { - return `${path}:${side}:${contents.length}:${hashFileContents(contents)}` + const text = contents ?? "" + return `${path}:${side}:${text.length}:${hashFileContents(text)}` } let highlighterWarmup: Promise | null = null