Files
kandev/apps/web/components/task/changes-panel.test.ts
T
João Salavisa 9fe2cbd874 fix: source commit pushed status from git remote, not PR commits (#833)
* fix: source commit pushed status from git remote, not PR commits

Commits showed as "not pushed" whenever the branch had no open PR,
since the UI derived pushed status by matching local SHAs against
the PR's commit list. Sourcing it from the upstream tracking ref in
agentctl's GetLog keeps the answer correct without a PR, after
rebases, and across multi-repo workspaces.

* fix: treat PR-matched commits as pushed alongside the git remote signal

mergeCommits now flags a commit as pushed when EITHER the backend's
git-sourced flag is true OR a PR commit matches by SHA. The git signal
fixes the original "no PR = unpushed" bug; the PR signal preserves the
"PR exists ⇒ commit is on remote" assumption that the E2E mocks rely
on (and that holds in practice, since GitHub PRs require pushed
branches). Also corrects the validation-path comment in
markPushedCommits to describe the actual non-branch fall-through.

* perf: bound rev-list walk in markPushedCommits to result length

Without -n the walk was unbounded — a branch with many local-only
commits would traverse full unpushed history per GetLog call. Capping
at len(commits) keeps the two walks proportional. rev-list walks
newest-first like GetLog, so the cap covers every commit we mark.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-05-05 22:09:02 +01:00

146 lines
5.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { filterUnpushedCommits, mergeCommits } from "./changes-panel";
const makeLocal = (sha: string, message = "msg") => ({
commit_sha: sha,
commit_message: message,
insertions: 1,
deletions: 0,
});
const makePR = (sha: string) => ({
sha,
message: "msg",
author_login: "user",
author_date: "2026-03-29T00:00:00Z",
});
describe("filterUnpushedCommits", () => {
it("returns empty when all local commits are in PR", () => {
const local = [makeLocal("abc1234def"), makeLocal("fff9999aaa")];
const pr = [makePR("abc1234def567890"), makePR("fff9999aaa123456")];
expect(filterUnpushedCommits(local, pr)).toEqual([]);
});
it("returns only unpushed commits", () => {
const local = [makeLocal("abc1234def"), makeLocal("fff9999aaa")];
const pr = [makePR("abc1234def567890")];
expect(filterUnpushedCommits(local, pr)).toEqual([makeLocal("fff9999aaa")]);
});
it("returns all local commits when no PR commits exist", () => {
const local = [makeLocal("abc1234def"), makeLocal("fff9999aaa")];
expect(filterUnpushedCommits(local, [])).toEqual(local);
});
it("returns empty when no local commits exist", () => {
const pr = [makePR("abc1234def567890")];
expect(filterUnpushedCommits([], pr)).toEqual([]);
});
});
const makePRFull = (sha: string, message = "msg", author = "user") => ({
sha,
message,
author_login: author,
author_date: "2026-03-29T00:00:00Z",
additions: 5,
deletions: 2,
files_changed: 1,
});
describe("mergeCommits", () => {
it("marks all local commits as unpushed when no PR commits exist", () => {
const local = [makeLocal("aaa1111", "first"), makeLocal("bbb2222", "second")];
const result = mergeCommits(local, []);
expect(result).toEqual([
{
commit_sha: "aaa1111",
commit_message: "first",
insertions: 1,
deletions: 0,
pushed: false,
},
{
commit_sha: "bbb2222",
commit_message: "second",
insertions: 1,
deletions: 0,
pushed: false,
},
]);
});
it("trusts the backend pushed flag even with no PR (the bug fix)", () => {
// The original bug: local commits were marked unpushed whenever no PR
// existed, because pushed was derived from PR-SHA matching alone.
// Sourcing pushed from git on the backend means commits pushed to a
// branch without a PR show as pushed.
const local = [
{ ...makeLocal("aaa1111", "pushed"), pushed: true },
{ ...makeLocal("bbb2222", "local"), pushed: false },
];
const result = mergeCommits(local, []);
expect(result[0]).toMatchObject({ commit_sha: "bbb2222", pushed: false });
expect(result[1]).toMatchObject({ commit_sha: "aaa1111", pushed: true });
});
it("backend pushed flag wins over PR-SHA mismatch (post-rebase scenario)", () => {
// After a rebase the local SHA no longer matches the PR's SHA, but the
// commit IS on the remote (force-pushed). The backend's pushed=true
// must override the missing PR match.
const local = [{ ...makeLocal("rebased1", "rebased"), pushed: true }];
const pr = [makePRFull("oldsha9999", "rebased")];
const result = mergeCommits(local, pr);
expect(result[0]).toMatchObject({ commit_sha: "rebased1", pushed: true });
// PR-only SHA still appears, since it isn't matched.
expect(result).toHaveLength(2);
expect(result[1]).toMatchObject({ commit_sha: "oldsha9999", pushed: true });
});
it("falls back to PR-SHA matching when backend pushed flag is absent", () => {
// Older commit_created notifications don't carry the pushed flag.
// PR matching kicks in as a backstop.
const local = [makeLocal("aaa1111", "first")];
const pr = [makePRFull("aaa1111bbbccc", "first", "user")];
const result = mergeCommits(local, pr);
expect(result).toEqual([
{ commit_sha: "aaa1111", commit_message: "first", insertions: 1, deletions: 0, pushed: true },
]);
});
it("includes PR-only commits (from other contributors) as pushed", () => {
const local: ReturnType<typeof makeLocal>[] = [];
const pr = [makePRFull("ccc3333", "external fix", "other-dev")];
const result = mergeCommits(local, pr);
expect(result).toEqual([
{
commit_sha: "ccc3333",
commit_message: "external fix",
insertions: 5,
deletions: 2,
pushed: true,
},
]);
});
it("orders unpushed commits first, then pushed", () => {
const local = [makeLocal("aaa1111", "pushed one"), makeLocal("bbb2222", "unpushed one")];
const pr = [makePRFull("aaa1111fff", "pushed one")];
const result = mergeCommits(local, pr);
expect(result[0]).toMatchObject({ commit_sha: "bbb2222", pushed: false });
expect(result[1]).toMatchObject({ commit_sha: "aaa1111", pushed: true });
});
it("handles mixed local, matched, and PR-only commits", () => {
const local = [makeLocal("aaa1111", "local pushed"), makeLocal("bbb2222", "local only")];
const pr = [makePRFull("aaa1111fff", "local pushed"), makePRFull("ddd4444", "external")];
const result = mergeCommits(local, pr);
// unpushed first, then pushed (local matched + PR-only)
expect(result).toHaveLength(3);
expect(result[0]).toMatchObject({ commit_sha: "bbb2222", pushed: false });
expect(result[1]).toMatchObject({ commit_sha: "aaa1111", pushed: true });
expect(result[2]).toMatchObject({ commit_sha: "ddd4444", pushed: true });
});
});