vault-link/frontend/test-support/file-application.test.ts
2026-09-21 21:29:56 +01:00

219 lines
8.3 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import { fixture, bytes } from "./sync-fixture";
import { toStoredSnapshot } from "../sync-client/src/sync-operations/content";
import { LocalChangesDuringReconciliation } from "../sync-client/src/errors/errors";
for (const equal of [false, true])
test(`swaps preserve identities with ${equal ? "equal" : "distinct"} content and save once`, async () => {
const f = await fixture({ "a.md": "A", "b.md": equal ? "A" : "B" });
let saves = 0;
f.persistence.boundary = (label) => {
if (label === "before:save") saves++;
};
const next = structuredClone(f.database.state);
next.local = { a: "b.md", b: "a.md" };
await f.files.apply(next);
assert.deepEqual(f.database.state.local, next.local);
assert.deepEqual(
f.disk.userFiles(),
new Map([
["a.md", bytes(equal ? "A" : "B")],
["b.md", bytes("A")]
])
);
assert.equal(saves, 1);
assert.deepEqual(await f.disk.listFilesRecursively(), ["a.md", "b.md"]);
});
for (const direction of ["file-to-directory", "directory-to-file"])
test(direction, async () => {
const f = await fixture(
direction === "file-to-directory"
? { folder: "A", "b.md": "B" }
: { "a.md": "A", "folder/b.md": "B" }
);
const ids = Object.keys(f.database.state.local);
const next = structuredClone(f.database.state);
next.local =
direction === "file-to-directory"
? { [ids[0]]: "folder/a.md", [ids[1]]: "b.md" }
: { [ids[0]]: "folder", [ids[1]]: "other.md" };
const expected = structuredClone(next.local);
await f.files.apply(next);
assert.deepEqual(f.database.state.local, expected);
assert.deepEqual(
new Set(f.disk.userFiles().keys()),
new Set(Object.values(f.database.state.local))
);
assert.deepEqual(
[...f.disk.userFiles().values()]
.map((value) => Buffer.from(value).toString())
.sort(),
["A", "B"]
);
});
test("an unexpected occupant is preserved under a visible conflict name", async () => {
const f = await fixture();
await f.disk.userWrite("remote.md", bytes("unexpected"));
const next = structuredClone(f.database.state);
next.local.a = "remote.md";
await f.files.apply(next);
assert.equal(f.database.state.local.a, "remote.md");
assert.equal(f.disk.userFiles().size, 2);
assert.deepEqual(
[...f.disk.userFiles().values()]
.map((value) => Buffer.from(value).toString())
.sort(),
["local", "unexpected"]
);
assert.equal(Object.keys(f.database.state.local).length, 2);
});
test("content edited after planning is merged with the incoming snapshot", async () => {
const f = await fixture({ "a.md": "base\n" });
const expected = (await f.files.snapshot("a.md"))!;
await f.disk.userWrite("a.md", bytes("base\nlocal\n"));
await f.files.apply(structuredClone(f.database.state), {
a: {
expected,
replacement: await toStoredSnapshot({
content: bytes("remote\nbase\n")
})
}
});
assert.deepEqual(
f.disk.userFiles().get("a.md"),
bytes("remote\nbase\nlocal\n")
);
});
test("deleting the source after planning does not resurrect it", async () => {
const f = await fixture();
const next = structuredClone(f.database.state);
next.local.a = "remote.md";
await f.disk.userDelete("a.md");
await f.files.apply(next, {
a: { replacement: await toStoredSnapshot({ content: bytes("remote") }) }
});
assert.deepEqual(f.disk.userFiles(), new Map());
assert.deepEqual(f.database.state.local, {});
});
for (const protect of ["ignore", "size"] as const)
test(`protect ${protect} at application time`, async () => {
const f = await fixture();
const next = structuredClone(f.database.state);
next.local.a = "remote.md";
if (protect === "ignore")
await f.settings.setSetting("ignorePatterns", ["a.md"]);
else await f.settings.setSetting("maxFileSizeMB", 0);
await f.files.apply(next, {
a: {
replacement: await toStoredSnapshot({
content: bytes("remote")
})
}
});
assert.deepEqual(
f.disk.userFiles(),
new Map([["a.md", bytes("local")]])
);
assert.equal(f.database.state.local.a, "a.md");
assert.equal(f.database.state.excluded?.a, "a.md");
});
for (const when of ["planning", "installed"])
test(`notified move during ${when} is attributed to the right identity on rescan`, async () => {
const f = await fixture({ "a.md": "A", "b.md": "B" });
let changed = false;
f.disk.boundary = async (label) => {
if (
changed ||
label !==
(when === "planning"
? "read:stat:a.md"
: "visible:rename:a.md->b.md")
)
return;
changed = true;
const oldPath = when === "planning" ? "a.md" : "b.md";
await f.disk.userRename(oldPath, "editor.md");
await f.syncer.syncLocallyUpdatedFile({
oldPath,
relativePath: "editor.md"
});
};
const next = structuredClone(f.database.state);
next.local = { a: "b.md", b: "a.md" };
if (when === "planning")
await assert.rejects(
f.files.apply(next),
LocalChangesDuringReconciliation
);
else await f.files.apply(next);
assert(changed);
await f.internals.scan();
assert.equal(f.database.state.local.a, "editor.md");
assert.deepEqual(
f.disk.userFiles().get(f.database.state.local.a),
bytes("A")
);
assert.deepEqual(
f.disk.userFiles().get(f.database.state.local.b),
bytes("B")
);
});
for (const action of ["move", "delete"] as const)
test(`notified ${action}/recreate between file operations does not repurpose the replacement file`, async () => {
const f = await fixture({ "a.md": "A", "b.md": "B" });
await f.settings.setSetting("ignorePatterns", ["private.md"]);
let changed = false;
f.disk.boundary = async (label) => {
if (changed || label !== "visible:rename:a.md->remote-a.md") return;
changed = true;
if (action === "move") {
await f.disk.userRename("b.md", "private.md");
await f.syncer.syncLocallyUpdatedFile({
oldPath: "b.md",
relativePath: "private.md"
});
} else {
await f.disk.userDelete("b.md");
await f.syncer.syncLocallyDeletedFile("b.md");
}
await f.disk.userWrite("b.md", bytes("replacement"));
await f.syncer.syncLocallyCreatedFile("b.md");
};
const next = structuredClone(f.database.state);
next.local = { a: "remote-a.md", b: "remote-b.md" };
await f.files.apply(next);
assert(changed);
await f.internals.scan();
assert.deepEqual(f.disk.userFiles().get("b.md"), bytes("replacement"));
assert.equal(f.disk.userFiles().has("remote-b.md"), false);
assert.equal(
f.database.state.local.b,
action === "move" ? "private.md" : undefined
);
if (action === "move")
assert.deepEqual(f.disk.userFiles().get("private.md"), bytes("B"));
});
test("a file and nested directory exchange their exact paths", async () => {
const f = await fixture({ a: "A", "b/deep/child": "B" });
const [a, b] = Object.keys(f.database.state.local);
const next = structuredClone(f.database.state);
next.local = { [a]: "b", [b]: "a/deep/child" };
await f.files.apply(next);
assert.deepEqual(f.database.state.local, { [a]: "b", [b]: "a/deep/child" });
assert.deepEqual(
f.disk.userFiles(),
new Map([
["b", bytes("A")],
["a/deep/child", bytes("B")]
])
);
});