feat(v029): useInbox view enum + counts + setView + listByStatus/countsByStatus IPC
- store.ts: view enum ('inbox'|'completed'|'archived'|'trash'|'settings') + counts +
setView + loadByView. setShowSettings delegates to setView (mirror).
- types.ts + preload + ipc/inboxApi: listByStatus + countsByStatus IPC.
- NoteRepository.countByStatus 신규.
- store.view.test (5) + NoteRepository.countByStatus test (1).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -966,6 +966,24 @@ describe('NoteRepository — setStatus + listByStatus', () => {
|
||||
expect(note.moveReason).toBeNull();
|
||||
});
|
||||
|
||||
it('countByStatus returns accurate count per status', () => {
|
||||
const a = repo.create({ rawText: 'a' }).id; // active
|
||||
repo.create({ rawText: 'b' }); // active
|
||||
const c = repo.create({ rawText: 'c' }).id;
|
||||
repo.setStatus(c, 'completed', null, new Date('2026-05-10T00:00:00.000Z'));
|
||||
const d = repo.create({ rawText: 'd' }).id;
|
||||
repo.setStatus(d, 'archived', null, new Date('2026-05-10T00:00:00.000Z'));
|
||||
const e = repo.create({ rawText: 'e' }).id;
|
||||
repo.setStatus(e, 'trashed', null, new Date('2026-05-10T00:00:00.000Z'));
|
||||
|
||||
expect(repo.countByStatus('active')).toBe(2);
|
||||
expect(repo.countByStatus('completed')).toBe(1);
|
||||
expect(repo.countByStatus('archived')).toBe(1);
|
||||
expect(repo.countByStatus('trashed')).toBe(1);
|
||||
// sanity — a 가 여전히 active.
|
||||
expect(repo.findById(a)!.status).toBe('active');
|
||||
});
|
||||
|
||||
it('restoreNote sets status=active + clears moveReason', () => {
|
||||
const { id } = repo.create({ rawText: 'r' });
|
||||
repo.setStatus(id, 'trashed', '실수', new Date('2026-05-15T00:00:00.000Z'));
|
||||
|
||||
86
tests/unit/store.view.test.ts
Normal file
86
tests/unit/store.view.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import type { Note } from '@shared/types';
|
||||
|
||||
const mockApi = {
|
||||
listNotes: vi.fn(async () => [] as Note[]),
|
||||
listTrash: vi.fn(async () => [] as Note[]),
|
||||
listByStatus: vi.fn(async () => [] as Note[]),
|
||||
countsByStatus: vi.fn(async () => ({ active: 0, completed: 0, archived: 0, trashed: 0 })),
|
||||
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),
|
||||
getFailedCount: vi.fn(async () => 0),
|
||||
listExpired: vi.fn(async () => [] as Note[]),
|
||||
listRecallCandidate: vi.fn(async () => null),
|
||||
restoreNote: vi.fn(async () => {}),
|
||||
permanentDeleteNote: vi.fn(async () => ({ confirmed: true })),
|
||||
emptyTrash: vi.fn(async () => ({ confirmed: true, count: 0 })),
|
||||
trashExpiredBatch: vi.fn(async () => ({ confirmed: true, trashedCount: 0 })),
|
||||
onNoteUpdated: vi.fn(() => () => {}),
|
||||
updateAiFields: vi.fn(async () => {}),
|
||||
setDueDate: vi.fn(async () => {}),
|
||||
setIntent: vi.fn(async () => {}),
|
||||
dismissIntent: vi.fn(async () => {}),
|
||||
ollamaRecheck: vi.fn(async () => ({ ok: true })),
|
||||
retryAllFailed: vi.fn(async () => {}),
|
||||
markRecallOpened: vi.fn(async () => {}),
|
||||
dismissRecall: vi.fn(async () => {}),
|
||||
emitRecallSnoozed: vi.fn(async () => {})
|
||||
};
|
||||
|
||||
vi.mock('../../src/renderer/inbox/api.js', () => ({ inboxApi: mockApi }));
|
||||
|
||||
describe('inbox store — view enum', () => {
|
||||
beforeEach(async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
useInbox.setState({
|
||||
view: 'inbox',
|
||||
counts: { active: 0, completed: 0, archived: 0, trashed: 0 },
|
||||
notes: [], trashNotes: [], trashCount: 0,
|
||||
showTrash: false, showSettings: 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,
|
||||
failedCount: 0, recallCandidate: null, recallSnoozeUntilMs: null
|
||||
});
|
||||
Object.values(mockApi).forEach((fn) => 'mockClear' in fn && (fn as any).mockClear());
|
||||
});
|
||||
|
||||
it('initial view is inbox', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
expect(useInbox.getState().view).toBe('inbox');
|
||||
});
|
||||
|
||||
it('setView changes view', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
useInbox.getState().setView('completed');
|
||||
expect(useInbox.getState().view).toBe('completed');
|
||||
});
|
||||
|
||||
it('counts initialized to zero per status', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
expect(useInbox.getState().counts).toEqual({ active: 0, completed: 0, archived: 0, trashed: 0 });
|
||||
});
|
||||
|
||||
it('backward-compat: showTrash mirrors view==="trash"', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
useInbox.getState().setView('trash');
|
||||
expect(useInbox.getState().showTrash).toBe(true);
|
||||
useInbox.getState().setView('inbox');
|
||||
expect(useInbox.getState().showTrash).toBe(false);
|
||||
});
|
||||
|
||||
it('backward-compat: showSettings mirrors view==="settings"', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
useInbox.getState().setView('settings');
|
||||
expect(useInbox.getState().showSettings).toBe(true);
|
||||
useInbox.getState().setView('inbox');
|
||||
expect(useInbox.getState().showSettings).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user