// @vitest-environment happy-dom import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { Tabs } from './tabs.js'; // Helper utility for async delays const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); describe('Tabs Plugin', () => { const fixture = `
Test 1
Test 2
Test 3
Test 4
Test 5
Test 6
Test 7
Test 8
`; let container: HTMLDivElement; beforeEach(() => { // Mount fixture container = document.createElement('div'); container.innerHTML = fixture; document.body.appendChild(container); const normalTabs = document.querySelector('.tabs.normal')!; Tabs.init(normalTabs, { duration: 0 }); window.location.hash = ''; // Force selection of test2 as active tab Tabs.getInstance(normalTabs)?.select('test2'); }); afterEach(() => { const normalTabs = document.querySelector('.tabs.normal'); if (normalTabs) { Tabs.getInstance(normalTabs)?.destroy(); } document.body.innerHTML = ''; }); describe('Tabs', () => { it('should open to active tab', () => { const normalTabs = document.querySelector('.tabs.normal')!; const activeTab = normalTabs.querySelector('.active')!; const activeTabHash = activeTab.getAttribute('href')!; const tabLinks = normalTabs.querySelectorAll('.tab a'); tabLinks.forEach((tabLink) => { const tabHash = tabLink.getAttribute('href')!; const contentEl = document.querySelector(tabHash)!; if (tabHash === activeTabHash) { expect(contentEl.style.display).not.toBe('none'); } else { expect(contentEl.style.display).toBe('none'); } }); const indicator = normalTabs.querySelector('.indicator'); expect(indicator).not.toBeNull(); }); it('should switch to clicked tab', async () => { const normalTabs = document.querySelector('.tabs.normal')!; const activeTab = normalTabs.querySelector('.active')!; const activeTabHash = activeTab.getAttribute('href')!; const disabledTab = normalTabs.querySelector('.disabled a')!; const disabledTabHash = disabledTab.getAttribute('href')!; const firstTab = normalTabs.querySelector('.tab a')!; const firstTabHash = firstTab.getAttribute('href')!; const indicator = normalTabs.querySelector('.indicator')!; expect(indicator).not.toBeNull(); // Click disabled tab disabledTab.click(); await delay(10); const activeContent = document.querySelector(activeTabHash)!; const disabledContent = document.querySelector(disabledTabHash)!; expect(activeContent.style.display).not.toBe('none'); expect(disabledContent.style.display).toBe('none'); // Click first tab firstTab.click(); await delay(10); const firstContent = document.querySelector(firstTabHash)!; expect(activeContent.style.display).toBe('none'); expect(firstContent.style.display).not.toBe('none'); expect(indicator.offsetLeft).toBe(firstTab.offsetLeft); }); it("shouldn't hide active tab if clicked while active", async () => { const normalTabs = document.querySelector('.tabs.normal')!; const activeTab = normalTabs.querySelector('.active')!; const activeTabHash = activeTab.getAttribute('href')!; const indicator = normalTabs.querySelector('.indicator'); expect(indicator).not.toBeNull(); activeTab.click(); await delay(10); const activeContent = document.querySelector(activeTabHash)!; expect(activeContent.style.display).not.toBe('none'); }); it('should horizontally scroll when too many tabs', () => { const normalTabs = document.querySelector('.tabs.normal')!; const tabsInstance = Tabs.getInstance(normalTabs); // Mock container visible width (400px) and total scroll width (800px) vi.spyOn(normalTabs, 'getBoundingClientRect').mockReturnValue({ width: 400, height: 50, top: 0, left: 0, bottom: 50, right: 400, x: 0, y: 0, toJSON: () => {} }); Object.defineProperty(normalTabs, 'scrollWidth', { configurable: true, value: 800 }); // Trigger recalculation tabsInstance._setTabsAndTabWidth(); // Verify internal width state expect(normalTabs.scrollWidth).toBeGreaterThan(tabsInstance._tabsWidth); expect(tabsInstance._tabWidth).toBe(100); // 800px scrollWidth / 8 tabs }); it('should programmatically switch tabs', async () => { const normalTabs = document.querySelector('.tabs.normal')!; const activeTab = normalTabs.querySelector('.active')!; const activeTabHash = activeTab.getAttribute('href')!; const firstTab = normalTabs.querySelector('li a')!; const firstTabHash = firstTab.getAttribute('href')!; const indicator = normalTabs.querySelector('.indicator')!; const tabs = normalTabs.querySelectorAll('.tab a'); tabs.forEach((tab) => { const tabHash = tab.getAttribute('href')!; const contentEl = document.querySelector(tabHash)!; if (tabHash === activeTabHash) { expect(contentEl.style.display).not.toBe('none'); } else { expect(contentEl.style.display).toBe('none'); } }); Tabs.getInstance(normalTabs)?.select('test1'); await delay(10); const activeContent = document.querySelector(activeTabHash)!; const firstContent = document.querySelector(firstTabHash)!; expect(activeContent.style.display).toBe('none'); expect(firstContent.style.display).not.toBe('none'); expect(indicator.offsetLeft).toBe(firstTab.offsetLeft); }); it("shouldn't error if tab has no associated content", async () => { document.querySelector('#test8')?.remove(); const tabNoContent = document.querySelector('[href="#test8"]')!; expect(tabNoContent.classList.contains('active')).toBe(false); tabNoContent.click(); await delay(10); expect(tabNoContent.classList.contains('active')).toBe(true); }); }); });