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