@mozaic-ds/vue
Version:
Mozaic-Vue is the Vue.js implementation of ADEO Design system
398 lines (322 loc) • 12.5 kB
text/typescript
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import MTabs from './MTabs.vue';
import { defineComponent, h, markRaw } from 'vue';
describe('MTabs.vue', () => {
const tabs = [
{ id: '1', label: 'Tab 1' },
{ id: '2', label: 'Tab 2' },
{ id: '3', label: 'Tab 3' },
];
it('renders tabs with correct labels', () => {
const wrapper = mount(MTabs, {
props: {
tabs,
},
});
const tabElements = wrapper.findAll('li.mc-tabs__item');
expect(tabElements.length).toBe(tabs.length);
tabs.forEach((tab, i) => {
expect(tabElements[i].text()).toContain(tab.label);
});
});
it('applies selected class and aria-selected attribute based on modelValue and updates on tab click - string type model', async () => {
const wrapper = mount(MTabs, {
props: {
tabs,
modelValue: '1',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
buttons.forEach((button, i) => {
if (i === 0) {
expect(button.classes()).toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('true');
} else {
expect(button.classes()).not.toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('false');
}
});
await buttons[1].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['2']);
await wrapper.setProps({ modelValue: '2' });
const updatedButtons = wrapper.findAll('button.mc-tabs__tab');
updatedButtons.forEach((button, i) => {
if (i === 1) {
expect(button.classes()).toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('true');
} else {
expect(button.classes()).not.toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('false');
}
});
});
it('applies selected class and aria-selected attribute based on modelValue and updates on tab click - number type model', async () => {
const wrapper = mount(MTabs, {
props: {
tabs,
modelValue: 0,
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
buttons.forEach((button, i) => {
if (i === 0) {
expect(button.classes()).toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('true');
} else {
expect(button.classes()).not.toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('false');
}
});
await buttons[1].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')![0]).toEqual([1]);
await wrapper.setProps({ modelValue: 1 });
const updatedButtons = wrapper.findAll('button.mc-tabs__tab');
updatedButtons.forEach((button, i) => {
if (i === 1) {
expect(button.classes()).toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('true');
} else {
expect(button.classes()).not.toContain('mc-tabs__tab--selected');
expect(button.attributes('aria-selected')).toBe('false');
}
});
});
it('does not add size class when size is "m" (default)', () => {
const wrapper = mount(MTabs, { props: { tabs } });
expect(wrapper.classes()).not.toContain('mc-tabs--m');
expect(wrapper.classes()).not.toContain('mc-tabs--s');
});
it('adds mc-tabs--s class when size is "s" and removes it when changed to "m"', async () => {
const wrapper = mount(MTabs, {
props: { tabs, size: 's' },
});
expect(wrapper.classes()).toContain('mc-tabs--s');
await wrapper.setProps({ size: 'm' });
expect(wrapper.classes()).not.toContain('mc-tabs--s');
expect(wrapper.classes()).not.toContain('mc-tabs--m');
});
it('adds divider and centered classes based on props', async () => {
const wrapper = mount(MTabs, {
props: {
tabs,
divider: true,
centered: true,
},
});
expect(wrapper.classes()).toContain('mc-tabs--centered');
expect(wrapper.findComponent({ name: 'MDivider' }).exists()).toBe(true);
await wrapper.setProps({ divider: false, centered: false });
expect(wrapper.classes()).not.toContain('mc-tabs--centered');
expect(wrapper.findComponent({ name: 'MDivider' }).exists()).toBe(false);
});
it('sets aria-label on tablist based on description prop', () => {
const description = 'Main tabs navigation';
const wrapper = mount(MTabs, {
props: {
tabs,
description,
},
});
const ul = wrapper.find('ul[role="tablist"]');
expect(ul.attributes('aria-label')).toBe(description);
});
it('renders no tabs if tabs prop is empty', () => {
const wrapper = mount(MTabs, { props: { tabs: [] } });
expect(wrapper.findAll('li.mc-tabs__item').length).toBe(0);
});
it('emits update:modelValue on tab click if tab is not disabled', async () => {
const wrapper = mount(MTabs, {
props: {
tabs: [
{ id: '1', label: 'Tab 1' },
{ id: '2', label: 'Tab 2', disabled: true },
{ id: '3', label: 'Tab 3' },
],
modelValue: '1',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[2].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['3']);
await buttons[1].trigger('click');
expect(wrapper.emitted('update:modelValue')!.length).toBe(1);
});
it('selected tab has tabindex="0", others have tabindex="-1"', () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '2' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
expect(buttons[0].attributes('tabindex')).toBe('-1');
expect(buttons[1].attributes('tabindex')).toBe('0');
expect(buttons[2].attributes('tabindex')).toBe('-1');
});
it('falls back to first enabled tab for tabindex when selected tab is disabled', () => {
const wrapper = mount(MTabs, {
props: {
tabs: [
{ id: '1', label: 'Tab 1', disabled: true },
{ id: '2', label: 'Tab 2' },
{ id: '3', label: 'Tab 3' },
],
modelValue: '1',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
expect(buttons[0].attributes('tabindex')).toBe('-1');
expect(buttons[1].attributes('tabindex')).toBe('0');
expect(buttons[2].attributes('tabindex')).toBe('-1');
});
it('falls back to first enabled tab for tabindex when modelValue is invalid', () => {
const wrapper = mount(MTabs, {
props: {
tabs,
modelValue: 'unknown-id',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
expect(buttons[0].attributes('tabindex')).toBe('0');
expect(buttons[1].attributes('tabindex')).toBe('-1');
expect(buttons[2].attributes('tabindex')).toBe('-1');
});
it('sets disabled and aria-disabled attributes on disabled tabs', () => {
const wrapper = mount(MTabs, {
props: {
tabs: [
{ id: '1', label: 'Tab 1' },
{ id: '2', label: 'Tab 2', disabled: true },
],
modelValue: '1',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
expect(buttons[0].attributes('aria-disabled')).toBeUndefined();
expect(buttons[1].attributes('aria-disabled')).toBe('true');
});
it('ArrowRight moves selection to the next tab', async () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '1' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[0].trigger('keydown', { key: 'ArrowRight' });
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['2']);
});
it('ArrowLeft moves selection to the previous tab', async () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '2' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[1].trigger('keydown', { key: 'ArrowLeft' });
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['1']);
});
it('ArrowRight wraps from last to first tab', async () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '3' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[2].trigger('keydown', { key: 'ArrowRight' });
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['1']);
});
it('Home moves selection to the first tab', async () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '3' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[2].trigger('keydown', { key: 'Home' });
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['1']);
});
it('End moves selection to the last tab', async () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '1' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[0].trigger('keydown', { key: 'End' });
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['3']);
});
it('does not react to ArrowUp and ArrowDown keys', async () => {
const wrapper = mount(MTabs, {
props: { tabs, modelValue: '1' },
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[0].trigger('keydown', { key: 'ArrowDown' });
await buttons[0].trigger('keydown', { key: 'ArrowUp' });
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
});
it('ArrowRight skips disabled tabs', async () => {
const wrapper = mount(MTabs, {
props: {
tabs: [
{ id: '1', label: 'Tab 1' },
{ id: '2', label: 'Tab 2', disabled: true },
{ id: '3', label: 'Tab 3' },
],
modelValue: '1',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[0].trigger('keydown', { key: 'ArrowRight' });
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['3']);
});
it('does not emit on keydown when all tabs are disabled', async () => {
const wrapper = mount(MTabs, {
props: {
tabs: [
{ id: '1', label: 'Tab 1', disabled: true },
{ id: '2', label: 'Tab 2', disabled: true },
],
modelValue: '1',
},
});
const buttons = wrapper.findAll('button.mc-tabs__tab');
await buttons[0].trigger('keydown', { key: 'ArrowRight' });
await buttons[0].trigger('keydown', { key: 'Home' });
expect(wrapper.emitted('update:modelValue')).toBeUndefined();
});
it('renders icon component when icon prop is provided', () => {
const DummyIcon = markRaw(
defineComponent({
name: 'DummyIcon',
render() {
return h('svg', { class: 'dummy-icon' }, [
h('circle', { cx: 10, cy: 10, r: 10 }),
]);
},
}),
);
const tabsWithIcon = [
{ id: '1', label: 'Tab 1', icon: DummyIcon },
{ id: '2', label: 'Tab 2' },
];
const wrapper = mount(MTabs, {
props: {
tabs: tabsWithIcon,
},
});
const firstTabButton = wrapper.findAll('button.mc-tabs__tab')[0];
expect(firstTabButton.findComponent(DummyIcon).exists()).toBe(true);
expect(firstTabButton.find('svg.dummy-icon').exists()).toBe(true);
const secondTabButton = wrapper.findAll('button.mc-tabs__tab')[1];
expect(secondTabButton.findComponent(DummyIcon).exists()).toBe(false);
});
it('renders badge when badge prop is provided', () => {
const tabsWithBadges = [
{ id: '1', label: 'Tab 1', badge: 5 },
{ id: '2', label: 'Tab 2' },
];
const wrapper = mount(MTabs, {
props: {
tabs: tabsWithBadges,
},
});
const firstTabButton = wrapper.findAll('button.mc-tabs__tab')[0];
expect(firstTabButton.find('span.mc-tabs__badge').exists()).toBe(true);
expect(
firstTabButton.find('span.mc-tabs__badge .mc-number-badge').text(),
).toBe('5');
const secondTabButton = wrapper.findAll('button.mc-tabs__tab')[1];
expect(secondTabButton.find('span.mc-tabs__badge').exists()).toBe(false);
});
});