UNPKG

@convex-dev/prosemirror-sync

Version:

Sync ProseMirror documents for Tiptap using this Convex component.

283 lines (279 loc) 7.51 kB
/// <reference types="vite/client" /> import { webcrypto as crypto } from "node:crypto"; import { describe, expect, test } from "vitest"; import { convexTest } from "convex-test"; import schema from "./schema.js"; import { api } from "./_generated/api.js"; const modules = import.meta.glob("./**/*.*s"); describe("prosemirror lib", () => { test("submitSteps needs rebase", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); const clientId = "client1"; await t.run(async (ctx) => { await ctx.db.insert("snapshots", { id, version: 0, content: "", }); await ctx.db.insert("deltas", { id, version: 2, clientId, steps: ["1", "2"], }); }); expect( await t.mutation(api.lib.submitSteps, { id, clientId, version: 0, steps: ["a"], }) ).toEqual({ status: "needs-rebase", clientIds: [clientId, clientId], steps: ["1", "2"], }); }); test("submitSteps synced", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); const clientId = "client1"; await t.run(async (ctx) => { await ctx.db.insert("snapshots", { id, version: 0, content: "", }); }); expect( await t.mutation(api.lib.submitSteps, { id, clientId, version: 0, steps: ["a", "b"], }) ).toEqual({ status: "synced" }); const { steps, clientIds, version } = await t.query(api.lib.getSteps, { id, version: 0, }); expect(steps).toEqual(["a", "b"]); expect(clientIds).toEqual([clientId, clientId]); expect(version).toEqual(2); }); test("getSteps skips steps before version", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); const clientId = "client1"; const clientId2 = "client2"; await t.run(async (ctx) => { await ctx.db.insert("deltas", { id, version: 2, clientId, steps: ["1", "2"], }); await ctx.db.insert("deltas", { id, version: 3, clientId: clientId2, steps: ["3"], }); }); const { steps, clientIds, version } = await t.query(api.lib.getSteps, { id, version: 1, }); expect(steps).toEqual(["2", "3"]); expect(clientIds).toEqual([clientId, clientId2]); expect(version).toEqual(3); }); test("get handles missing snapshot", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); const { content, version } = await t.query(api.lib.getSnapshot, { id, version: 2, }); expect(content).toEqual(null); expect(version).toEqual(undefined); const ignoringSteps = await t.query(api.lib.getSnapshot, { id, version: 2, }); expect(ignoringSteps.content).toEqual(null); expect(ignoringSteps.version).toEqual(undefined); }); test("get works with only snapshot", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); await t.run(async (ctx) => { await ctx.db.insert("snapshots", { id, version: 0, content: "content", }); }); const { content, version } = await t.query(api.lib.getSnapshot, { id, version: 0, }); expect(content).toEqual("content"); expect(version).toEqual(0); }); test("get ignores steps version", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); const clientId = "client1"; await t.run(async (ctx) => { await ctx.db.insert("snapshots", { id, version: 0, content: "content", }); await ctx.db.insert("deltas", { id, version: 2, clientId, steps: ["1", "2"], }); }); const { content, version } = await t.query(api.lib.getSnapshot, { id, version: 2, }); expect(content).toEqual("content"); expect(version).toEqual(0); }); test("getSnapshot gets latest snapshot", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); const clientId = "client1"; await t.run(async (ctx) => { await ctx.db.insert("snapshots", { id, version: 1, content: "content", }); await ctx.db.insert("deltas", { id, version: 2, clientId, steps: ["1", "2"], }); await ctx.db.insert("snapshots", { id, version: 2, content: "content2", }); }); const { content, version } = await t.query(api.lib.getSnapshot, { id, version: 2, }); expect(content).toEqual("content2"); expect(version).toEqual(2); }); test("create, submit, then get works", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); await t.mutation(api.lib.submitSnapshot, { id, version: 0, content: "content", }); await t.mutation(api.lib.submitSteps, { id, clientId: "client1", version: 0, steps: ["a", "b"], }); const { content, version } = await t.query(api.lib.getSnapshot, { id, version: 2, }); expect(content).toEqual("content"); expect(version).toEqual(0); await t.mutation(api.lib.submitSnapshot, { id, version: 2, content: "content2", }); const snapshot2 = await t.query(api.lib.getSnapshot, { id, version: 2, }); expect(snapshot2.content).toEqual("content2"); expect(snapshot2.version).toEqual(2); }); test("submitSnapshot same content is idempotent", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); await t.mutation(api.lib.submitSnapshot, { id, version: 0, content: "content", }); await t.mutation(api.lib.submitSnapshot, { id, version: 0, content: "content", }); }); test("submitSnapshot different content is error", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); await t.mutation(api.lib.submitSnapshot, { id, version: 0, content: "content", }); await expect( t.mutation(api.lib.submitSnapshot, { id, version: 0, content: "other content", }) ).rejects.toThrow(); }); test("submitSnapshot deletes older snapshot", async () => { const t = convexTest(schema, modules); const id = crypto.randomUUID(); // first one should stick around await t.mutation(api.lib.submitSnapshot, { id, version: 1, content: "content", pruneSnapshots: true, }); await t.mutation(api.lib.submitSnapshot, { id, version: 2, content: "content2", pruneSnapshots: true, }); await t.mutation(api.lib.submitSnapshot, { id, version: 3, content: "content3", pruneSnapshots: true, }); const { content, version } = await t.query(api.lib.getSnapshot, { id, version: 3, }); expect(content).toEqual("content3"); expect(version).toEqual(3); const { content: content2, version: version2 } = await t.query( api.lib.getSnapshot, { id, version: 2, } ); expect(content2).toEqual("content"); expect(version2).toEqual(1); }); });