/** * Keeping this as an example, but since there is no Button component yet exposed, this tests imports primevue's Button directly. */ import Button from 'primevue/button'; // TODO: update this import once Button is introduced and revisit these tests. Most likely we won't need it to be as extensive. We will also need to fix the viewport size and browser flashing. import { describe, expect, it } from 'vitest'; import { page } from 'vitest/browser'; import { expectComponentAccessible } from '../../../tests/accessibility'; import { mountSonikComponent, mountSonikComponentWithTheme } from '../../../tests/component-utils'; describe('Button Component', () => { it('renders default button correctly', async () => { const wrapper = mountSonikComponent(Button, { props: { label: 'Default Button', }, }); // Functional assertions expect(wrapper.exists()).toBe(true); expect(wrapper.text()).toContain('Default Button'); // Visual regression using Vitest built-in screenshot testing await expect(page.getByRole('button', { name: 'Default Button' })).toMatchScreenshot( 'button-default.png', ); // Accessibility await expectComponentAccessible('button', { level: 'AA', }); wrapper.unmount(); }); it('handles click interactions and visual states', async () => { const wrapper = mountSonikComponent(Button, { props: { label: 'Interactive Button', severity: 'primary', }, }); // Functional: Test click functionality await wrapper.find('button').trigger('click'); // Button should be clickable and focusable expect(wrapper.find('button').element).toBeDefined(); // Visual: Capture button in interactive state await expect(page.getByRole('button', { name: 'Interactive Button' })).toMatchScreenshot( 'button-interactive.png', ); // Accessibility: Button should be clickable await expectComponentAccessible('button', { level: 'AA', excludeRules: ['page-has-heading-one'], }); wrapper.unmount(); }); it('renders all severity variants correctly', async () => { // Create a container to hold all variants for comparison const container = document.createElement('div'); container.setAttribute('data-testid', 'severity-container'); container.style.display = 'flex'; container.style.gap = '1rem'; container.style.padding = '1rem'; document.body.appendChild(container); const severities = ['primary', 'secondary', 'success', 'info', 'warning', 'help', 'danger']; const wrappers: ReturnType[] = []; // Mount all severity variants for (const severity of severities) { const wrapper = mountSonikComponent(Button, { props: { label: `${severity} Button`, severity, }, attachTo: container, }); wrappers.push(wrapper); } // Wait for all components to be stable await new Promise((resolve) => setTimeout(resolve, 200)); // Visual regression test for all variants await expect(page.getByTestId('severity-container')).toMatchScreenshot( 'button-all-severities.png', ); // Accessibility test on first button (representative) await expectComponentAccessible('button', { level: 'AA', excludeRules: ['page-has-heading-one'], }); // Cleanup wrappers.forEach((wrapper) => wrapper.unmount()); container.remove(); }); it('renders all size variants correctly', async () => { const container = document.createElement('div'); container.setAttribute('data-testid', 'sizes-container'); container.style.display = 'flex'; container.style.alignItems = 'center'; container.style.gap = '1rem'; container.style.padding = '1rem'; document.body.appendChild(container); const sizes = ['small', 'normal', 'large']; const wrappers: ReturnType[] = []; for (const size of sizes) { const wrapper = mountSonikComponent(Button, { props: { label: `${size} Button`, size, }, attachTo: container, }); wrappers.push(wrapper); } await new Promise((resolve) => setTimeout(resolve, 200)); // Visual regression for size variants await expect(page.getByTestId('sizes-container')).toMatchScreenshot('button-all-sizes.png'); // Test that each size has different computed styles const buttons = container.querySelectorAll('button'); expect(buttons).toHaveLength(3); // Cleanup wrappers.forEach((wrapper) => wrapper.unmount()); container.remove(); }); it('handles different states correctly', async () => { const container = document.createElement('div'); container.setAttribute('data-testid', 'states-container'); container.style.display = 'flex'; container.style.gap = '1rem'; container.style.padding = '1rem'; document.body.appendChild(container); // Test normal, disabled, and loading states const states = [ { label: 'Normal', props: {} }, { label: 'Disabled', props: { disabled: true } }, { label: 'Loading', props: { loading: true } }, ]; const wrappers: ReturnType[] = []; for (const state of states) { const wrapper = mountSonikComponent(Button, { props: { label: `${state.label} Button`, ...state.props, }, attachTo: container, }); wrappers.push(wrapper); } await new Promise((resolve) => setTimeout(resolve, 200)); // Visual regression for different states await expect(page.getByTestId('states-container')).toMatchScreenshot('button-all-states.png'); // Accessibility: Disabled button should have aria-disabled const disabledButton = container.querySelector('button[disabled]'); if (disabledButton) { expect(disabledButton.getAttribute('disabled')).toBeDefined(); } // Cleanup wrappers.forEach((wrapper) => wrapper.unmount()); container.remove(); }); it('supports keyboard navigation', async () => { const wrapper = mountSonikComponent(Button, { props: { label: 'Keyboard Button', }, }); const button = wrapper.find('button'); // Focus the button button.element.focus(); expect(document.activeElement).toBe(button.element); // Visual: Button should show focus state await expect(page.getByRole('button', { name: 'Keyboard Button' })).toMatchScreenshot( 'button-focused.png', ); // Accessibility: Should support keyboard activation await expectComponentAccessible('button', { level: 'AA', excludeRules: ['page-has-heading-one'], }); wrapper.unmount(); }); it('is responsive across different screen contexts', async () => { // Test button behavior at different container widths const viewports = [ { name: 'mobile', width: 375 }, { name: 'tablet', width: 768 }, { name: 'desktop', width: 1024 }, ]; for (const viewport of viewports) { const container = document.createElement('div'); container.style.width = `${viewport.width}px`; container.style.padding = '1rem'; container.setAttribute('data-testid', `${viewport.name}-container`); document.body.appendChild(container); const wrapper = mountSonikComponent(Button, { props: { label: `${viewport.name} Button`, severity: 'primary', }, attachTo: container, }); await new Promise((resolve) => setTimeout(resolve, 100)); // Visual regression for responsive behavior await expect(page.getByTestId(`${viewport.name}-container`)).toMatchScreenshot( `button-${viewport.name}.png`, ); // Visual regression covers styling changes through screenshots wrapper.unmount(); container.remove(); } }); it('renders correctly with LoopAdmin theme', async () => { const wrapper = mountSonikComponentWithTheme(Button, 'LoopAdmin', { props: { label: 'LoopAdmin Themed Button', severity: 'primary', }, }); // Functional: Component should render with theme expect(wrapper.exists()).toBe(true); expect(wrapper.text()).toContain('LoopAdmin Themed Button'); // Visual: Capture theme-specific styling await expect(page.getByRole('button', { name: 'LoopAdmin Themed Button' })).toMatchScreenshot( 'button-loopadmin-theme.png', ); // Accessibility: Theme shouldn't affect accessibility await expectComponentAccessible('button', { level: 'AA', }); wrapper.unmount(); }); });