- inbox:emitRecallShown / emitRecallSnoozed: ipcMain.handle → on (fire-and-forget honest pattern, return value 의존자 0) - preload: ipcRenderer.invoke → send (matching on the main side) - shared/types: Promise<void> → void on both recall emit methods - store.ts: drop await on emitRecallSnoozed (now void) - inboxApi-*.test.ts: add ipcMain.on to electron mock (broken by above) - tests/unit/recall-ipc.test.ts: new TDD test for handle→on migration Note: #20 CaptureService telemetry .catch debug log skipped — CaptureService has no logger field; adding one would require non-trivial constructor signature change. Reported as CONCERN below. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
85 lines
3.4 KiB
TypeScript
85 lines
3.4 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
|
vi.mock('electron', () => ({ default: { ipcMain: { handle: vi.fn(), on: vi.fn() } } }));
|
|
import electron from 'electron';
|
|
import { registerInboxApi } from '../../src/main/ipc/inboxApi.js';
|
|
import type { InboxIpcDeps } from '../../src/main/ipc/inboxApi.js';
|
|
|
|
function getHandler(channel: string): (...args: unknown[]) => unknown {
|
|
const handle = (electron.ipcMain as unknown as { handle: ReturnType<typeof vi.fn> }).handle;
|
|
const call = handle.mock.calls.find((c) => c[0] === channel);
|
|
if (!call) throw new Error(`channel ${channel} not registered`);
|
|
return call[1] as (...args: unknown[]) => unknown;
|
|
}
|
|
|
|
function makeDeps(overrides: Partial<InboxIpcDeps> = {}): InboxIpcDeps {
|
|
const repo = {
|
|
search: vi.fn(() => []),
|
|
reviewAggregate: vi.fn(() => ({ totalCount: 0, recentNotes: [], tagCounts: [], dueProgress: { total: 0, passed: 0, pending: 0 } })),
|
|
list: vi.fn(),
|
|
listByStatus: vi.fn(),
|
|
countByStatus: vi.fn(() => 0),
|
|
countByAiStatus: vi.fn(() => 0),
|
|
countTrashed: vi.fn(() => 0),
|
|
countFailed: vi.fn(() => 0),
|
|
listTrashed: vi.fn(() => []),
|
|
setStatus: vi.fn(),
|
|
requeueDisabled: vi.fn(() => 0),
|
|
getAllPendingJobs: vi.fn(() => []),
|
|
getPendingCount: vi.fn(() => 0),
|
|
countToday: vi.fn(() => 0),
|
|
findById: vi.fn(),
|
|
listRevisions: vi.fn(() => []),
|
|
restoreRevision: vi.fn(),
|
|
updateRawText: vi.fn()
|
|
} as unknown as InboxIpcDeps['repo'];
|
|
return {
|
|
repo,
|
|
continuity: { get: vi.fn() } as unknown as InboxIpcDeps['continuity'],
|
|
capture: {} as InboxIpcDeps['capture'],
|
|
health: {} as InboxIpcDeps['health'],
|
|
intent: {} as InboxIpcDeps['intent'],
|
|
getInboxWindow: () => null,
|
|
settings: {} as InboxIpcDeps['settings'],
|
|
providerHolder: {} as InboxIpcDeps['providerHolder'],
|
|
paths: { profileDir: '/tmp' },
|
|
...overrides
|
|
};
|
|
}
|
|
|
|
describe('inboxApi search/review IPC', () => {
|
|
beforeEach(() => {
|
|
(electron.ipcMain as unknown as { handle: ReturnType<typeof vi.fn> }).handle.mockClear();
|
|
});
|
|
|
|
it('inbox:search — repo.search 호출 결과 반환', async () => {
|
|
const deps = makeDeps();
|
|
(deps.repo.search as ReturnType<typeof vi.fn>).mockReturnValue([{ id: 'a' }]);
|
|
registerInboxApi(deps);
|
|
const h = getHandler('inbox:search');
|
|
const r = await h({}, '회의', { status: 'active', limit: 10 });
|
|
expect(deps.repo.search).toHaveBeenCalledWith('회의', { status: 'active', limit: 10 });
|
|
expect(r).toEqual([{ id: 'a' }]);
|
|
});
|
|
|
|
it('inbox:review-aggregate — repo.reviewAggregate 호출 결과 반환', async () => {
|
|
const deps = makeDeps();
|
|
const fake = { totalCount: 5, recentNotes: [], tagCounts: [{ tag: 'x', count: 2 }], dueProgress: { total: 1, passed: 1, pending: 0 } };
|
|
(deps.repo.reviewAggregate as ReturnType<typeof vi.fn>).mockReturnValue(fake);
|
|
registerInboxApi(deps);
|
|
const h = getHandler('inbox:review-aggregate');
|
|
const r = await h({}, 'weekly');
|
|
expect(deps.repo.reviewAggregate).toHaveBeenCalledWith('weekly');
|
|
expect(r).toEqual(fake);
|
|
});
|
|
|
|
it('inbox:review-aggregate — 잘못된 period reject', async () => {
|
|
const deps = makeDeps();
|
|
registerInboxApi(deps);
|
|
const h = getHandler('inbox:review-aggregate');
|
|
const r = await h({}, 'yearly');
|
|
expect(deps.repo.reviewAggregate).not.toHaveBeenCalled();
|
|
expect(r).toMatchObject({ totalCount: 0 });
|
|
});
|
|
});
|