feat(retry): store retryAllFailed action + failedCount (#2 v0.2.3)
This commit is contained in:
@@ -17,6 +17,7 @@ interface InboxState {
|
||||
tagFilter: string | null;
|
||||
expiredCandidates: Note[];
|
||||
expiredSnoozeUntilMs: number | null;
|
||||
failedCount: number;
|
||||
loadInitial: () => Promise<void>;
|
||||
refreshMeta: () => Promise<void>;
|
||||
upsertNote: (note: Note) => void;
|
||||
@@ -31,6 +32,7 @@ interface InboxState {
|
||||
trashExpiredBatch: (ids: string[]) => Promise<void>;
|
||||
snoozeExpired: () => void;
|
||||
recheckOllama: () => Promise<void>;
|
||||
retryAllFailed: () => Promise<void>;
|
||||
}
|
||||
|
||||
const emptyContinuity: WeeklyContinuity = {
|
||||
@@ -51,29 +53,32 @@ export const useInbox = create<InboxState>((set, get) => ({
|
||||
tagFilter: null,
|
||||
expiredCandidates: [],
|
||||
expiredSnoozeUntilMs: null,
|
||||
failedCount: 0,
|
||||
async loadInitial() {
|
||||
set({ loading: true });
|
||||
const [notes, continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates] = await Promise.all([
|
||||
const [notes, continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount] = await Promise.all([
|
||||
inboxApi.listNotes({ limit: 50 }),
|
||||
inboxApi.getContinuity(),
|
||||
inboxApi.getPendingCount(),
|
||||
inboxApi.getOllamaStatus(),
|
||||
inboxApi.getTodayCount(),
|
||||
inboxApi.getTrashCount(),
|
||||
inboxApi.listExpired()
|
||||
inboxApi.listExpired(),
|
||||
inboxApi.getFailedCount()
|
||||
]);
|
||||
set({ notes, continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, loading: false });
|
||||
set({ notes, continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount, loading: false });
|
||||
},
|
||||
async refreshMeta() {
|
||||
const [continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates] = await Promise.all([
|
||||
const [continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount] = await Promise.all([
|
||||
inboxApi.getContinuity(),
|
||||
inboxApi.getPendingCount(),
|
||||
inboxApi.getOllamaStatus(),
|
||||
inboxApi.getTodayCount(),
|
||||
inboxApi.getTrashCount(),
|
||||
inboxApi.listExpired()
|
||||
inboxApi.listExpired(),
|
||||
inboxApi.getFailedCount()
|
||||
]);
|
||||
set({ continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates });
|
||||
set({ continuity, pendingCount, ollamaStatus, todayCount, trashCount, expiredCandidates, failedCount });
|
||||
},
|
||||
upsertNote(note) {
|
||||
// trashCount 는 server-authoritative. trashNotes 가 cache-loaded (showTrash=true) 일
|
||||
@@ -172,5 +177,11 @@ export const useInbox = create<InboxState>((set, get) => ({
|
||||
async recheckOllama() {
|
||||
const status = await inboxApi.ollamaRecheck();
|
||||
set({ ollamaStatus: status });
|
||||
},
|
||||
async retryAllFailed() {
|
||||
await inboxApi.retryAllFailed();
|
||||
// 낙관적 갱신: failedCount = 0. AiWorker 처리 진행 중에 PendingBanner 가 N건 노출.
|
||||
// refreshMeta 가 트리거되면 자연 동기 (worker.onUpdate → main → renderer).
|
||||
set({ failedCount: 0 });
|
||||
}
|
||||
}));
|
||||
|
||||
50
tests/unit/store.aiRetry.test.ts
Normal file
50
tests/unit/store.aiRetry.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
const mockApi = {
|
||||
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),
|
||||
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 () => []),
|
||||
trashExpiredBatch: vi.fn(async () => ({ trashedCount: 0, confirmed: false })),
|
||||
ollamaRecheck: vi.fn(async (): Promise<{ ok: boolean; reason?: string }> => ({ ok: true })),
|
||||
onOllamaStatus: vi.fn(() => () => {}),
|
||||
retryAllFailed: vi.fn(async () => ({ count: 0 })),
|
||||
getFailedCount: vi.fn(async () => 0)
|
||||
};
|
||||
|
||||
vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: mockApi }));
|
||||
|
||||
describe('useInbox — AI retry (v0.2.3 #2)', () => {
|
||||
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, failedCount: 5,
|
||||
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());
|
||||
});
|
||||
|
||||
it('retryAllFailed action — failedCount=0 reset 후 IPC 호출', async () => {
|
||||
mockApi.retryAllFailed.mockResolvedValueOnce({ count: 5 });
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
await useInbox.getState().retryAllFailed();
|
||||
expect(mockApi.retryAllFailed).toHaveBeenCalledTimes(1);
|
||||
expect(useInbox.getState().failedCount).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user