UNPKG

@eweser/db

Version:
261 lines (229 loc) 7.34 kB
import * as Y from 'yjs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { Database } from '../..'; import type { Room, EweDocument } from '../../types'; import type { ServerRoom } from '@eweser/shared'; import { loadRoom } from './loadRoom'; const { initializeDocAndLocalProviderMock, providerInstances } = vi.hoisted( () => ({ initializeDocAndLocalProviderMock: vi.fn(), providerInstances: [] as Array<{ configuration: Record<string, unknown> }>, }) ); vi.mock('../../utils/connection/initializeDoc', () => ({ initializeDocAndLocalProvider: initializeDocAndLocalProviderMock, })); vi.mock('@hocuspocus/provider', () => { class MockHocuspocusProvider { configuration: Record<string, unknown>; status = 'connected'; awareness: unknown; private listeners = new Map<string, Set<(payload: unknown) => void>>(); constructor(configuration: Record<string, unknown>) { this.configuration = configuration; this.awareness = configuration.awareness; providerInstances.push(this); } on(event: string, handler: (payload: unknown) => void) { const handlers = this.listeners.get(event) ?? new Set(); handlers.add(handler); this.listeners.set(event, handlers); return this; } off(event: string, handler: (payload: unknown) => void) { this.listeners.get(event)?.delete(handler); return this; } emit(event: string, payload: unknown) { this.listeners.get(event)?.forEach((handler) => handler(payload)); return true; } disconnect = vi.fn(() => { this.status = 'disconnected'; this.emit('status', { status: 'disconnected' }); }); destroy = vi.fn(); } return { HocuspocusProvider: MockHocuspocusProvider, WebSocketStatus: { Connecting: 'connecting', Connected: 'connected', Disconnected: 'disconnected', }, }; }); describe('loadRoom', () => { beforeEach(() => { providerInstances.length = 0; vi.clearAllMocks(); initializeDocAndLocalProviderMock.mockResolvedValue({ yDoc: new Y.Doc(), localProvider: {}, }); }); it('creates a Hocuspocus provider using the sync url and token loader', async () => { const refreshSyncToken = vi.fn().mockResolvedValue({ syncUrl: 'ws://localhost:8080', syncToken: 'sync-token', tokenExpiry: new Date(Date.now() + 10 * 60 * 1000).toISOString(), }); const db = { collections: { notes: {}, flashcards: {}, profiles: {}, }, indexedDBProviderPolyfill: undefined, getToken: () => 'access-grant-token', useSync: true, refreshSyncToken, emit: vi.fn(), info: vi.fn(), debug: vi.fn(), error: vi.fn(), } as unknown as Database; const serverRoom: ServerRoom = { id: 'room-1', name: 'Room 1', collectionKey: 'notes', tokenExpiry: null, syncUrl: 'ws://localhost:8080', publicAccess: 'private', readAccess: [], writeAccess: [], adminAccess: [], createdAt: null, updatedAt: null, _deleted: false, _ttl: null, }; const room = await loadRoom(db)(serverRoom, { loadRemote: true, awaitLoadRemote: false, withAwareness: true, }); expect(providerInstances).toHaveLength(1); const provider = providerInstances[0]; expect(provider).toBeDefined(); if (!provider) { throw new Error('Expected provider to be created'); } expect(provider.configuration.url).toBe('ws://localhost:8080'); expect(provider.configuration.name).toBe('room-1'); expect(provider.configuration.document).toBe(room.ydoc); expect(typeof provider.configuration.token).toBe('function'); const token = await ( provider.configuration.token as () => Promise<string> )(); expect(token).toBe('sync-token'); expect(room.syncProvider).toBe(provider); expect(room.syncToken).toBe('sync-token'); expect(room.syncUrl).toBe('ws://localhost:8080'); expect(refreshSyncToken).toHaveBeenCalledWith(room as Room<EweDocument>); }); it('loads local IndexedDB data first and skips remote sync when disabled', async () => { const refreshSyncToken = vi.fn(); const db = { collections: { notes: {}, flashcards: {}, profiles: {}, }, indexedDBProviderPolyfill: undefined, getToken: () => 'access-grant-token', useSync: true, refreshSyncToken, emit: vi.fn(), info: vi.fn(), debug: vi.fn(), error: vi.fn(), } as unknown as Database; const serverRoom: ServerRoom = { id: 'room-2', name: 'Room 2', collectionKey: 'notes', tokenExpiry: null, syncUrl: 'ws://localhost:8080', publicAccess: 'private', readAccess: [], writeAccess: [], adminAccess: [], createdAt: null, updatedAt: null, _deleted: false, _ttl: null, }; const room = await loadRoom(db)(serverRoom, { loadRemote: false, withAwareness: true, }); expect(initializeDocAndLocalProviderMock).toHaveBeenCalledWith( 'room-2', undefined, undefined ); expect(providerInstances).toHaveLength(0); expect(room.ydoc).toBeInstanceOf(Y.Doc); expect(room.indexedDbProvider).toBeDefined(); expect(room.syncProvider).toBeUndefined(); expect(refreshSyncToken).not.toHaveBeenCalled(); expect(db.emit).toHaveBeenCalledWith('roomLoaded', room); }); it('returns an already loaded room without rebuilding local state', async () => { const refreshSyncToken = vi.fn().mockResolvedValue({ syncUrl: 'ws://localhost:8080', syncToken: 'sync-token', tokenExpiry: new Date(Date.now() + 10 * 60 * 1000).toISOString(), }); const db = { collections: { notes: {}, flashcards: {}, profiles: {}, }, indexedDBProviderPolyfill: undefined, getToken: () => 'access-grant-token', useSync: true, refreshSyncToken, emit: vi.fn(), info: vi.fn(), debug: vi.fn(), error: vi.fn(), } as unknown as Database; const serverRoom: ServerRoom = { id: 'room-3', name: 'Room 3', collectionKey: 'notes', tokenExpiry: null, syncUrl: 'ws://localhost:8080', publicAccess: 'private', readAccess: [], writeAccess: [], adminAccess: [], createdAt: null, updatedAt: null, _deleted: false, _ttl: null, }; const room = await loadRoom(db)(serverRoom, { loadRemote: false, withAwareness: true, }); room.connectionStatus = 'connected'; room.syncUrl = 'ws://localhost:8080'; room.syncProvider = { status: 'connected' } as never; db.collections.notes[room.id] = room as never; vi.clearAllMocks(); providerInstances.length = 0; const loadedRoom = await loadRoom(db)(serverRoom, { loadRemote: true, awaitLoadRemote: true, }); expect(loadedRoom).toBe(room); expect(initializeDocAndLocalProviderMock).not.toHaveBeenCalled(); expect(providerInstances).toHaveLength(0); expect(refreshSyncToken).not.toHaveBeenCalled(); expect(db.emit).not.toHaveBeenCalledWith('roomLoaded', expect.anything()); }); });