import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import BaseTabs from '../../components/BaseTabs.vue' describe('BaseTabs', () => { it('accepts string shorthand tabs', async () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Run', tabs: ['Run', 'Results', { id: 'settings', label: 'Settings', disabled: true }], }, }) expect(wrapper.findAll('.mint-tab').map(tab => tab.text())).toEqual([ 'Run', 'Results', 'Settings', ]) await wrapper.findAll('.mint-tab')[1].trigger('click') await wrapper.findAll('.mint-tab')[2].trigger('click') expect(wrapper.emitted('update:modelValue')).toEqual([['Results']]) }) it('renders svg path icons', () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'users', tabs: [ { id: 'users', label: 'Users', icon: 'M12 4h8M12 12h8M12 20h8' }, { id: 'logs', label: 'Logs', icon: ['M4 4h16v16H4z', 'M8 8h8'] }, ], }, }) expect(wrapper.findAll('svg.mint-tab__icon--svg')).toHaveLength(2) expect(wrapper.findAll('path')).toHaveLength(3) }) describe('keyboard navigation (WAI-ARIA tabs pattern)', () => { const tabs = ['Run', 'Results', { id: 'settings', label: 'Settings', disabled: true }, 'Logs'] it('puts tabindex="0" only on the active tab', () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } }) const buttons = wrapper.findAll('.mint-tab') expect(buttons.map(b => b.attributes('tabindex'))).toEqual(['-1', '0', '-1', '-1']) }) it('moves to and activates the next enabled tab on ArrowRight, skipping disabled tabs', async () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } }) await wrapper.findAll('.mint-tab')[1].trigger('keydown', { key: 'ArrowRight' }) expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Logs']) }) it('moves to the previous enabled tab on ArrowLeft, wrapping past the start', async () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Run', tabs } }) await wrapper.findAll('.mint-tab')[0].trigger('keydown', { key: 'ArrowLeft' }) expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Logs']) }) it('wraps past the end on ArrowRight', async () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Logs', tabs } }) await wrapper.findAll('.mint-tab')[3].trigger('keydown', { key: 'ArrowRight' }) expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Run']) }) it('jumps to the first tab on Home and the last on End', async () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } }) await wrapper.findAll('.mint-tab')[1].trigger('keydown', { key: 'End' }) expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Logs']) await wrapper.findAll('.mint-tab')[1].trigger('keydown', { key: 'Home' }) expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['Run']) }) it('moves DOM focus onto the newly-activated tab', async () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Run', tabs }, attachTo: document.body, }) await wrapper.findAll('.mint-tab')[0].trigger('keydown', { key: 'ArrowRight' }) await wrapper.setProps({ modelValue: 'Results' }) await wrapper.vm.$nextTick() expect(document.activeElement).toBe(wrapper.findAll('.mint-tab')[1].element) wrapper.unmount() }) it('keeps aria-selected and aria-controls consistent with the active tab', () => { const wrapper = mount(BaseTabs, { props: { modelValue: 'Results', tabs } }) const active = wrapper.findAll('.mint-tab')[1] expect(active.attributes('aria-selected')).toBe('true') expect(active.attributes('id')).toBe(active.attributes('aria-controls')?.replace('tabpanel', 'tab')) }) }) })