import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import MockAdapter from 'axios-mock-adapter'; import { apiClient } from '@/api/client'; import { TemplatePickerModal } from '@/components/TemplatePickerModal'; import { renderWithProviders } from './utils'; import type { SimulationTemplate } from '@/api/types'; const TEMPLATES: SimulationTemplate[] = [ { id: 1, name: 'Mimikatz LSASS Dump', description: null, commands: null, prerequisites: null, techniques: [{ id: 'T1003', name: 'OS Credential Dumping', tactics: [] }], tactics: [], created_at: '2026-05-28T00:00:00', updated_at: null, created_by: { id: 1, username: 'alice' }, }, { id: 2, name: 'PowerShell Empire', description: null, commands: null, prerequisites: null, techniques: [], tactics: [{ id: 'TA0002', name: 'Execution' }], created_at: '2026-05-28T01:00:00', updated_at: null, created_by: { id: 1, username: 'alice' }, }, ]; const onClose = vi.fn(); const onInstantiated = vi.fn(); const onSelectTemplate = vi.fn(); describe('TemplatePickerModal', () => { let mock: MockAdapter; beforeEach(() => { mock = new MockAdapter(apiClient); vi.clearAllMocks(); }); afterEach(() => { mock.restore(); }); it('shows loading state while fetching', () => { mock.onGet('/simulation-templates').reply(() => new Promise(() => {})); renderWithProviders( ); expect(screen.getByTestId('loading-state')).toBeInTheDocument(); }); it('shows empty state when no templates', async () => { mock.onGet('/simulation-templates').reply(200, []); renderWithProviders( ); await waitFor(() => { expect(screen.getByTestId('empty-state')).toBeInTheDocument(); }); expect(screen.getByText(/No templates available/i)).toBeInTheDocument(); }); it('lists templates with name and MITRE count', async () => { mock.onGet('/simulation-templates').reply(200, TEMPLATES); renderWithProviders( ); await waitFor(() => { expect(screen.getByTestId('template-picker-table')).toBeInTheDocument(); }); expect(screen.getByText('Mimikatz LSASS Dump')).toBeInTheDocument(); expect(screen.getByText('PowerShell Empire')).toBeInTheDocument(); // T1(1 tech) + 0 tactics = 1 for first, 0 tech + 1 tactic = 1 for second expect(screen.getAllByText('1').length).toBe(2); }); it('calls onSelectTemplate when a template row is clicked', async () => { mock.onGet('/simulation-templates').reply(200, TEMPLATES); const user = userEvent.setup(); renderWithProviders( ); await waitFor(() => { expect(screen.getByText('Mimikatz LSASS Dump')).toBeInTheDocument(); }); await user.click(screen.getByTestId('template-row-1')); expect(onSelectTemplate).toHaveBeenCalledWith(TEMPLATES[0]); }); it('calls onClose when Cancel is clicked', async () => { mock.onGet('/simulation-templates').reply(200, TEMPLATES); const user = userEvent.setup(); renderWithProviders( ); await waitFor(() => { expect(screen.getByText('Cancel')).toBeInTheDocument(); }); await user.click(screen.getByText('Cancel')); expect(onClose).toHaveBeenCalledOnce(); }); it('shows error state on fetch failure', async () => { mock.onGet('/simulation-templates').reply(500, { error: 'Server error' }); renderWithProviders( ); await waitFor(() => { expect(screen.getByTestId('error-state')).toBeInTheDocument(); }); }); });