357 lines · 15.9 KB
import { describe, expect, test } from "bun:test";
import { mergeText } from "../git/merge3";
import type { MergeConflict } from "../git/merge";
import { acceptGenerated, assembleResolution, baseCandidates, redactSecrets, restoreSecrets } from "./candidates";
import { applyChoices, parseConflictText } from "~/lib/conflict-blocks";
import { above, buildClefRequest, decideFile, DEFAULT_THRESHOLDS } from "./decide";
import type { ClefRequest, ClefResponse } from "./decide";
import { mockProvider } from "./mock";
import { DEFAULT_LIMITS, isInterleaving, planMergeFix } from "./plan";
import type { MergeFixProvider } from "./plan";
import { isStale, resolveMode, selectResolutions, shouldAutoApply } from "./proposal";

const labels = { ours: "feature", theirs: "main" };
const base = "a\nb\nc\nd\ne\n";
const ours = "a\nB-ours\nc\nd\ne\n";
const theirs = "a\nB-theirs\nc\nd\ne\n";

function conflictFor(baseText: string, ourText: string, theirText: string, path = "src/app.ts"): MergeConflict {
  const merged = mergeText(baseText, ourText, theirText, labels);
  return { path, kind: "content", content: merged.text, hunks: merged.hunks };
}

const plain = { title: "Rename b", body: "Updates b" };
const noopMeter = async () => {};

describe("hunk capture and parsing", () => {
  test("mergeText records base, ours and theirs per hunk", () => {
    const merged = mergeText(base, ours, theirs, labels);
    expect(merged.hunks).toEqual([{ base: "b\n", ours: "B-ours\n", theirs: "B-theirs\n" }]);
    const parsed = parseConflictText(merged.text);
    expect(parsed?.blocks).toEqual([{ ours: "B-ours\n", theirs: "B-theirs\n" }]);
  });

  test("assembles a clean file from chosen texts", () => {
    const conflict = conflictFor(base, ours, theirs);
    const text = assembleResolution(conflict.content ?? "", conflict.hunks ?? [], ["B-both\n"]);
    expect(text).toBe("a\nB-both\nc\nd\ne\n");
  });

  test("refuses when a choice reintroduces markers or blocks do not line up", () => {