feat(ui): BatchMoveModal + 'AI 정리하기' 버튼 (default notebook 일괄 분류)
This commit is contained in:
143
tests/unit/BatchMoveModal.test.tsx
Normal file
143
tests/unit/BatchMoveModal.test.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
const { mockMoveNote, mockBatchClassifyDefault } = vi.hoisted(() => ({
|
||||
mockMoveNote: vi.fn(async () => ({ ok: true as const })),
|
||||
mockBatchClassifyDefault: vi.fn(async () => ({ assignments: [] }))
|
||||
}));
|
||||
|
||||
vi.mock('../../src/renderer/inbox/api.js', () => ({
|
||||
inboxApi: {
|
||||
batchClassifyDefault: mockBatchClassifyDefault,
|
||||
getContinuity: vi.fn(async () => ({ weekStart: '', weekCount: 0, weekTarget: 7, consecutiveCompleteWeeks: 0, showRecoveryToast: false, lastNoteAt: null })),
|
||||
countsByStatus: vi.fn(async () => ({ active: 0, completed: 0, trashed: 0 })),
|
||||
getSettings: vi.fn(async () => ({ ai_enabled: true })),
|
||||
getPendingCount: vi.fn(async () => 0),
|
||||
getOllamaStatus: vi.fn(async () => ({ ok: true })),
|
||||
getTodayCount: vi.fn(async () => 0),
|
||||
getTrashCount: vi.fn(async () => 0),
|
||||
listExpired: vi.fn(async () => []),
|
||||
getFailedCount: vi.fn(async () => 0),
|
||||
listRecallCandidate: vi.fn(async () => null),
|
||||
listByStatus: vi.fn(async () => [])
|
||||
},
|
||||
notebookApi: {
|
||||
moveNote: mockMoveNote,
|
||||
list: vi.fn(async () => [])
|
||||
}
|
||||
}));
|
||||
|
||||
import { BatchMoveModal } from '../../src/renderer/inbox/components/BatchMoveModal';
|
||||
import { useInbox } from '../../src/renderer/inbox/store';
|
||||
|
||||
const baseNote = {
|
||||
id: 'n1',
|
||||
rawText: '테스트 노트 내용',
|
||||
aiTitle: 'AI 제목',
|
||||
aiSummary: null,
|
||||
aiStatus: 'done' as const,
|
||||
aiError: null,
|
||||
aiProvider: null,
|
||||
aiGeneratedAt: null,
|
||||
titleEditedByUser: false,
|
||||
summaryEditedByUser: false,
|
||||
userIntent: null,
|
||||
intentPromptedAt: null,
|
||||
dueDate: null,
|
||||
dueDateEditedByUser: false,
|
||||
deletedAt: null,
|
||||
lastRecalledAt: null,
|
||||
recallDismissedAt: null,
|
||||
status: 'active' as const,
|
||||
statusChangedAt: null,
|
||||
moveReason: null,
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
updatedAt: '2026-01-01T00:00:00Z',
|
||||
tags: [],
|
||||
media: [],
|
||||
notebookId: 'nb-default'
|
||||
};
|
||||
|
||||
describe('BatchMoveModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
cleanup();
|
||||
useInbox.setState({
|
||||
batchClassifyResult: null,
|
||||
batchClassifying: false,
|
||||
notes: [],
|
||||
notebooks: []
|
||||
} as Partial<ReturnType<typeof useInbox.getState>>);
|
||||
});
|
||||
|
||||
it('batchClassifyResult null 시 null 반환', () => {
|
||||
useInbox.setState({ batchClassifyResult: null } as Partial<ReturnType<typeof useInbox.getState>>);
|
||||
const { container } = render(<BatchMoveModal />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('actionable 0건 시 "찾지 못했어요" 메시지 표시', () => {
|
||||
useInbox.setState({
|
||||
batchClassifyResult: { assignments: [{ noteId: 'n1', notebookId: null, notebookName: null }] }
|
||||
} as Partial<ReturnType<typeof useInbox.getState>>);
|
||||
render(<BatchMoveModal />);
|
||||
expect(screen.getByText(/찾지 못했어요/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('actionable 노트 표시 + checkbox 기본 체크', () => {
|
||||
useInbox.setState({
|
||||
batchClassifyResult: {
|
||||
assignments: [
|
||||
{ noteId: 'n1', notebookId: 'nb1', notebookName: '업무' }
|
||||
]
|
||||
},
|
||||
notes: [{ ...baseNote, id: 'n1', aiTitle: 'AI 제목' }]
|
||||
} as Partial<ReturnType<typeof useInbox.getState>>);
|
||||
render(<BatchMoveModal />);
|
||||
expect(screen.getByText('AI 제목')).toBeInTheDocument();
|
||||
expect(screen.getByText(/→ 업무/)).toBeInTheDocument();
|
||||
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(true);
|
||||
});
|
||||
|
||||
it('checkbox toggle 시 selectedIds 변경 (체크 해제 후 확인 버튼 비활성)', () => {
|
||||
useInbox.setState({
|
||||
batchClassifyResult: {
|
||||
assignments: [
|
||||
{ noteId: 'n1', notebookId: 'nb1', notebookName: '업무' }
|
||||
]
|
||||
},
|
||||
notes: [{ ...baseNote, id: 'n1' }]
|
||||
} as Partial<ReturnType<typeof useInbox.getState>>);
|
||||
render(<BatchMoveModal />);
|
||||
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(true);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(false);
|
||||
const confirmBtn = screen.getByRole('button', { name: /건 이동/ });
|
||||
expect(confirmBtn).toBeDisabled();
|
||||
});
|
||||
|
||||
it('확인 클릭 → acceptBatchAssignments 호출 후 modal 닫힘', async () => {
|
||||
const mockAccept = vi.fn(async () => {});
|
||||
useInbox.setState({
|
||||
batchClassifyResult: {
|
||||
assignments: [
|
||||
{ noteId: 'n1', notebookId: 'nb1', notebookName: '업무' }
|
||||
]
|
||||
},
|
||||
notes: [{ ...baseNote, id: 'n1' }],
|
||||
acceptBatchAssignments: mockAccept
|
||||
} as Partial<ReturnType<typeof useInbox.getState>>);
|
||||
render(<BatchMoveModal />);
|
||||
const confirmBtn = screen.getByRole('button', { name: /건 이동/ });
|
||||
fireEvent.click(confirmBtn);
|
||||
await waitFor(() => {
|
||||
expect(mockAccept).toHaveBeenCalledWith([{ noteId: 'n1', notebookId: 'nb1' }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user