UNPKG

@mozaic-ds/vue

Version:

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

276 lines (214 loc) 8.45 kB
import { describe, it, expect } from 'vitest'; import { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; import MModal from './MModal.vue'; const stubs = { Cross24: { template: '<svg />' }, MIconButton: { template: `<button @click="$emit('click')"><slot name="icon"/></button>`, }, MOverlay: { name: 'MOverlay', template: `<div class="overlay" @click="$emit('click')"><slot/></div>`, }, }; describe('MModal component', () => { it('renders title and description', () => { const wrapper = mount(MModal, { props: { open: true, title: 'Test Modal', description: 'This is a description', }, global: { stubs }, }); expect(wrapper.text()).toContain('Test Modal'); expect(wrapper.text()).toContain('This is a description'); }); it('renders close button if closable is true', () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closable: true }, global: { stubs }, }); expect(wrapper.find('button.mc-modal__close').exists()).toBe(true); }); it('does not render close button if closable is false', () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closable: false }, global: { stubs }, }); expect(wrapper.find('button.mc-modal__close').exists()).toBe(false); }); it('emits update:open with false when close button clicked', async () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closable: true }, global: { stubs }, }); await wrapper.find('button.mc-modal__close').trigger('click'); const emitted = wrapper.emitted('update:open'); expect(emitted).toBeTruthy(); expect(emitted![emitted!.length - 1]).toEqual([false]); }); it('renders slots content', () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title' }, slots: { default: '<div class="default-slot">Default Content</div>', icon: '<span class="icon-slot">ICON</span>', footer: '<div class="footer-slot">Footer Content</div>', link: '<a href="#" class="link-slot">Link Content</a>', }, global: { stubs }, }); expect(wrapper.find('.icon-slot').exists()).toBe(true); expect(wrapper.find('.default-slot').exists()).toBe(true); expect(wrapper.find('.footer-slot').exists()).toBe(true); expect(wrapper.find('.link-slot').exists()).toBe(true); }); it('has inert attribute set correctly based on open prop', async () => { const wrapper = mount(MModal, { props: { open: false, title: 'Title' }, global: { stubs }, }); expect(wrapper.find('.mc-modal').attributes('inert')).toBeDefined(); await wrapper.setProps({ open: true }); expect(wrapper.find('.mc-modal').attributes('inert')).toBeUndefined(); }); it('adds "is-open" class when open is true', async () => { const wrapper = mount(MModal, { props: { open: false, title: 'Title' }, global: { stubs }, }); expect(wrapper.find('.mc-modal').classes()).not.toContain('is-open'); await wrapper.setProps({ open: true }); expect(wrapper.find('.mc-modal').classes()).toContain('is-open'); }); it('emits update:open with false when overlay clicked and closeOnOverlay is true', async () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closeOnOverlay: true }, global: { stubs }, }); await wrapper.findComponent({ name: 'MOverlay' }).trigger('click'); const emitted = wrapper.emitted('update:open'); expect(emitted).toBeTruthy(); expect(emitted![emitted!.length - 1]).toEqual([false]); }); it('does not emit update:open when overlay clicked and closeOnOverlay is false', async () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closeOnOverlay: false }, global: { stubs }, }); await wrapper.findComponent({ name: 'MOverlay' }).trigger('click'); const emitted = wrapper.emitted('update:open'); if (emitted) { const lastValue = emitted[emitted.length - 1][0]; expect(lastValue).not.toBe(false); } else { expect(emitted).toBeFalsy(); } }); it('locks scroll when open and unlocks when closed', async () => { const wrapper = mount(MModal, { props: { open: false, title: 'Title', scroll: false }, global: { stubs }, }); await wrapper.setProps({ open: true }); expect(document.body.style.overflow).toBe('hidden'); expect(document.documentElement.style.overflow).toBe('hidden'); await wrapper.setProps({ open: false }); expect(document.body.style.overflow).toBe(''); expect(document.documentElement.style.overflow).toBe(''); }); it('does not lock scroll if scroll prop is true', async () => { const wrapper = mount(MModal, { props: { open: false, title: 'Title', scroll: true }, global: { stubs }, }); await wrapper.setProps({ open: true }); expect(document.body.style.overflow).toBe(''); expect(document.documentElement.style.overflow).toBe(''); }); it('has role="dialog" on the modal element', () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title' }, global: { stubs }, }); expect(wrapper.find('.mc-modal').attributes('role')).toBe('dialog'); }); it('sets aria-modal based on open prop', async () => { const wrapper = mount(MModal, { props: { open: false, title: 'Title' }, global: { stubs }, }); expect(wrapper.find('.mc-modal').attributes('aria-modal')).toBe('false'); await wrapper.setProps({ open: true }); expect(wrapper.find('.mc-modal').attributes('aria-modal')).toBe('true'); }); it('links aria-labelledby to the title element', () => { const wrapper = mount(MModal, { props: { open: true, title: 'Accessible Title' }, global: { stubs }, }); const modal = wrapper.find('.mc-modal'); const labelledBy = modal.attributes('aria-labelledby'); expect(labelledBy).toMatch(/^modalTitle-/); const title = wrapper.find(`#${labelledBy}`); expect(title.exists()).toBe(true); expect(title.text()).toBe('Accessible Title'); }); it('closes when Escape key is pressed', async () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title' }, global: { stubs }, }); await wrapper.find('.mc-modal').trigger('keydown', { key: 'Escape' }); const emitted = wrapper.emitted('update:open'); expect(emitted).toBeTruthy(); expect(emitted![emitted!.length - 1]).toEqual([false]); }); it('moves focus to the modal when opened', async () => { const wrapper = mount(MModal, { props: { open: false, title: 'Title' }, global: { stubs }, attachTo: document.body, }); await wrapper.setProps({ open: true }); await nextTick(); expect(document.activeElement).toBe(wrapper.find('.mc-modal').element); wrapper.unmount(); }); it('traps focus from last to first element on Tab', async () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closable: true }, slots: { default: '<button data-test="last-focusable">Last</button>', }, global: { stubs }, attachTo: document.body, }); await nextTick(); const closeButton = wrapper.find('button.mc-modal__close'); const lastFocusable = wrapper.find('[data-test="last-focusable"]'); (lastFocusable.element as HTMLButtonElement).focus(); await lastFocusable.trigger('keydown', { key: 'Tab' }); expect(document.activeElement).toBe(closeButton.element); wrapper.unmount(); }); it('traps focus from first to last element on Shift+Tab', async () => { const wrapper = mount(MModal, { props: { open: true, title: 'Title', closable: true }, slots: { default: '<button data-test="last-focusable">Last</button>', }, global: { stubs }, attachTo: document.body, }); await nextTick(); const closeButton = wrapper.find('button.mc-modal__close'); const lastFocusable = wrapper.find('[data-test="last-focusable"]'); (closeButton.element as HTMLButtonElement).focus(); await closeButton.trigger('keydown', { key: 'Tab', shiftKey: true }); expect(document.activeElement).toBe(lastFocusable.element); wrapper.unmount(); }); });