test(frontend): expand AgentTypeForm test coverage to ~88%

Add comprehensive tests for AgentTypeForm component covering:
- Model Tab: temperature, max tokens, top p parameter inputs
- Permissions Tab: tab trigger and content presence
- Personality Tab: character count, prompt pre-filling
- Status Field: active/inactive display states
- Expertise Edge Cases: duplicates, empty, lowercase, trim
- Form Submission: onSubmit callback verification

Coverage improved from 78.94% to 87.71% statements.
Some Radix UI event handlers remain untested due to JSDOM limitations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-01 12:00:06 +01:00
parent 36ab7069cf
commit 246d2a6752

View File

@@ -235,4 +235,327 @@ describe('AgentTypeForm', () => {
expect(container.querySelector('form')).toHaveClass('custom-class');
});
});
describe('Model Tab', () => {
it('switches to model tab and shows model selection', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
expect(screen.getByText('Model Selection')).toBeInTheDocument();
expect(screen.getByText('Choose the AI models that power this agent type')).toBeInTheDocument();
expect(screen.getByLabelText(/primary model/i)).toBeInTheDocument();
expect(screen.getByLabelText(/fallover model/i)).toBeInTheDocument();
});
it('shows model parameters section', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
expect(screen.getByText('Model Parameters')).toBeInTheDocument();
expect(screen.getByLabelText(/temperature/i)).toBeInTheDocument();
expect(screen.getByLabelText(/max tokens/i)).toBeInTheDocument();
expect(screen.getByLabelText(/top p/i)).toBeInTheDocument();
});
it('allows changing temperature', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
const temperatureInput = screen.getByLabelText(/temperature/i) as HTMLInputElement;
expect(temperatureInput.value).toBe('0.7');
await user.clear(temperatureInput);
await user.type(temperatureInput, '0.9');
expect(temperatureInput.value).toBe('0.9');
});
it('allows changing max tokens', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
const maxTokensInput = screen.getByLabelText(/max tokens/i) as HTMLInputElement;
expect(maxTokensInput.value).toBe('8192');
await user.clear(maxTokensInput);
await user.type(maxTokensInput, '16384');
expect(maxTokensInput.value).toBe('16384');
});
it('allows changing top p', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
const topPInput = screen.getByLabelText(/top p/i) as HTMLInputElement;
expect(topPInput.value).toBe('0.95');
await user.clear(topPInput);
await user.type(topPInput, '0.8');
expect(topPInput.value).toBe('0.8');
});
it('shows primary model select trigger', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
// Verify the select trigger is present and labeled correctly
const primaryModelTrigger = screen.getByLabelText(/primary model/i);
expect(primaryModelTrigger).toBeInTheDocument();
expect(primaryModelTrigger).toHaveAttribute('role', 'combobox');
});
it('shows fallback model select trigger', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
// Verify the select trigger is present and labeled correctly
const fallbackModelTrigger = screen.getByLabelText(/fallover model/i);
expect(fallbackModelTrigger).toBeInTheDocument();
expect(fallbackModelTrigger).toHaveAttribute('role', 'combobox');
});
it('displays pre-selected model in edit mode', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
await user.click(screen.getByRole('tab', { name: /model/i }));
// Check that the selected model is displayed (multiple elements exist due to hidden native select)
const modelTexts = screen.getAllByText('Claude Opus 4.5');
expect(modelTexts.length).toBeGreaterThan(0);
});
});
describe('Permissions Tab', () => {
it('has a permissions tab trigger', () => {
render(<AgentTypeForm {...defaultProps} />);
const permissionsTab = screen.getByRole('tab', { name: /permissions/i });
expect(permissionsTab).toBeInTheDocument();
expect(permissionsTab).toHaveAttribute('aria-controls');
});
it('permissions tab content exists', () => {
render(<AgentTypeForm {...defaultProps} />);
// The tab panel for permissions should exist (even if hidden)
const tabPanels = document.querySelectorAll('[role="tabpanel"]');
expect(tabPanels.length).toBeGreaterThan(0);
});
});
describe('Personality Tab', () => {
it('switches to personality tab and shows prompt editor', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
await user.click(screen.getByRole('tab', { name: /personality/i }));
expect(screen.getByText('Personality Prompt')).toBeInTheDocument();
expect(screen.getByPlaceholderText(/you are a/i)).toBeInTheDocument();
});
it('shows character count', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
await user.click(screen.getByRole('tab', { name: /personality/i }));
// Should show character count for the existing prompt
expect(screen.getByText(/character count:/i)).toBeInTheDocument();
});
it('updates character count when typing', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
await user.click(screen.getByRole('tab', { name: /personality/i }));
const promptTextarea = screen.getByPlaceholderText(/you are a/i);
await user.type(promptTextarea, 'Test prompt');
expect(screen.getByText('Character count: 11')).toBeInTheDocument();
});
it('pre-fills personality prompt in edit mode', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
await user.click(screen.getByRole('tab', { name: /personality/i }));
const promptTextarea = screen.getByPlaceholderText(/you are a/i) as HTMLTextAreaElement;
expect(promptTextarea.value).toBe('You are a Software Architect...');
});
});
describe('Status Field', () => {
it('shows status select in basic info tab', () => {
render(<AgentTypeForm {...defaultProps} />);
expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
});
it('shows status select as combobox', () => {
render(<AgentTypeForm {...defaultProps} />);
const statusTrigger = screen.getByLabelText(/status/i);
expect(statusTrigger).toHaveAttribute('role', 'combobox');
});
it('displays active status for new agent by default', () => {
render(<AgentTypeForm {...defaultProps} />);
// The select trigger should show "Active" by default
expect(screen.getByText('Active')).toBeInTheDocument();
});
it('preserves active status in edit mode', () => {
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
// Mock agent has is_active = true
// Status select trigger should show "Active"
const statusTrigger = screen.getByLabelText(/status/i);
expect(statusTrigger).toHaveTextContent('Active');
});
it('shows inactive status when agent is inactive', () => {
const inactiveAgent = { ...mockAgentType, is_active: false };
render(<AgentTypeForm {...defaultProps} agentType={inactiveAgent} />);
// Status select trigger should show "Inactive / Draft"
const statusTrigger = screen.getByLabelText(/status/i);
expect(statusTrigger).toHaveTextContent('Inactive');
});
});
describe('Expertise Edge Cases', () => {
it('does not add duplicate expertise', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} agentType={mockAgentType} />);
// Agent type already has 'system design'
const expertiseInput = screen.getByPlaceholderText(/e.g., system design/i);
await user.type(expertiseInput, 'system design');
await user.click(screen.getByRole('button', { name: /^add$/i }));
// Should still only have one 'system design' badge
const badges = screen.getAllByText('system design');
expect(badges).toHaveLength(1);
});
it('does not add empty expertise', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
const addButton = screen.getByRole('button', { name: /^add$/i });
await user.click(addButton);
// No badges should be added
expect(screen.queryByRole('button', { name: /^remove/i })).not.toBeInTheDocument();
});
it('converts expertise to lowercase', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
const expertiseInput = screen.getByPlaceholderText(/e.g., system design/i);
await user.type(expertiseInput, 'API Design');
await user.click(screen.getByRole('button', { name: /^add$/i }));
expect(screen.getByText('api design')).toBeInTheDocument();
});
it('trims whitespace from expertise', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
const expertiseInput = screen.getByPlaceholderText(/e.g., system design/i);
await user.type(expertiseInput, ' testing ');
await user.click(screen.getByRole('button', { name: /^add$/i }));
expect(screen.getByText('testing')).toBeInTheDocument();
});
it('clears input after adding expertise', async () => {
const user = userEvent.setup();
render(<AgentTypeForm {...defaultProps} />);
const expertiseInput = screen.getByPlaceholderText(/e.g., system design/i) as HTMLInputElement;
await user.type(expertiseInput, 'new skill');
await user.click(screen.getByRole('button', { name: /^add$/i }));
expect(expertiseInput.value).toBe('');
});
});
describe('Form Submission', () => {
it('calls onSubmit when form is valid', async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(<AgentTypeForm {...defaultProps} onSubmit={onSubmit} agentType={mockAgentType} />);
// Submit the pre-filled form
await user.click(screen.getByRole('button', { name: /save changes/i }));
// React Hook Form should call onSubmit with form data
await waitFor(
() => {
expect(onSubmit).toHaveBeenCalled();
},
{ timeout: 2000 }
);
});
it('passes form data to onSubmit callback', async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(<AgentTypeForm {...defaultProps} onSubmit={onSubmit} agentType={mockAgentType} />);
await user.click(screen.getByRole('button', { name: /save changes/i }));
await waitFor(
() => {
expect(onSubmit).toHaveBeenCalled();
},
{ timeout: 2000 }
);
// Verify the first argument contains expected fields
const formData = onSubmit.mock.calls[0][0];
expect(formData).toHaveProperty('name', 'Software Architect');
expect(formData).toHaveProperty('slug', 'software-architect');
expect(formData).toHaveProperty('expertise');
expect(formData).toHaveProperty('personality_prompt');
});
});
describe('Null Model Params Handling', () => {
it('handles null model_params gracefully', () => {
const agentTypeWithNullParams: AgentTypeResponse = {
...mockAgentType,
model_params: null,
};
render(<AgentTypeForm {...defaultProps} agentType={agentTypeWithNullParams} />);
// Should render without errors
expect(screen.getByText('Edit Agent Type')).toBeInTheDocument();
});
});
});