Files
numolus/app/frontend/io/__tests__/library.spec.ts
T
wkhandClaude Opus 5.5 e14bada114 Add Rails backend: accounts via magic link, server documents, sharing
The app becomes a Rails 8 application; the Vue frontend moves to
app/frontend and is served through vite_rails.

- Without an account documents stay in the browser (IndexedDB). Sign-in
  is by magic link or the 6-digit code from the same mail; afterwards
  local documents can be uploaded into the account.
- Server documents (/d/<id>) with versioned saves and a single-editor
  lock per browser tab; others see them read-only and follow by polling.
- Sharing via link (works without an account) and via e-mail invitation,
  roles viewer/editor; the owner manages links and people.
- Store gains a read-only mode that blocks all content changes.
- Solid Queue, Cache and Cable share the one PostgreSQL database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 20:09:06 +02:00

65 lines
2.4 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { IDBFactory } from 'fake-indexeddb';
import { DocumentLibrary, newDocId, type DocMeta } from '../library';
const meta = (id: string, title: string, updatedAt: number): DocMeta => ({
id,
title,
createdAt: 1,
updatedAt,
});
/** a fresh, empty in-memory IndexedDB per test */
const openLib = () => DocumentLibrary.open(new IDBFactory());
describe('DocumentLibrary', () => {
it('stores meta and content separately and reads them back', async () => {
const lib = await openLib();
await lib.put(meta('a', 'Budget', 10), '{"x":1}');
expect(await lib.getMeta('a')).toEqual(meta('a', 'Budget', 10));
expect(await lib.getContent('a')).toBe('{"x":1}');
expect(await lib.getContent('missing')).toBeUndefined();
});
it('lists documents most recently changed first', async () => {
const lib = await openLib();
await lib.put(meta('old', 'Alt', 10), '{}');
await lib.put(meta('new', 'Neu', 30), '{}');
await lib.put(meta('mid', 'Mitte', 20), '{}');
expect((await lib.list()).map((d) => d.id)).toEqual(['new', 'mid', 'old']);
});
it('patchMeta renames without touching content; unknown ids are ignored', async () => {
const lib = await openLib();
await lib.put(meta('a', 'Alt', 10), '{"keep":true}');
await lib.patchMeta('a', { title: 'Neu' });
await lib.patchMeta('nope', { title: 'X' });
expect((await lib.getMeta('a'))?.title).toBe('Neu');
expect(await lib.getContent('a')).toBe('{"keep":true}');
expect(await lib.list()).toHaveLength(1);
});
it('remove deletes meta and content', async () => {
const lib = await openLib();
await lib.put(meta('a', 'A', 10), '{}');
await lib.remove('a');
expect(await lib.getMeta('a')).toBeUndefined();
expect(await lib.getContent('a')).toBeUndefined();
expect(await lib.list()).toEqual([]);
});
it('keeps data across re-opening the same database', async () => {
const factory = new IDBFactory();
await (await DocumentLibrary.open(factory)).put(meta('a', 'A', 10), '{"v":2}');
expect(await (await DocumentLibrary.open(factory)).getContent('a')).toBe('{"v":2}');
});
});
describe('newDocId', () => {
it('produces distinct v4 UUIDs', () => {
const a = newDocId();
expect(a).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
expect(newDocId()).not.toBe(a);
});
});