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(); }); }); });