130 lines
5.9 KiB
TypeScript
130 lines
5.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const { mockListByStatus, mockCountsByStatus, mockReorder } = vi.hoisted(() => ({
|
|
mockListByStatus: vi.fn(async () => []),
|
|
mockCountsByStatus: vi.fn(async () => ({ active: 0, completed: 0, archived: 0, trashed: 0 })),
|
|
mockReorder: vi.fn(async () => ({ ok: true as const }))
|
|
}));
|
|
|
|
vi.mock('../../src/renderer/inbox/api.js', () => ({
|
|
inboxApi: {
|
|
listNotes: vi.fn(async () => []),
|
|
listTrash: vi.fn(async () => []),
|
|
getTrashCount: vi.fn(async () => 0),
|
|
getContinuity: vi.fn(async () => ({ weekStart: '', weekCount: 0, weekTarget: 7, consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null })),
|
|
getPendingCount: vi.fn(async () => 0),
|
|
getOllamaStatus: vi.fn(async () => ({ ok: true })),
|
|
getTodayCount: vi.fn(async () => 0),
|
|
listExpired: vi.fn(async () => []),
|
|
getFailedCount: vi.fn(async () => 0),
|
|
listRecallCandidate: vi.fn(async () => null),
|
|
countsByStatus: mockCountsByStatus,
|
|
getSettings: vi.fn(async () => ({ ai_enabled: true })),
|
|
listByStatus: mockListByStatus,
|
|
setSidebarVisible: vi.fn(async () => ({ ok: true as const })),
|
|
setSidebarWidth: vi.fn(async () => ({ ok: true as const }))
|
|
},
|
|
notebookApi: {
|
|
list: vi.fn(async () => [
|
|
{ id: 'nb-1', name: '기본', color: null, createdAt: 't', updatedAt: 't', noteCount: 3 },
|
|
{ id: 'nb-2', name: '회사', color: '#0a4b80', createdAt: 't', updatedAt: 't', noteCount: 1 }
|
|
]),
|
|
create: vi.fn(async (i: { name: string; color?: string }) => ({ ok: true as const, notebook: { id: 'nb-new', name: i.name, color: i.color ?? null, createdAt: 't', updatedAt: 't', noteCount: 0 } })),
|
|
delete: vi.fn(async () => ({ ok: true as const })),
|
|
rename: vi.fn(async () => ({ ok: true as const })),
|
|
setColor: vi.fn(async () => ({ ok: true as const })),
|
|
moveNote: vi.fn(async () => ({ ok: true as const })),
|
|
reorder: mockReorder
|
|
}
|
|
}));
|
|
|
|
import { useInbox } from '../../src/renderer/inbox/store.js';
|
|
|
|
describe('store notebooks', () => {
|
|
beforeEach(() => {
|
|
mockListByStatus.mockClear();
|
|
mockCountsByStatus.mockClear();
|
|
mockReorder.mockClear();
|
|
useInbox.setState({ notebooks: [], selectedNotebookId: null, sidebarVisible: false, sidebarWidth: 240, view: 'inbox' } as never);
|
|
});
|
|
|
|
it('loadNotebooks 가 notebookApi.list 결과 반영', async () => {
|
|
await useInbox.getState().loadNotebooks();
|
|
expect(useInbox.getState().notebooks).toHaveLength(2);
|
|
});
|
|
|
|
it('loadNotebooks: selectedNotebookId null 이면 첫 notebook 으로 자동 설정', async () => {
|
|
await useInbox.getState().loadNotebooks();
|
|
expect(useInbox.getState().selectedNotebookId).toBe('nb-1');
|
|
});
|
|
|
|
it('selectNotebook 가 selectedNotebookId 설정', () => {
|
|
useInbox.getState().selectNotebook('nb-X');
|
|
expect(useInbox.getState().selectedNotebookId).toBe('nb-X');
|
|
});
|
|
|
|
it('createNotebook 성공 시 notebooks 에 추가', async () => {
|
|
await useInbox.getState().createNotebook('학습', '#ccc');
|
|
expect(useInbox.getState().notebooks.some((n) => n.name === '학습')).toBe(true);
|
|
});
|
|
|
|
it('toggleSidebar 가 sidebarVisible 반전', () => {
|
|
expect(useInbox.getState().sidebarVisible).toBe(false);
|
|
useInbox.getState().toggleSidebar();
|
|
expect(useInbox.getState().sidebarVisible).toBe(true);
|
|
});
|
|
|
|
it('deleteNotebook 성공 시 notebooks 에서 제거 + selected 였으면 첫 notebook 으로', async () => {
|
|
useInbox.setState({
|
|
notebooks: [
|
|
{ id: 'nb-1', name: '기본', color: null, createdAt: 't', updatedAt: 't', noteCount: 0 },
|
|
{ id: 'nb-2', name: '회사', color: null, createdAt: 't', updatedAt: 't', noteCount: 0 }
|
|
],
|
|
selectedNotebookId: 'nb-2'
|
|
} as never);
|
|
await useInbox.getState().deleteNotebook('nb-2');
|
|
expect(useInbox.getState().notebooks.map((n) => n.id)).toEqual(['nb-1']);
|
|
expect(useInbox.getState().selectedNotebookId).toBe('nb-1');
|
|
});
|
|
|
|
it('selectNotebook 가 loadByView + refreshMeta 호출', async () => {
|
|
// inbox view 에서 notebook 전환 → listByStatus + countsByStatus 호출됨.
|
|
useInbox.setState({ view: 'inbox', selectedNotebookId: null } as never);
|
|
useInbox.getState().selectNotebook('nb-X');
|
|
expect(useInbox.getState().selectedNotebookId).toBe('nb-X');
|
|
// 비동기 완료 대기
|
|
await new Promise((r) => setTimeout(r, 0));
|
|
expect(mockListByStatus).toHaveBeenCalled();
|
|
expect(mockCountsByStatus).toHaveBeenCalled();
|
|
});
|
|
|
|
it('loadByView 가 selectedNotebookId 를 inboxApi.listByStatus 에 전달', async () => {
|
|
useInbox.setState({ selectedNotebookId: 'nb-X', view: 'inbox' } as never);
|
|
await useInbox.getState().loadByView('inbox');
|
|
expect(mockListByStatus).toHaveBeenCalledWith('active', expect.objectContaining({ notebookId: 'nb-X' }));
|
|
});
|
|
|
|
it('refreshMeta 가 selectedNotebookId 를 inboxApi.countsByStatus 에 전달', async () => {
|
|
useInbox.setState({ selectedNotebookId: 'nb-X' } as never);
|
|
await useInbox.getState().refreshMeta();
|
|
expect(mockCountsByStatus).toHaveBeenCalledWith(expect.objectContaining({ notebookId: 'nb-X' }));
|
|
});
|
|
|
|
it('selectNotebook: review view 에선 loadByView 를 호출하지 않음', async () => {
|
|
useInbox.setState({ view: 'review-weekly', selectedNotebookId: null } as never);
|
|
useInbox.getState().selectNotebook('nb-X');
|
|
await new Promise((r) => setTimeout(r, 0));
|
|
// listByStatus 는 호출되지 않아야 함 (review view 는 list 대상 아님)
|
|
expect(mockListByStatus).not.toHaveBeenCalled();
|
|
// countsByStatus 는 refreshMeta 경로로 호출됨
|
|
expect(mockCountsByStatus).toHaveBeenCalled();
|
|
});
|
|
|
|
it('reorderNotebook 성공 시 notebookApi.reorder 호출 + loadNotebooks 재로드', async () => {
|
|
await useInbox.getState().reorderNotebook('nb-1', 'down');
|
|
expect(mockReorder).toHaveBeenCalledWith('nb-1', 'down');
|
|
// loadNotebooks 가 호출되어 notebooks 가 갱신됨
|
|
expect(useInbox.getState().notebooks).toHaveLength(2);
|
|
});
|
|
});
|