@zeix/ui-element
Version:
UIElement - a HTML-first library for reactive Web Components
452 lines (385 loc) • 13.3 kB
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>