From be3fd84901ee91036c9b7f29779ff53a490df2c3 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Wed, 28 Jan 2026 22:22:45 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20Add=20unit=20tests=20for=20BaseButt?= =?UTF-8?q?on=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/unit/components/BaseButton.test.js | 300 ++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 code/websites/pokedex.online/tests/unit/components/BaseButton.test.js diff --git a/code/websites/pokedex.online/tests/unit/components/BaseButton.test.js b/code/websites/pokedex.online/tests/unit/components/BaseButton.test.js new file mode 100644 index 0000000..2148972 --- /dev/null +++ b/code/websites/pokedex.online/tests/unit/components/BaseButton.test.js @@ -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(); + }); + }); +});