Files
hyperframes/packages/studio-server/src/helpers/applyFileMutations.test.ts
Miguel Ángel f92d017b99 feat(cli): add timeline editing verbs (#4221)
* feat(cli): add timeline editing verbs

* chore: satisfy fallow complexity gate

* test(cli): cover snap rounding at known fps

* test(cli): cover snap rounding at known fps

* test(cli): cover snap rounding at known fps

* test(cli): cover snap rounding at known fps

* fix(cli): derive split ids from stable refs

* fix(cli): remove timeline gate blockers

* fix(cli): close freshness and output gate gaps

* fix(cli): close remaining timeline review findings

* refactor(cli): simplify mutation decision flow

* refactor(cli): split trim and apply paths

* refactor(cli): isolate trim validation

* fix(cli): satisfy timeline lint gate

* style: format timeline mutation files

* fix(cli): preserve mutation result types

* fix(cli): close timeline receipt and nested bounds gaps

* fix(studio): rollback writes that fail during write

* fix(cli): bound moved clips by end time
2026-09-20 18:04:13 -04:00

81 lines
2.8 KiB
TypeScript

import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { describe, expect, it } from "vitest";
import { applyFileMutations } from "./applyFileMutations.js";
import { fileContentVersion, identifyFileWrite, resetFileWriteReceipts } from "./fileVersion.js";
function expectStaleMutation(after: string): void {
const projectDir = mkdtempSync(join(tmpdir(), "hf-mutation-version-"));
const path = join(projectDir, "index.html");
try {
writeFileSync(path, "before", "utf8");
const expectedVersion = fileContentVersion(readFileSync(path, "utf8"));
writeFileSync(path, "external", "utf8");
expect(() =>
applyFileMutations(projectDir, [
{ sourceFile: "index.html", absPath: path, before: "before", after, expectedVersion },
]),
).toThrow("file changed since the timeline was read");
expect(readFileSync(path, "utf8")).toBe("external");
} finally {
rmSync(projectDir, { recursive: true, force: true });
}
}
describe("applyFileMutations", () => {
it("refuses a file whose version changed since the caller read it", () => {
expectStaleMutation("after");
});
it("refuses a stale no-op instead of silently accepting it", () => {
expectStaleMutation("before");
});
it("clears receipts for writes rolled back after a partial batch", () => {
resetFileWriteReceipts();
const projectDir = mkdtempSync(join(tmpdir(), "hf-mutation-rollback-"));
const first = join(projectDir, "first.html");
const second = join(projectDir, "second.html");
try {
writeFileSync(first, "first-before", "utf8");
writeFileSync(second, "second-before", "utf8");
let writes = 0;
expect(() =>
applyFileMutations(
projectDir,
[
{
sourceFile: "first.html",
absPath: first,
before: "first-before",
after: "first-after",
},
{
sourceFile: "second.html",
absPath: second,
before: "second-before",
after: "second-after",
},
],
undefined,
(path, content, encoding) => {
writes += 1;
if (writes === 2) {
writeFileSync(path, "second-partial", encoding);
throw new Error("second write failed");
}
writeFileSync(path, content, encoding);
},
),
).toThrow("second write failed");
expect(readFileSync(first, "utf8")).toBe("first-before");
expect(readFileSync(second, "utf8")).toBe("second-before");
expect(identifyFileWrite(first, fileContentVersion("first-after"))).toBeNull();
} finally {
resetFileWriteReceipts();
rmSync(projectDir, { recursive: true, force: true });
}
});
});