feat(notebook): NotebookList 의 hover ↑↓ 버튼 + reorder action

This commit is contained in:
th-kim0823
2026-05-15 15:10:35 +09:00
parent eca91a1e7c
commit f9c7c960d5
5 changed files with 100 additions and 23 deletions

View File

@@ -41,4 +41,32 @@ describe('NotebookList', () => {
expect(btn1.style.background).not.toBe('transparent');
expect(btn2.style.background).toBe('transparent');
});
it('hover 시 ↑↓ 버튼 노출', () => {
const { container } = render(<NotebookList notebooks={notebooks} selectedId="nb-1" onSelect={() => {}} onCreate={() => {}} onReorder={async () => {}} />);
// 기본 상태: ↑↓ 미노출
expect(screen.queryByLabelText(/위로/)).not.toBeInTheDocument();
// hover 후 보임 — position:relative 인 row div 선택
const row = container.querySelector('div[style*="position: relative"]') as HTMLElement;
fireEvent.mouseEnter(row);
expect(screen.getAllByLabelText(/위로/).length).toBeGreaterThan(0);
});
it('↑ 클릭 시 onReorder up 호출', () => {
const onReorder = vi.fn();
const { container } = render(<NotebookList notebooks={notebooks} selectedId="nb-2" onSelect={() => {}} onCreate={() => {}} onReorder={onReorder} />);
const rows = container.querySelectorAll('div[style*="position: relative"]');
// 두번째 row (nb-2) hover → 위로 클릭
fireEvent.mouseEnter(rows[1] as Element);
fireEvent.click(screen.getByLabelText('회사 위로'));
expect(onReorder).toHaveBeenCalledWith('nb-2', 'up');
});
it('첫 row 의 ↑ 는 disabled', () => {
const { container } = render(<NotebookList notebooks={notebooks} selectedId="nb-1" onSelect={() => {}} onCreate={() => {}} onReorder={async () => {}} />);
const rows = container.querySelectorAll('div[style*="position: relative"]');
fireEvent.mouseEnter(rows[0] as Element);
const upBtn = screen.getByLabelText('기본 위로');
expect(upBtn).toBeDisabled();
});
});

View File

@@ -1,8 +1,9 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
const { mockListByStatus, mockCountsByStatus } = vi.hoisted(() => ({
const { mockListByStatus, mockCountsByStatus, mockReorder } = vi.hoisted(() => ({
mockListByStatus: vi.fn(async () => []),
mockCountsByStatus: vi.fn(async () => ({ active: 0, completed: 0, archived: 0, trashed: 0 }))
mockCountsByStatus: vi.fn(async () => ({ active: 0, completed: 0, archived: 0, trashed: 0 })),
mockReorder: vi.fn(async () => ({ ok: true as const }))
}));
vi.mock('../../src/renderer/inbox/api.js', () => ({
@@ -32,7 +33,8 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({
delete: vi.fn(async () => ({ ok: true as const })),
rename: vi.fn(async () => ({ ok: true as const })),
setColor: vi.fn(async () => ({ ok: true as const })),
moveNote: vi.fn(async () => ({ ok: true as const }))
moveNote: vi.fn(async () => ({ ok: true as const })),
reorder: mockReorder
}
}));
@@ -42,6 +44,7 @@ describe('store notebooks', () => {
beforeEach(() => {
mockListByStatus.mockClear();
mockCountsByStatus.mockClear();
mockReorder.mockClear();
useInbox.setState({ notebooks: [], selectedNotebookId: null, sidebarVisible: false, sidebarWidth: 240, view: 'inbox' } as never);
});
@@ -116,4 +119,11 @@ describe('store notebooks', () => {
// countsByStatus 는 refreshMeta 경로로 호출됨
expect(mockCountsByStatus).toHaveBeenCalled();
});
it('reorderNotebook 성공 시 notebookApi.reorder 호출 + loadNotebooks 재로드', async () => {
await useInbox.getState().reorderNotebook('nb-1', 'down');
expect(mockReorder).toHaveBeenCalledWith('nb-1', 'down');
// loadNotebooks 가 호출되어 notebooks 가 갱신됨
expect(useInbox.getState().notebooks).toHaveLength(2);
});
});