Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const { diffAtom, pagedDiffAtom } = await vi.hoisted(async () => {
};
});

const mocks = vi.hoisted(() => ({ paged: false }));
const mocks = vi.hoisted(() => ({ paged: false, diffCommits: [] as Array<string | undefined> }));

vi.mock(import("~/state/use-atom-command"), async (original) => ({
...(await original()),
Expand All @@ -62,8 +62,10 @@ vi.mock(import("~/state/pullRequests"), async (original) => {
...actual,
pullRequestEnvironment: {
...actual.pullRequestEnvironment,
diff: (({ input }: { input: { cursor?: string } }) =>
mocks.paged ? pagedDiffAtom(input.cursor ?? "first") : diffAtom) as never,
diff: (({ input }: { input: { cursor?: string; commit?: string } }) => {
mocks.diffCommits.push(input.commit);
return mocks.paged ? pagedDiffAtom(input.cursor ?? "first") : diffAtom;
}) as never,
},
};
});
Expand Down Expand Up @@ -125,6 +127,7 @@ const detail: PullRequestDetailView = {

function setup({ paged }: { paged: boolean }) {
mocks.paged = paged;
mocks.diffCommits = [];
}

const tab = (
Expand Down Expand Up @@ -199,3 +202,37 @@ it("pages forward to a file asked for that is not loaded yet, then scrolls to it

await expectFileHeaderShown(screen, container, "late.ts");
});

it("never requests the diff of a selected commit the pull request no longer has", async () => {
setup({ paged: false });
const container = document.createElement("div");
container.className = "flex h-[700px] w-[900px]";
// The timeline can open a commit that a force-push removed from the pull request.
await using screen = await render(
<PullRequestCodeTab
scrollerRef={() => {}}
environmentId={EnvironmentId.make("environment")}
reference={detail}
detail={{
...detail,
commits: [
{
oid: "kept",
messageHeadline: "Kept commit",
committedDate: "2026-09-01T00:00:00Z",
},
],
}}
selectedCommitOid="gone"
onSelectedCommitChange={() => {}}
onRefresh={() => {}}
/>,
{ container },
);

await expect.element(screen.getByText("1 file", { exact: true })).toBeVisible();
await expect
.element(screen.getByRole("button", { name: "Diff scope: All commits" }))
.toBeVisible();
expect(mocks.diffCommits).not.toContain("gone");
});
13 changes: 6 additions & 7 deletions apps/web/src/components/pullRequest/PullRequestCodeTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,12 @@ function PullRequestCodeTab({
}>({ key: "", cursor: null, slices: NO_SLICES });

const referenceKey = pullRequestReviewKey(reference);
const commit = selectedCommitOid;
// A commit opened from the timeline or left selected across a force-push may no longer be
// part of the pull request; read it as "All commits" instead of requesting its diff.
const commit =
selectedCommitOid !== null && detail.commits.some((entry) => entry.oid === selectedCommitOid)
? selectedCommitOid
: null;
const scopeKey = JSON.stringify([environmentId, referenceKey, commit]);
// The panel keeps this mounted across pull requests, so an open composer would otherwise
// survive the switch and attach its comment to whichever one is on screen when it is sent.
Expand Down Expand Up @@ -983,7 +988,6 @@ function PullRequestCodeTab({
</div>
);

// Clear obsolete commit scopes even when an empty list hides the dropdown.
const selectedCommit = orderedCommits.find((entry) => entry.oid === commit);
const scopeLabel = selectedCommit ? selectedCommit.messageHeadline : "All commits";
const changeIgnoreWhitespace = (next: boolean) => {
Expand Down Expand Up @@ -1297,11 +1301,6 @@ function PullRequestCodeTab({
answerReveal();
// oxlint-disable-next-line react/exhaustive-effect-dependencies -- Re-checked as pages load until the requested file arrives.
}, [canLoadNextSlice, items, revealRequest]);
useEffect(() => {
if (commit !== null && selectedCommit === undefined) {
onSelectedCommitChange(null);
}
}, [commit, onSelectedCommitChange, selectedCommit]);
const withToolbar = (body: ReactNode) => (
<div className="flex h-full min-h-0 flex-col">
{toolbar}
Expand Down
Loading