test(hashline/payload-syntax): migrated to inline payload syntax

- Updated hashline parser tests to use inline payload syntax (e.g., `tagvpayload` instead of `tagv\npl(payload)`).
- Removed deprecated test cases for bare-blank-line and explicit-blank-payload syntax.
This commit is contained in:
can1357
2026-05-26 15:27:06 +02:00
parent 42ec4b5381
commit 076ca4e71d
2 changed files with 51 additions and 105 deletions
+50 -104
View File
@@ -88,21 +88,12 @@ describe("hashline parser — suffix-op syntax", () => {
const content = "aaa\nbbb\nccc";
it("inserts payload before/after a Lid, and at BOF/EOF", () => {
const diff = [
`${tag(2, "bbb")}↑`,
pl("before b"),
`${tag(2, "bbb")}↓`,
pl("after b"),
"BOF↓",
pl("top"),
"EOF↓",
pl("tail"),
].join("\n");
const diff = [`${tag(2, "bbb")}↑before b`, `${tag(2, "bbb")}↓after b`, "BOF↓top", "EOF↓tail"].join("\n");
expect(applyDiff(content, diff)).toBe("top\naaa\nbefore b\nbbb\nafter b\nccc\ntail");
});
it("inserts after the final line via `ANCHOR↓` instead of falling off the file", () => {
const diff = [`${tag(3, "ccc")}↓`, pl("tail")].join("\n");
const diff = `${tag(3, "ccc")}↓tail`;
expect(applyDiff(content, diff)).toBe("aaa\nbbb\nccc\ntail");
});
@@ -112,15 +103,15 @@ describe("hashline parser — suffix-op syntax", () => {
});
it("blanks a line in place with `A:` when given an explicit empty payload", () => {
const explicit = `${sameLineRange(tag(2, "bbb"))}:\n\n`;
const explicit = `${sameLineRange(tag(2, "bbb"))}:`;
expect(applyDiff(content, explicit)).toBe("aaa\n\nccc");
});
it("replaces one line or an inclusive range with payload lines", () => {
const single = [`${tag(2, "bbb")}:`, pl("BBB")].join("\n");
const single = `${tag(2, "bbb")}:BBB`;
expect(applyDiff(content, single)).toBe("aaa\nBBB\nccc");
const range = [`${tag(2, "bbb")}-${tag(3, "ccc")}:`, pl("BBB"), pl("CCC")].join("\n");
const range = [`${tag(2, "bbb")}-${tag(3, "ccc")}:BBB`, pl("CCC")].join("\n");
expect(applyDiff(content, range)).toBe("aaa\nBBB\nCCC");
});
@@ -158,28 +149,28 @@ describe("hashline parser — suffix-op syntax", () => {
it("auto-absorbs duplicated multiline prefix boundaries during replacement", () => {
const source = ["// one", "// two", "old();"].join("\n");
const diff = [`${sameLineRange(tag(3, "old();"))}:`, pl("// one"), pl("// two"), pl("new();")].join("\n");
const diff = [`${sameLineRange(tag(3, "old();"))}:// one`, pl("// two"), pl("new();")].join("\n");
expect(applyDiff(source, diff)).toBe(["// one", "// two", "new();"].join("\n"));
});
it("auto-absorbs duplicated multiline suffix boundaries during replacement", () => {
const source = ["old();", "// one", "// two"].join("\n");
const diff = [`${sameLineRange(tag(1, "old();"))}:`, pl("new();"), pl("// one"), pl("// two")].join("\n");
const diff = [`${sameLineRange(tag(1, "old();"))}:new();`, pl("// one"), pl("// two")].join("\n");
expect(applyDiff(source, diff)).toBe(["new();", "// one", "// two"].join("\n"));
});
it("auto-absorbs a duplicated single structural suffix during replacement", () => {
const source = ["old();", "};"].join("\n");
const diff = [`${sameLineRange(tag(1, "old();"))}:`, pl("new();"), pl("};")].join("\n");
const diff = [`${sameLineRange(tag(1, "old();"))}:new();`, pl("};")].join("\n");
expect(applyDiff(source, diff)).toBe(["new();", "};"].join("\n"));
});
it("auto-absorbs a duplicated single structural prefix during replacement", () => {
const source = ["};", "old();"].join("\n");
const diff = [`${sameLineRange(tag(2, "old();"))}:`, pl("};"), pl("new();")].join("\n");
const diff = [`${sameLineRange(tag(2, "old();"))}:};`, pl("new();")].join("\n");
expect(applyDiff(source, diff)).toBe(["};", "new();"].join("\n"));
});
@@ -189,14 +180,14 @@ describe("hashline parser — suffix-op syntax", () => {
// `}` is a legitimate part of the new block, not a duplicate of the file's
// existing `}`. The single-line structural absorb must NOT fire here.
const source = ["old();", "}"].join("\n");
const diff = [`${sameLineRange(tag(1, "old();"))}:`, pl("if ok {"), pl("}")].join("\n");
const diff = [`${sameLineRange(tag(1, "old();"))}:if ok {`, pl("}")].join("\n");
expect(applyDiff(source, diff)).toBe(["if ok {", "}", "}"].join("\n"));
});
it("does not auto-absorb a single duplicated boundary line", () => {
const source = ["keep", "old();"].join("\n");
const diff = [`${sameLineRange(tag(2, "old();"))}:`, pl("keep"), pl("new();")].join("\n");
const diff = [`${sameLineRange(tag(2, "old();"))}:keep`, pl("new();")].join("\n");
expect(applyDiff(source, diff)).toBe(["keep", "keep", "new();"].join("\n"));
});
@@ -206,21 +197,14 @@ describe("hashline parser — suffix-op syntax", () => {
// is also the anchor of a separate insert. Absorbing it would silently
// steal that anchor and turn the insert into a replacement.
const source = ["A", "B", "X", "Y", "Z"].join("\n");
const diff = [
`${tag(1, "A")}-${tag(2, "B")}:`,
pl("alpha"),
pl("X"),
pl("Y"),
`${tag(4, "Y")}↑`,
pl("extra"),
].join("\n");
const diff = [`${tag(1, "A")}-${tag(2, "B")}:alpha`, pl("X"), pl("Y"), `${tag(4, "Y")}↑extra`].join("\n");
expect(applyDiff(source, diff)).toBe(["alpha", "X", "Y", "X", "extra", "Y", "Z"].join("\n"));
});
it("surfaces a warning when boundary duplicates are auto-absorbed", () => {
const source = ["// one", "// two", "old();"].join("\n");
const diff = [`${sameLineRange(tag(3, "old();"))}:`, pl("// one"), pl("// two"), pl("new();")].join("\n");
const diff = [`${sameLineRange(tag(3, "old();"))}:// one`, pl("// two"), pl("new();")].join("\n");
const result = applyHashlineEdits(source, parseHashline(diff).edits);
expect(result.lines).toBe(["// one", "// two", "new();"].join("\n"));
@@ -235,41 +219,41 @@ describe("hashline parser — suffix-op syntax", () => {
// `autoDropPureInsertDuplicates` opt-in, unlike the single-line
// structural absorb covered by the test below.
const source = ["aaa", "bbb", "ccc"].join("\n");
const diff = [`${tag(2, "bbb")}↓`, pl("aaa"), pl("bbb"), pl("NEW")].join("\n");
const diff = [`${tag(2, "bbb")}↓aaa`, pl("bbb"), pl("NEW")].join("\n");
expect(applyDiff(source, diff)).toBe("aaa\nbbb\naaa\nbbb\nNEW\nccc");
});
it("auto-drops a duplicated single structural suffix for pure insert by default", () => {
const source = ["if ok {", " keep();", " }"].join("\n");
const diff = [`${tag(3, " }")}↑`, pl(" added();"), pl(" }")].join("\n");
const diff = [`${tag(3, " }")}↑ added();`, pl(" }")].join("\n");
expect(applyDiff(source, diff)).toBe(["if ok {", " keep();", " added();", " }"].join("\n"));
});
it("auto-drops a duplicated single structural prefix for pure insert by default", () => {
const source = [" });", "next();"].join("\n");
const diff = [`${tag(1, " });")}↓`, pl(" });"), pl("added();")].join("\n");
const diff = [`${tag(1, " });")}↓ });`, pl("added();")].join("\n");
expect(applyDiff(source, diff)).toBe([" });", "added();", "next();"].join("\n"));
});
it("preserves an intentional non-structural anchor duplicate for `ANCHOR↓` by default", () => {
const source = ["aaa", "bbb", "ccc"].join("\n");
const diff = [`${tag(2, "bbb")}↓`, pl("bbb"), pl("NEW")].join("\n");
const diff = [`${tag(2, "bbb")}↓bbb`, pl("NEW")].join("\n");
expect(applyDiff(source, diff)).toBe("aaa\nbbb\nbbb\nNEW\nccc");
});
it("preserves an intentional non-structural anchor duplicate for `ANCHOR↑` by default", () => {
const source = ["aaa", "bbb", "ccc"].join("\n");
const diff = [`${tag(2, "bbb")}↑`, pl("NEW"), pl("bbb")].join("\n");
const diff = [`${tag(2, "bbb")}↑NEW`, pl("bbb")].join("\n");
expect(applyDiff(source, diff)).toBe("aaa\nNEW\nbbb\nbbb\nccc");
});
it("does not drop a single structural pure-insert suffix when it preserves balance", () => {
const source = ["if outer {", "}"].join("\n");
const diff = [`${tag(2, "}")}↑`, pl("if inner {"), pl("}")].join("\n");
const diff = [`${tag(2, "}")}↑if inner {`, pl("}")].join("\n");
expect(applyDiff(source, diff)).toBe(["if outer {", "if inner {", "}", "}"].join("\n"));
});
@@ -278,7 +262,7 @@ describe("hashline parser — suffix-op syntax", () => {
// Payload echoes the two file lines AT/ABOVE the insertion point
// (aaa, bbb), then adds NEW. The leading echo is absorbed.
const source = ["aaa", "bbb", "ccc"].join("\n");
const diff = [`${tag(2, "bbb")}↓`, pl("aaa"), pl("bbb"), pl("NEW")].join("\n");
const diff = [`${tag(2, "bbb")}↓aaa`, pl("bbb"), pl("NEW")].join("\n");
expect(applyDiffWithPureInsertAutoDrop(source, diff)).toBe("aaa\nbbb\nNEW\nccc");
});
@@ -286,7 +270,7 @@ describe("hashline parser — suffix-op syntax", () => {
// Payload wraps NEW with context above (aaa, bbb) AND below (ccc, ddd).
// Both ends should be absorbed, leaving only NEW inserted after bbb.
const source = ["aaa", "bbb", "ccc", "ddd"].join("\n");
const diff = [`${tag(2, "bbb")}↓`, pl("aaa"), pl("bbb"), pl("NEW"), pl("ccc"), pl("ddd")].join("\n");
const diff = [`${tag(2, "bbb")}↓aaa`, pl("bbb"), pl("NEW"), pl("ccc"), pl("ddd")].join("\n");
expect(applyDiffWithPureInsertAutoDrop(source, diff)).toBe("aaa\nbbb\nNEW\nccc\nddd");
});
@@ -294,34 +278,34 @@ describe("hashline parser — suffix-op syntax", () => {
// Insert before line 3 ("ccc"). Trailing payload echoes the anchor and the
// line after it. Drop the trailing duplicates.
const source = ["aaa", "bbb", "ccc", "ddd"].join("\n");
const diff = [`${tag(3, "ccc")}↑`, pl("NEW"), pl("ccc"), pl("ddd")].join("\n");
const diff = [`${tag(3, "ccc")}↑NEW`, pl("ccc"), pl("ddd")].join("\n");
expect(applyDiffWithPureInsertAutoDrop(source, diff)).toBe("aaa\nbbb\nNEW\nccc\nddd");
});
it("auto-absorbs duplicated leading payload at EOF insert", () => {
const source = ["aaa", "bbb", "ccc"].join("\n");
// `EOF↓` payload echoes the last two file lines, then adds NEW.
const diff = ["EOF↓", pl("bbb"), pl("ccc"), pl("NEW")].join("\n");
const diff = ["EOF↓bbb", pl("ccc"), pl("NEW")].join("\n");
expect(applyDiffWithPureInsertAutoDrop(source, diff)).toBe("aaa\nbbb\nccc\nNEW");
});
it("auto-absorbs duplicated trailing payload at BOF insert", () => {
const source = ["aaa", "bbb", "ccc"].join("\n");
// `BOF↑` payload prepends NEW but trails with the first two file lines.
const diff = ["BOF↑", pl("NEW"), pl("aaa"), pl("bbb")].join("\n");
const diff = ["BOF↑NEW", pl("aaa"), pl("bbb")].join("\n");
expect(applyDiffWithPureInsertAutoDrop(source, diff)).toBe("NEW\naaa\nbbb\nccc");
});
it("auto-drops a single duplicated anchor line in a pure insert when generic duplicate absorption is enabled", () => {
const source = ["aaa", "bbb", "ccc"].join("\n");
const diff = [`${tag(2, "bbb")}↓`, pl("bbb"), pl("NEW")].join("\n");
const diff = [`${tag(2, "bbb")}↓bbb`, pl("NEW")].join("\n");
expect(applyDiffWithPureInsertAutoDrop(source, diff)).toBe("aaa\nbbb\nNEW\nccc");
});
it("surfaces a warning when pure-insert duplicates are auto-dropped", () => {
const source = ["aaa", "bbb", "ccc"].join("\n");
const diff = [`${tag(2, "bbb")}↓`, pl("aaa"), pl("bbb"), pl("NEW")].join("\n");
const diff = [`${tag(2, "bbb")}↓aaa`, pl("bbb"), pl("NEW")].join("\n");
const result = applyHashlineEdits(source, parseHashline(diff).edits, { autoDropPureInsertDuplicates: true });
expect(result.lines).toBe("aaa\nbbb\nNEW\nccc");
expect(result.warnings).toBeDefined();
@@ -331,20 +315,16 @@ describe("hashline parser — suffix-op syntax", () => {
});
it("preserves payload text exactly", () => {
const diff = [
`${sameLineRange(tag(2, "bbb"))}:`,
pl(""),
pl("# not a header"),
pl("+ not an op"),
pl(" spaced"),
].join("\n");
const diff = [`${sameLineRange(tag(2, "bbb"))}:`, pl("# not a header"), pl("+ not an op"), pl(" spaced")].join(
"\n",
);
expect(applyDiff(content, diff)).toBe("aaa\n\n# not a header\n+ not an op\n spaced\nccc");
});
it("treats blank lines inside a payload run as empty payload lines", () => {
// Truly blank lines inside an active payload run are verbatim empty
// payload lines as long as more payload follows.
const diff = [`${sameLineRange(tag(2, "bbb"))}:`, pl("first"), "", "", pl("after")].join("\n");
const diff = [`${sameLineRange(tag(2, "bbb"))}:first`, "", "", pl("after")].join("\n");
expect(applyDiff(content, diff)).toBe("aaa\nfirst\n\n\nafter\nccc");
});
@@ -352,14 +332,7 @@ describe("hashline parser — suffix-op syntax", () => {
// Blank lines immediately before a next op are visual separators, not
// payload. This prevents agents from silently inflating a payload and
// shifting downstream line numbers.
const diff = [
`${sameLineRange(tag(1, "aaa"))}:`,
pl("AAA"),
"",
"",
`${sameLineRange(tag(3, "ccc"))}:`,
pl("CCC"),
].join("\n");
const diff = [`${sameLineRange(tag(1, "aaa"))}:AAA`, "", "", `${sameLineRange(tag(3, "ccc"))}:CCC`].join("\n");
expect(applyDiff(content, diff)).toBe("AAA\nbbb\nCCC");
});
@@ -378,16 +351,6 @@ describe("hashline parser — suffix-op syntax", () => {
expect(() => parseHashline(pl("orphan")).edits).toThrow(/payload line has no preceding/);
});
it("leniently treats a bare blank line after ↑ / ↓ as an empty payload", () => {
const anchor = { line: 5 };
expect(parseHashline(`${tag(5, "aaa")}↑\n\n`).edits).toEqual([
{ kind: "insert", cursor: { kind: "before_anchor", anchor }, text: "", lineNum: 1, index: 0 },
]);
expect(parseHashline(`${tag(5, "aaa")}↓\n\n`).edits).toEqual([
{ kind: "insert", cursor: { kind: "after_anchor", anchor }, text: "", lineNum: 1, index: 0 },
]);
});
it("rejects op sigils written in prefix position", () => {
expect(() => parseHashline(`↑${tag(1, "aaa")}\nold`).edits).toThrow(/unrecognized op/);
expect(() => parseHashline(`↓${tag(1, "aaa")}\nold`).edits).toThrow(/unrecognized op/);
@@ -416,8 +379,8 @@ describe("hashline parser — suffix-op syntax", () => {
it("leniently strips `*`/`>` line-marker decoration from anchors", () => {
const anchor = tag(2, "bbb");
expect(applyDiff(content, [`*${anchor}:`, pl("BBB")].join("\n"))).toBe("aaa\nBBB\nccc");
expect(applyDiff(content, [`>${anchor}↑`, pl("X")].join("\n"))).toBe("aaa\nX\nbbb\nccc");
expect(applyDiff(content, `*${anchor}:BBB`)).toBe("aaa\nBBB\nccc");
expect(applyDiff(content, `>${anchor}↑X`)).toBe("aaa\nX\nbbb\nccc");
});
it("rejects arrow replace syntax as an unrecognized payload line", () => {
@@ -446,7 +409,7 @@ describe("hashline — file hash binding", () => {
});
it("applies line-number edits without per-anchor hash validation", () => {
const diff = [`${sameLineRange(tag(2, "bbb"))}:`, pl("BBB")].join("\n");
const diff = `${sameLineRange(tag(2, "bbb"))}:BBB`;
expect(applyDiff("aaa\nbbb\nccc", diff)).toBe("aaa\nBBB\nccc");
});
});
@@ -512,7 +475,7 @@ describe("splitHashlineInput — ¶ headers", () => {
describe("hashline executor", () => {
it("creates a missing file with a file-scoped insert", async () => {
await withTempDir(async tempDir => {
const input = `¶new.ts\nBOF↓\n${pl("export const x = 1;")}\n`;
const input = `¶new.ts\nBOF↓${pl("export const x = 1;")}\n`;
const result = await executeHashlineSingle(hashlineExecuteOptions(tempDir, input));
expect(result.content[0]?.type === "text" ? result.content[0].text : "").toContain("new.ts:");
expect(await Bun.file(path.join(tempDir, "new.ts")).text()).toBe("export const x = 1;");
@@ -523,7 +486,7 @@ describe("hashline executor", () => {
await withTempDir(async tempDir => {
const filePath = path.join(tempDir, "a.ts");
const source = ["aaa", "bbb", "ccc"].join("\n");
const input = `${header("a.ts", source)}\n${tag(2, "bbb")}↓\n${pl("aaa")}\n${pl("bbb")}\n${pl("NEW")}\n`;
const input = `${header("a.ts", source)}\n${tag(2, "bbb")}↓${pl("aaa")}\n${pl("bbb")}\n${pl("NEW")}\n`;
await Bun.write(filePath, source);
await executeHashlineSingle(hashlineExecuteOptions(tempDir, input));
@@ -546,11 +509,9 @@ describe("hashline executor", () => {
const bHeader = "¶b.ts#0000";
const input = [
header("a.ts", "aaa\n"),
`${sameLineRange(tag(1, "aaa"))}:`,
pl("AAA"),
`${sameLineRange(tag(1, "aaa"))}:AAA`,
bHeader,
`${sameLineRange(tag(1, "bbb"))}:`,
pl("BBB"),
`${sameLineRange(tag(1, "bbb"))}:BBB`,
].join("\n");
await expect(executeHashlineSingle(hashlineExecuteOptions(tempDir, input))).rejects.toThrow(
@@ -574,8 +535,7 @@ describe("hashline executor", () => {
// validation outright.
const input = [
header("a.ts", `${original}\n`),
`${sameLineRange(tag(2, "L2"))}:`,
pl("L2a"),
`${sameLineRange(tag(2, "L2"))}:L2a`,
pl("L2b"),
pl("L2c"),
pl("L2d"),
@@ -585,8 +545,7 @@ describe("hashline executor", () => {
pl("L2h"),
pl("L2i"),
header("a.ts", `${original}\n`),
`${tag(8, "L8")}↓`,
pl("INSERTED"),
`${tag(8, "L8")}↓INSERTED`,
].join("\n");
await executeHashlineSingle(hashlineExecuteOptions(tempDir, input));
@@ -689,7 +648,7 @@ describe("hashline — anchor-stale recovery via read snapshot cache", () => {
await Bun.write(filePath, `${v1Lines.join("\n")}\n`);
// Model authors anchor against V0 — line 2 is "L2" in V0.
const input = `${header("a.ts", v0Text)}\n${sameLineRange(tag(2, "L2"))}:\n${pl("L2-MODEL")}\n`;
const input = `${header("a.ts", v0Text)}\n${sameLineRange(tag(2, "L2"))}:${pl("L2-MODEL")}\n`;
const result = await executeHashlineSingle(hashlineExecuteOptions(tempDir, input, undefined, session));
const finalLines = (await Bun.file(filePath).text()).replace(/\n$/, "").split("\n");
@@ -724,7 +683,7 @@ describe("hashline — anchor-stale recovery via read snapshot cache", () => {
v1Lines[5] = "L6-CHANGED";
await Bun.write(filePath, `${v1Lines.join("\n")}\n`);
const input = `${header("a.ts", v0Text)}\n${sameLineRange(tag(6, "L6"))}:\n${pl("L6-MODEL")}\n`;
const input = `${header("a.ts", v0Text)}\n${sameLineRange(tag(6, "L6"))}:${pl("L6-MODEL")}\n`;
await expect(
executeHashlineSingle(hashlineExecuteOptions(tempDir, input, undefined, session)),
).rejects.toThrow(HashlineMismatchError);
@@ -745,7 +704,7 @@ describe("hashline — anchor-stale recovery via read snapshot cache", () => {
// Live file is completely different — patch context cannot match even
// with fuzz tolerance.
const currentText = "totally\nunrelated\ncontent\nhere\nnow\n";
const edits = parseHashline(`${sameLineRange(tag(2, "beta"))}:\n${pl("BETA-MODEL")}`).edits;
const edits = parseHashline(`${sameLineRange(tag(2, "beta"))}:${pl("BETA-MODEL")}`).edits;
const recovered = tryRecoverHashlineWithCache({
cache,
@@ -783,7 +742,7 @@ describe("hashline — anchor-stale recovery via read snapshot cache", () => {
// First edit: change line 2 : BETA. After the write, the cache should
// reflect V1 (post-edit), not V0.
const firstInput = `${header("a.ts", v0Text)}\n${sameLineRange(tag(2, "beta"))}:\n${pl("BETA")}\n`;
const firstInput = `${header("a.ts", v0Text)}\n${sameLineRange(tag(2, "beta"))}:${pl("BETA")}\n`;
await executeHashlineSingle(hashlineExecuteOptions(tempDir, firstInput, undefined, session));
const v1Lines = ["alpha", "BETA", "gamma", "delta", "epsilon"];
expect(await Bun.file(filePath).text()).toBe(`${v1Lines.join("\n")}\n`);
@@ -799,7 +758,7 @@ describe("hashline — anchor-stale recovery via read snapshot cache", () => {
const v2Lines = ["H1", "H2", "H3", "H4", "H5", "H6", "H7", ...v1Lines];
await Bun.write(filePath, `${v2Lines.join("\n")}\n`);
const secondInput = `${header("a.ts", `${v1Lines.join("\n")}\n`)}\n${sameLineRange(tag(3, "gamma"))}:\n${pl("GAMMA")}\n`;
const secondInput = `${header("a.ts", `${v1Lines.join("\n")}\n`)}\n${sameLineRange(tag(3, "gamma"))}:${pl("GAMMA")}\n`;
const result = await executeHashlineSingle(hashlineExecuteOptions(tempDir, secondInput, undefined, session));
const finalLines = (await Bun.file(filePath).text()).replace(/\n$/, "").split("\n");
@@ -832,7 +791,7 @@ describe("hashline — anchor-stale recovery via read snapshot cache", () => {
absolutePath: fakePath,
currentText,
fileHash: computeFileHash(v0Text),
edits: parseHashline(`10:\nL10-EDITED`).edits,
edits: parseHashline(`10:L10-EDITED`).edits,
options: {},
});
@@ -893,7 +852,7 @@ describe("hashline *** Abort recovery sentinel (harmony-leak mitigation)", () =>
const sentinel = "*** Abort";
it("parser breaks at *** Abort and surfaces a warning", () => {
const diff = [`${tag(1, "alpha")}↓`, pl("HELLO"), sentinel, `${tag(99, "junk")}↓`, pl("never")].join("\n");
const diff = [`${tag(1, "alpha")}↓HELLO`, sentinel, `${tag(99, "junk")}↓never`].join("\n");
const { edits, warnings } = parseHashline(diff);
expect(edits).toHaveLength(1);
expect(edits[0]).toMatchObject({ kind: "insert", text: "HELLO" });
@@ -903,7 +862,7 @@ describe("hashline *** Abort recovery sentinel (harmony-leak mitigation)", () =>
it("appended sentinel from harmony-leak truncation: ops above are preserved", () => {
// Mirrors the exact shape harmony-leak emits inside a single section.
const diff = `${tag(1, "alpha")}↓\n${pl("KEPT")}\n*** Abort\n`;
const diff = `${tag(1, "alpha")}↓${pl("KEPT")}\n*** Abort\n`;
const { edits, warnings } = parseHashline(diff);
expect(edits).toHaveLength(1);
expect(edits[0]).toMatchObject({ text: "KEPT" });
@@ -927,7 +886,7 @@ describe("hashline *** Abort recovery sentinel (harmony-leak mitigation)", () =>
});
it("clean input without sentinel produces no warning", () => {
const diff = `${tag(1, "alpha")}↓\n${pl("PAYLOAD")}\n`;
const diff = `${tag(1, "alpha")}↓${pl("PAYLOAD")}\n`;
const { warnings } = parseHashline(diff);
expect(warnings).toEqual([]);
});
@@ -952,12 +911,6 @@ describe("hashline parser — bare ':' replaces with a single blank line", () =>
expect(applyDiff(text, diff)).toBe("line1\nreplacement\nline3\n");
});
it("A: with explicit blank payload line also replaces with blank", () => {
const text = "line1\nline2\nline3\n";
const { diff } = splitHashlineInput(`${header("a.ts", text)}\n2:\n\n`);
expect(applyDiff(text, diff)).toBe("line1\n\nline3\n");
});
it("bare A↑ still inserts a blank line above", () => {
const text = "line1\nline2\nline3\n";
const { diff } = splitHashlineInput(`${header("a.ts", text)}\n2↑\n`);
@@ -1023,15 +976,8 @@ describe("hashline parser — blank line is a separator before next op", () => {
it("blank line inside payload between two content lines is preserved", () => {
const text = "a\nb\nc\n";
const ops = `${header("a.ts", text)}\n2:\nfirst\n\nsecond\n`;
const ops = `${header("a.ts", text)}\n2:first\n\nsecond\n`;
const { diff } = splitHashlineInput(ops);
expect(applyDiff(text, diff)).toBe("a\nfirst\n\nsecond\nc\n");
});
it("trailing blank in payload at EOF is preserved (explicit blank replace)", () => {
const text = "a\nb\nc\n";
const ops = `${header("a.ts", text)}\n2:\n\n`;
const { diff } = splitHashlineInput(ops);
expect(applyDiff(text, diff)).toBe("a\n\nc\n");
});
});
+1 -1
View File
@@ -238,7 +238,7 @@ describe("computeHashlineDiff", () => {
// `1:` with the same line as payload is a true no-op: the edit
// fires through computeHashlineDiff but produces identical content.
const input = `${sourcePath}#${computeFileHash(`${line}\n`)}\n1:\n${line}\n`;
const input = `${sourcePath}#${computeFileHash(`${line}\n`)}\n1:${line}\n`;
const result = await computeHashlineDiff({ input }, tempDir);
expect("error" in result).toBe(true);
if ("error" in result) {