dánwòdánwò

Realtime

Tiptap

Tiptap Cloud collaboration, the socket editors sync documents over and the documents REST API, with a test that reads what a document says and makes the server misbehave.

danwo's Tiptap emulator answers every Tiptap Cloud collaboration app: the WebSocket server editors sync documents with, and the documents REST API. Several editors of one document see each other's changes, and a test reads what the document says.

Set up

Add tiptapService with your app id and the secret your server signs collaboration tokens with:

danwo.config.ts
import { appEnvironment, defineConfig, tiptapService } from "@danwo/sdk"

const app = { id: "notes", secret: "danwo-collab-secret", apiKey: "danwo-tiptap-key" }

export default defineConfig({
  testDir: "tests",
  readiness: { mode: "page+backend", settle: true },
  environment: () =>
    appEnvironment({
      app: {
        command: "npm run dev",
        url: "http://localhost:3000",
        env: {
          TIPTAP_APP_ID: app.id,
          TIPTAP_COLLAB_SECRET: app.secret,
          TIPTAP_API_KEY: app.apiKey,
        },
      },
      services: [
        tiptapService({
          seed: {
            apps: [app],
            documents: {
              "note-1": {
                type: "doc",
                content: [{ type: "paragraph", content: [{ type: "text", text: "Agenda" }] }],
              },
            },
          },
        }),
      ],
    }),
})

Seed it

FieldWhat it seeds
appsCollaboration apps: the secret that verifies clients' JWTs, and the REST API's apiKey. Without them, any token or key is accepted.
documentsDocuments by name, written as ProseMirror JSON.

Use it in a test

Type into the editor, then check the server kept it:

tests/collab.danwo.ts
import { type TiptapFixtures, test as base, tiptapCollab } from "@danwo/sdk"

const test = base.extend<TiptapFixtures>()

test("saves what was typed", async ({ app, screen, expect, tiptap }) => {
  await app.open("/notes/note-1")
  await expect(screen.getByText("Agenda")).toBeVisible()
  await screen.getByRole("textbox").press("End")
  await screen.getByRole("textbox").press("Enter")
  await screen.getByRole("textbox").fill("Ship on Friday")

  const collab = tiptapCollab(tiptap)

  expect(await collab.waitForText("note-1", "Ship on Friday")).toContain("Ship on Friday")
  expect(collab.editors("note-1")).toBe(1)
})
tiptapCollab(tiptap)What it's for
names(), json(name), text(name)The documents, as JSON or text.
waitForText(name, text)Wait until a document says something.
write(name, doc)Change a document, as another editor would.
editors(name)How many editors are connected.
trouble(…)Refuse tokens or answer syncs late, to test how your editor shows it.

What's covered

AreaCovered
SocketThe Yjs sync protocol, awareness and stateless messages, with tokens checked.
REST APIDocuments: list, read, create, update, delete.
DocumentsProseMirror JSON in and out, and fragments.

Faults

CallFaults the matrix tries
A REST write, such as POST /api/documents/:name429, 500, connection reset, answer lost after the document was saved
The socketthe upgrade refused, and the connection dropped for one person

Webhooks

Tiptap Cloud's document webhooks aren't sent yet.

Fidelity

Live. danwo fidelity tiptap runs 1 scenario against a real collaboration app: creating a document, reading it as JSON and deleting it. It matches.

Not covered yet

  • Webhooks
  • Comments, versions and AI features
  • Document conversion (import and export)

Next

On this page

Run it on every pull request

A GitHub check and one comment per pull request, with the flight records attached.

Set up CI