Add unit tests for BaseButton component

This commit is contained in:
2026-01-28 22:22:45 +00:00
parent b06382c0bb
commit be3fd84901

View File

@@ -0,0 +1,300 @@
import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import BaseButton from '../../../src/components/shared/BaseButton.vue';
describe('BaseButton', () => {
describe('Rendering', () => {
it('renders with default props', () => {
const wrapper = mount(BaseButton, {
slots: {
default: 'Click me'
}
});
expect(wrapper.find('button').exists()).toBe(true);
expect(wrapper.text()).toBe('Click me');
expect(wrapper.classes()).toContain('base-button--primary');
expect(wrapper.classes()).toContain('base-button--medium');
});
it('renders all button variants', () => {
const variants = ['primary', 'secondary', 'danger', 'ghost', 'icon-only'];
variants.forEach(variant => {
const wrapper = mount(BaseButton, {
props: { variant },
slots: { default: 'Button' }
});
expect(wrapper.classes()).toContain(`base-button--${variant}`);
});
});
it('renders all button sizes', () => {
const sizes = ['small', 'medium', 'large'];
sizes.forEach(size => {
const wrapper = mount(BaseButton, {
props: { size },
slots: { default: 'Button' }
});
expect(wrapper.classes()).toContain(`base-button--${size}`);
});
});
it('renders with icon on left', () => {
const wrapper = mount(BaseButton, {
props: {
icon: '🚀',
iconPosition: 'left'
},
slots: { default: 'Launch' }
});
const icons = wrapper.findAll('.icon');
expect(icons.length).toBe(1);
expect(icons[0].classes()).toContain('icon-left');
expect(icons[0].text()).toBe('🚀');
});
it('renders with icon on right', () => {
const wrapper = mount(BaseButton, {
props: {
icon: '→',
iconPosition: 'right'
},
slots: { default: 'Next' }
});
const icons = wrapper.findAll('.icon');
expect(icons.length).toBe(1);
expect(icons[0].classes()).toContain('icon-right');
expect(icons[0].text()).toBe('→');
});
it('renders as icon-only button when no slot content', () => {
const wrapper = mount(BaseButton, {
props: {
icon: '×'
}
});
expect(wrapper.classes()).toContain('base-button--icon-only');
expect(wrapper.text()).toBe('×');
});
it('renders as full width', () => {
const wrapper = mount(BaseButton, {
props: { fullWidth: true },
slots: { default: 'Button' }
});
expect(wrapper.classes()).toContain('base-button--full-width');
});
});
describe('Button Types', () => {
it('renders with button type by default', () => {
const wrapper = mount(BaseButton);
expect(wrapper.attributes('type')).toBe('button');
});
it('renders with submit type', () => {
const wrapper = mount(BaseButton, {
props: { type: 'submit' }
});
expect(wrapper.attributes('type')).toBe('submit');
});
it('renders with reset type', () => {
const wrapper = mount(BaseButton, {
props: { type: 'reset' }
});
expect(wrapper.attributes('type')).toBe('reset');
});
});
describe('Loading State', () => {
it('shows spinner when loading', () => {
const wrapper = mount(BaseButton, {
props: { loading: true },
slots: { default: 'Loading...' }
});
expect(wrapper.find('.spinner').exists()).toBe(true);
expect(wrapper.classes()).toContain('base-button--loading');
});
it('hides icon when loading', () => {
const wrapper = mount(BaseButton, {
props: {
loading: true,
icon: '🚀'
},
slots: { default: 'Launch' }
});
expect(wrapper.find('.icon').exists()).toBe(false);
expect(wrapper.find('.spinner').exists()).toBe(true);
});
it('makes button text screen-reader only when loading', () => {
const wrapper = mount(BaseButton, {
props: { loading: true },
slots: { default: 'Processing' }
});
const textSpan = wrapper.find('span:not(.spinner)');
expect(textSpan.classes()).toContain('sr-only');
});
it('does not emit click when loading', async () => {
const wrapper = mount(BaseButton, {
props: { loading: true },
slots: { default: 'Button' }
});
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toBeUndefined();
});
});
describe('Disabled State', () => {
it('applies disabled class', () => {
const wrapper = mount(BaseButton, {
props: { disabled: true },
slots: { default: 'Button' }
});
expect(wrapper.classes()).toContain('base-button--disabled');
expect(wrapper.attributes('disabled')).toBeDefined();
});
it('does not emit click when disabled', async () => {
const wrapper = mount(BaseButton, {
props: { disabled: true },
slots: { default: 'Button' }
});
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toBeUndefined();
});
});
describe('Click Events', () => {
it('emits click event when clicked', async () => {
const wrapper = mount(BaseButton, {
slots: { default: 'Button' }
});
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toBeTruthy();
expect(wrapper.emitted('click')).toHaveLength(1);
});
it('passes event to click handler', async () => {
const wrapper = mount(BaseButton, {
slots: { default: 'Button' }
});
await wrapper.trigger('click');
const clickEvents = wrapper.emitted('click');
expect(clickEvents[0][0]).toBeInstanceOf(Event);
});
it('can be clicked multiple times', async () => {
const wrapper = mount(BaseButton, {
slots: { default: 'Button' }
});
await wrapper.trigger('click');
await wrapper.trigger('click');
await wrapper.trigger('click');
expect(wrapper.emitted('click')).toHaveLength(3);
});
});
describe('Accessibility', () => {
it('has proper focus-visible styling', () => {
const wrapper = mount(BaseButton, {
slots: { default: 'Button' }
});
// Check that focus-visible class exists in CSS (component has focus styling)
expect(wrapper.html()).toContain('button');
});
it('spinner has aria-hidden', () => {
const wrapper = mount(BaseButton, {
props: { loading: true },
slots: { default: 'Loading' }
});
const spinner = wrapper.find('.spinner');
expect(spinner.attributes('aria-hidden')).toBe('true');
});
it('maintains text for screen readers when loading', () => {
const wrapper = mount(BaseButton, {
props: { loading: true },
slots: { default: 'Submit Form' }
});
expect(wrapper.text()).toContain('Submit Form');
});
});
describe('Edge Cases', () => {
it('handles empty slot gracefully', () => {
const wrapper = mount(BaseButton);
expect(wrapper.find('button').exists()).toBe(true);
});
it('handles both loading and disabled', () => {
const wrapper = mount(BaseButton, {
props: {
loading: true,
disabled: true
},
slots: { default: 'Button' }
});
expect(wrapper.classes()).toContain('base-button--loading');
expect(wrapper.classes()).toContain('base-button--disabled');
expect(wrapper.attributes('disabled')).toBeDefined();
});
it('validates variant prop', () => {
// Valid variants should not throw
expect(() => {
mount(BaseButton, {
props: { variant: 'primary' }
});
}).not.toThrow();
// Invalid variant should fail validation (but mount won't throw, just warn)
const wrapper = mount(BaseButton, {
props: { variant: 'invalid' }
});
expect(wrapper.exists()).toBe(true);
});
it('validates size prop', () => {
expect(() => {
mount(BaseButton, {
props: { size: 'medium' }
});
}).not.toThrow();
});
it('validates type prop', () => {
expect(() => {
mount(BaseButton, {
props: { type: 'submit' }
});
}).not.toThrow();
});
});
});