import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import {
Button,
buttonVariants,
} from '../../../../src/renderer/components/ui/Button';
jest.mock('../../../../src/renderer/lib/utils', () => ({
cn: (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' '),
}));
jest.mock('class-variance-authority', () => ({
cva: jest.fn((base, config) => {
return (props: any) => {
const classes = [base];
if (props.variant && config.variants.variant[props.variant]) {
classes.push(config.variants.variant[props.variant]);
} else if (config.defaultVariants?.variant) {
classes.push(config.variants.variant[config.defaultVariants.variant]);
}
if (props.size && config.variants.size[props.size]) {
classes.push(config.variants.size[props.size]);
} else if (config.defaultVariants?.size) {
classes.push(config.variants.size[config.defaultVariants.size]);
}
if (props.className) {
classes.push(props.className);
}
return classes.join(' ');
};
}),
}));
jest.mock('@radix-ui/react-slot', () => ({
Slot: React.forwardRef(({ children, ...props }, ref) => (
{children}
)),
}));
describe('Button', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Basic Rendering', () => {
test('should render button with default props', () => {
render();
const button = screen.getByRole('button', { name: /click me/i });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('data-slot', 'button');
});
test('should render with custom className', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('custom-class');
});
test('should render with custom props', () => {
render(
);
const button = screen.getByRole('button');
expect(button).toHaveAttribute('type', 'submit');
expect(button).toBeDisabled();
});
test('should render children correctly', () => {
render(
);
expect(screen.getByText('Icon')).toBeInTheDocument();
expect(screen.getByText('Button Text')).toBeInTheDocument();
});
});
describe('Variants', () => {
test('should render default variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass(
'bg-gradient-to-r',
'from-hgo-blue',
'to-hgo-blue'
);
});
test('should render gradient variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass(
'bg-gradient-to-r',
'from-hgo-purple',
'via-hgo-blue',
'to-hgo-green'
);
});
test('should render destructive variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('bg-red-600', 'text-white');
});
test('should render outline variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('border', 'border-white/10', 'bg-white/5');
});
test('should render secondary variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('bg-white/10', 'backdrop-blur-sm');
});
test('should render ghost variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('hover:bg-white/10');
});
test('should render link variant', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('text-hgo-blue', 'underline-offset-4');
});
});
describe('Sizes', () => {
test('should render small size', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('px-2', 'py-1', 'text-xs');
});
test('should render default size', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('px-3', 'py-1.5', 'text-xs');
});
test('should render large size', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('px-3.5', 'py-2', 'text-sm');
});
test('should render extra large size', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('h-14', 'px-10', 'py-4', 'text-xl');
});
test('should render icon size', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveClass('size-10');
});
});
describe('Button Element', () => {
test('should render as button when asChild is false', () => {
render();
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('data-slot', 'button');
});
test('should render as button by default', () => {
render();
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('data-slot', 'button');
});
});
describe('Event Handling', () => {
test('should handle click events', () => {
const handleClick = jest.fn();
render();
const button = screen.getByRole('button');
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('should not handle click when disabled', () => {
const handleClick = jest.fn();
render(
);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(handleClick).not.toHaveBeenCalled();
expect(button).toBeDisabled();
});
test('should handle keyboard events', () => {
const handleKeyDown = jest.fn();
render();
const button = screen.getByRole('button');
fireEvent.keyDown(button, { key: 'Enter' });
expect(handleKeyDown).toHaveBeenCalledWith(
expect.objectContaining({ key: 'Enter' })
);
});
});
describe('Accessibility', () => {
test('should have proper ARIA attributes', () => {
render();
const button = screen.getByRole('button', { name: /custom label/i });
expect(button).toHaveAttribute('aria-label', 'Custom label');
});
test('should support aria-expanded', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveAttribute('aria-expanded', 'true');
});
test('should support aria-controls', () => {
render();
const button = screen.getByRole('button');
expect(button).toHaveAttribute('aria-controls', 'menu');
});
});
describe('buttonVariants', () => {
test('should return base classes when no props provided', () => {
const classes = buttonVariants({});
expect(classes).toContain('inline-flex');
expect(classes).toContain('items-center');
expect(classes).toContain('justify-center');
});
test('should include variant classes', () => {
const classes = buttonVariants({ variant: 'destructive' });
expect(classes).toContain('bg-red-600');
expect(classes).toContain('text-white');
});
test('should include size classes', () => {
const classes = buttonVariants({ size: 'lg' });
expect(classes).toContain('px-3.5');
expect(classes).toContain('py-2');
});
test('should include custom className', () => {
const classes = buttonVariants({ className: 'custom-class' });
expect(classes).toContain('custom-class');
});
test('should combine all variant options', () => {
const classes = buttonVariants({
variant: 'gradient',
size: 'xl',
className: 'additional-class',
});
expect(classes).toContain('bg-gradient-to-r');
expect(classes).toContain('from-hgo-purple');
expect(classes).toContain('h-14');
expect(classes).toContain('px-10');
expect(classes).toContain('additional-class');
});
});
describe('Integration Scenarios', () => {
test('should render complete button with all features', () => {
const handleClick = jest.fn();
render(
);
const button = screen.getByRole('button', { name: /test button/i });
const icon = screen.getByTestId('icon');
expect(button).toHaveClass('test-class');
expect(button).toHaveAttribute('aria-label', 'Test button');
expect(icon).toBeInTheDocument();
expect(screen.getByText('Complete Button')).toBeInTheDocument();
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('should handle form submission', () => {
const handleSubmit = jest.fn((e) => e.preventDefault());
render(
);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(handleSubmit).toHaveBeenCalledTimes(1);
});
test('should work with React.forwardRef', () => {
const ref = React.createRef();
render();
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
expect(ref.current?.textContent).toBe('Ref Button');
});
});
});