UNPKG

@mozaic-ds/vue

Version:

Mozaic-Vue is the Vue.js implementation of ADEO Design system

252 lines (219 loc) 8.24 kB
import { describe, it, expect } from 'vitest'; import { mount } from '@vue/test-utils'; import { defineComponent, h, markRaw } from 'vue'; import MActionListbox from './MActionListbox.vue'; import MDivider from '../divider/MDivider.vue'; import MIconButton from '../iconbutton/MIconButton.vue'; import { Cross24 } from '@mozaic-ds/icons-vue'; // Dummy icon wrapped with markRaw to avoid reactive warnings const DummyIcon = markRaw( defineComponent({ name: 'DummyIcon', render() { return h('svg', { 'data-testid': 'icon' }, [ h('circle', { cx: 10, cy: 10, r: 10 }), ]); }, }), ); const items = [ { label: 'Duplicate', icon: DummyIcon, }, { id: 'move', label: 'Move to...', icon: DummyIcon, }, { label: 'Download', icon: DummyIcon, }, { label: 'Delete', icon: DummyIcon, appearance: 'danger' as const, divider: true, }, ]; describe('MActionListbox', () => { function mountComponent(props = {}, options = {}) { return mount(MActionListbox, { props: { items, ...props }, global: { components: { MDivider, MIconButton, Cross24, }, }, ...options, }); } it('renders the title if provided', () => { const wrapper = mountComponent({ title: 'Action List' }); expect(wrapper.find('.mc-listbox__title').text()).toBe('Action List'); }); it('renders all item labels', () => { const wrapper = mountComponent(); items.forEach((item) => { expect(wrapper.text()).toContain(item.label); }); }); it('renders icons for each item', () => { const wrapper = mountComponent(); const icons = wrapper.findAll('[data-testid="icon"]'); expect(icons.length).toBe(items.length); }); it('adds danger class to items with appearance set to danger', () => { const wrapper = mountComponent(); const dangerousItem = wrapper.findAll('.mc-action-list__element--danger'); expect(dangerousItem.length).toBe(1); expect(dangerousItem[0].text()).toContain('Delete'); }); it('renders MDivider when divider is true', () => { const wrapper = mountComponent(); const dividers = wrapper.findAll('.mc-action-list__divider'); expect(dividers.length).toBe(1); }); it('applies disabled class when item.disabled is true', () => { const disabledItems = [...items, { label: 'Disabled', disabled: true }]; const wrapper = mountComponent({ items: disabledItems }); const disabled = wrapper.findAll('.mc-action-list__element--disabled'); expect(disabled.length).toBe(1); expect(disabled[0].text()).toContain('Disabled'); }); it('applies mc-listbox--bottom by default', () => { const wrapper = mountComponent(); expect(wrapper.find('.mc-listbox').classes()).toContain( 'mc-listbox--bottom', ); }); it('applies mc-listbox--top when position is "top"', () => { const wrapper = mountComponent({ position: 'top' }); expect(wrapper.find('.mc-listbox').classes()).toContain('mc-listbox--top'); }); it('applies mc-listbox--left when position is "left"', () => { const wrapper = mountComponent({ position: 'left' }); expect(wrapper.find('.mc-listbox').classes()).toContain('mc-listbox--left'); }); it('emits "close" when close button is clicked', async () => { const wrapper = mountComponent({ title: 'Action List' }); await wrapper.find('.mc-listbox__close').trigger('click'); expect(wrapper.emitted('close')).toBeTruthy(); expect(wrapper.emitted('close')?.length).toBe(1); }); it('emits "action" when an item is clicked', async () => { const wrapper = mountComponent({ title: 'Action List' }); const actions = wrapper.findAll('.mc-action-list__button'); await actions[0].trigger('click'); expect(wrapper.emitted('action')).toBeTruthy(); expect(wrapper.emitted('action')?.[0][0]).toBe(0); await actions[1].trigger('click'); expect(wrapper.emitted('action')?.[1][0]).toBe('move'); }); it('has role="menu" on the list and role="menuitem" on each button', () => { const wrapper = mountComponent(); expect(wrapper.find('ul.mc-action-list').attributes('role')).toBe('menu'); const menuItems = wrapper.findAll('button[role="menuitem"]'); expect(menuItems.length).toBe(items.length); }); describe('keyboard navigation', () => { function mountAttached(props = {}) { const div = document.createElement('div'); document.body.appendChild(div); const wrapper = mount(MActionListbox, { props: { items, ...props }, attachTo: div, global: { components: { MDivider, MIconButton, Cross24 } }, }); return wrapper; } it('ArrowDown moves focus to the next item', async () => { const wrapper = mountAttached(); const buttons = wrapper.findAll('button[role="menuitem"]'); await buttons[0].element.focus(); await wrapper .find('ul[role="menu"]') .trigger('keydown', { key: 'ArrowDown' }); expect(document.activeElement).toBe(buttons[1].element); wrapper.unmount(); }); it('ArrowDown wraps from last to first item', async () => { const wrapper = mountAttached(); const buttons = wrapper.findAll('button[role="menuitem"]'); await buttons[buttons.length - 1].element.focus(); await wrapper .find('ul[role="menu"]') .trigger('keydown', { key: 'ArrowDown' }); expect(document.activeElement).toBe(buttons[0].element); wrapper.unmount(); }); it('ArrowUp moves focus to the previous item', async () => { const wrapper = mountAttached(); const buttons = wrapper.findAll('button[role="menuitem"]'); await buttons[1].element.focus(); await wrapper .find('ul[role="menu"]') .trigger('keydown', { key: 'ArrowUp' }); expect(document.activeElement).toBe(buttons[0].element); wrapper.unmount(); }); it('ArrowUp wraps from first to last item', async () => { const wrapper = mountAttached(); const buttons = wrapper.findAll('button[role="menuitem"]'); await buttons[0].element.focus(); await wrapper .find('ul[role="menu"]') .trigger('keydown', { key: 'ArrowUp' }); expect(document.activeElement).toBe(buttons[buttons.length - 1].element); wrapper.unmount(); }); it('Home moves focus to the first item', async () => { const wrapper = mountAttached(); const buttons = wrapper.findAll('button[role="menuitem"]'); await buttons[2].element.focus(); await wrapper.find('ul[role="menu"]').trigger('keydown', { key: 'Home' }); expect(document.activeElement).toBe(buttons[0].element); wrapper.unmount(); }); it('End moves focus to the last item', async () => { const wrapper = mountAttached(); const buttons = wrapper.findAll('button[role="menuitem"]'); await buttons[0].element.focus(); await wrapper.find('ul[role="menu"]').trigger('keydown', { key: 'End' }); expect(document.activeElement).toBe(buttons[buttons.length - 1].element); wrapper.unmount(); }); it('Escape emits "close"', async () => { const wrapper = mountAttached(); await wrapper .find('ul[role="menu"]') .trigger('keydown', { key: 'Escape' }); expect(wrapper.emitted('close')).toBeTruthy(); wrapper.unmount(); }); it('disabled items are skipped during ArrowDown navigation', async () => { const itemsWithDisabled = [ { label: 'First' }, { label: 'Disabled', disabled: true }, { label: 'Third' }, ]; const wrapper = mount(MActionListbox, { props: { items: itemsWithDisabled }, attachTo: document.body, global: { components: { MDivider, MIconButton, Cross24 } }, }); const enabledButtons = wrapper.findAll( 'button[role="menuitem"]:not([disabled])', ); await enabledButtons[0].element.focus(); await wrapper .find('ul[role="menu"]') .trigger('keydown', { key: 'ArrowDown' }); expect(document.activeElement).toBe(enabledButtons[1].element); wrapper.unmount(); }); }); });