feat(v0210): inbox:{update-raw-text,list-revisions,restore-revision} IPC
This commit is contained in:
@@ -269,6 +269,29 @@ export function registerInboxApi(deps: InboxIpcDeps): void {
|
||||
await deps.health.runOnce();
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// v0.2.10 Cut C — raw_text 가변 + revision 보존.
|
||||
// updateRawText: 빈 문자열 reject (trim 후 length===0). 그 외엔 그대로 (newline/space 보존).
|
||||
// listRevisions: 그대로 반환 (camelCase 이미 hydrate 됨).
|
||||
// restoreRevision: repo throw → { ok: false } (UI 가 에러 표시).
|
||||
ipcMain.handle('inbox:update-raw-text', async (_e, id: string, newText: string) => {
|
||||
if (typeof newText !== 'string' || newText.trim().length === 0) {
|
||||
return { ok: false as const, reason: 'empty' as const };
|
||||
}
|
||||
deps.repo.updateRawText(id, newText);
|
||||
return { ok: true as const };
|
||||
});
|
||||
|
||||
ipcMain.handle('inbox:list-revisions', (_e, id: string) => deps.repo.listRevisions(id));
|
||||
|
||||
ipcMain.handle('inbox:restore-revision', async (_e, id: string, revId: number) => {
|
||||
try {
|
||||
deps.repo.restoreRevision(id, revId);
|
||||
return { ok: true as const };
|
||||
} catch (e) {
|
||||
return { ok: false as const, reason: (e as Error).message };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function pushNoteUpdated(getWin: () => BrowserWindow | null, note: Note): void {
|
||||
|
||||
99
tests/unit/inboxApi-revisions.test.ts
Normal file
99
tests/unit/inboxApi-revisions.test.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
default: {
|
||||
ipcMain: { handle: 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 = {
|
||||
updateRawText: vi.fn(),
|
||||
listRevisions: vi.fn(() => []),
|
||||
restoreRevision: vi.fn(),
|
||||
findById: vi.fn(),
|
||||
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)
|
||||
} 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 revisions IPC', () => {
|
||||
beforeEach(() => {
|
||||
(electron.ipcMain as unknown as { handle: ReturnType<typeof vi.fn> }).handle.mockClear();
|
||||
});
|
||||
|
||||
it('inbox:update-raw-text — repo.updateRawText 호출 + ok:true', async () => {
|
||||
const deps = makeDeps();
|
||||
registerInboxApi(deps);
|
||||
const h = getHandler('inbox:update-raw-text');
|
||||
const r = await h({}, 'note-1', 'new text');
|
||||
expect(deps.repo.updateRawText).toHaveBeenCalledWith('note-1', 'new text');
|
||||
expect(r).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it('inbox:update-raw-text — 빈 문자열 reject', async () => {
|
||||
const deps = makeDeps();
|
||||
registerInboxApi(deps);
|
||||
const h = getHandler('inbox:update-raw-text');
|
||||
const r = await h({}, 'note-1', ' ');
|
||||
expect(deps.repo.updateRawText).not.toHaveBeenCalled();
|
||||
expect(r).toEqual({ ok: false, reason: 'empty' });
|
||||
});
|
||||
|
||||
it('inbox:list-revisions — repo.listRevisions 결과 반환', async () => {
|
||||
const deps = makeDeps();
|
||||
(deps.repo.listRevisions as ReturnType<typeof vi.fn>).mockReturnValue([
|
||||
{ revId: 1, noteId: 'a', rawText: 'v1', editedAt: 't1', editedBy: 'capture' }
|
||||
]);
|
||||
registerInboxApi(deps);
|
||||
const h = getHandler('inbox:list-revisions');
|
||||
const r = await h({}, 'a');
|
||||
expect(r).toEqual([
|
||||
{ revId: 1, noteId: 'a', rawText: 'v1', editedAt: 't1', editedBy: 'capture' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('inbox:restore-revision — repo throw 시 ok:false', async () => {
|
||||
const deps = makeDeps();
|
||||
(deps.repo.restoreRevision as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
||||
throw new Error('revision 99 not found for note a');
|
||||
});
|
||||
registerInboxApi(deps);
|
||||
const h = getHandler('inbox:restore-revision');
|
||||
const r = await h({}, 'a', 99);
|
||||
expect(r).toEqual({ ok: false, reason: 'revision 99 not found for note a' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user