import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { describe, expect, it, vi } from 'vitest'; import { MdButton } from '../MdButton'; describe('MdButton', () => { describe('rendering', () => { it('renders with default props', () => { render(Click me); const button = screen.getByRole('button'); expect(button).toBeInTheDocument(); expect(button).toHaveClass('md-button'); }); it('renders children correctly', () => { render(Button Text); expect(screen.getByRole('button')).toHaveTextContent('Button Text'); }); it('renders with ReactNode children', () => { render( Complex child , ); expect(screen.getByTestId('child')).toBeInTheDocument(); }); }); describe('props forwarding', () => { it('forwards id attribute', () => { render(Click); expect(screen.getByRole('button')).toHaveAttribute('id', 'my-button'); }); it('forwards aria-label', () => { render(X); expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'Close dialog'); }); it('forwards data-* attributes', () => { render(Click); expect(screen.getByTestId('custom-button')).toBeInTheDocument(); }); it('merges custom className with component classes', () => { render(Click); const button = screen.getByRole('button'); expect(button).toHaveClass('md-button'); expect(button).toHaveClass('custom-class'); }); }); describe('interactions', () => { it('handles click events', async () => { const user = userEvent.setup(); const onClick = vi.fn(); render(Click me); await user.click(screen.getByRole('button')); expect(onClick).toHaveBeenCalledTimes(1); }); it('does not fire click when disabled', async () => { const user = userEvent.setup(); const onClick = vi.fn(); render( Click me , ); await user.click(screen.getByRole('button')); expect(onClick).not.toHaveBeenCalled(); }); it('can be triggered with keyboard (Enter)', async () => { const user = userEvent.setup(); const onClick = vi.fn(); render(Click me); screen.getByRole('button').focus(); await user.keyboard('{Enter}'); expect(onClick).toHaveBeenCalledTimes(1); }); it('can be triggered with keyboard (Space)', async () => { const user = userEvent.setup(); const onClick = vi.fn(); render(Click me); screen.getByRole('button').focus(); await user.keyboard(' '); expect(onClick).toHaveBeenCalledTimes(1); }); }); describe('disabled state', () => { it('can be disabled', () => { render(Click me); expect(screen.getByRole('button')).toBeDisabled(); }); it('is not disabled by default', () => { render(Click me); expect(screen.getByRole('button')).not.toBeDisabled(); }); }); describe('themes', () => { it.each([ ['secondary', 'md-button--secondary'], ['tertiary', 'md-button--tertiary'], ['danger', 'md-button--danger'], ['danger-secondary', 'md-button--danger-secondary'], ['danger-tertiary', 'md-button--danger-tertiary'], ] as const)('renders %s theme with correct class', (theme, expectedClass) => { render(Button); expect(screen.getByRole('button')).toHaveClass(expectedClass); }); it('renders primary theme without modifier class', () => { render(Button); const button = screen.getByRole('button'); expect(button).toHaveClass('md-button'); expect(button).not.toHaveClass('md-button--secondary'); expect(button).not.toHaveClass('md-button--tertiary'); }); }); describe('modes', () => { it('renders small mode', () => { render(Small); expect(screen.getByRole('button')).toHaveClass('md-button--small'); }); it('renders large mode', () => { render(Large); expect(screen.getByRole('button')).toHaveClass('md-button--large'); }); it('renders medium mode without modifier class', () => { render(Medium); const button = screen.getByRole('button'); expect(button).not.toHaveClass('md-button--small'); expect(button).not.toHaveClass('md-button--large'); }); it('supports deprecated small prop for backward compatibility', () => { render(Small); expect(screen.getByRole('button')).toHaveClass('md-button--small'); }); it('small prop overrides mode prop', () => { render( Small , ); expect(screen.getByRole('button')).toHaveClass('md-button--small'); }); }); describe('type attribute', () => { it('defaults to type="button"', () => { render(Click); expect(screen.getByRole('button')).toHaveAttribute('type', 'button'); }); it('can be type="submit"', () => { render(Submit); expect(screen.getByRole('button')).toHaveAttribute('type', 'submit'); }); it('can be type="reset"', () => { render(Reset); expect(screen.getByRole('button')).toHaveAttribute('type', 'reset'); }); }); describe('loading state', () => { it('renders loading spinner when loading', () => { render(Loading); expect(screen.getByRole('button').querySelector('.md-button__rightIcon')).toBeInTheDocument(); }); }); describe('icons', () => { it('renders left icon', () => { render(←}>With Icon); expect(screen.getByTestId('left-icon')).toBeInTheDocument(); expect(screen.getByRole('button').querySelector('.md-button__leftIcon')).toBeInTheDocument(); }); it('renders right icon', () => { render(→}>With Icon); expect(screen.getByTestId('right-icon')).toBeInTheDocument(); expect(screen.getByRole('button').querySelector('.md-button__rightIcon')).toBeInTheDocument(); }); it('renders top icon with column layout', () => { render(↑}>With Top Icon); expect(screen.getByTestId('top-icon')).toBeInTheDocument(); expect(screen.getByRole('button')).toHaveClass('md-button--column'); }); it('marks icons as aria-hidden', () => { render(Icon}>With Icon); expect(screen.getByRole('button').querySelector('.md-button__leftIcon')).toHaveAttribute('aria-hidden', 'true'); }); }); describe('asChild pattern', () => { it('renders as custom element when asChild is true', () => { render( Link}> Link Button , ); const link = screen.getByRole('link'); expect(link).toBeInTheDocument(); expect(link).toHaveClass('md-button'); expect(link).toHaveAttribute('href', '/test'); }); }); });