3df7cc01ea
Adds WhiteboardPayload wire-format variant to MessagePayload/ParsedMessagePayload, creates the whiteboards.ts CRUD wrapper (createWhiteboard, listWhiteboardStrokes, insertWhiteboardStroke, clearWhiteboardStrokes), writes 5 unit tests (38/38 pass), re-exports from chat/index.ts, and registers conversation_whiteboards + whiteboard_strokes in packages/db-types so typecheck passes cleanly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
clearWhiteboardStrokes,
|
|
createWhiteboard,
|
|
insertWhiteboardStroke,
|
|
listWhiteboardStrokes,
|
|
} from './whiteboards';
|
|
|
|
function makeClient(opts: {
|
|
user?: { id: string } | null;
|
|
insertReturn?: { data: unknown; error: unknown };
|
|
selectReturn?: { data: unknown[]; error: unknown };
|
|
deleteReturn?: { error: unknown };
|
|
}): any {
|
|
const single = vi.fn().mockResolvedValue(opts.insertReturn ?? { data: {}, error: null });
|
|
const order = vi.fn().mockResolvedValue(opts.selectReturn ?? { data: [], error: null });
|
|
const eqDelete = vi.fn().mockResolvedValue(opts.deleteReturn ?? { error: null });
|
|
const insertSelect = vi.fn().mockReturnValue({ single });
|
|
const insertChain = vi.fn().mockReturnValue({ select: insertSelect });
|
|
const selectChain = vi.fn().mockReturnValue({
|
|
eq: vi.fn().mockReturnValue({ order }),
|
|
});
|
|
const deleteChain = vi.fn().mockReturnValue({ eq: eqDelete });
|
|
const from = vi.fn().mockReturnValue({
|
|
insert: insertChain,
|
|
select: selectChain,
|
|
delete: deleteChain,
|
|
});
|
|
return {
|
|
auth: { getUser: vi.fn().mockResolvedValue({ data: { user: opts.user ?? { id: 'u-1' } } }) },
|
|
from,
|
|
};
|
|
}
|
|
|
|
describe('createWhiteboard', () => {
|
|
it('inserts with owner = current user', async () => {
|
|
const client = makeClient({
|
|
insertReturn: {
|
|
data: {
|
|
id: 'w-1',
|
|
conversation_id: 'c-1',
|
|
owner_user_id: 'u-1',
|
|
created_at: '2026-05-16T00:00:00Z',
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
const out = await createWhiteboard(client, 'c-1');
|
|
expect(out).toEqual({
|
|
id: 'w-1',
|
|
conversationId: 'c-1',
|
|
ownerUserId: 'u-1',
|
|
createdAt: '2026-05-16T00:00:00Z',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('listWhiteboardStrokes', () => {
|
|
it('maps DB rows to camelCase + ascending order', async () => {
|
|
const client = makeClient({
|
|
selectReturn: {
|
|
data: [
|
|
{
|
|
id: 's-1',
|
|
whiteboard_id: 'w-1',
|
|
author_user_id: 'u-1',
|
|
stroke_json: { tool: 'pen' },
|
|
created_at: '2026-05-16T00:00:00Z',
|
|
},
|
|
],
|
|
error: null,
|
|
},
|
|
});
|
|
const out = await listWhiteboardStrokes(client, 'w-1');
|
|
expect(out).toEqual([
|
|
{
|
|
id: 's-1',
|
|
whiteboardId: 'w-1',
|
|
authorUserId: 'u-1',
|
|
strokeJson: { tool: 'pen' },
|
|
createdAt: '2026-05-16T00:00:00Z',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('insertWhiteboardStroke', () => {
|
|
it('writes author + stroke_json', async () => {
|
|
const client = makeClient({
|
|
insertReturn: {
|
|
data: {
|
|
id: 's-2',
|
|
whiteboard_id: 'w-1',
|
|
author_user_id: 'u-1',
|
|
stroke_json: { tool: 'pen', points: [[0, 0, 0]] },
|
|
created_at: '2026-05-16T00:00:01Z',
|
|
},
|
|
error: null,
|
|
},
|
|
});
|
|
const out = await insertWhiteboardStroke(client, {
|
|
whiteboardId: 'w-1',
|
|
strokeJson: { tool: 'pen', points: [[0, 0, 0]] },
|
|
});
|
|
expect(out.id).toBe('s-2');
|
|
expect((out.strokeJson as { tool: string }).tool).toBe('pen');
|
|
});
|
|
});
|
|
|
|
describe('clearWhiteboardStrokes', () => {
|
|
it('does not throw on a successful delete', async () => {
|
|
const client = makeClient({ deleteReturn: { error: null } });
|
|
await expect(clearWhiteboardStrokes(client, 'w-1')).resolves.toBeUndefined();
|
|
});
|
|
|
|
it('throws when delete returns an error', async () => {
|
|
const client = makeClient({ deleteReturn: { error: { message: 'rls denied' } as any } });
|
|
await expect(clearWhiteboardStrokes(client, 'w-1')).rejects.toBeTruthy();
|
|
});
|
|
});
|