UNPKG

@zeix/ui-element

Version:

UIElement - a HTML-first library for reactive Web Components

400 lines (343 loc) 10.7 kB
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Tab Group Component Tests</title> </head> <body> <!-- Test fixtures --> <module-tabgroup id="test1"> <div role="tablist"> <button type="button" role="tab" id="trigger1" aria-controls="panel1" aria-selected="true" tabindex="0" > Tab 1 </button> <button type="button" role="tab" id="trigger2" aria-controls="panel2" aria-selected="false" tabindex="-1" > Tab 2 </button> <button type="button" role="tab" id="trigger3" aria-controls="panel3" aria-selected="false" tabindex="-1" > Tab 3 </button> </div> <div role="tabpanel" id="panel1" aria-labelledby="trigger1"> Tab 1 content </div> <div role="tabpanel" id="panel2" aria-labelledby="trigger2" hidden> Tab 2 content </div> <div role="tabpanel" id="panel3" aria-labelledby="trigger3" hidden> Tab 3 content </div> </module-tabgroup> <module-tabgroup id="test2"> <div role="tablist"> <button type="button" role="tab" id="trigger4" aria-controls="panel4" aria-selected="false" tabindex="-1" > Tab A </button> <button type="button" role="tab" id="trigger5" aria-controls="panel5" aria-selected="true" tabindex="0" > Tab B </button> </div> <div role="tabpanel" id="panel4" aria-labelledby="trigger4" hidden> Tab A content </div> <div role="tabpanel" id="panel5" aria-labelledby="trigger5"> Tab B content </div> </module-tabgroup> <module-tabgroup id="test3"> <div role="tablist"> <button type="button" role="tab" id="trigger6" aria-controls="panel6" aria-selected="false" tabindex="-1" > Single Tab </button> </div> <div role="tabpanel" id="panel6" aria-labelledby="trigger6" hidden> Single tab content </div> </module-tabgroup> <script type="module"> import { runTests } from '@web/test-runner-mocha' import { assert } from '@esm-bundle/chai' import '../../../docs/assets/main.js' // Built components bundle const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) const animationFrame = () => new Promise(requestAnimationFrame) const microtask = () => new Promise(queueMicrotask) const tick = async () => { await animationFrame() // Wait for effects to execute await microtask() // Wait for DOM to reflect changes } // Helper function to simulate keyboard events const simulateKeydown = (element, key) => { const event = new KeyboardEvent('keydown', { key: key, bubbles: true, cancelable: true, }) element.dispatchEvent(event) return event } runTests(() => { describe('Tab Group Component', () => { it('should verify component exists and has expected structure', () => { const el = document.getElementById('test1') assert.isNotNull(el) assert.equal( el.tagName.toLowerCase(), 'module-tabgroup', ) assert.isDefined(el.selected) assert.isString(el.selected) }) it('should initialize with correct default selection', async () => { const el = document.getElementById('test1') // Component reads aria-selected="true" from DOM to determine initial selection assert.equal(el.selected, 'panel1') }) it('should initialize with pre-selected tab', async () => { const el = document.getElementById('test2') await tick() assert.equal(el.selected, 'panel5') }) it('should handle no initially selected tab', async () => { const el = document.getElementById('test3') await tick() // If no tab is has aria-selected="true" first tab is auto-selected assert.equal(el.selected, 'panel6') }) it('should update selection when tab is clicked', async () => { const el = document.getElementById('test1') const tab1 = el.querySelector('#trigger1') const tab2 = el.querySelector('#trigger2') // Reset component state to ensure clean test tab1.click() await tick() tab2.click() await tick() assert.equal(el.selected, 'panel2') }) it('should update ARIA attributes when selection changes', async () => { const el = document.getElementById('test1') const tab1 = el.querySelector('#trigger1') const tab2 = el.querySelector('#trigger2') const tab3 = el.querySelector('#trigger3') // Reset component state to ensure clean test tab1.click() await tick() assert.equal(tab1.getAttribute('aria-selected'), 'true') assert.equal( tab2.getAttribute('aria-selected'), 'false', ) assert.equal( tab3.getAttribute('aria-selected'), 'false', ) // Click tab2 tab2.click() await tick() assert.equal(el.selected, 'panel2') assert.equal( tab1.getAttribute('aria-selected'), 'false', ) assert.equal(tab2.getAttribute('aria-selected'), 'true') assert.equal( tab3.getAttribute('aria-selected'), 'false', ) }) it('should update tabIndex when selection changes', async () => { const el = document.getElementById('test1') const tab1 = el.querySelector('#trigger1') const tab2 = el.querySelector('#trigger2') const tab3 = el.querySelector('#trigger3') // Reset component state to ensure clean test tab1.click() await tick() assert.equal(tab1.tabIndex, 0) assert.equal(tab2.tabIndex, -1) assert.equal(tab3.tabIndex, -1) // Click tab3 tab3.click() await tick() assert.equal(el.selected, 'panel3') assert.equal(tab1.tabIndex, -1) assert.equal(tab2.tabIndex, -1) assert.equal(tab3.tabIndex, 0) }) it('should show/hide panels based on selection', async () => { const el = document.getElementById('test1') const panel1 = el.querySelector('#panel1') const panel2 = el.querySelector('#panel2') const panel3 = el.querySelector('#panel3') const tab1 = el.querySelector('#trigger1') const tab2 = el.querySelector('#trigger2') // Reset component state to ensure clean test tab1.click() await tick() assert.isFalse(panel1.hidden) assert.isTrue(panel2.hidden) assert.isTrue(panel3.hidden) // Click tab2 tab2.click() await tick() assert.isTrue(panel1.hidden) assert.isFalse(panel2.hidden) assert.isTrue(panel3.hidden) }) it('should handle multiple rapid tab clicks', async () => { const el = document.getElementById('test1') const tab1 = el.querySelector('#trigger1') const tab2 = el.querySelector('#trigger2') const tab3 = el.querySelector('#trigger3') // Reset component state to ensure clean test tab1.click() await tick() // Click multiple tabs rapidly tab2.click() await tick() tab3.click() await tick() tab1.click() await tick() assert.equal(el.selected, 'panel1') assert.equal(tab1.getAttribute('aria-selected'), 'true') assert.equal( tab2.getAttribute('aria-selected'), 'false', ) assert.equal( tab3.getAttribute('aria-selected'), 'false', ) }) it('should handle keyboard navigation with arrow keys', async () => { const el = document.getElementById('test1') const tablist = el.querySelector('[role="tablist"]') const tab1 = el.querySelector('#trigger1') // Reset component state to ensure clean test tab1.click() await tick() // Focus first tab and simulate arrow key tab1.focus() simulateKeydown(tablist, 'ArrowRight') await tick() // Note: The actual focus change depends on manageArrowKeyFocus implementation // We're testing that the keydown event is properly bound to the tablist assert.equal(tablist.tagName.toLowerCase(), 'div') assert.equal(tablist.getAttribute('role'), 'tablist') }) it('should maintain proper ARIA structure', () => { const el = document.getElementById('test1') // Check tablist structure const tablist = el.querySelector('[role="tablist"]') assert.isNotNull(tablist) // Check tabs const tabs = el.querySelectorAll('[role="tab"]') assert.equal(tabs.length, 3) tabs.forEach(tab => { assert.isNotNull(tab.getAttribute('aria-controls')) assert.isNotNull(tab.getAttribute('aria-selected')) }) // Check panels const panels = el.querySelectorAll('[role="tabpanel"]') assert.equal(panels.length, 3) panels.forEach(panel => { assert.isNotNull( panel.getAttribute('aria-labelledby'), ) assert.isNotNull(panel.id) }) }) it('should work with single tab', async () => { const el = document.getElementById('test3') const tab = el.querySelector('#trigger6') const panel = el.querySelector('#panel6') // Click the single tab tab.click() await tick() assert.equal(el.selected, 'panel6') assert.equal(tab.getAttribute('aria-selected'), 'true') assert.equal(tab.tabIndex, 0) assert.isFalse(panel.hidden) }) it('should not allow programmatic property updates', async () => { const el = document.getElementById('test1') const originalSelected = el.selected const originalTabs = el.tabs // Should throw TypeError when trying to set selected assert.throws(() => { el.selected = 'panel2' }, TypeError) // Selected should remain unchanged after failed assignment assert.equal(el.selected, originalSelected) // Should throw for different value types assert.throws(() => { el.selected = 123 }, TypeError) assert.throws(() => { el.selected = null }, TypeError) assert.throws(() => { el.selected = undefined }, TypeError) // Should throw TypeError when trying to set tabs assert.throws(() => { el.tabs = [] }, TypeError) assert.throws(() => { el.tabs = [document.createElement('button')] }, TypeError) assert.throws(() => { el.tabs = null }, TypeError) // Verify properties are still the original values assert.equal(el.selected, originalSelected) assert.deepEqual(el.tabs, originalTabs) }) }) }) </script> </body> </html>