import { describe, it, expect } from "vitest"; import { buildFingerprint, resolve, shift, type OffsetRange } from "../src/anchorer"; const DOC = "line0\nline1 needle here\nline2\nneedle again on line3\n"; describe("buildFingerprint", () => { it("captures the exact text, bounded context, and 0-based lineHint", () => { const start = DOC.indexOf("needle here"); const range: OffsetRange = { start, end: start + "needle".length }; const fp = buildFingerprint(DOC, range); expect(fp.text).toBe("needle"); expect(fp.lineHint).toBe(1); expect(fp.before.endsWith("line1 ")).toBe(true); expect(fp.after.startsWith(" here")).toBe(true); expect(fp.before.length).toBeLessThanOrEqual(120); expect(fp.after.length).toBeLessThanOrEqual(120); }); }); describe("resolve", () => { it("exact-unique: returns the single occurrence's range", () => { const fp = { text: "line2", before: "", after: "", lineHint: 0 }; const start = DOC.indexOf("line2"); expect(resolve(DOC, fp)).toEqual({ start, end: start + 5 }); }); it("context-disambiguated: picks the occurrence whose before/after match", () => { const fp = { text: "needle", before: "line1 ", after: " here", lineHint: 99 }; const start = DOC.indexOf("needle here"); expect(resolve(DOC, fp)).toEqual({ start, end: start + 6 }); }); it("lineHint tiebreak: when context does not disambiguate, the closest line wins", () => { // both occurrences of 'needle', no usable context, hint points at line 3 const fp = { text: "needle", before: "", after: "", lineHint: 3 }; const start = DOC.indexOf("needle again"); expect(resolve(DOC, fp)).toEqual({ start, end: start + 6 }); }); it("orphaned: returns 'orphaned' when the text is gone", () => { const fp = { text: "absent-text", before: "", after: "", lineHint: 0 }; expect(resolve(DOC, fp)).toBe("orphaned"); }); it("orphaned: returns 'orphaned' rather than guessing on an unbreakable tie (INV-1)", () => { const doc = "needle\n....\nneedle\n"; // two identical occurrences, equidistant from hint const fp = { text: "needle", before: "", after: "", lineHint: 1 }; expect(resolve(doc, fp)).toBe("orphaned"); }); }); describe("shift", () => { it("edit entirely before the range shifts both endpoints by the delta", () => { const r: OffsetRange = { start: 10, end: 15 }; // insert 2 chars at offset 0 (replace [0,0) with len 2) expect(shift(r, { start: 0, end: 0, newLength: 2 })).toEqual({ start: 12, end: 17 }); }); it("edit entirely after the range leaves it unchanged", () => { const r: OffsetRange = { start: 2, end: 5 }; expect(shift(r, { start: 10, end: 12, newLength: 0 })).toEqual({ start: 2, end: 5 }); }); it("edit overlapping the range clamps the touched endpoints to the edit start", () => { const r: OffsetRange = { start: 5, end: 10 }; // replace [3,7) with 1 char (delta = -3) expect(shift(r, { start: 3, end: 7, newLength: 1 })).toEqual({ start: 3, end: 7 }); }); });