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