chore(release): v0.3.8 — UX hole 일괄 hotfix 8건
전수 audit 후 핵심 root fix 3 + edge cases 5: ROOT - inbox:set-status IPC 가 pushNoteUpdated emit (이전엔 stale → 호출처별 refreshMeta 필요) - upsertNote 가 current view status 인식 (이전엔 잘못된 status 노트 잔류) - store async 함수 try/catch (이전엔 IPC fail 시 무한 loading) EDGE - restoreNote 가 status='active' 도 갱신 - upsertNote trash 판정 deletedAt → status='trashed' - Modal Escape dismiss 통일 (5개 modal) - OnboardingWizard IPC fail fallback (try/catch + skip) - MoveStatusModal overlay 클릭 close Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -137,6 +137,42 @@ describe('MoveStatusModal', () => {
|
||||
expect(screen.queryByRole('button', { name: '휴지통' })).toBeNull();
|
||||
});
|
||||
|
||||
it('Escape key → onClose 호출', () => {
|
||||
const onClose = vi.fn();
|
||||
render(
|
||||
<MoveStatusModal
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="active"
|
||||
onClose={onClose}
|
||||
onMoved={vi.fn()}
|
||||
/>
|
||||
);
|
||||
fireEvent.keyDown(document, { key: 'Escape' });
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('overlay 클릭 → onClose, modal body 클릭 → 무반응', () => {
|
||||
const onClose = vi.fn();
|
||||
render(
|
||||
<MoveStatusModal
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="active"
|
||||
onClose={onClose}
|
||||
onMoved={vi.fn()}
|
||||
/>
|
||||
);
|
||||
// body 클릭 (textarea) → onClose 호출 안 됨
|
||||
fireEvent.click(screen.getByRole('textbox'));
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
// overlay (dialog) 클릭 → onClose
|
||||
fireEvent.click(screen.getByRole('dialog', { name: '이동' }));
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('빈 사유 → null reason 전달', async () => {
|
||||
const onMoved = vi.fn();
|
||||
render(
|
||||
|
||||
@@ -83,6 +83,20 @@ describe('inbox:set-status IPC', () => {
|
||||
expect(r.reason).toBe('invalid status');
|
||||
expect(mockSetStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits note:updated to renderer after setStatus (v0.3.8 push-based)', async () => {
|
||||
const send = vi.fn();
|
||||
const win = { webContents: { send }, isDestroyed: () => false } as never;
|
||||
const deps = makeDeps();
|
||||
deps.getInboxWindow = () => win;
|
||||
const updatedNote = { id: 'n1', status: 'completed' };
|
||||
mockFindById.mockReturnValue(updatedNote);
|
||||
registerInboxApi(deps);
|
||||
const handler = handlers['inbox:set-status'];
|
||||
if (handler === undefined) throw new Error('handler not registered');
|
||||
await handler(null, 'n1', 'completed', null);
|
||||
expect(send).toHaveBeenCalledWith('note:updated', updatedNote);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ai:classify-status IPC', () => {
|
||||
|
||||
@@ -95,6 +95,37 @@ describe('useInbox — trash state (v0.2.3 #4)', () => {
|
||||
expect(useInbox.getState().notes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('view-aware upsertNote — inbox view 에서 status=completed 노트 push → notes 에서 제거', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
// view='inbox' (default), active 노트 upsert
|
||||
useInbox.getState().upsertNote(noteStub('a'));
|
||||
expect(useInbox.getState().notes).toHaveLength(1);
|
||||
// 같은 노트가 completed 로 status 변경 → 현재 view 와 안 맞으므로 notes 에서 제거
|
||||
const completed: Note = { ...noteStub('a'), status: 'completed' };
|
||||
useInbox.getState().upsertNote(completed);
|
||||
expect(useInbox.getState().notes).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('view-aware upsertNote — completed view 에서 active 노트 push → notes 에 추가 안 됨', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
useInbox.setState({ view: 'completed' });
|
||||
useInbox.getState().upsertNote(noteStub('a')); // status='active'
|
||||
expect(useInbox.getState().notes).toHaveLength(0);
|
||||
// completed status 면 추가
|
||||
const completed: Note = { ...noteStub('a'), status: 'completed' };
|
||||
useInbox.getState().upsertNote(completed);
|
||||
expect(useInbox.getState().notes).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('view-aware upsertNote — searchResults 가 있을 때 status mismatch → searchResults 에서 제거', async () => {
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
// 이전 test 가 view='completed' 로 설정한 채 끝났을 수 있어 명시적 초기화.
|
||||
useInbox.setState({ view: 'inbox', searchResults: [noteStub('a')] });
|
||||
const completed: Note = { ...noteStub('a'), status: 'completed' };
|
||||
useInbox.getState().upsertNote(completed);
|
||||
expect(useInbox.getState().searchResults).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('emptyTrash with cancelled confirm leaves trashNotes intact', async () => {
|
||||
mockApi.emptyTrash.mockResolvedValueOnce({ confirmed: false, count: 0 });
|
||||
const { useInbox } = await import('../../src/renderer/inbox/store.js');
|
||||
|
||||
Reference in New Issue
Block a user