import { renderHook } from '@testing-library/react'; import { useTabsOverflow } from '../useTabsOverflow'; function stubWidth(element: Element, width: number) { element.getBoundingClientRect = () => ({ width }) as DOMRect; } function createTabsDom({ containerWidth, itemWidths, moreItemsWidth, listPadding = 0, }: { containerWidth: number; itemWidths: number[]; moreItemsWidth: number; listPadding?: number; }) { const container = document.createElement('div'); const list = document.createElement('ul'); container.append(list); document.body.append(container); stubWidth(container, containerWidth); list.style.paddingLeft = `${listPadding}px`; list.style.paddingRight = `${listPadding}px`; for (const width of itemWidths) { const item = document.createElement('li'); stubWidth(item, width); list.append(item); } const moreItems = document.createElement('li'); stubWidth(moreItems, moreItemsWidth); list.append(moreItems); return { tabsRef: { current: container }, tabsPanelRef: { current: list } }; } describe('useTabsOverflow hook', () => { afterEach(() => { document.body.innerHTML = ''; }); it('shows all tabs when they exactly fill the container, without reserving the "..." width', () => { const tabs = ['a', 'b', 'c', 'd', 'e']; const { tabsRef, tabsPanelRef } = createTabsDom({ containerWidth: 500, itemWidths: [100, 100, 100, 100, 100], moreItemsWidth: 40, }); const { result } = renderHook(() => useTabsOverflow(tabs, tabsRef, tabsPanelRef)); expect(result.current).toEqual([]); }); it('folds overflowing tabs and keeps room for the "..." item', () => { const tabs = ['a', 'b', 'c', 'd', 'e', 'f']; const { tabsRef, tabsPanelRef } = createTabsDom({ containerWidth: 500, itemWidths: [100, 100, 100, 100, 100, 100], moreItemsWidth: 40, }); const { result } = renderHook(() => useTabsOverflow(tabs, tabsRef, tabsPanelRef)); expect(result.current).toEqual(['e', 'f']); }); it('compares fractional widths without rounding', () => { const tabs = ['a', 'b', 'c', 'd', 'e']; const { tabsRef, tabsPanelRef } = createTabsDom({ containerWidth: 500, itemWidths: [99.9, 99.9, 99.9, 99.9, 99.9], moreItemsWidth: 40, }); const { result } = renderHook(() => useTabsOverflow(tabs, tabsRef, tabsPanelRef)); expect(result.current).toEqual([]); }); it('subtracts the tab list padding from the available width', () => { const tabs = ['a', 'b', 'c', 'd', 'e']; const { tabsRef, tabsPanelRef } = createTabsDom({ containerWidth: 500, itemWidths: [100, 100, 100, 100, 100], moreItemsWidth: 40, listPadding: 2, }); const { result } = renderHook(() => useTabsOverflow(tabs, tabsRef, tabsPanelRef)); expect(result.current).toEqual(['e']); }); it('keeps at least one tab visible even when nothing fits', () => { const tabs = ['a', 'b']; const { tabsRef, tabsPanelRef } = createTabsDom({ containerWidth: 50, itemWidths: [100, 100], moreItemsWidth: 40, }); const { result } = renderHook(() => useTabsOverflow(tabs, tabsRef, tabsPanelRef)); expect(result.current).toEqual(['b']); }); it('reports no overflow when the DOM is not measurable (jsdom zero sizes)', () => { const tabs = ['a', 'b', 'c']; const { tabsRef, tabsPanelRef } = createTabsDom({ containerWidth: 0, itemWidths: [0, 0, 0], moreItemsWidth: 0, }); const { result } = renderHook(() => useTabsOverflow(tabs, tabsRef, tabsPanelRef)); expect(result.current).toEqual([]); }); });