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: 'Slot Content', }, }); expect(wrapper.html()).toContain('Slot Content'); }); 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); }); });