import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import AppAvatarMenu from '../../components/AppAvatarMenu.vue' describe('AppAvatarMenu', () => { it('should vertically center avatar initials with a compact line box', () => { const styles = readFileSync( resolve(process.cwd(), 'src/styles/components/app-avatar-menu.css'), 'utf8', ) expect(styles).toMatch(/\.mint-avatar-menu__avatar\s*\{[^}]*line-height:\s*1;/s) }) it('renders mixed menu actions and emits selected items', async () => { const docs = { id: 'docs', label: 'Docs', href: '/docs' } const profile = { id: 'profile', label: 'Profile' } const wrapper = mount(AppAvatarMenu, { props: { userName: 'Ada Lovelace', items: [profile, { id: 'divider', label: '', divider: true }, docs], showSignOut: false, }, }) await wrapper.get('.mint-avatar-menu__trigger').trigger('click') const items = wrapper.findAll('.mint-avatar-menu__item') expect(items[1].element.tagName.toLowerCase()).toBe('a') await items[1].trigger('click') expect(wrapper.emitted('select')).toEqual([[docs]]) expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(false) }) describe('keyboard handling (menu button pattern)', () => { const items = [{ id: 'profile', label: 'Profile' }, { id: 'settings', label: 'Settings' }] function mountMenu() { return mount(AppAvatarMenu, { props: { userName: 'Ada Lovelace', items, showSignOut: true }, attachTo: document.body, }) } it('sets aria-expanded on the trigger and toggles it open/closed', async () => { const wrapper = mountMenu() const trigger = wrapper.get('.mint-avatar-menu__trigger') expect(trigger.attributes('aria-expanded')).toBe('false') await trigger.trigger('click') expect(trigger.attributes('aria-expanded')).toBe('true') wrapper.unmount() }) it('opens and focuses the first item on ArrowDown', async () => { const wrapper = mountMenu() await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' }) await wrapper.vm.$nextTick() expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(true) expect(document.activeElement).toBe(wrapper.findAll('[role="menuitem"]')[0].element) wrapper.unmount() }) it('opens and focuses the last item on ArrowUp', async () => { const wrapper = mountMenu() await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowUp' }) await wrapper.vm.$nextTick() const menuItems = wrapper.findAll('[role="menuitem"]') expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element) wrapper.unmount() }) it('opens and focuses the first item on Enter and Space', async () => { for (const key of ['Enter', ' ']) { const wrapper = mountMenu() await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key }) await wrapper.vm.$nextTick() expect(document.activeElement).toBe(wrapper.findAll('[role="menuitem"]')[0].element) wrapper.unmount() } }) it('cycles item focus with ArrowDown/ArrowUp, wrapping at the ends', async () => { const wrapper = mountMenu() await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' }) await wrapper.vm.$nextTick() const menuItems = wrapper.findAll('[role="menuitem"]') const panel = wrapper.get('.mint-avatar-menu__panel') await panel.trigger('keydown', { key: 'ArrowDown' }) expect(document.activeElement).toBe(menuItems[1].element) await panel.trigger('keydown', { key: 'ArrowDown' }) expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element) await panel.trigger('keydown', { key: 'ArrowDown' }) expect(document.activeElement).toBe(menuItems[0].element) await panel.trigger('keydown', { key: 'ArrowUp' }) expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element) wrapper.unmount() }) it('jumps to the first item on Home and the last on End', async () => { const wrapper = mountMenu() await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' }) await wrapper.vm.$nextTick() const menuItems = wrapper.findAll('[role="menuitem"]') const panel = wrapper.get('.mint-avatar-menu__panel') await panel.trigger('keydown', { key: 'End' }) expect(document.activeElement).toBe(menuItems[menuItems.length - 1].element) await panel.trigger('keydown', { key: 'Home' }) expect(document.activeElement).toBe(menuItems[0].element) wrapper.unmount() }) it('closes on Escape and returns focus to the trigger', async () => { const wrapper = mountMenu() const trigger = wrapper.get('.mint-avatar-menu__trigger') await trigger.trigger('keydown', { key: 'ArrowDown' }) await wrapper.vm.$nextTick() await wrapper.get('.mint-avatar-menu__panel').trigger('keydown', { key: 'Escape' }) await wrapper.vm.$nextTick() expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(false) expect(document.activeElement).toBe(trigger.element) wrapper.unmount() }) it('closes on Tab without forcing focus', async () => { const wrapper = mountMenu() await wrapper.get('.mint-avatar-menu__trigger').trigger('keydown', { key: 'ArrowDown' }) await wrapper.vm.$nextTick() await wrapper.get('.mint-avatar-menu__panel').trigger('keydown', { key: 'Tab' }) await wrapper.vm.$nextTick() expect(wrapper.find('.mint-avatar-menu__panel').exists()).toBe(false) wrapper.unmount() }) }) it('should render the panel on the shared popover surface when opened', async () => { const wrapper = mount(AppAvatarMenu, { props: { userName: 'Ada Lovelace', items: [{ id: 'profile', label: 'Profile' }] } }) await wrapper.get('.mint-avatar-menu__trigger').trigger('click') expect(wrapper.get('.mint-avatar-menu__panel').classes()).toContain('mint-popover') }) it('should give sign out the shared danger menu item treatment when opened', async () => { const wrapper = mount(AppAvatarMenu, { props: { userName: 'Ada Lovelace', items: [{ id: 'profile', label: 'Profile' }] } }) await wrapper.get('.mint-avatar-menu__trigger').trigger('click') expect(wrapper.findAll('.mint-avatar-menu__item').at(-1)?.classes()).toContain('mint-menu-item--danger') }) it('should paint the avatar with a solid primary fill', () => { const styles = readFileSync( resolve(process.cwd(), 'src/styles/components/app-avatar-menu.css'), 'utf8', ) expect(styles).toMatch(/\.mint-avatar-menu__avatar\s*\{[^}]*background:\s*var\(--color-primary\);/s) }) })