mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
fix(run-files): refresh patch diffs when scope changes
This commit is contained in:
parent
8b7d5bd16c
commit
bfb633ba6e
2 changed files with 109 additions and 24 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { afterEach, describe, expect, mock, test } from "bun:test";
|
||||
import { useRef } from "react";
|
||||
import TestRenderer, { act } from "react-test-renderer";
|
||||
import { MemoryRouter, Route, Routes } from "react-router";
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ const useRunFilesCalls: any[] = [];
|
|||
|
||||
const multiFileDiffCalls: any[] = [];
|
||||
const patchDiffCalls: any[] = [];
|
||||
let patchDiffMountSeq = 0;
|
||||
const virtualizerCalls: any[] = [];
|
||||
const providerCalls: any[] = [];
|
||||
const mountedRenderers: TestRenderer.ReactTestRenderer[] = [];
|
||||
|
|
@ -20,8 +22,13 @@ mock.module("@pierre/diffs/react", () => ({
|
|||
return <div data-pierre-multi="true">{props.newFile.name}</div>;
|
||||
},
|
||||
PatchDiff: (props: any) => {
|
||||
patchDiffCalls.push(props);
|
||||
return <div data-pierre-patch="true">{props.patch}</div>;
|
||||
const mountId = useRef(++patchDiffMountSeq);
|
||||
patchDiffCalls.push({ ...props, mountId: mountId.current });
|
||||
return (
|
||||
<div data-pierre-patch="true" data-mount-id={mountId.current}>
|
||||
{props.patch}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
Virtualizer: (props: any) => {
|
||||
virtualizerCalls.push(props);
|
||||
|
|
@ -89,19 +96,46 @@ function makePayload(count: number, source = "sandbox") {
|
|||
};
|
||||
}
|
||||
|
||||
function makePatchPayload(patch: string) {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
change_kind: "modified",
|
||||
old_file: { name: "docs/live.md", contents: null },
|
||||
new_file: { name: "docs/live.md", contents: null },
|
||||
unified_patch: patch,
|
||||
},
|
||||
],
|
||||
source: "sandbox",
|
||||
meta: {
|
||||
degraded: false,
|
||||
degraded_reason: null,
|
||||
total_changed: 1,
|
||||
stats: { additions: 1, deletions: 0 },
|
||||
truncated: false,
|
||||
to_sha: "abc1234",
|
||||
to_sha_committed_at: "2026-05-05T12:00:00Z",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function runFilesTree(initialEntry = "/runs/run_1/files") {
|
||||
return (
|
||||
<ToastProvider>
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<Routes>
|
||||
<Route path="/runs/:id/files" element={<RunFiles />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function renderRunFiles(initialEntry = "/runs/run_1/files") {
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
let renderer: TestRenderer.ReactTestRenderer | undefined;
|
||||
act(() => {
|
||||
renderer = TestRenderer.create(
|
||||
<ToastProvider>
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<Routes>
|
||||
<Route path="/runs/:id/files" element={<RunFiles />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</ToastProvider>,
|
||||
);
|
||||
renderer = TestRenderer.create(runFilesTree(initialEntry));
|
||||
});
|
||||
mountedRenderers.push(renderer!);
|
||||
return renderer!;
|
||||
|
|
@ -118,6 +152,7 @@ describe("RunFiles rendering", () => {
|
|||
currentRunStatus = "succeeded";
|
||||
multiFileDiffCalls.length = 0;
|
||||
patchDiffCalls.length = 0;
|
||||
patchDiffMountSeq = 0;
|
||||
virtualizerCalls.length = 0;
|
||||
providerCalls.length = 0;
|
||||
useRunFilesCalls.length = 0;
|
||||
|
|
@ -147,7 +182,7 @@ describe("RunFiles rendering", () => {
|
|||
const sandboxRenderer = renderRunFiles();
|
||||
expect(
|
||||
sandboxRenderer.root.findAllByProps({ "aria-label": "Diff scope" }),
|
||||
).toHaveLength(1);
|
||||
).not.toHaveLength(0);
|
||||
|
||||
act(() => sandboxRenderer.unmount());
|
||||
mountedRenderers.pop();
|
||||
|
|
@ -176,15 +211,7 @@ describe("RunFiles rendering", () => {
|
|||
const firstNewKey = multiFileDiffCalls[0].newFile.cacheKey;
|
||||
|
||||
act(() => {
|
||||
renderer.update(
|
||||
<ToastProvider>
|
||||
<MemoryRouter initialEntries={["/runs/run_1/files"]}>
|
||||
<Routes>
|
||||
<Route path="/runs/:id/files" element={<RunFiles />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</ToastProvider>,
|
||||
);
|
||||
renderer.update(runFilesTree());
|
||||
});
|
||||
|
||||
const lastCall = multiFileDiffCalls[multiFileDiffCalls.length - 1];
|
||||
|
|
@ -194,4 +221,24 @@ describe("RunFiles rendering", () => {
|
|||
expect(firstNewKey).toContain("fabro-run-file:run_1:abc1234:new:src/file-0.ts:");
|
||||
expect(lastCall.options).not.toHaveProperty("theme");
|
||||
});
|
||||
|
||||
test("remounts patch diffs when the patch changes for the same file", () => {
|
||||
currentFilesPayload = makePatchPayload(
|
||||
"diff --git a/docs/live.md b/docs/live.md\n@@ -1 +1 @@\n+committed\n",
|
||||
);
|
||||
|
||||
const renderer = renderRunFiles("/runs/run_1/files?scope=all");
|
||||
const firstMountId = patchDiffCalls[0].mountId;
|
||||
|
||||
currentFilesPayload = makePatchPayload(
|
||||
"diff --git a/docs/live.md b/docs/live.md\n@@ -1,0 +1,2 @@\n+committed\n+uncommitted\n",
|
||||
);
|
||||
act(() => {
|
||||
renderer.update(runFilesTree("/runs/run_1/files?scope=all"));
|
||||
});
|
||||
|
||||
const lastCall = patchDiffCalls[patchDiffCalls.length - 1];
|
||||
expect(lastCall.patch).toContain("+uncommitted");
|
||||
expect(lastCall.mountId).not.toBe(firstMountId);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import {
|
|||
} from "./run-files/states";
|
||||
import { useFileKeyboardNav } from "./run-files/keyboard";
|
||||
import { Toolbar, type DiffStyle } from "./run-files/toolbar";
|
||||
import { fileCacheKey } from "./run-files/cache-keys";
|
||||
import { fileCacheKey, stringHash } from "./run-files/cache-keys";
|
||||
import { VirtualizedDiffList } from "./run-files/virtualized-diff-list";
|
||||
import { ApiError, extractRequestId } from "../lib/api-client";
|
||||
import { useRun, useRunFiles } from "../lib/queries";
|
||||
|
|
@ -211,6 +211,33 @@ interface RunFileRowProps {
|
|||
toSha: string | null | undefined;
|
||||
}
|
||||
|
||||
function fileDiffRenderKey({
|
||||
file,
|
||||
index,
|
||||
scope,
|
||||
toSha,
|
||||
}: {
|
||||
file: ApiFileDiff;
|
||||
index: number;
|
||||
scope: RunFileScope;
|
||||
toSha: string | null | undefined;
|
||||
}): string {
|
||||
const display = file.new_file.name || file.old_file.name || `file-${index}`;
|
||||
const oldContents = file.old_file.contents ?? "";
|
||||
const newContents = file.new_file.contents ?? "";
|
||||
const contentFingerprint = file.unified_patch
|
||||
? `patch:${stringHash(file.unified_patch)}`
|
||||
: `contents:${stringHash(oldContents)}:${stringHash(newContents)}`;
|
||||
return [
|
||||
scope,
|
||||
toSha ?? "no-sha",
|
||||
display,
|
||||
index,
|
||||
file.change_kind ?? "modified",
|
||||
contentFingerprint,
|
||||
].join(":");
|
||||
}
|
||||
|
||||
const RunFileRow = memo(function RunFileRow({
|
||||
file,
|
||||
diffStyle,
|
||||
|
|
@ -280,7 +307,13 @@ const RunFileRow = memo(function RunFileRow({
|
|||
/>
|
||||
);
|
||||
} else if (patch) {
|
||||
body = <PatchDiff patch={patch} options={patchOptions} />;
|
||||
body = (
|
||||
<PatchDiff
|
||||
key={stringHash(patch)}
|
||||
patch={patch}
|
||||
options={patchOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -564,7 +597,12 @@ export default function RunFiles() {
|
|||
(file.new_file.name === hashFile || file.old_file.name === hashFile);
|
||||
return (
|
||||
<RunFileRow
|
||||
key={`${display}-${idx}`}
|
||||
key={fileDiffRenderKey({
|
||||
file,
|
||||
index: idx,
|
||||
scope: effectiveScope,
|
||||
toSha: meta.to_sha,
|
||||
})}
|
||||
file={file}
|
||||
diffStyle={diffStyle}
|
||||
isDeepLinkTarget={isDeepLinkTarget}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue