examples/live-notes Public
import assert from "node:assert/strict";import { describe, test } from "node:test";import { del, insert, type Op } from "../src/ops.ts";import { LOG_SIZE, MAX_LENGTH, Room } from "../src/room.ts";import { MemoryStore } from "./helpers/memory-store.ts";const send = (base: number, op: Op) => JSON.stringify({ type: "op", base, op });describe("Room", () => { test("a new note is empty at version 0", () => { const room = new Room(new MemoryStore()); assert.deepEqual(room.snapshot(), { type: "snapshot", text: "", version: 0 }); }); test("applies an op, acks the sender and broadcasts it to the others", () => { const store = new MemoryStore(); const room = new Room(store); const out = room.receive(send(0, insert(0, "milk")), "otter"); assert.deepEqual(out.reply, { type: "ack", version: 1 }); assert.deepEqual(out.broadcast, { type: "op", version: 1, op: insert(0, "milk"), by: "otter" }); assert.deepEqual(store.note, { text: "milk", version: 1 }); }); test("transforms an op past the ops its sender had not seen", () => { const room = new Room(new MemoryStore({ text: "milk\n", version: 0 })); // Two editors at version 0: one adds a line at the top, the other appends. room.receive(send(0, insert(0, "eggs\n")), "otter"); const out = room.receive(send(0, insert(5, "bread\n")), "heron"); assert.deepEqual(out.broadcast, { type: "op", version: 2, op: insert(10, "bread\n"), by: "heron" }); assert.equal(room.note.text, "eggs\nmilk\nbread\n"); }); test("acks an op that another edit already made pointless without bumping the version", () => { const room = new Room(new MemoryStore({ text: "milk eggs", version: 0 })); room.receive(send(0, del(4, 5)), "otter"); const out = room.receive(send(0, del(5, 4)), "otter"); assert.deepEqual(out, { reply: { type: "ack", version: 1 } }); assert.equal(room.note.text, "milk"); }); test("sends a snapshot to an editor further behind than the log reaches", () => { const room = new Room(new MemoryStore()); for (let v = 0; v < LOG_SIZE + 1; v++) room.receive(send(v, insert(v, "x")), "otter"); const out = room.receive(send(0, insert(0, "late")), "otter"); assert.equal(out.reply.type, "snapshot"); assert.equal(out.broadcast, undefined); // One version less behind is still within the log. assert.equal(room.receive(send(1, insert(0, "y")), "otter").reply.type, "ack"); }); test("sends a snapshot for a base version from the future", () => { const room = new Room(new MemoryStore()); assert.deepEqual(room.receive(send(3, insert(0, "x")), "otter").reply, { type: "snapshot", text: "", version: 0 }); }); test("sends a snapshot when an op does not fit the note", () => { const store = new MemoryStore({ text: "abc", version: 4 }); const room = new Room(store); assert.equal(room.receive(send(4, del(2, 5)), "otter").reply.type, "snapshot"); assert.deepEqual(store.note, { text: "abc", version: 4 }); }); test("refuses an op that would make the note too long", () => { const store = new MemoryStore({ text: "x".repeat(MAX_LENGTH - 2), version: 7 }); const room = new Room(store); assert.equal(room.receive(send(7, insert(0, "ab")), "otter").reply.type, "ack"); const out = room.receive(send(8, insert(0, "c")), "otter"); assert.deepEqual(out.reply, { type: "snapshot", text: "ab" + "x".repeat(MAX_LENGTH - 2), version: 8, reason: "notes are limited to 100,000 characters" }); assert.equal(out.broadcast, undefined); assert.equal(store.note.version, 8); }); test("answers a malformed frame with an error", () => { const room = new Room(new MemoryStore()); assert.equal(room.receive("{", "otter").reply.type, "error"); assert.equal(room.receive(JSON.stringify({ type: "op", base: 0, op: { type: "upsert", pos: 0 } }), "otter").reply.type, "error"); }); test("picks up where it left off from the store", () => { const store = new MemoryStore(); new Room(store).receive(send(0, insert(0, "kept")), "otter"); // A Durable Object woken from hibernation builds a new Room over the same storage. const woken = new Room(store); assert.deepEqual(woken.note, { text: "kept", version: 1 }); assert.deepEqual(woken.receive(send(1, insert(0, "> ")), "otter").broadcast, { type: "op", version: 2, op: insert(0, "> "), by: "otter" }); });});