learning_ai_notes/mobile/src/store/notes-store.test.ts
2026-03-31 00:17:41 -07:00

113 lines
4.0 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
const listNotesMock = vi.fn();
const getNoteMock = vi.fn();
const createNoteMock = vi.fn();
const updateNoteMock = vi.fn();
const enqueueNoteCreateMock = vi.fn();
const enqueueNoteUpdateMock = vi.fn();
vi.mock('../api/notes', () => ({
listNotes: (...args: unknown[]) => listNotesMock(...args),
getNote: (...args: unknown[]) => getNoteMock(...args),
createNote: (...args: unknown[]) => createNoteMock(...args),
updateNote: (...args: unknown[]) => updateNoteMock(...args),
}));
vi.mock('../lib/offline-queue', () => ({
enqueueNoteCreate: (...args: unknown[]) => enqueueNoteCreateMock(...args),
enqueueNoteUpdate: (...args: unknown[]) => enqueueNoteUpdateMock(...args),
}));
import { useNotesStore } from './notes-store';
function resetStore() {
useNotesStore.setState({
notes: [],
selectedNote: null,
isLoading: false,
});
}
const fakeNote = {
id: 'n1',
workspaceId: 'ws-1',
title: 'Test',
body: 'Body',
workspaceName: 'Work',
status: 'draft' as const,
updatedAt: '2025-01-01T00:00:00Z',
};
describe('useNotesStore', () => {
beforeEach(() => {
vi.clearAllMocks();
resetStore();
});
it('hydrate loads notes', async () => {
listNotesMock.mockResolvedValueOnce([fakeNote]);
await useNotesStore.getState().hydrate();
expect(useNotesStore.getState().notes).toHaveLength(1);
expect(useNotesStore.getState().notes[0].id).toBe('n1');
expect(useNotesStore.getState().isLoading).toBe(false);
});
it('openNote sets selectedNote', async () => {
useNotesStore.setState({ notes: [fakeNote] });
getNoteMock.mockResolvedValueOnce(fakeNote);
await useNotesStore.getState().openNote('n1');
expect(useNotesStore.getState().selectedNote?.id).toBe('n1');
});
it('openNote clears selectedNote for unknown id', async () => {
useNotesStore.setState({ notes: [fakeNote] });
await useNotesStore.getState().openNote('missing');
expect(useNotesStore.getState().selectedNote).toBeNull();
});
it('saveDraft creates note and prepends to list', async () => {
const created = { ...fakeNote, id: 'n2', title: 'New' };
createNoteMock.mockResolvedValueOnce(created);
const ok = await useNotesStore.getState().saveDraft('ws-1', 'New', 'Body');
expect(ok).toBe(true);
expect(useNotesStore.getState().notes[0].id).toBe('n2');
expect(useNotesStore.getState().selectedNote?.id).toBe('n2');
});
it('saveDraft returns false without workspaceId', async () => {
const ok = await useNotesStore.getState().saveDraft(null, 'Title', 'Body');
expect(ok).toBe(false);
expect(createNoteMock).not.toHaveBeenCalled();
});
it('saveDraft enqueues create when persistence fails', async () => {
createNoteMock.mockRejectedValueOnce(new Error('offline'));
const ok = await useNotesStore.getState().saveDraft('ws-1', 'Queued', 'Body');
expect(ok).toBe(true);
expect(enqueueNoteCreateMock).toHaveBeenCalledTimes(1);
expect(useNotesStore.getState().notes[0].title).toBe('Queued');
});
it('updateNote persists and updates state', async () => {
useNotesStore.setState({ notes: [fakeNote] });
const updated = { ...fakeNote, title: 'Updated' };
updateNoteMock.mockResolvedValueOnce(updated);
await useNotesStore.getState().updateNote('n1', 'Updated', 'Body');
expect(useNotesStore.getState().notes[0].title).toBe('Updated');
});
it('updateNote does nothing for unknown id', async () => {
await useNotesStore.getState().updateNote('missing', 'Title', 'Body');
expect(updateNoteMock).not.toHaveBeenCalled();
});
it('updateNote enqueues update when persistence fails', async () => {
useNotesStore.setState({ notes: [fakeNote] });
updateNoteMock.mockRejectedValueOnce(new Error('offline'));
await useNotesStore.getState().updateNote('n1', 'Updated offline', 'Body');
expect(enqueueNoteUpdateMock).toHaveBeenCalledTimes(1);
expect(useNotesStore.getState().notes[0].title).toBe('Updated offline');
});
});