fix: synchronized snapshot-tag checks and fixed ANSI width emoji handling
- Introduced a shared missingSnapshotTagMessage helper and reused it from both hashline patcher and preview validation. - Required snapshot tags in preview hashline sections for all edits, including head/tail inserts, so preview failures now match apply-time rejections. - Updated TUI width calculation to strip ANSI escapes before grapheme splitting, preserving styled ZWJ emoji width behavior.
This commit is contained in:
@@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { formatHashlineHeader, InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
||||
import { formatHashlineHeader, InMemorySnapshotStore, missingSnapshotTagMessage } from "@oh-my-pi/hashline";
|
||||
import {
|
||||
adjustIndentation,
|
||||
computeEditDiff,
|
||||
@@ -251,18 +251,40 @@ describe("computeHashlineDiff", () => {
|
||||
|
||||
test("accepts hashline input edits", async () => {
|
||||
const sourcePath = path.join(tempDir, "source.txt");
|
||||
await Bun.write(sourcePath, "first\n");
|
||||
const text = "first\n";
|
||||
await Bun.write(sourcePath, text);
|
||||
|
||||
const snapshotStore = new InMemorySnapshotStore();
|
||||
const tag = snapshotStore.record(sourcePath, text);
|
||||
const result = await computeHashlineDiff(
|
||||
{ input: `¶${sourcePath}\ninsert tail:\n+second` },
|
||||
{ input: `${formatHashlineHeader(sourcePath, tag)}\ninsert tail:\n+second` },
|
||||
tempDir,
|
||||
new InMemorySnapshotStore(),
|
||||
snapshotStore,
|
||||
);
|
||||
expect("diff" in result).toBe(true);
|
||||
if ("diff" in result) {
|
||||
expect(result.diff).toContain("second");
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects a tagless head/tail insert in the preview path, matching apply", async () => {
|
||||
const relativePath = "source.txt";
|
||||
await Bun.write(path.join(tempDir, relativePath), "first\n");
|
||||
|
||||
// A tagless `insert tail:` carries no anchored edit, yet the apply path
|
||||
// (Patcher.prepare) rejects it for the missing mandatory tag. The
|
||||
// preview/diff path MUST emit the SAME rejection so a successful preview
|
||||
// never precedes a failing apply.
|
||||
const result = await computeHashlineDiff(
|
||||
{ input: `¶${relativePath}\ninsert tail:\n+second` },
|
||||
tempDir,
|
||||
new InMemorySnapshotStore(),
|
||||
);
|
||||
expect("error" in result).toBe(true);
|
||||
if ("error" in result) {
|
||||
expect(result.error).toBe(missingSnapshotTagMessage(relativePath));
|
||||
}
|
||||
});
|
||||
test("returns a handled error when the source path is a local URL", async () => {
|
||||
const result = await computeHashlineDiff(
|
||||
{ input: "¶local://PLAN.md\ninsert tail:\n+x" },
|
||||
|
||||
Reference in New Issue
Block a user