examples/live-notes Public
import assert from "node:assert/strict";import { describe, test } from "node:test";import { OpError, apply, compose, del, diff, insert, isNoop, transform, transformPast, type Op } from "../src/ops.ts";import { randomOp, rng } from "./helpers/random.ts";describe("apply", () => { test("inserts and deletes at an offset", () => { assert.equal(apply("milk eggs", insert(5, "bread ")), "milk bread eggs"); assert.equal(apply("milk bread eggs", del(4, 6)), "milk eggs"); assert.equal(apply("", insert(0, "hello")), "hello"); }); test("refuses an op that does not fit the document", () => { assert.throws(() => apply("abc", insert(4, "x")), OpError); assert.throws(() => apply("abc", insert(-1, "x")), OpError); assert.throws(() => apply("abc", del(2, 2)), OpError); assert.throws(() => apply("abc", del(1.5, 1)), OpError); });});describe("transform", () => { test("an insert before another shifts it right", () => { assert.deepEqual(transform(insert(5, "b"), insert(2, "aa"), true), insert(7, "b")); assert.deepEqual(transform(insert(2, "aa"), insert(5, "b"), false), insert(2, "aa")); }); test("inserts at the same offset are ordered by the room", () => { // The logged op came first, so the incoming one goes after it. assert.deepEqual(transform(insert(3, "B"), insert(3, "A"), true), insert(4, "B")); // An editor moving the room's op past its own pending insert keeps the room's op first. assert.deepEqual(transform(insert(3, "A"), insert(3, "B"), false), insert(3, "A")); }); test("an insert after a delete moves left by the deleted length", () => { assert.deepEqual(transform(insert(8, "x"), del(2, 3), true), insert(5, "x")); assert.deepEqual(transform(insert(2, "x"), del(2, 3), true), insert(2, "x")); assert.deepEqual(transform(insert(5, "x"), del(2, 3), true), insert(2, "x")); }); test("an insert inside a deleted range is dropped and the delete swallows it", () => { const ins = insert(3, "XY"); const d = del(1, 4); const ins2 = transform(ins, d, true); assert.ok(isNoop(ins2)); assert.deepEqual(transform(d, ins, false), del(1, 6)); assert.equal(apply(apply("abcdefg", d), ins2), "afg"); assert.equal(apply(apply("abcdefg", ins), transform(d, ins, false)), "afg"); }); test("overlapping deletes remove the text once", () => { assert.deepEqual(transform(del(1, 3), del(2, 4), true), del(1, 1)); assert.deepEqual(transform(del(2, 4), del(1, 3), false), del(1, 2)); assert.deepEqual(transform(del(2, 2), del(1, 5), true), del(1, 0)); assert.deepEqual(transform(del(6, 2), del(1, 3), true), del(3, 2)); assert.deepEqual(transform(del(0, 1), del(1, 3), true), del(0, 1)); }); test("two concurrent ops converge in either order", () => { const next = rng(7); for (let i = 0; i < 5000; i++) { const doc = "the quick brown fox".slice(0, Math.floor(next() * 20)); const a = randomOp(doc, next); const b = randomOp(doc, next); // The room sequenced a first: b is moved past a. An editor that already applied b // receives a and moves it past b. const viaA = apply(apply(doc, a), transform(b, a, true)); const viaB = apply(apply(doc, b), transform(a, b, false)); assert.equal(viaA, viaB, `doc ${JSON.stringify(doc)} a ${JSON.stringify(a)} b ${JSON.stringify(b)}`); } });});describe("transformPast", () => { test("moves an op past a run of logged ops in order", () => { const logged: Op[] = [insert(0, "## "), del(10, 2), insert(3, "Groceries\n")]; assert.deepEqual(transformPast(insert(12, "!"), logged), insert(23, "!")); });});describe("diff", () => { test("finds a typed character", () => { assert.deepEqual(diff("helo", "hello"), [insert(3, "l")]); }); test("finds a deleted selection", () => { assert.deepEqual(diff("milk, eggs", "milk"), [del(4, 6)]); }); test("a replaced selection is a delete then an insert", () => { const ops = diff("buy milk today", "buy oat milk today"); assert.deepEqual(ops, [insert(4, "oat ")]); const replaced = diff("buy milk today", "buy bread today"); assert.deepEqual(replaced, [del(4, 4), insert(4, "bread")]); assert.equal(replaced.reduce(apply, "buy milk today"), "buy bread today"); }); test("no change, no ops", () => { assert.deepEqual(diff("same", "same"), []); });});describe("compose", () => { test("folds typing and backspacing into one op", () => { assert.deepEqual(compose(insert(4, "o"), insert(5, "a")), insert(4, "oa")); assert.deepEqual(compose(insert(4, "oat"), del(6, 1)), insert(4, "oa")); assert.deepEqual(compose(del(9, 1), del(8, 1)), del(8, 2)); assert.deepEqual(compose(del(3, 1), del(3, 2)), del(3, 3)); }); test("leaves ops in separate places alone", () => { assert.equal(compose(insert(4, "o"), insert(9, "x")), null); assert.equal(compose(insert(4, "oat"), del(2, 3)), null); assert.equal(compose(del(3, 1), insert(3, "x")), null); assert.equal(compose(del(3, 1), del(6, 1)), null); }); test("a composed op has the effect of the pair", () => { const next = rng(11); for (let i = 0; i < 5000; i++) { const doc = "jump over the lazy dog".slice(0, Math.floor(next() * 23)); const a = randomOp(doc, next); const mid = apply(doc, a); const b = randomOp(mid, next); const c = compose(a, b); if (c) assert.equal(apply(doc, c), apply(mid, b), `doc ${JSON.stringify(doc)} a ${JSON.stringify(a)} b ${JSON.stringify(b)}`); } });});