@mozaic-ds/vue
Version:
Mozaic-Vue is the Vue.js implementation of ADEO Design system
241 lines (201 loc) • 8.19 kB
text/typescript
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import { nextTick } from 'vue';
import MSegmentedControl from './MSegmentedControl.vue';
describe('MSegmentedControl.vue', () => {
const segments = [
{ id: '1', label: 'First' },
{ id: '2', label: 'Second' },
{ id: '3', label: 'Third' },
];
it('renders segments with correct labels', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments },
});
const buttons = wrapper.findAll('button');
expect(buttons).toHaveLength(segments.length);
buttons.forEach((btn, i) => {
expect(btn.text()).toBe(segments[i].label);
});
});
it('sets default active segment based on modelValue prop', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: '1' },
});
const buttons = wrapper.findAll('button');
expect(buttons[0].classes()).toContain(
'mc-segmented-control__segment--selected',
);
expect(buttons[0].attributes('aria-checked')).toBe('true');
});
describe('Segment selection - string type model', () => {
it('emits update:modelValue and changes active segment on click', async () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: '1' },
});
const buttons = wrapper.findAll('button');
await buttons[2].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['3']);
expect(buttons[2].classes()).toContain(
'mc-segmented-control__segment--selected',
);
expect(buttons[2].attributes('aria-checked')).toBe('true');
});
it('does not emit update event if clicking already active segment', async () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: '2' },
});
const buttons = wrapper.findAll('button');
await buttons[1].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeFalsy();
});
});
describe('Segment selection - number type model', () => {
it('emits update:modelValue and changes active segment on click', async () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: 0 },
});
const buttons = wrapper.findAll('button');
await buttons[2].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]);
expect(buttons[2].classes()).toContain(
'mc-segmented-control__segment--selected',
);
expect(buttons[2].attributes('aria-checked')).toBe('true');
});
it('does not emit update event if clicking already active segment', async () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: 1 },
});
const buttons = wrapper.findAll('button');
await buttons[1].trigger('click');
expect(wrapper.emitted('update:modelValue')).toBeFalsy();
});
});
it('applies full width class when full prop is true', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, full: true },
});
expect(wrapper.classes()).toContain('mc-segmented-control--full');
});
it('applies size class when size prop is "m"', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, size: 'm' },
});
expect(wrapper.classes()).toContain('mc-segmented-control--m');
});
it('does not apply size class when size is "s" (default)', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments },
});
expect(wrapper.classes()).not.toContain('mc-segmented-control--s');
});
it('updates active segment when modelValue prop changes', async () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: '1' },
});
const buttons = wrapper.findAll('button');
expect(buttons[0].classes()).toContain(
'mc-segmented-control__segment--selected',
);
await wrapper.setProps({ modelValue: '3' });
expect(buttons[2].classes()).toContain(
'mc-segmented-control__segment--selected',
);
expect(buttons[2].attributes('aria-checked')).toBe('true');
});
it('adds role="radio" attribute to buttons', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments },
});
const buttons = wrapper.findAll('button');
buttons.forEach((button) => {
expect(button.attributes('role')).toBe('radio');
});
});
it('forwards attrs to the radiogroup so callers can name the group', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments },
attrs: { 'aria-label': 'View options' },
});
expect(wrapper.find('[role="radiogroup"]').attributes('aria-label')).toBe(
'View options',
);
});
describe('Keyboard navigation (radiogroup pattern)', () => {
it('selected button has tabindex="0", others have tabindex="-1"', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: '2' },
});
const buttons = wrapper.findAll('button');
expect(buttons[0].attributes('tabindex')).toBe('-1');
expect(buttons[1].attributes('tabindex')).toBe('0');
expect(buttons[2].attributes('tabindex')).toBe('-1');
});
it('keeps the first segment tabbable when modelValue does not match any segment', () => {
const wrapper = mount(MSegmentedControl, {
props: { segments, modelValue: 'unknown' },
});
const buttons = wrapper.findAll('button');
expect(buttons[0].attributes('tabindex')).toBe('0');
expect(buttons[1].attributes('tabindex')).toBe('-1');
expect(buttons[2].attributes('tabindex')).toBe('-1');
expect(buttons[0].attributes('aria-checked')).toBe('false');
});
it('ArrowRight selects and focuses the next segment', async () => {
const wrapper = mount(MSegmentedControl, {
attachTo: document.body,
props: { segments, modelValue: '1' },
});
const buttons = wrapper.findAll('button');
await buttons[0].element.focus();
await buttons[0].trigger('keydown', { key: 'ArrowRight' });
await nextTick();
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['2']);
expect(document.activeElement).toBe(buttons[1].element);
wrapper.unmount();
});
it('ArrowLeft selects and focuses the previous segment', async () => {
const wrapper = mount(MSegmentedControl, {
attachTo: document.body,
props: { segments, modelValue: '2' },
});
const buttons = wrapper.findAll('button');
await buttons[1].trigger('keydown', { key: 'ArrowLeft' });
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['1']);
wrapper.unmount();
});
it('ArrowRight wraps around from last to first segment', async () => {
const wrapper = mount(MSegmentedControl, {
attachTo: document.body,
props: { segments, modelValue: '3' },
});
const buttons = wrapper.findAll('button');
await buttons[2].trigger('keydown', { key: 'ArrowRight' });
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['1']);
wrapper.unmount();
});
it('Home selects the first segment', async () => {
const wrapper = mount(MSegmentedControl, {
attachTo: document.body,
props: { segments, modelValue: '3' },
});
const buttons = wrapper.findAll('button');
await buttons[2].trigger('keydown', { key: 'Home' });
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['1']);
wrapper.unmount();
});
it('End selects the last segment', async () => {
const wrapper = mount(MSegmentedControl, {
attachTo: document.body,
props: { segments, modelValue: '1' },
});
const buttons = wrapper.findAll('button');
await buttons[0].trigger('keydown', { key: 'End' });
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['3']);
wrapper.unmount();
});
});
});