Note.notebookId 필드 추가(required), NoteRepository.create/importNote/upsertFromSync INSERT 에 notebook_id 컬럼 포함 — 미지정 시 getDefaultNotebookId() 로 가장 오래된 notebook 자동 할당. hydrate 에 notebookId 반환 추가. 관련 test fixture 5곳 notebookId 보강. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
4.2 KiB
TypeScript
94 lines
4.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import type { Note } from '@shared/types';
|
|
|
|
const mockApi = {
|
|
listNotes: vi.fn(async () => [] as Note[]),
|
|
listTrash: vi.fn(async () => [] as Note[]),
|
|
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),
|
|
restoreNote: vi.fn(async () => {}),
|
|
permanentDeleteNote: vi.fn(async () => ({ confirmed: true })),
|
|
emptyTrash: vi.fn(async () => ({ confirmed: true, count: 0 })),
|
|
deleteNote: vi.fn(async () => {}),
|
|
onNoteUpdated: vi.fn(() => () => {}),
|
|
updateAiFields: vi.fn(async () => {}),
|
|
setDueDate: vi.fn(async () => {}),
|
|
setIntent: vi.fn(async () => {}),
|
|
dismissIntent: vi.fn(async () => {}),
|
|
listExpired: vi.fn(async () => [] as Note[]),
|
|
trashExpiredBatch: vi.fn(async (_ids: string[]) => ({ trashedCount: 0, confirmed: false }))
|
|
};
|
|
|
|
vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: mockApi }));
|
|
|
|
const noteStub = (id: string): Note => ({
|
|
id, rawText: 'x',
|
|
aiTitle: null, aiSummary: null, aiStatus: 'done', aiError: null,
|
|
aiProvider: null, aiGeneratedAt: null,
|
|
titleEditedByUser: false, summaryEditedByUser: false,
|
|
userIntent: null, intentPromptedAt: null,
|
|
dueDate: '2026-04-20', dueDateEditedByUser: false,
|
|
deletedAt: null, lastRecalledAt: null, recallDismissedAt: null,
|
|
status: 'active', statusChangedAt: null, moveReason: null,
|
|
createdAt: '2026-05-01T00:00:00Z', updatedAt: '2026-05-01T00:00:00Z',
|
|
tags: [], media: [], notebookId: 'nb-default'
|
|
});
|
|
|
|
describe('useInbox — expired state (v0.2.3 #5)', () => {
|
|
beforeEach(async () => {
|
|
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
|
useInbox.setState({
|
|
notes: [], trashNotes: [], trashCount: 0, showTrash: false,
|
|
loading: false, tagFilter: null, pendingCount: 0, todayCount: 0,
|
|
ollamaStatus: { ok: true },
|
|
continuity: { weekStart: '', weekCount: 0, weekTarget: 7, consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null },
|
|
expiredCandidates: [], expiredSnoozeUntilMs: null
|
|
});
|
|
Object.values(mockApi).forEach((fn) => 'mockClear' in fn && (fn as any).mockClear());
|
|
});
|
|
|
|
afterEach(() => { vi.restoreAllMocks(); });
|
|
|
|
it('trashExpiredBatch removes ids and increments trashCount when confirmed', async () => {
|
|
mockApi.trashExpiredBatch.mockResolvedValueOnce({ trashedCount: 2, confirmed: true });
|
|
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
|
useInbox.setState({
|
|
expiredCandidates: [noteStub('n1'), noteStub('n2'), noteStub('n3')],
|
|
notes: [noteStub('n1'), noteStub('n2'), noteStub('n3')],
|
|
trashCount: 5
|
|
});
|
|
await useInbox.getState().trashExpiredBatch(['n1', 'n2']);
|
|
const s = useInbox.getState();
|
|
expect(s.expiredCandidates.map((n) => n.id)).toEqual(['n3']);
|
|
expect(s.notes.map((n) => n.id)).toEqual(['n3']);
|
|
expect(s.trashCount).toBe(7);
|
|
});
|
|
|
|
it('trashExpiredBatch does NOT mutate state when not confirmed', async () => {
|
|
mockApi.trashExpiredBatch.mockResolvedValueOnce({ trashedCount: 0, confirmed: false });
|
|
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
|
useInbox.setState({
|
|
expiredCandidates: [noteStub('n1'), noteStub('n2')],
|
|
notes: [noteStub('n1'), noteStub('n2')],
|
|
trashCount: 5
|
|
});
|
|
await useInbox.getState().trashExpiredBatch(['n1']);
|
|
const s = useInbox.getState();
|
|
expect(s.expiredCandidates).toHaveLength(2);
|
|
expect(s.notes).toHaveLength(2);
|
|
expect(s.trashCount).toBe(5);
|
|
});
|
|
|
|
it('snoozeExpired sets expiredSnoozeUntilMs to next KST midnight', async () => {
|
|
// 2026-05-01 12:00 UTC = 2026-05-01 21:00 KST → next KST midnight = 2026-05-02 00:00 KST = 2026-05-01 15:00 UTC
|
|
const fixedNow = Date.parse('2026-05-01T12:00:00Z');
|
|
vi.spyOn(Date, 'now').mockReturnValue(fixedNow);
|
|
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
|
useInbox.getState().snoozeExpired();
|
|
expect(useInbox.getState().expiredSnoozeUntilMs).toBe(Date.parse('2026-05-01T15:00:00Z'));
|
|
});
|
|
});
|