@mozaic-ds/vue
Version:
Mozaic-Vue is the Vue.js implementation of ADEO Design system
276 lines (214 loc) • 8.45 kB
text/typescript
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(`
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();
});
});