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() 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('[role="dialog"] button[aria-haspopup]') } function sheet() { return document.body.querySelector('[role="menu"]') } function menuItem(label: string) { return Array.from( document.body.querySelectorAll('[role="menu"] [role="menuitem"]'), ).find((item) => item.textContent?.trim() === label)! } async function openDrawer() { document.body.querySelector('[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('[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) }) })