UNPKG

@zeix/ui-element

Version:

UIElement - a HTML-first library for reactive Web Components

452 lines (385 loc) 13.3 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) // 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') // Reset component state to ensure clean test el.selected = '' await animationFrame() // Re-initialize by setting to the expected initial state el.selected = 'panel1' await animationFrame() // Wait for scheduled effect execution // 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 animationFrame() // Wait for scheduled effect execution assert.equal(el.selected, 'panel5') }) it('should handle no initially selected tab', async () => { const el = document.getElementById('test3') await animationFrame() // Wait for scheduled effect execution assert.equal(el.selected, '') }) it('should update selection when tab is clicked', async () => { const el = document.getElementById('test1') const tab2 = el.querySelector('#trigger2') // Reset component state to ensure clean test el.selected = 'panel1' await animationFrame() tab2.click() await animationFrame() // Wait for event handling and effect execution 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 el.selected = 'panel1' await animationFrame() // Wait for initial state 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 animationFrame() // Wait for event handling and effect execution await animationFrame() // Extra frame for effect processing 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 el.selected = 'panel1' await animationFrame() assert.equal(tab1.tabIndex, 0) assert.equal(tab2.tabIndex, -1) assert.equal(tab3.tabIndex, -1) // Click tab3 tab3.click() await animationFrame() // Wait for event handling and effect execution await animationFrame() // Extra frame for effect processing 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 tab2 = el.querySelector('#trigger2') // Reset component state to ensure clean test el.selected = 'panel1' await animationFrame() // Wait for initial state assert.isFalse(panel1.hidden) assert.isTrue(panel2.hidden) assert.isTrue(panel3.hidden) // Click tab2 tab2.click() await animationFrame() // Wait for event handling and effect execution await animationFrame() // Extra frame for effect processing assert.isTrue(panel1.hidden) assert.isFalse(panel2.hidden) assert.isTrue(panel3.hidden) }) it('should handle programmatic selection changes', async () => { const el = document.getElementById('test1') const tab1 = el.querySelector('#trigger1') const tab3 = el.querySelector('#trigger3') const panel1 = el.querySelector('#panel1') const panel3 = el.querySelector('#panel3') // Set selected programmatically el.selected = 'panel3' await animationFrame() // Wait for scheduled effect execution assert.equal( tab1.getAttribute('aria-selected'), 'false', ) assert.equal(tab3.getAttribute('aria-selected'), 'true') assert.equal(tab1.tabIndex, -1) assert.equal(tab3.tabIndex, 0) assert.isTrue(panel1.hidden) assert.isFalse(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 el.selected = 'panel1' await animationFrame() // Click multiple tabs rapidly tab2.click() await animationFrame() // Wait for first click to process tab3.click() await animationFrame() // Wait for second click to process tab1.click() await animationFrame() // Wait for final click to process await animationFrame() // Extra frame for effect processing 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 el.selected = 'panel1' await animationFrame() // Focus first tab and simulate arrow key tab1.focus() simulateKeydown(tablist, 'ArrowRight') await animationFrame() // Wait for focus management // 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 handle selection with invalid panel ID', async () => { const el = document.getElementById('test1') // Reset component state first el.selected = 'panel1' await animationFrame() // Set to invalid panel ID el.selected = 'nonexistent-panel' await animationFrame() // Wait for scheduled effect execution // All panels should be hidden when selection doesn't match any panel const panels = el.querySelectorAll('[role="tabpanel"]') panels.forEach(panel => { assert.isTrue(panel.hidden) }) // All tabs should show as not selected const tabs = el.querySelectorAll('[role="tab"]') tabs.forEach(tab => { assert.equal( tab.getAttribute('aria-selected'), 'false', ) assert.equal(tab.tabIndex, -1) }) }) it('should handle empty selection', async () => { const el = document.getElementById('test1') // Reset component state first el.selected = 'panel1' await animationFrame() // Set to empty selection el.selected = '' await animationFrame() // Wait for scheduled effect execution // All panels should be hidden const panels = el.querySelectorAll('[role="tabpanel"]') panels.forEach(panel => { assert.isTrue(panel.hidden) }) // All tabs should show as not selected const tabs = el.querySelectorAll('[role="tab"]') tabs.forEach(tab => { assert.equal( tab.getAttribute('aria-selected'), 'false', ) assert.equal(tab.tabIndex, -1) }) }) it('should work with single tab', async () => { const el = document.getElementById('test3') const tab = el.querySelector('#trigger6') const panel = el.querySelector('#panel6') // Reset component state first el.selected = '' await animationFrame() // Click the single tab tab.click() await animationFrame() // Wait for event handling and effect execution await animationFrame() // Extra frame for effect processing assert.equal(el.selected, 'panel6') assert.equal(tab.getAttribute('aria-selected'), 'true') assert.equal(tab.tabIndex, 0) assert.isFalse(panel.hidden) }) it('should preserve selected property type', async () => { const el = document.getElementById('test1') assert.isString(el.selected) el.selected = 'panel2' await animationFrame() // Wait for property update assert.isString(el.selected) assert.equal(el.selected, 'panel2') }) }) }) </script> </body> </html>