Rebasely Beta: data may be purged

examples/live-notes Public

TypeScript · 128 lines · 5.4 KiB · at bd4f325

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)}`);    }  });});