mitlist/fe/src/components/valerie/VButton.spec.ts

160 lines
5.5 KiB
TypeScript

import { mount } from '@vue/test-utils';
import VButton from './VButton.vue';
import VIcon from './VIcon.vue'; // Import VIcon as it's a child component
import { describe, it, expect, vi } from 'vitest';
// Mock VIcon to simplify testing VButton in isolation if needed,
// or allow it to render if its behavior is simple and reliable.
// For now, we'll allow it to render as it's part of the visual output.
describe('VButton.vue', () => {
it('renders with default props', () => {
const wrapper = mount(VButton);
expect(wrapper.text()).toBe('Button');
expect(wrapper.classes()).toContain('btn');
expect(wrapper.classes()).toContain('btn-primary');
expect(wrapper.classes()).toContain('btn-md');
expect(wrapper.attributes('type')).toBe('button');
expect(wrapper.attributes('disabled')).toBeUndefined();
});
it('renders with specified label', () => {
const wrapper = mount(VButton, { props: { label: 'Click Me' } });
expect(wrapper.text()).toBe('Click Me');
});
it('renders with slot content', () => {
const wrapper = mount(VButton, {
slots: {
default: '<i>Slot Content</i>',
},
});
expect(wrapper.html()).toContain('<i>Slot Content</i>');
});
it('applies variant classes', () => {
const wrapper = mount(VButton, { props: { variant: 'secondary' } });
expect(wrapper.classes()).toContain('btn-secondary');
});
it('applies size classes', () => {
const wrapper = mount(VButton, { props: { size: 'sm' } });
expect(wrapper.classes()).toContain('btn-sm');
});
it('is disabled when disabled prop is true', () => {
const wrapper = mount(VButton, { props: { disabled: true } });
expect(wrapper.attributes('disabled')).toBeDefined();
expect(wrapper.classes()).toContain('btn-disabled');
});
it('emits click event when not disabled', async () => {
const handleClick = vi.fn();
const wrapper = mount(VButton, {
attrs: {
onClick: handleClick, // For native event handling by test runner
}
});
await wrapper.trigger('click');
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('emits click event via emits options when not disabled', async () => {
const wrapper = mount(VButton);
await wrapper.trigger('click');
expect(wrapper.emitted().click).toBeTruthy();
expect(wrapper.emitted().click.length).toBe(1);
});
it('does not emit click event when disabled', async () => {
const handleClick = vi.fn();
const wrapper = mount(VButton, {
props: { disabled: true },
attrs: {
onClick: handleClick, // For native event handling by test runner
}
});
await wrapper.trigger('click');
expect(handleClick).not.toHaveBeenCalled();
// Check emitted events from component as well
const wrapperEmitted = mount(VButton, { props: { disabled: true } });
await wrapperEmitted.trigger('click');
expect(wrapperEmitted.emitted().click).toBeUndefined();
});
it('renders left icon', () => {
const wrapper = mount(VButton, {
props: { iconLeft: 'search' },
// Global stubs or components might be needed if VIcon isn't registered globally for tests
global: { components: { VIcon } }
});
const icon = wrapper.findComponent(VIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('search');
expect(wrapper.text()).toContain('Button'); // Label should still be there
});
it('renders right icon', () => {
const wrapper = mount(VButton, {
props: { iconRight: 'alert' },
global: { components: { VIcon } }
});
const icon = wrapper.findComponent(VIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('alert');
});
it('renders icon only button', () => {
const wrapper = mount(VButton, {
props: { iconLeft: 'close', iconOnly: true, label: 'Close' },
global: { components: { VIcon } }
});
const icon = wrapper.findComponent(VIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('close');
expect(wrapper.classes()).toContain('btn-icon-only');
// Label should be visually hidden but present for accessibility
const labelSpan = wrapper.find('span');
expect(labelSpan.exists()).toBe(true);
expect(labelSpan.classes()).toContain('sr-only');
expect(labelSpan.text()).toBe('Close');
});
it('renders icon only button with iconRight', () => {
const wrapper = mount(VButton, {
props: { iconRight: 'search', iconOnly: true, label: 'Search' },
global: { components: { VIcon } }
});
const icon = wrapper.findComponent(VIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('search');
expect(wrapper.classes()).toContain('btn-icon-only');
});
it('validates variant prop', () => {
const validator = VButton.props.variant.validator;
expect(validator('primary')).toBe(true);
expect(validator('secondary')).toBe(true);
expect(validator('neutral')).toBe(true);
expect(validator('danger')).toBe(true);
expect(validator('invalid-variant')).toBe(false);
});
it('validates size prop', () => {
const validator = VButton.props.size.validator;
expect(validator('sm')).toBe(true);
expect(validator('md')).toBe(true);
expect(validator('lg')).toBe(true);
expect(validator('xl')).toBe(false);
});
it('validates type prop', () => {
const validator = VButton.props.type.validator;
expect(validator('button')).toBe(true);
expect(validator('submit')).toBe(true);
expect(validator('reset')).toBe(true);
expect(validator('link')).toBe(false);
});
});