feat(ui): NoteCard 의 notebook chip + 1-click 이동

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
th-kim0823
2026-05-15 11:05:20 +09:00
parent 274c171ee8
commit a7a90b8701
2 changed files with 116 additions and 5 deletions

View File

@@ -2,16 +2,17 @@
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 type { Note } from '@shared/types';
import type { Note, Notebook } from '@shared/types';
const { mockOpenMedia, mockSetStatus, mockClassify, mockUpdateRawText } = vi.hoisted(() => ({
const { mockOpenMedia, mockSetStatus, mockClassify, mockUpdateRawText, mockMoveNoteToNotebook } = vi.hoisted(() => ({
mockOpenMedia: vi.fn(async () => ({ ok: true })),
mockSetStatus: vi.fn(async () => ({ ok: true as const })),
mockClassify: vi.fn(async () => ({
recommended: 'archived' as const,
rationale: 'stub'
})),
mockUpdateRawText: vi.fn(async () => ({ ok: true as const }))
mockUpdateRawText: vi.fn(async () => ({ ok: true as const })),
mockMoveNoteToNotebook: vi.fn(async () => {})
}));
vi.mock('../../src/renderer/inbox/api.js', () => ({
@@ -33,9 +34,24 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({
}));
const mockRefreshMeta = vi.fn();
// Notebooks used across notebook-chip tests.
const stubNotebooks: Notebook[] = [
{ id: 'nb-1', name: '회사', color: '#4a90d9', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', noteCount: 1 },
{ id: 'nb-2', name: '개인', color: '#e67e22', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', noteCount: 0 }
];
vi.mock('../../src/renderer/inbox/store.js', () => ({
useInbox: Object.assign(
() => ({}),
// Selector-aware: if selector is a function, call it with the mock state.
(selector?: (s: unknown) => unknown) => {
const state = {
notebooks: stubNotebooks,
moveNoteToNotebook: mockMoveNoteToNotebook
};
if (typeof selector === 'function') return selector(state);
return state;
},
{ getState: () => ({ setTagFilter: vi.fn(), refreshMeta: mockRefreshMeta }) }
)
}));
@@ -190,3 +206,33 @@ describe('NoteCard — raw_text editing', () => {
expect(last.rawText).toBe('new');
});
});
describe('NoteCard — notebook chip (Task 17)', () => {
beforeEach(() => {
vi.clearAllMocks();
cleanup();
});
it('현재 notebook 이름 chip 렌더링', () => {
render(<NoteCard note={{ ...baseNote, notebookId: 'nb-1' }} onUpdated={vi.fn()} mode="inbox" />);
expect(screen.getByTitle('다른 노트북으로 이동')).toBeInTheDocument();
expect(screen.getByTitle('다른 노트북으로 이동').textContent).toContain('회사');
});
it('chip 클릭 → 다른 notebook 목록 dropdown', () => {
render(<NoteCard note={{ ...baseNote, notebookId: 'nb-1' }} onUpdated={vi.fn()} mode="inbox" />);
fireEvent.click(screen.getByTitle('다른 노트북으로 이동'));
// 현재 nb-1('회사') 는 제외, nb-2('개인') 만 보임.
expect(screen.getByText('개인')).toBeInTheDocument();
expect(screen.queryAllByText('회사').length).toBe(1); // chip 버튼 안에만 존재
});
it('dropdown 의 notebook 클릭 → store.moveNoteToNotebook 호출', async () => {
render(<NoteCard note={{ ...baseNote, notebookId: 'nb-1' }} onUpdated={vi.fn()} mode="inbox" />);
fireEvent.click(screen.getByTitle('다른 노트북으로 이동'));
fireEvent.click(screen.getByText('개인'));
await waitFor(() => {
expect(mockMoveNoteToNotebook).toHaveBeenCalledWith('n1', 'nb-2');
});
});
});