// @vitest-environment jsdom // // `/` — the collaborative editor, driven through `makeVoltroTestClient` // (`@voltro/testing/client`) rather than a hand-rolled `vi.mock('@voltro/client')`. // // WHY THE HARNESS HERE and not a mock. This page is stateful: `useCrdtText` // owns a sync client per document cell, seeded from the server body and // folding every INCOMING body back in. A `vi.mock` can return one snapshot per // mount, so the fold — the whole point of the page — was untestable, and the // suite that replaced this one never pushed a second value into a mounted // tree. `setSubscription` is a real server delta, so the last case below // asserts convergence. // // Two other things fall out of it: the real `@voltro/client` hooks run (so a // change in their loading/error semantics is caught here instead of being // re-implemented by the mock), and the page can be imported statically — // `await import('./page')` only existed to sequence around `vi.mock`'s hoist. // // `@voltro/local-first` is NOT faked either: the real `useCrdtText` hook // decodes the seeded body and turns keystrokes into encoded updates (via its // `crdtTextEdit` span diff), which is the behaviour worth asserting. import { afterEach, beforeEach, describe, expect, test } from 'vitest' import { act, createElement, type ReactNode } from 'react' import { createRoot, type Root } from 'react-dom/client' import { I18nProvider } from '@voltro/i18n' import { crdtText, type CrdtState } from '@voltro/local-first' import { makeVoltroTestClient, type VoltroTestClient } from '@voltro/testing/client' import enCatalog from '../locales/en' import IndexPage from './page' ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true const LIST = 'documents.list' let container: HTMLDivElement let root: Root let client: VoltroTestClient /** A CRDT state holding `text`, from a fresh document. */ const bodyWith = (text: string): CrdtState => crdtText().insert(0, text).encode() /** A REMOTE edit derived from `base`, so merging it back converges instead of * interleaving two independently-created runs of the same characters. */ const remoteEditOf = (base: CrdtState, at: number, insert: string): CrdtState => crdtText().merge(base).insert(at, insert).encode() const docRow = (over: Partial<{ id: string; title: string; body: CrdtState | null }> = {}) => ({ id: over.id ?? 'doc_1', title: over.title ?? 'Shared document', body: over.body ?? null, tenantId: 'acme', createdAt: new Date(0), }) const render = (): void => { container = document.createElement('div') document.body.appendChild(container) act(() => { root = createRoot(container) root.render( createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: createElement(client.Provider, null, createElement(IndexPage) as ReactNode), }), ) }) } const setTextarea = (el: HTMLTextAreaElement, value: string): void => { const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')!.set! act(() => { setter.call(el, value) el.dispatchEvent(new Event('input', { bubbles: true })) }) } const textarea = (): HTMLTextAreaElement => container.querySelector('textarea') as HTMLTextAreaElement /** The harness with both writes wired. Handlers return void; the assertions * read `client.calls`, which records tag + input in order. */ const harness = (subscriptions: Readonly> = {}): VoltroTestClient => makeVoltroTestClient({ subscriptions, mutations: { 'documents.create': () => undefined, 'documents.setBody': () => undefined, }, }) beforeEach(() => { client = harness() }) afterEach(() => { if (root) act(() => root.unmount()) container?.remove() document.body.innerHTML = '' }) describe('collab editor — subscription states', () => { test('shows the loading hint before the first snapshot', () => { // A tag with NO fixture is the harness's cold state — the real thing a // component sees before the first delta arrives. render() expect(container.textContent).toContain('loading…') expect(container.querySelector('textarea')).toBeNull() }) test('shows the empty state + create button once a snapshot arrives with no documents', () => { client = harness({ [LIST]: [] }) render() expect(container.textContent).toContain('No document yet') expect(container.querySelector('button')).not.toBeNull() expect(container.querySelector('textarea')).toBeNull() }) test('renders the error banner when the subscription errors', () => { render() act(() => { client.failSubscription(LIST, 'boom') }) expect(container.textContent).toContain('subscription error') expect(container.textContent).toContain('boom') }) }) describe('collab editor — create', () => { test('clicking Create document calls documents.create with the tenant + a title', async () => { client = harness({ [LIST]: [] }) render() const button = container.querySelector('button') as HTMLButtonElement await act(async () => { button.dispatchEvent(new MouseEvent('click', { bubbles: true })) }) expect(client.calls).toEqual([ { kind: 'mutation', tag: 'documents.create', input: { tenantId: 'acme', title: 'Shared document' } }, ]) }) }) describe('collab editor — editing a document', () => { test('binds the textarea to the decoded CRDT body', () => { client = harness({ [LIST]: [docRow({ body: bodyWith('Hello') })] }) render() expect(textarea()).not.toBeNull() expect(textarea().value).toBe('Hello') expect(container.textContent).toContain('Shared document') }) test('typing sends an encoded CRDT update via documents.setBody', () => { client = harness({ [LIST]: [docRow({ body: bodyWith('Hello') })] }) render() setTextarea(textarea(), 'Hello!') expect(client.calls).toHaveLength(1) const call = client.calls[0]! expect(call.tag).toBe('documents.setBody') const input = call.input as { id: string; update: unknown } expect(input.id).toBe('doc_1') expect(input.update).toBeInstanceOf(Uint8Array) }) // The case the hand-rolled mock could not express, and the page's whole // reason to exist: a body edited in ANOTHER tab arrives as a delta, and the // local handle folds it in without remounting the editor. test('a remote edit pushed by the server converges into the open editor', () => { const base = bodyWith('Hello') client = harness({ [LIST]: [docRow({ body: base })] }) render() expect(textarea().value).toBe('Hello') act(() => { client.setSubscription(LIST, [docRow({ body: remoteEditOf(base, 5, ' world') })]) }) expect(textarea().value).toBe('Hello world') // A fold is not a write — nothing is echoed back to the server. expect(client.calls).toEqual([]) }) test('a local edit survives a concurrent remote edit — both are in the merged text', () => { const base = bodyWith('Hello') client = harness({ [LIST]: [docRow({ body: base })] }) render() setTextarea(textarea(), 'Hello!') expect(textarea().value).toBe('Hello!') // The other tab branched from the SAME base and appended elsewhere. act(() => { client.setSubscription(LIST, [docRow({ body: remoteEditOf(base, 0, 'Oh, ') })]) }) expect(textarea().value).toContain('Oh, ') expect(textarea().value).toContain('!') }) })