124 lines
4.2 KiB
TypeScript
124 lines
4.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import * as Automerge from "@automerge/automerge";
|
|
import {
|
|
AUTOMERGE_CHANGES_ENCODING,
|
|
AUTOMERGE_SNAPSHOT_ENCODING,
|
|
createAutomergeFieldValue,
|
|
createAutomergeFieldChanges,
|
|
encodeAutomergeFieldWrite,
|
|
isAutomergeFieldDoc,
|
|
loadAutomergeFieldDoc,
|
|
materializeAutomergeFieldValue,
|
|
mergeAutomergeFieldValues,
|
|
saveAutomergeFieldDoc,
|
|
} from "../src/crdt-automerge.js";
|
|
|
|
const type = "camino.CrdtJson";
|
|
|
|
test("Automerge CRDT fields explicitly create stored envelopes", () => {
|
|
const stored = createAutomergeFieldValue({ title: "draft" }, type);
|
|
|
|
assert.equal(stored.$caminoCrdtType, type);
|
|
assert.equal(stored.$caminoCrdtEncoding, AUTOMERGE_SNAPSHOT_ENCODING);
|
|
assert.deepEqual(materializeAutomergeFieldValue(stored, type), {
|
|
title: "draft",
|
|
});
|
|
});
|
|
|
|
test("Automerge CRDT field writes reject plain values", () => {
|
|
assert.throws(
|
|
() => encodeAutomergeFieldWrite({ title: "draft" }, type),
|
|
/CRDT field writes must use an Automerge document/,
|
|
);
|
|
});
|
|
|
|
test("Automerge CRDT fields encode rehydrated docs for writes", () => {
|
|
const base = loadAutomergeFieldDoc(
|
|
createAutomergeFieldValue({ title: "draft" }, type),
|
|
type,
|
|
);
|
|
const changed = Automerge.change(base, (doc) => {
|
|
(doc.value as { title: string }).title = "edited";
|
|
});
|
|
|
|
assert.equal(isAutomergeFieldDoc(changed), true);
|
|
|
|
const stored = encodeAutomergeFieldWrite(changed, type);
|
|
|
|
assert.deepEqual(materializeAutomergeFieldValue(stored, type), {
|
|
title: "edited",
|
|
});
|
|
});
|
|
|
|
test("Automerge CRDT fields merge divergent saved documents", () => {
|
|
const base = loadAutomergeFieldDoc(
|
|
createAutomergeFieldValue({ left: false, right: false }, type),
|
|
type,
|
|
);
|
|
const left = Automerge.change(Automerge.clone(base, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), (doc) => {
|
|
(doc.value as { left: boolean }).left = true;
|
|
});
|
|
const right = Automerge.change(Automerge.clone(base, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), (doc) => {
|
|
(doc.value as { right: boolean }).right = true;
|
|
});
|
|
|
|
const result = mergeAutomergeFieldValues({
|
|
existing: saveAutomergeFieldDoc(left, type),
|
|
incoming: saveAutomergeFieldDoc(right, type),
|
|
type,
|
|
});
|
|
|
|
assert.deepEqual(result.materialized, { left: true, right: true });
|
|
assert.deepEqual(materializeAutomergeFieldValue(result.stored, type), {
|
|
left: true,
|
|
right: true,
|
|
});
|
|
});
|
|
|
|
test("Automerge CRDT fields encode and apply incremental changes", () => {
|
|
const stored = createAutomergeFieldValue({ title: "draft" }, type);
|
|
const base = loadAutomergeFieldDoc(stored, type);
|
|
const changed = Automerge.change(base, (doc) => {
|
|
(doc.value as { title: string }).title = "edited";
|
|
});
|
|
const update = createAutomergeFieldChanges(base, changed, type);
|
|
|
|
assert.equal(update.$caminoCrdtEncoding, AUTOMERGE_CHANGES_ENCODING);
|
|
assert.ok(update.$caminoCrdtPayload.length < saveAutomergeFieldDoc(changed, type).$caminoCrdtPayload.length);
|
|
|
|
const result = mergeAutomergeFieldValues({ existing: stored, incoming: update, type });
|
|
assert.deepEqual(result.materialized, { title: "edited" });
|
|
assert.equal(result.stored.$caminoCrdtEncoding, AUTOMERGE_SNAPSHOT_ENCODING);
|
|
});
|
|
|
|
test("Automerge change application is idempotent", () => {
|
|
const stored = createAutomergeFieldValue({ count: 0 }, type);
|
|
const base = loadAutomergeFieldDoc(stored, type);
|
|
const changed = Automerge.change(base, (doc) => {
|
|
(doc.value as { count: number }).count = 1;
|
|
});
|
|
const update = createAutomergeFieldChanges(base, changed, type);
|
|
|
|
const once = mergeAutomergeFieldValues({ existing: stored, incoming: update, type });
|
|
const twice = mergeAutomergeFieldValues({ existing: once.stored, incoming: update, type });
|
|
assert.deepEqual(twice.materialized, { count: 1 });
|
|
});
|
|
|
|
test("Automerge change batches reject malformed framing", () => {
|
|
const stored = createAutomergeFieldValue({ count: 0 }, type);
|
|
assert.throws(
|
|
() =>
|
|
mergeAutomergeFieldValues({
|
|
existing: stored,
|
|
incoming: {
|
|
$caminoCrdtType: type,
|
|
$caminoCrdtEncoding: AUTOMERGE_CHANGES_ENCODING,
|
|
$caminoCrdtPayload: Buffer.from([0, 0, 0, 1]).toString("base64"),
|
|
},
|
|
type,
|
|
}),
|
|
/missing change length/,
|
|
);
|
|
});
|