/* global describe, it, expect, vi */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Stepper } from './Stepper';
const mockSteps = [
{
value: 'step1',
title: 'Contact Info',
description: 'Enter your contact details',
},
{
value: 'step2',
title: 'Date & Time',
description: 'Select a date and time',
},
{ value: 'step3', title: 'Review', description: 'Review your information' },
];
describe('Stepper', () => {
describe('Rendering', () => {
it('renders with default props', () => {
render();
// Should render all step titles
expect(screen.getByText('Contact Info')).toBeInTheDocument();
expect(screen.getByText('Date & Time')).toBeInTheDocument();
expect(screen.getByText('Review')).toBeInTheDocument();
});
it('renders all step indicators with numbers', () => {
render();
// Should show step numbers (1, 2, 3)
expect(screen.getByText('1')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
});
it('renders step content when showContent is true', () => {
render();
// Should show first step's description
expect(
screen.getByText('Enter your contact details'),
).toBeInTheDocument();
});
it('hides step content when showContent is false', () => {
render();
// Should not show step descriptions
expect(
screen.queryByText('Enter your contact details'),
).not.toBeInTheDocument();
});
it('renders separators between steps', () => {
const { container } = render();
// Should have n-1 separators for n steps
const separators = container.querySelectorAll('[class*="separator"]');
expect(separators).toHaveLength(2);
});
});
describe('Visual States', () => {
it('applies current state styling to active step', () => {
const { container } = render(
,
);
const indicators = container.querySelectorAll('[data-part="indicator"]');
// First indicator should have data-current attribute
expect(indicators[0]).toHaveAttribute('data-current');
});
it('applies completed state styling to previous steps', async () => {
const { container } = render(
,
);
const indicators = container.querySelectorAll('[data-part="indicator"]');
// First indicator should be completed when on step 2
expect(indicators[0]).toHaveAttribute('data-complete');
});
it('applies upcoming state styling to future steps', () => {
const { container } = render(
,
);
const indicators = container.querySelectorAll('[data-part="indicator"]');
// Second and third should have incomplete state
expect(indicators[1]).toHaveAttribute('data-incomplete');
expect(indicators[2]).toHaveAttribute('data-incomplete');
});
it('styles separators based on completion', () => {
const { container } = render(
,
);
const separators = container.querySelectorAll('[data-part="separator"]');
// First separator should be completed
expect(separators[0]).toHaveAttribute('data-complete');
});
});
describe('Interaction', () => {
it('handles step change events', async () => {
const onStepChange = vi.fn();
render(
,
);
const user = userEvent.setup();
const triggers = document.querySelectorAll('[data-part="trigger"]');
await user.click(triggers[1]);
expect(onStepChange).toHaveBeenCalled();
});
it('supports keyboard navigation', async () => {
const { container } = render(
,
);
const triggers = container.querySelectorAll('[data-part="trigger"]');
// Triggers should be tabbable
expect(triggers[0]).toHaveAttribute('tabindex');
expect(triggers[1]).toHaveAttribute('tabindex');
});
it('respects linear mode', () => {
const { container } = render(
,
);
const triggers = container.querySelectorAll('[data-part="trigger"]');
// In linear mode, step 1 (index 1) should not be clickable since step 0 is current
// Step 3 (index 2) should have incomplete/disabled state
const secondTrigger = triggers[1];
const thirdTrigger = triggers[2];
// Future steps should have data-incomplete
expect(secondTrigger).toHaveAttribute('data-incomplete');
expect(thirdTrigger).toHaveAttribute('data-incomplete');
});
});
describe('Variants', () => {
it('applies size variants correctly', () => {
const { container, rerender } = render(
,
);
let root = container.querySelector('[data-part="root"]');
expect(root).toHaveClass('stepper__root--size_sm');
rerender();
root = container.querySelector('[data-part="root"]');
expect(root).toHaveClass('stepper__root--size_lg');
});
it('supports horizontal orientation', () => {
const { container } = render(
,
);
const root = container.querySelector('[data-part="root"]');
expect(root).toHaveAttribute('data-orientation', 'horizontal');
});
it('supports vertical orientation', () => {
const { container } = render(
,
);
const root = container.querySelector('[data-part="root"]');
expect(root).toHaveAttribute('data-orientation', 'vertical');
});
});
describe('Accessibility', () => {
it('has proper ARIA attributes', () => {
const { container } = render(
,
);
const triggers = container.querySelectorAll('[role="tab"]');
// Step triggers should have tab role
expect(triggers.length).toBe(mockSteps.length);
// Should have accessible names
expect(screen.getByText('Contact Info')).toBeInTheDocument();
});
it('supports screen readers', () => {
render();
const currentContent = screen.getByText('Enter your contact details');
// Current step content should be visible for screen readers
expect(currentContent).toBeVisible();
});
it('has focus indicators', () => {
const { container } = render();
const triggers = container.querySelectorAll('[data-part="trigger"]');
// Triggers should be focusable
expect(triggers[0]).toHaveAttribute('tabindex', '0');
});
});
describe('StepperActions', () => {
it('renders navigation buttons', () => {
render();
expect(screen.getByText('Back')).toBeInTheDocument();
expect(screen.getByText('Next')).toBeInTheDocument();
});
it('allows custom button labels', () => {
render(
,
);
expect(screen.getByText('Previous')).toBeInTheDocument();
expect(screen.getByText('Continue')).toBeInTheDocument();
});
it('navigates steps when clicked', async () => {
render(
,
);
const user = userEvent.setup();
const nextButton = screen.getByText('Next');
await user.click(nextButton);
// Should show second step content
expect(
await screen.findByText('Select a date and time'),
).toBeInTheDocument();
});
});
describe('Completed State', () => {
it('shows completed content when all steps done', () => {
render();
expect(screen.getByText('All steps completed!')).toBeInTheDocument();
});
it('hides step content when completed', () => {
render();
// Content exists in DOM but should be hidden
const content = screen.getByText('Enter your contact details');
expect(content).not.toBeVisible();
});
});
});