forked from cardosofelipe/fast-next-template
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:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user