- 41 unit and component suites under admin/tests/{app,state,shell,list,form,relation,views,ui}
covering states and a11y roles; every src module is imported by a test
- typed fixture helper assigns each JSON fixture to its generated OpenAPI type
- fix: iconFor ignores inherited object members such as "constructor"
- fix: field controls import ./control instead of the registry (import cycle
left a renderer unregistered depending on module load order)
- fix: dropdown shows the placeholder for an unknown stored value next to an emptyOption
- fix: list announces a failed schema load even when the rows arrive after it
- tailwind no longer scans admin/tests; boardwalk/dist rebuilt
89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
|
import SectionFlyout from '../../src/components/shell/SectionFlyout.vue'
|
|
import type { NavigationEntry } from '../../src/api/types'
|
|
import { clone, navigationFixture } from '../fixtures/typed'
|
|
import { keydown, resetState, routerAt, signIn } from '../helpers'
|
|
|
|
function entryWithThree(): NavigationEntry {
|
|
const entry = clone(navigationFixture.data[0]!)
|
|
entry.sideMenu.push({ code: 'gizmos', label: 'Gizmos', icon: 'list', order: 0, controller: 'acme.demo.gizmos', sideMenu: [] })
|
|
entry.sideMenu.push({ code: 'broken', label: 'Broken', icon: 'list', order: 0, controller: 'bad', sideMenu: [] })
|
|
return entry
|
|
}
|
|
|
|
async function mountFlyout(path = '/acme/demo/gadgets') {
|
|
await signIn()
|
|
const router = await routerAt(path)
|
|
const wrapper = mount(SectionFlyout, {
|
|
props: { entry: entryWithThree(), id: 'section-flyout-demo' },
|
|
global: { plugins: [router] },
|
|
attachTo: document.body,
|
|
})
|
|
await flushPromises()
|
|
return wrapper
|
|
}
|
|
|
|
const active = () => document.activeElement?.textContent?.trim()
|
|
|
|
beforeEach(() => {
|
|
resetState()
|
|
})
|
|
|
|
afterEach(() => {
|
|
document.body.innerHTML = ''
|
|
})
|
|
|
|
enableAutoUnmount(afterEach)
|
|
|
|
describe('section flyout', () => {
|
|
it('is a labelled menu of menuitems outside the tab order, the current one aria-current', async () => {
|
|
const wrapper = await mountFlyout()
|
|
const menu = wrapper.find('[role="menu"]')
|
|
expect(menu.attributes('id')).toBe('section-flyout-demo')
|
|
expect(menu.attributes('aria-label')).toBe('Demo')
|
|
const items = wrapper.findAll('[role="menuitem"]')
|
|
expect(items.map((item) => item.text())).toEqual(['Widgets', 'Gadgets', 'Gizmos'])
|
|
expect(items.every((item) => item.attributes('tabindex') === '-1')).toBe(true)
|
|
expect(items[1]!.attributes('aria-current')).toBe('page')
|
|
expect(items[0]!.attributes('aria-current')).toBeUndefined()
|
|
})
|
|
|
|
it('focuses the first item and moves with arrows, Home and End, wrapping around', async () => {
|
|
const wrapper = await mountFlyout()
|
|
const menu = wrapper.find('[role="menu"]').element
|
|
await (wrapper.vm as unknown as { focusFirst: () => Promise<void> }).focusFirst()
|
|
expect(active()).toBe('Widgets')
|
|
keydown(document.activeElement!, 'ArrowDown')
|
|
expect(active()).toBe('Gadgets')
|
|
keydown(document.activeElement!, 'End')
|
|
expect(active()).toBe('Gizmos')
|
|
keydown(document.activeElement!, 'ArrowDown')
|
|
expect(active()).toBe('Widgets')
|
|
keydown(document.activeElement!, 'ArrowUp')
|
|
expect(active()).toBe('Gizmos')
|
|
keydown(document.activeElement!, 'Home')
|
|
expect(active()).toBe('Widgets')
|
|
expect(keydown(menu, 'x').defaultPrevented).toBe(false)
|
|
})
|
|
|
|
it('asks the rail to close on Escape and reports Tab and navigation', async () => {
|
|
const wrapper = await mountFlyout()
|
|
const menu = wrapper.find('[role="menu"]').element
|
|
expect(keydown(menu, 'Escape').defaultPrevented).toBe(true)
|
|
keydown(menu, 'Tab')
|
|
await wrapper.findAll('[role="menuitem"]')[2]!.trigger('click')
|
|
expect(wrapper.emitted('escape')).toHaveLength(1)
|
|
expect(wrapper.emitted('leave')).toHaveLength(1)
|
|
expect(wrapper.emitted('navigate')).toHaveLength(1)
|
|
})
|
|
|
|
it('tells whether an element sits inside it', async () => {
|
|
const wrapper = await mountFlyout()
|
|
const vm = wrapper.vm as unknown as { contains: (element: Element | null) => boolean }
|
|
expect(vm.contains(wrapper.find('[role="menuitem"]').element)).toBe(true)
|
|
expect(vm.contains(document.body)).toBe(false)
|
|
expect(vm.contains(null)).toBe(false)
|
|
})
|
|
})
|