Files
inkling/tests/unit/store.recall.test.ts
altair823 facbf54025 feat(v029): NoteRepository.setStatus + listByStatus + restoreNote 재구현
- NoteStatus 타입 추가 ('active'/'completed'/'archived'/'trashed')
- Note interface 에 status / statusChangedAt / moveReason 필드 추가
- setStatus(id, status, reason, now?) — 단일 transaction 으로 status + move_reason +
  status_changed_at + updated_at 갱신. status='trashed' ↔ deleted_at 동기화
  (backward compat). 그 외 status 는 deleted_at NULL.
- listByStatus(status, opts) — status 별 필터 + ORDER BY COALESCE(status_changed_at,
  created_at) DESC. limit cap 200.
- hydrate 에 status / statusChangedAt / moveReason 매핑 추가. 미설정 row 는 'active' fallback.
- restoreNote 재구현 — setStatus('active', null) 로 status + deleted_at 동기화 +
  v0.2.6 #10 round 1 fix (ai_status='failed'/'pending' → pending_jobs 재투입) 보존.
- 기존 테스트 fixture 5건에 새 필드 추가 (NoteCard, store.expired/recall/tagFilter/trash).
- 신규 테스트 11건 (setStatus + listByStatus + restoreNote 회귀).
2026-05-09 15:33:49 +09:00

79 lines
3.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { Note } from '@shared/types';
vi.mock('../../src/renderer/inbox/api.js', () => ({
inboxApi: {
listRecallCandidate: vi.fn(),
markRecallOpened: vi.fn(),
dismissRecall: vi.fn(),
emitRecallShown: vi.fn(),
emitRecallSnoozed: vi.fn(),
listNotes: vi.fn(async () => []),
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),
getTrashCount: vi.fn(async () => 0),
listExpired: vi.fn(async () => []),
getFailedCount: vi.fn(async () => 0)
}
}));
import { useInbox } from '../../src/renderer/inbox/store.js';
import { inboxApi } from '../../src/renderer/inbox/api.js';
const inboxApiMock = inboxApi as unknown as {
listRecallCandidate: ReturnType<typeof vi.fn>;
markRecallOpened: ReturnType<typeof vi.fn>;
dismissRecall: ReturnType<typeof vi.fn>;
emitRecallShown: ReturnType<typeof vi.fn>;
emitRecallSnoozed: ReturnType<typeof vi.fn>;
};
const note = (id: string): Note => ({
id, rawText: 'x', aiTitle: 't', aiSummary: 'a\nb\nc',
tags: [], media: [], aiStatus: 'done', aiProvider: null, aiGeneratedAt: null, aiError: null,
titleEditedByUser: false, summaryEditedByUser: false,
dueDate: null, dueDateEditedByUser: false,
userIntent: null, intentPromptedAt: null,
createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
deletedAt: null, lastRecalledAt: null, recallDismissedAt: null,
status: 'active', statusChangedAt: null, moveReason: null
});
describe('store recall actions', () => {
beforeEach(() => {
vi.clearAllMocks();
useInbox.setState({
recallCandidate: null,
recallSnoozeUntilMs: null,
} as Parameters<typeof useInbox.setState>[0]);
});
it('snoozeRecall sets snoozeUntilMs to next KST midnight + emits recall_snoozed', async () => {
useInbox.setState({ recallCandidate: note('n1') } as Parameters<typeof useInbox.setState>[0]);
await useInbox.getState().snoozeRecall();
const ms = useInbox.getState().recallSnoozeUntilMs;
expect(ms).not.toBeNull();
expect(ms!).toBeGreaterThan(Date.now());
expect(inboxApiMock.emitRecallSnoozed).toHaveBeenCalledWith('n1');
});
it('openRecall calls API + fetches next candidate', async () => {
inboxApiMock.markRecallOpened.mockResolvedValueOnce({ note: note('n1') });
inboxApiMock.listRecallCandidate.mockResolvedValueOnce(null);
await useInbox.getState().openRecall('n1');
expect(inboxApiMock.markRecallOpened).toHaveBeenCalledWith('n1');
expect(inboxApiMock.listRecallCandidate).toHaveBeenCalled();
expect(useInbox.getState().recallCandidate).toBeNull();
});
it('dismissRecallNote calls API + fetches next candidate', async () => {
inboxApiMock.dismissRecall.mockResolvedValueOnce({ note: note('n1') });
inboxApiMock.listRecallCandidate.mockResolvedValueOnce(note('n2'));
await useInbox.getState().dismissRecallNote('n1');
expect(inboxApiMock.dismissRecall).toHaveBeenCalledWith('n1');
expect(useInbox.getState().recallCandidate?.id).toBe('n2');
});
});