diff --git a/frontend/tests/components/agents/AgentTypeForm.test.tsx b/frontend/tests/components/agents/AgentTypeForm.test.tsx
index 3d9dab9..6eceea7 100644
--- a/frontend/tests/components/agents/AgentTypeForm.test.tsx
+++ b/frontend/tests/components/agents/AgentTypeForm.test.tsx
@@ -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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ const permissionsTab = screen.getByRole('tab', { name: /permissions/i });
+ expect(permissionsTab).toBeInTheDocument();
+ expect(permissionsTab).toHaveAttribute('aria-controls');
+ });
+
+ it('permissions tab content exists', () => {
+ render();
+
+ // 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
+ });
+
+ it('shows status select as combobox', () => {
+ render();
+
+ const statusTrigger = screen.getByLabelText(/status/i);
+ expect(statusTrigger).toHaveAttribute('role', 'combobox');
+ });
+
+ it('displays active status for new agent by default', () => {
+ render();
+
+ // The select trigger should show "Active" by default
+ expect(screen.getByText('Active')).toBeInTheDocument();
+ });
+
+ it('preserves active status in edit mode', () => {
+ render();
+
+ // 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();
+
+ // 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();
+
+ // 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ 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();
+
+ // 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();
+
+ 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();
+
+ // Should render without errors
+ expect(screen.getByText('Edit Agent Type')).toBeInTheDocument();
+ });
+ });
});