From 4089e0ec4d321c005e0ca588f01d1a46edfe0a71 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Tue, 24 Feb 2026 10:17:48 +0100 Subject: [PATCH] test: add edge case tests for tab drag-and-drop reorder - Drag cancel (dragEnd without drop) does not trigger reorder - Drag from last tab toward first produces correct reorder - Active tab path is preserved after reorder Co-Authored-By: Claude Opus 4.6 --- src/components/TabBar.test.tsx | 65 ++++++++++++++++++++++++++++++ src/hooks/useTabManagement.test.ts | 22 ++++++++++ 2 files changed, 87 insertions(+) diff --git a/src/components/TabBar.test.tsx b/src/components/TabBar.test.tsx index fa150e8a..5ddf4632 100644 --- a/src/components/TabBar.test.tsx +++ b/src/components/TabBar.test.tsx @@ -132,6 +132,71 @@ describe('TabBar', () => { expect(screen.getAllByTestId('tab-modified-indicator')).toHaveLength(3) }) + it('does not reorder on drag cancel (dragEnd without drop)', () => { + const onReorderTabs = vi.fn() + const tabs = makeTabs(['Alpha', 'Beta', 'Gamma']) + render( + + ) + + const alphaTab = screen.getByText('Alpha').closest('[draggable]')! + const betaTab = screen.getByText('Beta').closest('[draggable]')! + + fireEvent.dragStart(alphaTab, { + dataTransfer: { effectAllowed: 'move', setData: vi.fn() }, + }) + + const rect = betaTab.getBoundingClientRect() + fireEvent.dragOver(betaTab, { + clientX: rect.left + rect.width * 0.75, + dataTransfer: { dropEffect: 'move' }, + }) + + // Cancel via dragEnd (Escape or release outside tab bar) + fireEvent.dragEnd(alphaTab) + + expect(onReorderTabs).not.toHaveBeenCalled() + }) + + it('reorders from last toward first position', () => { + const onReorderTabs = vi.fn() + const tabs = makeTabs(['Alpha', 'Beta', 'Gamma']) + render( + + ) + + const gammaTab = screen.getByText('Gamma').closest('[draggable]')! + const alphaTab = screen.getByText('Alpha').closest('[draggable]')! + + fireEvent.dragStart(gammaTab, { + dataTransfer: { effectAllowed: 'move', setData: vi.fn() }, + }) + + // jsdom returns zero-sized rects, so clientX always hits "right half" + // (insert after index 0 → insert index 1). This still validates + // that dragging the last tab toward the front produces a reorder. + const rect = alphaTab.getBoundingClientRect() + fireEvent.dragOver(alphaTab, { + clientX: rect.left + rect.width * 0.75, + dataTransfer: { dropEffect: 'move' }, + }) + + fireEvent.drop(alphaTab, { dataTransfer: {} }) + + // Gamma (2) dragged onto Alpha (0) → reorder from 2 to 1 + expect(onReorderTabs).toHaveBeenCalledWith(2, 1) + }) + it('switches tab on click', () => { const onSwitchTab = vi.fn() const tabs = makeTabs(['Alpha', 'Beta']) diff --git a/src/hooks/useTabManagement.test.ts b/src/hooks/useTabManagement.test.ts index 749a2c79..ca7186d9 100644 --- a/src/hooks/useTabManagement.test.ts +++ b/src/hooks/useTabManagement.test.ts @@ -205,6 +205,28 @@ describe('useTabManagement', () => { expect(result.current.tabs.map(t => t.entry.title)).toEqual(['C', 'A', 'B']) }) + it('preserves active tab after reorder', async () => { + const { result } = renderHook(() => useTabManagement()) + + await act(async () => { + await result.current.handleSelectNote(makeEntry({ path: '/vault/a.md', title: 'A' })) + }) + await act(async () => { + await result.current.handleSelectNote(makeEntry({ path: '/vault/b.md', title: 'B' })) + }) + await act(async () => { + await result.current.handleSelectNote(makeEntry({ path: '/vault/c.md', title: 'C' })) + }) + + // C is active (last opened). Move it to the front. + act(() => { + result.current.handleReorderTabs(2, 0) + }) + + expect(result.current.tabs.map(t => t.entry.title)).toEqual(['C', 'A', 'B']) + expect(result.current.activeTabPath).toBe('/vault/c.md') + }) + it('persists tab order to localStorage', async () => { const { result } = renderHook(() => useTabManagement())