Files

179 lines
6.5 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 PR_DATE = "2026-03-29T00:00:00Z";
const WORKSPACE_ID = "workspace-1";
const PR_OWNER = "acme";
const PR_REPO = "widget";
const REMOTE_SHA = "ccc3333";
const makePR = (sha: string) => ({
sha,
message: "msg",
author_login: "user",
author_date: PR_DATE,
});
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: PR_DATE,
additions: 5,
deletions: 2,
files_changed: 1,
workspace_id: WORKSPACE_ID,
owner: PR_OWNER,
repo: PR_REPO,
});
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).toMatchObject([
{
commit_sha: "aaa1111",
pushed: false,
statsAvailable: true,
detailTarget: { source: "local", sha: "aaa1111" },
},
{
commit_sha: "bbb2222",
pushed: false,
statsAvailable: true,
detailTarget: { source: "local", sha: "bbb2222" },
},
]);
});
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).toMatchObject([
{
commit_sha: "aaa1111",
pushed: true,
statsAvailable: true,
detailTarget: { source: "local", sha: "aaa1111" },
},
]);
});
it("includes PR-only commits (from other contributors) as pushed", () => {
const local: ReturnType<typeof makeLocal>[] = [];
const pr = [makePRFull(REMOTE_SHA, "external fix", "other-dev")];
const result = mergeCommits(local, pr);
expect(result).toMatchObject([
{
commit_sha: REMOTE_SHA,
commit_message: "external fix",
pushed: true,
committed_at: PR_DATE,
statsAvailable: false,
detailTarget: {
source: "github",
sha: REMOTE_SHA,
workspaceId: WORKSPACE_ID,
owner: PR_OWNER,
repo: PR_REPO,
},
},
]);
});
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 the provider timeline newest-first. The shared
// local row stays in its provider position and the provider-only row is
// explicitly marked as current PR history.
expect(result).toHaveLength(3);
expect(result[0]).toMatchObject({ commit_sha: "bbb2222", pushed: false });
expect(result[1]).toMatchObject({ commit_sha: "ddd4444", pushed: true });
expect(result[2]).toMatchObject({ commit_sha: "aaa1111", pushed: true });
});
it("propagates committed_at from local commits", () => {
const local = [{ ...makeLocal("aaa1111"), committed_at: "2026-05-31T10:00:00Z" }];
const result = mergeCommits(local, []);
expect(result[0].committed_at).toBe("2026-05-31T10:00:00Z");
});
it("forwards author_date as committed_at for PR-only commits", () => {
const pr = [makePRFull("ccc3333", "external fix", "other-dev")];
const result = mergeCommits([], pr);
expect(result[0].committed_at).toBe(PR_DATE);
});
});