135 lines
3.5 KiB
TypeScript
135 lines
3.5 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { mount, flushPromises, type VueWrapper } from '@vue/test-utils'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import uiPlugin from '@nuxt/ui/vue-plugin'
|
|
import router from '@/router'
|
|
import { useApi } from '@/composables/useApi'
|
|
import DashboardShell from '../DashboardShell.vue'
|
|
import { MOBILE_WIDTH, setViewportWidth } from './viewport'
|
|
|
|
vi.mock('@/composables/useApi', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('@/composables/useApi')>()
|
|
return {
|
|
...actual,
|
|
useApi: vi.fn(),
|
|
}
|
|
})
|
|
|
|
let mountedShell: VueWrapper | null = null
|
|
|
|
async function mountMobileShell() {
|
|
localStorage.clear()
|
|
setViewportWidth(MOBILE_WIDTH)
|
|
setActivePinia(createPinia())
|
|
vi.mocked(useApi).mockReturnValue({
|
|
getMe: vi.fn().mockResolvedValue({
|
|
verified: true,
|
|
username: 'marta',
|
|
email: 'marta@renew.example',
|
|
displayName: '',
|
|
}),
|
|
request: vi.fn().mockResolvedValue(undefined),
|
|
})
|
|
await router.push('/home')
|
|
mountedShell = mount(DashboardShell, {
|
|
attachTo: document.body,
|
|
global: { plugins: [router, uiPlugin] },
|
|
})
|
|
await flushPromises()
|
|
}
|
|
|
|
function drawerChip() {
|
|
return document.body.querySelector<HTMLButtonElement>('[role="dialog"] button[aria-haspopup]')
|
|
}
|
|
|
|
function sheet() {
|
|
return document.body.querySelector('[role="menu"]')
|
|
}
|
|
|
|
function menuItem(label: string) {
|
|
return Array.from(
|
|
document.body.querySelectorAll<HTMLElement>('[role="menu"] [role="menuitem"]'),
|
|
).find((item) => item.textContent?.trim() === label)!
|
|
}
|
|
|
|
async function openDrawer() {
|
|
document.body.querySelector<HTMLButtonElement>('[data-slot="toggle"]')!.click()
|
|
await flushPromises()
|
|
}
|
|
|
|
async function openSheetFromChip() {
|
|
await openDrawer()
|
|
drawerChip()!.click()
|
|
await flushPromises()
|
|
}
|
|
|
|
const closingActions = [
|
|
{
|
|
name: 'pressing Escape',
|
|
close: async () => {
|
|
document.activeElement?.dispatchEvent(
|
|
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }),
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: 'tapping the backdrop',
|
|
close: async () => {
|
|
const overlay = document.body.querySelector<HTMLElement>('[data-slot="overlay"]')!
|
|
overlay.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true }))
|
|
overlay.dispatchEvent(new MouseEvent('pointerup', { bubbles: true }))
|
|
overlay.click()
|
|
},
|
|
},
|
|
{
|
|
name: 'activating "Profile"',
|
|
close: async () => {
|
|
menuItem('Profile').click()
|
|
},
|
|
},
|
|
]
|
|
|
|
afterEach(async () => {
|
|
mountedShell?.unmount()
|
|
mountedShell = null
|
|
await router.push('/login')
|
|
document.body.innerHTML = ''
|
|
})
|
|
|
|
describe('Mobile account sheet focus', () => {
|
|
it.each(closingActions)(
|
|
'returns focus to the navbar toggle when the sheet closes by $name',
|
|
async ({ close }) => {
|
|
await mountMobileShell()
|
|
await openSheetFromChip()
|
|
expect(sheet()).not.toBeNull()
|
|
|
|
await close()
|
|
await flushPromises()
|
|
|
|
expect(sheet()).toBeNull()
|
|
expect(document.activeElement).toBe(document.body.querySelector('[data-slot="toggle"]'))
|
|
},
|
|
)
|
|
|
|
it('logs out from the sheet without throwing', async () => {
|
|
const unhandled = vi.fn()
|
|
process.on('unhandledRejection', unhandled)
|
|
await mountMobileShell()
|
|
await openSheetFromChip()
|
|
|
|
menuItem('Log out').click()
|
|
await flushPromises()
|
|
|
|
expect(router.currentRoute.value.path).toBe('/login')
|
|
expect(unhandled).not.toHaveBeenCalled()
|
|
process.off('unhandledRejection', unhandled)
|
|
})
|
|
|
|
it('does not put focus on the chip or the toggle when the sheet was never opened', async () => {
|
|
await mountMobileShell()
|
|
|
|
expect(document.activeElement).toBe(document.body)
|
|
})
|
|
})
|