/** * AutonomyStep Component Tests * * Tests for the autonomy level selection step with approval matrix. */ import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AutonomyStep } from '@/components/projects/wizard/steps/AutonomyStep'; import { autonomyOptions } from '@/components/projects/wizard/constants'; import { approvalLabels } from '@/components/projects/wizard/types'; import type { WizardState } from '@/components/projects/wizard/types'; describe('AutonomyStep', () => { const defaultState: WizardState = { step: 4, projectName: 'Test Project', description: '', repoUrl: '', complexity: 'medium', clientMode: 'technical', autonomyLevel: null, }; const mockUpdateState = jest.fn(); beforeEach(() => { jest.clearAllMocks(); }); describe('Rendering', () => { it('renders the step title', () => { render(); expect(screen.getByText('Autonomy Level')).toBeInTheDocument(); }); it('renders the description text', () => { render(); expect(screen.getByText(/how much control do you want/i)).toBeInTheDocument(); }); it('renders all autonomy options', () => { render(); autonomyOptions.forEach((option) => { // Labels appear multiple times (in cards and matrix), use getAllByText const labels = screen.getAllByText(option.label); expect(labels.length).toBeGreaterThan(0); expect(screen.getByText(option.description)).toBeInTheDocument(); }); }); it('renders "Best for" recommendations', () => { render(); autonomyOptions.forEach((option) => { expect(screen.getByText(option.recommended)).toBeInTheDocument(); }); }); it('has accessible radiogroup role', () => { render(); expect(screen.getByRole('radiogroup', { name: /autonomy level options/i })).toBeInTheDocument(); }); }); describe('Selection', () => { it('calls updateState when clicking full_control option', async () => { const user = userEvent.setup(); render(); const fullControlOption = screen.getByRole('button', { name: /full control.*review every action/i }); await user.click(fullControlOption); expect(mockUpdateState).toHaveBeenCalledWith({ autonomyLevel: 'full_control' }); }); it('calls updateState when clicking milestone option', async () => { const user = userEvent.setup(); render(); const milestoneOption = screen.getByRole('button', { name: /milestone.*review at sprint/i }); await user.click(milestoneOption); expect(mockUpdateState).toHaveBeenCalledWith({ autonomyLevel: 'milestone' }); }); it('calls updateState when clicking autonomous option', async () => { const user = userEvent.setup(); render(); const autonomousOption = screen.getByRole('button', { name: /autonomous.*only major decisions/i }); await user.click(autonomousOption); expect(mockUpdateState).toHaveBeenCalledWith({ autonomyLevel: 'autonomous' }); }); it('shows visual selection indicator when an option is selected', () => { const stateWithSelection: WizardState = { ...defaultState, autonomyLevel: 'milestone', }; render(); // The selected card should have the check icon const checkIcons = document.querySelectorAll('.lucide-check'); expect(checkIcons.length).toBeGreaterThan(0); }); }); describe('Approval Badges', () => { it('renders approval badges for each option', () => { render(); // Check that badges are rendered with approval labels Object.values(approvalLabels).forEach((label) => { const badges = screen.getAllByText(new RegExp(label)); expect(badges.length).toBeGreaterThan(0); }); }); it('shows Approve prefix for required approvals', () => { render(); const approveBadges = screen.getAllByText(/^Approve:/); expect(approveBadges.length).toBeGreaterThan(0); }); it('shows Auto prefix for automatic approvals', () => { render(); const autoBadges = screen.getAllByText(/^Auto:/); expect(autoBadges.length).toBeGreaterThan(0); }); }); describe('Approval Matrix Table', () => { it('renders the approval matrix card', () => { render(); expect(screen.getByText('Approval Matrix')).toBeInTheDocument(); }); it('renders table with column headers', () => { render(); expect(screen.getByRole('columnheader', { name: 'Action Type' })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: 'Full Control' })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: 'Milestone' })).toBeInTheDocument(); expect(screen.getByRole('columnheader', { name: 'Autonomous' })).toBeInTheDocument(); }); it('renders all action types as rows', () => { render(); const table = screen.getByRole('table', { name: /approval requirements/i }); Object.values(approvalLabels).forEach((label) => { expect(within(table).getByText(label)).toBeInTheDocument(); }); }); it('shows Required badges in the matrix', () => { render(); const requiredBadges = screen.getAllByText('Required'); expect(requiredBadges.length).toBeGreaterThan(0); }); it('shows Automatic text in the matrix', () => { render(); const automaticTexts = screen.getAllByText('Automatic'); expect(automaticTexts.length).toBeGreaterThan(0); }); }); describe('Accessibility', () => { it('each option has accessible aria-label', () => { render(); autonomyOptions.forEach((option) => { const button = screen.getByRole('button', { name: new RegExp(`${option.label}.*${option.description}`, 'i') }); expect(button).toBeInTheDocument(); }); }); it('table has accessible aria-label', () => { render(); expect(screen.getByRole('table', { name: /approval requirements/i })).toBeInTheDocument(); }); it('icons have aria-hidden attribute', () => { render(); const hiddenIcons = document.querySelectorAll('[aria-hidden="true"]'); expect(hiddenIcons.length).toBeGreaterThan(0); }); }); describe('Edge cases', () => { it('allows changing selection', async () => { const user = userEvent.setup(); const stateWithFullControl: WizardState = { ...defaultState, autonomyLevel: 'full_control', }; render(); const autonomousOption = screen.getByRole('button', { name: /autonomous.*only major decisions/i }); await user.click(autonomousOption); expect(mockUpdateState).toHaveBeenCalledWith({ autonomyLevel: 'autonomous' }); }); }); });