feat(FN-2382): finalize presets-first new-agent tab flow
- Split New Agent step-0 into Presets and Custom tabs with presets-first default behavior - Refine tab layout and styling in dashboard styles to address review feedback and improve responsive presentation - Expand NewAgentDialog and AgentsView tests to cover tab switching flows and related route behavior - Update agent documentation to reflect the revised new-agent dialog flow
This commit is contained in:
@@ -181,7 +181,12 @@ There are two ways to provide custom instructions:
|
|||||||
|
|
||||||
## New Agent Presets (Dashboard UI)
|
## New Agent Presets (Dashboard UI)
|
||||||
|
|
||||||
The New Agent dialog in the dashboard provides quick-start presets for common agent roles. Each preset includes:
|
The New Agent dialog keeps the existing 3-step flow, and step 0 is split into two tabs:
|
||||||
|
|
||||||
|
- **Preset personas** (default) — quick-start persona cards that prefill the same fields and immediately advance to step 1 when selected
|
||||||
|
- **Custom agent** — manual setup for identity, configuration, and the Generate with AI entry point
|
||||||
|
|
||||||
|
The dashboard provides quick-start presets for common agent roles. Each preset includes:
|
||||||
|
|
||||||
- **Name and icon** - Display identification
|
- **Name and icon** - Display identification
|
||||||
- **Professional title** - Descriptive role title
|
- **Professional title** - Descriptive role title
|
||||||
|
|||||||
@@ -35,8 +35,11 @@ interface RuntimeConfig {
|
|||||||
maxTurns: number;
|
maxTurns: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StepZeroTab = "presets" | "custom";
|
||||||
|
|
||||||
export function NewAgentDialog({ isOpen, onClose, onCreated, projectId }: NewAgentDialogProps) {
|
export function NewAgentDialog({ isOpen, onClose, onCreated, projectId }: NewAgentDialogProps) {
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
|
const [stepZeroTab, setStepZeroTab] = useState<StepZeroTab>("presets");
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
const [icon, setIcon] = useState("");
|
const [icon, setIcon] = useState("");
|
||||||
@@ -181,6 +184,7 @@ export function NewAgentDialog({ isOpen, onClose, onCreated, projectId }: NewAge
|
|||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setStep(0);
|
setStep(0);
|
||||||
|
setStepZeroTab("presets");
|
||||||
setName("");
|
setName("");
|
||||||
setTitle("");
|
setTitle("");
|
||||||
setIcon("");
|
setIcon("");
|
||||||
@@ -265,161 +269,208 @@ export function NewAgentDialog({ isOpen, onClose, onCreated, projectId }: NewAge
|
|||||||
<div className="agent-dialog-body">
|
<div className="agent-dialog-body">
|
||||||
{step === 0 && (
|
{step === 0 && (
|
||||||
<div>
|
<div>
|
||||||
{/* Quick Start Presets */}
|
<div className="agent-dialog-tabs" role="tablist" aria-label="Agent setup mode">
|
||||||
<div className="agent-presets">
|
<button
|
||||||
<div className="agent-presets-header">
|
id="agent-dialog-tab-presets"
|
||||||
Choose a preset or fill in details manually
|
type="button"
|
||||||
</div>
|
role="tab"
|
||||||
<div className="agent-presets-grid">
|
aria-controls="agent-dialog-panel-presets"
|
||||||
{AGENT_PRESETS.map(preset => (
|
aria-selected={stepZeroTab === "presets"}
|
||||||
<button
|
tabIndex={stepZeroTab === "presets" ? 0 : -1}
|
||||||
key={preset.id}
|
className={`agent-dialog-tab${stepZeroTab === "presets" ? " active" : ""}`}
|
||||||
type="button"
|
onClick={() => setStepZeroTab("presets")}
|
||||||
className={`agent-preset-card${selectedPresetId === preset.id ? " selected" : ""}`}
|
data-testid="agent-dialog-tab-presets"
|
||||||
data-testid={`preset-${preset.id}`}
|
>
|
||||||
onClick={() => handlePresetSelect(preset)}
|
Preset personas
|
||||||
title={preset.title}
|
</button>
|
||||||
>
|
<button
|
||||||
<span className="agent-preset-icon">{preset.icon}</span>
|
id="agent-dialog-tab-custom"
|
||||||
<span className="agent-preset-name">{preset.name}</span>
|
type="button"
|
||||||
<span className="agent-preset-role">{preset.role}</span>
|
role="tab"
|
||||||
{preset.description && (
|
aria-controls="agent-dialog-panel-custom"
|
||||||
<span className="agent-preset-description">{preset.description}</span>
|
aria-selected={stepZeroTab === "custom"}
|
||||||
)}
|
tabIndex={stepZeroTab === "custom" ? 0 : -1}
|
||||||
</button>
|
className={`agent-dialog-tab${stepZeroTab === "custom" ? " active" : ""}`}
|
||||||
))}
|
onClick={() => setStepZeroTab("custom")}
|
||||||
</div>
|
data-testid="agent-dialog-tab-custom"
|
||||||
|
>
|
||||||
|
Custom agent
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="agent-dialog-section">
|
|
||||||
<div className="agent-dialog-section-header">Identity</div>
|
{stepZeroTab === "presets" && (
|
||||||
<div className="agent-dialog-field">
|
<div
|
||||||
<label htmlFor="agent-name">Name {!selectedPresetId && <span className="agent-dialog-required">*</span>}</label>
|
id="agent-dialog-panel-presets"
|
||||||
<input
|
className="agent-dialog-tab-panel"
|
||||||
id="agent-name"
|
role="tabpanel"
|
||||||
type="text"
|
aria-labelledby="agent-dialog-tab-presets"
|
||||||
className="input"
|
>
|
||||||
placeholder="e.g. Frontend Reviewer"
|
<div className="agent-presets">
|
||||||
value={name}
|
<div className="agent-presets-header">
|
||||||
onChange={e => setName(e.target.value)}
|
Choose a preset persona to prefill role, identity, soul, and instructions
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="agent-presets-grid">
|
||||||
<div className="agent-dialog-field">
|
{AGENT_PRESETS.map(preset => (
|
||||||
<label htmlFor="agent-title">Title <span className="agent-dialog-optional">(optional)</span></label>
|
<button
|
||||||
<input
|
key={preset.id}
|
||||||
id="agent-title"
|
type="button"
|
||||||
type="text"
|
className={`agent-preset-card${selectedPresetId === preset.id ? " selected" : ""}`}
|
||||||
className="input"
|
data-testid={`preset-${preset.id}`}
|
||||||
placeholder="e.g. Senior Code Reviewer"
|
onClick={() => handlePresetSelect(preset)}
|
||||||
value={title}
|
title={preset.title}
|
||||||
onChange={e => setTitle(e.target.value)}
|
>
|
||||||
/>
|
<span className="agent-preset-icon">{preset.icon}</span>
|
||||||
</div>
|
<span className="agent-preset-name">{preset.name}</span>
|
||||||
<div className="agent-dialog-field">
|
<span className="agent-preset-role">{preset.role}</span>
|
||||||
<label htmlFor="agent-icon">Icon <span className="agent-dialog-optional">(optional)</span></label>
|
{preset.description && (
|
||||||
<input
|
<span className="agent-preset-description">{preset.description}</span>
|
||||||
id="agent-icon"
|
)}
|
||||||
type="text"
|
</button>
|
||||||
className="input"
|
))}
|
||||||
placeholder="e.g. 🤖"
|
</div>
|
||||||
value={icon}
|
|
||||||
onChange={e => setIcon(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="agent-dialog-field">
|
|
||||||
<label>Role</label>
|
|
||||||
<div className="agent-role-grid">
|
|
||||||
{AGENT_ROLES.map(r => (
|
|
||||||
<button
|
|
||||||
key={r.value}
|
|
||||||
type="button"
|
|
||||||
className={`agent-role-option${role === r.value ? " selected" : ""}`}
|
|
||||||
onClick={() => setRole(r.value)}
|
|
||||||
>
|
|
||||||
<span className="agent-role-option-icon">{r.icon}</span>
|
|
||||||
<span className="agent-role-option-label">{r.label}</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className="agent-dialog-section">
|
|
||||||
<div className="agent-dialog-section-header">Configuration</div>
|
{stepZeroTab === "custom" && (
|
||||||
<div className="agent-dialog-field">
|
<div
|
||||||
<label htmlFor="agent-reports-to">Reports To <span className="agent-dialog-optional">(optional)</span></label>
|
id="agent-dialog-panel-custom"
|
||||||
<select
|
className="agent-dialog-tab-panel"
|
||||||
id="agent-reports-to"
|
role="tabpanel"
|
||||||
className="select"
|
aria-labelledby="agent-dialog-tab-custom"
|
||||||
value={reportsTo}
|
|
||||||
onChange={e => setReportsTo(e.target.value)}
|
|
||||||
disabled={managersLoading}
|
|
||||||
>
|
|
||||||
<option value="">No manager</option>
|
|
||||||
{availableManagers.map((manager) => (
|
|
||||||
<option key={manager.id} value={manager.id}>
|
|
||||||
{manager.name} ({manager.id})
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="agent-dialog-field">
|
|
||||||
<label htmlFor="agent-soul">Soul <span className="agent-dialog-optional">(optional)</span></label>
|
|
||||||
<textarea
|
|
||||||
id="agent-soul"
|
|
||||||
className="input"
|
|
||||||
rows={2}
|
|
||||||
placeholder="Describe the agent's personality and communication style..."
|
|
||||||
value={soul}
|
|
||||||
onChange={e => setSoul(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="agent-dialog-field">
|
|
||||||
<label htmlFor="agent-memory">Agent Memory <span className="agent-dialog-optional">(optional)</span></label>
|
|
||||||
<textarea
|
|
||||||
id="agent-memory"
|
|
||||||
className="input"
|
|
||||||
rows={2}
|
|
||||||
placeholder="Private to this agent — durable preferences, operating habits, and context it should carry across tasks..."
|
|
||||||
value={memory}
|
|
||||||
onChange={e => setMemory(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="agent-dialog-field">
|
|
||||||
<label htmlFor="agent-instructions-path">Instructions Path <span className="agent-dialog-optional">(optional)</span></label>
|
|
||||||
<input
|
|
||||||
id="agent-instructions-path"
|
|
||||||
type="text"
|
|
||||||
className="input"
|
|
||||||
placeholder="e.g. .fusion/agents/reviewer.md"
|
|
||||||
value={instructionsPath}
|
|
||||||
onChange={e => setInstructionsPath(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="agent-dialog-field">
|
|
||||||
<label htmlFor="agent-instructions-text">Inline Instructions <span className="agent-dialog-optional">(optional)</span></label>
|
|
||||||
<textarea
|
|
||||||
id="agent-instructions-text"
|
|
||||||
className="input"
|
|
||||||
rows={4}
|
|
||||||
placeholder="Add custom behavior instructions..."
|
|
||||||
value={instructionsText}
|
|
||||||
onChange={e => setInstructionsText(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* AI-assisted generation */}
|
|
||||||
<div className="agent-dialog-ai-generate">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn--ai-generate"
|
|
||||||
onClick={() => setIsGenerationModalOpen(true)}
|
|
||||||
>
|
>
|
||||||
<span>✨</span>
|
<div className="agent-dialog-section">
|
||||||
Generate with AI
|
<div className="agent-dialog-section-header">Identity</div>
|
||||||
</button>
|
<div className="agent-dialog-field">
|
||||||
<p className="agent-dialog-ai-hint">
|
<label htmlFor="agent-name">Name {!selectedPresetId && <span className="agent-dialog-required">*</span>}</label>
|
||||||
Describe your agent's role and let AI generate a specification
|
<input
|
||||||
</p>
|
id="agent-name"
|
||||||
</div>
|
type="text"
|
||||||
|
className="input"
|
||||||
|
placeholder="e.g. Frontend Reviewer"
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-title">Title <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<input
|
||||||
|
id="agent-title"
|
||||||
|
type="text"
|
||||||
|
className="input"
|
||||||
|
placeholder="e.g. Senior Code Reviewer"
|
||||||
|
value={title}
|
||||||
|
onChange={e => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-icon">Icon <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<input
|
||||||
|
id="agent-icon"
|
||||||
|
type="text"
|
||||||
|
className="input"
|
||||||
|
placeholder="e.g. 🤖"
|
||||||
|
value={icon}
|
||||||
|
onChange={e => setIcon(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label>Role</label>
|
||||||
|
<div className="agent-role-grid">
|
||||||
|
{AGENT_ROLES.map(r => (
|
||||||
|
<button
|
||||||
|
key={r.value}
|
||||||
|
type="button"
|
||||||
|
className={`agent-role-option${role === r.value ? " selected" : ""}`}
|
||||||
|
onClick={() => setRole(r.value)}
|
||||||
|
>
|
||||||
|
<span className="agent-role-option-icon">{r.icon}</span>
|
||||||
|
<span className="agent-role-option-label">{r.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-section">
|
||||||
|
<div className="agent-dialog-section-header">Configuration</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-reports-to">Reports To <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<select
|
||||||
|
id="agent-reports-to"
|
||||||
|
className="select"
|
||||||
|
value={reportsTo}
|
||||||
|
onChange={e => setReportsTo(e.target.value)}
|
||||||
|
disabled={managersLoading}
|
||||||
|
>
|
||||||
|
<option value="">No manager</option>
|
||||||
|
{availableManagers.map((manager) => (
|
||||||
|
<option key={manager.id} value={manager.id}>
|
||||||
|
{manager.name} ({manager.id})
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-soul">Soul <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<textarea
|
||||||
|
id="agent-soul"
|
||||||
|
className="input"
|
||||||
|
rows={2}
|
||||||
|
placeholder="Describe the agent's personality and communication style..."
|
||||||
|
value={soul}
|
||||||
|
onChange={e => setSoul(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-memory">Agent Memory <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<textarea
|
||||||
|
id="agent-memory"
|
||||||
|
className="input"
|
||||||
|
rows={2}
|
||||||
|
placeholder="Private to this agent — durable preferences, operating habits, and context it should carry across tasks..."
|
||||||
|
value={memory}
|
||||||
|
onChange={e => setMemory(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-instructions-path">Instructions Path <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<input
|
||||||
|
id="agent-instructions-path"
|
||||||
|
type="text"
|
||||||
|
className="input"
|
||||||
|
placeholder="e.g. .fusion/agents/reviewer.md"
|
||||||
|
value={instructionsPath}
|
||||||
|
onChange={e => setInstructionsPath(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="agent-dialog-field">
|
||||||
|
<label htmlFor="agent-instructions-text">Inline Instructions <span className="agent-dialog-optional">(optional)</span></label>
|
||||||
|
<textarea
|
||||||
|
id="agent-instructions-text"
|
||||||
|
className="input"
|
||||||
|
rows={4}
|
||||||
|
placeholder="Add custom behavior instructions..."
|
||||||
|
value={instructionsText}
|
||||||
|
onChange={e => setInstructionsText(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* AI-assisted generation */}
|
||||||
|
<div className="agent-dialog-ai-generate">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn--ai-generate"
|
||||||
|
onClick={() => setIsGenerationModalOpen(true)}
|
||||||
|
>
|
||||||
|
<span>✨</span>
|
||||||
|
Generate with AI
|
||||||
|
</button>
|
||||||
|
<p className="agent-dialog-ai-hint">
|
||||||
|
Describe your agent's role and let AI generate a specification
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -939,7 +939,8 @@ describe("AgentsView", () => {
|
|||||||
// Open create dialog
|
// Open create dialog
|
||||||
fireEvent.click(screen.getByText("New Agent"));
|
fireEvent.click(screen.getByText("New Agent"));
|
||||||
|
|
||||||
// Step 0: Fill in agent name
|
// Step 0: switch to Custom tab and fill in agent name
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "Custom agent" }));
|
||||||
const nameInput = screen.getByPlaceholderText("e.g. Frontend Reviewer");
|
const nameInput = screen.getByPlaceholderText("e.g. Frontend Reviewer");
|
||||||
fireEvent.change(nameInput, { target: { value: "My Agent" } });
|
fireEvent.change(nameInput, { target: { value: "My Agent" } });
|
||||||
|
|
||||||
@@ -972,10 +973,12 @@ describe("AgentsView", () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText("New Agent"));
|
fireEvent.click(screen.getByText("New Agent"));
|
||||||
|
|
||||||
// Wait for the dialog to settle after the model fetch completes
|
// Presets tab is default and custom fields appear after switching tabs
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByPlaceholderText("e.g. Frontend Reviewer")).toBeTruthy();
|
expect(screen.getByRole("tab", { name: "Preset personas", selected: true })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "Custom agent" }));
|
||||||
|
expect(screen.getByPlaceholderText("e.g. Frontend Reviewer")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not allow proceeding with empty name", async () => {
|
it("does not allow proceeding with empty name", async () => {
|
||||||
@@ -1009,6 +1012,7 @@ describe("AgentsView", () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText("New Agent"));
|
fireEvent.click(screen.getByText("New Agent"));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "Custom agent" }));
|
||||||
const nameInput = screen.getByPlaceholderText("e.g. Frontend Reviewer");
|
const nameInput = screen.getByPlaceholderText("e.g. Frontend Reviewer");
|
||||||
fireEvent.change(nameInput, { target: { value: "Fail Agent" } });
|
fireEvent.change(nameInput, { target: { value: "Fail Agent" } });
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,23 @@ function clickModelOption(portal: HTMLElement, optionText: RegExp) {
|
|||||||
fireEvent.click(option!);
|
fireEvent.click(option!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openPresetTab(user: ReturnType<typeof userEvent.setup>) {
|
||||||
|
await user.click(screen.getByRole("tab", { name: "Preset personas" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openCustomTab(user: ReturnType<typeof userEvent.setup>) {
|
||||||
|
await user.click(screen.getByRole("tab", { name: "Custom agent" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCustomTabSync() {
|
||||||
|
fireEvent.click(screen.getByRole("tab", { name: "Custom agent" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStepZeroField(label: string | RegExp) {
|
||||||
|
openCustomTabSync();
|
||||||
|
return screen.getByLabelText(label);
|
||||||
|
}
|
||||||
|
|
||||||
describe("NewAgentDialog", () => {
|
describe("NewAgentDialog", () => {
|
||||||
const mockOnClose = vi.fn();
|
const mockOnClose = vi.fn();
|
||||||
const mockOnCreated = vi.fn();
|
const mockOnCreated = vi.fn();
|
||||||
@@ -164,17 +181,50 @@ describe("NewAgentDialog", () => {
|
|||||||
expect(screen.getByRole("dialog", { name: "Create new agent" })).toBeTruthy();
|
expect(screen.getByRole("dialog", { name: "Create new agent" })).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows Identity and Configuration section headers on step 0", async () => {
|
it("renders tabbed step-0 UI with preset tab active by default", async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(screen.getByText("Identity")).toBeInTheDocument();
|
const tabList = screen.getByRole("tablist", { name: "Agent setup mode" });
|
||||||
expect(screen.getByText("Configuration")).toBeInTheDocument();
|
expect(tabList).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText(/Name/)).toBeInTheDocument();
|
|
||||||
expect(screen.getByLabelText(/Reports To/)).toBeInTheDocument();
|
const customTab = screen.getByRole("tab", { name: "Custom agent" });
|
||||||
|
const presetsTab = screen.getByRole("tab", { name: "Preset personas" });
|
||||||
|
|
||||||
|
expect(presetsTab).toHaveAttribute("aria-selected", "true");
|
||||||
|
expect(presetsTab).toHaveAttribute("aria-controls", "agent-dialog-panel-presets");
|
||||||
|
expect(customTab).toHaveAttribute("aria-selected", "false");
|
||||||
|
expect(customTab).toHaveAttribute("aria-controls", "agent-dialog-panel-custom");
|
||||||
|
|
||||||
|
expect(screen.getByRole("tabpanel", { name: "Preset personas" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("preset-ceo")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("Identity")).toBeNull();
|
||||||
|
expect(screen.queryByLabelText(/Name/)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("switches between tabs and preserves manual values", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(
|
||||||
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
|
await openCustomTab(user);
|
||||||
|
await user.type(screen.getByLabelText(/Name/), "Custom Value");
|
||||||
|
await openPresetTab(user);
|
||||||
|
|
||||||
|
expect(screen.getByRole("tabpanel", { name: "Preset personas" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("preset-ceo")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByLabelText(/Name/)).toBeNull();
|
||||||
|
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
|
const nameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
|
expect(nameInput.value).toBe("Custom Value");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,7 +248,7 @@ describe("NewAgentDialog", () => {
|
|||||||
expect(mockFetchAgents).toHaveBeenCalledOnce();
|
expect(mockFetchAgents).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
const reportsToSelect = screen.getByLabelText(/Reports To/) as HTMLSelectElement;
|
const reportsToSelect = getStepZeroField(/Reports To/) as HTMLSelectElement;
|
||||||
expect(reportsToSelect.tagName).toBe("SELECT");
|
expect(reportsToSelect.tagName).toBe("SELECT");
|
||||||
expect(within(reportsToSelect).getByRole("option", { name: "No manager" })).toBeTruthy();
|
expect(within(reportsToSelect).getByRole("option", { name: "No manager" })).toBeTruthy();
|
||||||
expect(within(reportsToSelect).getByRole("option", { name: "Manager One (agent-manager-1)" })).toBeTruthy();
|
expect(within(reportsToSelect).getByRole("option", { name: "Manager One (agent-manager-1)" })).toBeTruthy();
|
||||||
@@ -213,10 +263,10 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
await waitFor(() => expect(mockFetchAgents).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchAgents).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Agent With Manager");
|
await user.type(nameInput, "Agent With Manager");
|
||||||
|
|
||||||
const reportsToSelect = screen.getByLabelText(/Reports To/);
|
const reportsToSelect = getStepZeroField(/Reports To/);
|
||||||
await user.selectOptions(reportsToSelect, "agent-manager-1");
|
await user.selectOptions(reportsToSelect, "agent-manager-1");
|
||||||
|
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
@@ -245,7 +295,7 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
await waitFor(() => expect(mockFetchAgents).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchAgents).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
await user.type(screen.getByLabelText(/Name/), "Agent Without Manager");
|
await user.type(getStepZeroField(/Name/), "Agent Without Manager");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByText("Create"));
|
await user.click(screen.getByText("Create"));
|
||||||
@@ -269,11 +319,11 @@ describe("NewAgentDialog", () => {
|
|||||||
expect(mockFetchAgents).toHaveBeenCalledOnce();
|
expect(mockFetchAgents).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
const reportsToSelect = screen.getByLabelText(/Reports To/) as HTMLSelectElement;
|
const reportsToSelect = getStepZeroField(/Reports To/) as HTMLSelectElement;
|
||||||
expect(within(reportsToSelect).getByRole("option", { name: "No manager" })).toBeTruthy();
|
expect(within(reportsToSelect).getByRole("option", { name: "No manager" })).toBeTruthy();
|
||||||
expect(reportsToSelect.options).toHaveLength(1);
|
expect(reportsToSelect.options).toHaveLength(1);
|
||||||
|
|
||||||
await user.type(screen.getByLabelText(/Name/), "Agent Works Without Managers");
|
await user.type(getStepZeroField(/Name/), "Agent Works Without Managers");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByText("Create"));
|
await user.click(screen.getByText("Create"));
|
||||||
@@ -306,7 +356,7 @@ describe("NewAgentDialog", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Navigate to step 1 (model config step) by filling name and clicking Next
|
// Navigate to step 1 (model config step) by filling name and clicking Next
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await fireEvent.change(nameInput, { target: { value: "Test Agent" } });
|
await fireEvent.change(nameInput, { target: { value: "Test Agent" } });
|
||||||
await fireEvent.click(screen.getByText("Next"));
|
await fireEvent.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -332,7 +382,7 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Navigate to step 1
|
// Navigate to step 1
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -353,7 +403,7 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Navigate to step 1
|
// Navigate to step 1
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -372,7 +422,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1
|
// Navigate to step 1
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -398,7 +448,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 2
|
// Navigate to step 2
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
@@ -418,7 +468,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1
|
// Navigate to step 1
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -445,7 +495,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Step 0: Fill name
|
// Step 0: Fill name
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
|
|
||||||
// Step 1: Navigate and select model
|
// Step 1: Navigate and select model
|
||||||
@@ -477,7 +527,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Step 0: Fill name
|
// Step 0: Fill name
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
|
|
||||||
// Step 1: Leave model as default
|
// Step 1: Leave model as default
|
||||||
@@ -506,7 +556,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Step 0: Fill name
|
// Step 0: Fill name
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Thinking Agent");
|
await user.type(nameInput, "Thinking Agent");
|
||||||
|
|
||||||
// Step 1: Select model and thinking level
|
// Step 1: Select model and thinking level
|
||||||
@@ -545,7 +595,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1 — should still show the dropdown (empty models)
|
// Navigate to step 1 — should still show the dropdown (empty models)
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -564,7 +614,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Fill in name
|
// Fill in name
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Agent Name");
|
await user.type(nameInput, "Agent Name");
|
||||||
|
|
||||||
// Navigate to step 1 and select model
|
// Navigate to step 1 and select model
|
||||||
@@ -588,7 +638,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledTimes(2));
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledTimes(2));
|
||||||
|
|
||||||
// Name should be empty
|
// Name should be empty
|
||||||
const newNameInput = screen.getByLabelText(/Name/) as HTMLInputElement;
|
const newNameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
expect(newNameInput.value).toBe("");
|
expect(newNameInput.value).toBe("");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -600,6 +650,7 @@ describe("NewAgentDialog", () => {
|
|||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
openCustomTabSync();
|
||||||
expect(screen.getByText("Generate with AI")).toBeTruthy();
|
expect(screen.getByText("Generate with AI")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -615,6 +666,7 @@ describe("NewAgentDialog", () => {
|
|||||||
expect(screen.queryByTestId("agent-generation-modal")).toBeNull();
|
expect(screen.queryByTestId("agent-generation-modal")).toBeNull();
|
||||||
|
|
||||||
// Click the Generate with AI button
|
// Click the Generate with AI button
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
|
|
||||||
// Generation modal should now be open
|
// Generation modal should now be open
|
||||||
@@ -630,6 +682,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Open generation modal and apply spec
|
// Open generation modal and apply spec
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
await user.click(screen.getByTestId("generation-modal-apply"));
|
await user.click(screen.getByTestId("generation-modal-apply"));
|
||||||
|
|
||||||
@@ -656,6 +709,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Open generation modal and apply spec with role "reviewer"
|
// Open generation modal and apply spec with role "reviewer"
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
await user.click(screen.getByTestId("generation-modal-apply"));
|
await user.click(screen.getByTestId("generation-modal-apply"));
|
||||||
|
|
||||||
@@ -676,6 +730,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Open generation modal and apply spec with unknown role "security-auditor"
|
// Open generation modal and apply spec with unknown role "security-auditor"
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
await user.click(screen.getByTestId("generation-modal-apply-custom-role"));
|
await user.click(screen.getByTestId("generation-modal-apply-custom-role"));
|
||||||
|
|
||||||
@@ -696,6 +751,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Open generation modal and apply spec
|
// Open generation modal and apply spec
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
await user.click(screen.getByTestId("generation-modal-apply"));
|
await user.click(screen.getByTestId("generation-modal-apply"));
|
||||||
|
|
||||||
@@ -714,10 +770,11 @@ describe("NewAgentDialog", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Fill in a name first
|
// Fill in a name first
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Manual Name");
|
await user.type(nameInput, "Manual Name");
|
||||||
|
|
||||||
// Open generation modal
|
// Open generation modal
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
expect(screen.getByTestId("agent-generation-modal")).toBeTruthy();
|
expect(screen.getByTestId("agent-generation-modal")).toBeTruthy();
|
||||||
|
|
||||||
@@ -725,7 +782,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await user.click(screen.getByTestId("generation-modal-close"));
|
await user.click(screen.getByTestId("generation-modal-close"));
|
||||||
|
|
||||||
// Should still be on step 0 with original name
|
// Should still be on step 0 with original name
|
||||||
const nameAfter = screen.getByLabelText(/Name/) as HTMLInputElement;
|
const nameAfter = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
expect(nameAfter.value).toBe("Manual Name");
|
expect(nameAfter.value).toBe("Manual Name");
|
||||||
expect(screen.queryByTestId("agent-generation-modal")).toBeNull();
|
expect(screen.queryByTestId("agent-generation-modal")).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -739,6 +796,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Open generation modal and apply spec
|
// Open generation modal and apply spec
|
||||||
|
openCustomTabSync();
|
||||||
await user.click(screen.getByText("Generate with AI"));
|
await user.click(screen.getByText("Generate with AI"));
|
||||||
await user.click(screen.getByTestId("generation-modal-apply"));
|
await user.click(screen.getByTestId("generation-modal-apply"));
|
||||||
|
|
||||||
@@ -763,23 +821,31 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("preset selection", () => {
|
describe("preset selection", () => {
|
||||||
it("renders all 20 preset cards in step 0", async () => {
|
it("renders all 20 preset cards in the preset tab", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await openPresetTab(user);
|
||||||
|
|
||||||
const presetCards = screen.getAllByTestId(/^preset-/);
|
const presetCards = screen.getAllByTestId(/^preset-/);
|
||||||
expect(presetCards).toHaveLength(20);
|
expect(presetCards).toHaveLength(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the quick start header text", async () => {
|
it("shows the preset tab header text", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
expect(screen.getByText("Choose a preset or fill in details manually")).toBeTruthy();
|
|
||||||
|
await openPresetTab(user);
|
||||||
|
|
||||||
|
expect(screen.getByText("Choose a preset persona to prefill role, identity, soul, and instructions")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clicking a preset populates name, title, icon, and role", async () => {
|
it("clicking a preset populates name, title, icon, and role", async () => {
|
||||||
@@ -791,16 +857,18 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the "Engineer" preset
|
// Click the "Engineer" preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-engineer"));
|
await user.click(screen.getByTestId("preset-engineer"));
|
||||||
|
|
||||||
// Should advance to step 1 (model config), go back to verify fields
|
// Should advance to step 1 (model config), go back to verify fields
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// Verify form fields were populated
|
// Verify form fields were populated
|
||||||
const nameInput = screen.getByLabelText(/Name/) as HTMLInputElement;
|
const nameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
expect(nameInput.value).toBe("Engineer");
|
expect(nameInput.value).toBe("Engineer");
|
||||||
|
|
||||||
const titleInput = screen.getByLabelText(/Title/) as HTMLInputElement;
|
const titleInput = getStepZeroField(/Title/) as HTMLInputElement;
|
||||||
// Title should be set to the preset's description (not the professional title)
|
// Title should be set to the preset's description (not the professional title)
|
||||||
expect(titleInput.value).toBe("Implements features, fixes bugs, and writes well-tested code across the full application stack.");
|
expect(titleInput.value).toBe("Implements features, fixes bugs, and writes well-tested code across the full application stack.");
|
||||||
|
|
||||||
@@ -819,6 +887,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the "CTO" preset
|
// Click the "CTO" preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-cto"));
|
await user.click(screen.getByTestId("preset-cto"));
|
||||||
|
|
||||||
// Should be on step 1 — model dropdown visible
|
// Should be on step 1 — model dropdown visible
|
||||||
@@ -834,6 +903,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the "CEO" preset
|
// Click the "CEO" preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-ceo"));
|
await user.click(screen.getByTestId("preset-ceo"));
|
||||||
|
|
||||||
// Go back to step 0 to verify visual feedback
|
// Go back to step 0 to verify visual feedback
|
||||||
@@ -852,10 +922,12 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click CEO preset
|
// Click CEO preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-ceo"));
|
await user.click(screen.getByTestId("preset-ceo"));
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
|
||||||
// Click CTO preset
|
// Click CTO preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-cto"));
|
await user.click(screen.getByTestId("preset-cto"));
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
|
||||||
@@ -864,7 +936,8 @@ describe("NewAgentDialog", () => {
|
|||||||
expect(screen.getByTestId("preset-ceo").classList.contains("selected")).toBe(false);
|
expect(screen.getByTestId("preset-ceo").classList.contains("selected")).toBe(false);
|
||||||
|
|
||||||
// Name should be updated
|
// Name should be updated
|
||||||
const nameInput = screen.getByLabelText(/Name/) as HTMLInputElement;
|
await openCustomTab(user);
|
||||||
|
const nameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
expect(nameInput.value).toBe("CTO");
|
expect(nameInput.value).toBe("CTO");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -877,11 +950,13 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select a preset
|
// Select a preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-engineer"));
|
await user.click(screen.getByTestId("preset-engineer"));
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// Override the name manually
|
// Override the name manually
|
||||||
const nameInput = screen.getByLabelText(/Name/) as HTMLInputElement;
|
const nameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
await user.clear(nameInput);
|
await user.clear(nameInput);
|
||||||
await user.type(nameInput, "My Custom Engineer");
|
await user.type(nameInput, "My Custom Engineer");
|
||||||
|
|
||||||
@@ -897,6 +972,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select a preset
|
// Select a preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-ceo"));
|
await user.click(screen.getByTestId("preset-ceo"));
|
||||||
|
|
||||||
// Close the dialog
|
// Close the dialog
|
||||||
@@ -914,10 +990,11 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledTimes(2));
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledTimes(2));
|
||||||
|
|
||||||
// Name should be empty (no preset selected)
|
// Name should be empty (no preset selected)
|
||||||
const nameInput = screen.getByLabelText(/Name/) as HTMLInputElement;
|
const nameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
expect(nameInput.value).toBe("");
|
expect(nameInput.value).toBe("");
|
||||||
|
|
||||||
// No preset cards should be selected
|
// No preset cards should be selected
|
||||||
|
await openPresetTab(user);
|
||||||
const selectedCards = document.querySelectorAll(".agent-preset-card.selected");
|
const selectedCards = document.querySelectorAll(".agent-preset-card.selected");
|
||||||
expect(selectedCards).toHaveLength(0);
|
expect(selectedCards).toHaveLength(0);
|
||||||
});
|
});
|
||||||
@@ -931,6 +1008,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the "Reviewer" preset (advances to step 1)
|
// Click the "Reviewer" preset (advances to step 1)
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-reviewer"));
|
await user.click(screen.getByTestId("preset-reviewer"));
|
||||||
|
|
||||||
// Step 1: navigate to summary
|
// Step 1: navigate to summary
|
||||||
@@ -964,12 +1042,15 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("preset card titles show the professional title", async () => {
|
it("preset card titles show the professional title", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await openPresetTab(user);
|
||||||
|
|
||||||
const ceoCard = screen.getByTestId("preset-ceo");
|
const ceoCard = screen.getByTestId("preset-ceo");
|
||||||
expect(ceoCard.getAttribute("title")).toBe("Chief Executive Officer");
|
expect(ceoCard.getAttribute("title")).toBe("Chief Executive Officer");
|
||||||
|
|
||||||
@@ -978,12 +1059,15 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders descriptions in all 20 preset cards", async () => {
|
it("renders descriptions in all 20 preset cards", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await openPresetTab(user);
|
||||||
|
|
||||||
// Every preset should have a description element rendered
|
// Every preset should have a description element rendered
|
||||||
const descriptionElements = screen.getAllByText(/.\./, {
|
const descriptionElements = screen.getAllByText(/.\./, {
|
||||||
selector: ".agent-preset-description",
|
selector: ".agent-preset-description",
|
||||||
@@ -998,12 +1082,15 @@ describe("NewAgentDialog", () => {
|
|||||||
|
|
||||||
it("all presets have non-empty description strings", async () => {
|
it("all presets have non-empty description strings", async () => {
|
||||||
// Import the array directly by checking the rendered cards
|
// Import the array directly by checking the rendered cards
|
||||||
|
const user = userEvent.setup();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(
|
render(
|
||||||
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
<NewAgentDialog isOpen={true} onClose={mockOnClose} onCreated={mockOnCreated} />,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await openPresetTab(user);
|
||||||
|
|
||||||
const presetIds = [
|
const presetIds = [
|
||||||
"ceo", "cto", "cmo", "cfo", "engineer", "backend-engineer",
|
"ceo", "cto", "cmo", "cfo", "engineer", "backend-engineer",
|
||||||
"frontend-engineer", "fullstack-engineer", "qa-engineer",
|
"frontend-engineer", "fullstack-engineer", "qa-engineer",
|
||||||
@@ -1029,12 +1116,14 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the CEO preset
|
// Click the CEO preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-ceo"));
|
await user.click(screen.getByTestId("preset-ceo"));
|
||||||
|
|
||||||
// Go back to verify the title field
|
// Go back to verify the title field
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
const titleInput = screen.getByLabelText(/Title/) as HTMLInputElement;
|
const titleInput = getStepZeroField(/Title/) as HTMLInputElement;
|
||||||
expect(titleInput.value).toBe("Oversees project strategy, sets priorities, and coordinates between departments to ensure alignment with business goals.");
|
expect(titleInput.value).toBe("Oversees project strategy, sets priorities, and coordinates between departments to ensure alignment with business goals.");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1046,6 +1135,7 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// On initial render (step 0, no preset), the * required indicator should be visible
|
// On initial render (step 0, no preset), the * required indicator should be visible
|
||||||
|
openCustomTabSync();
|
||||||
const nameLabel = screen.getByText("Name", { selector: "label" });
|
const nameLabel = screen.getByText("Name", { selector: "label" });
|
||||||
const requiredSpan = nameLabel.querySelector(".agent-dialog-required");
|
const requiredSpan = nameLabel.querySelector(".agent-dialog-required");
|
||||||
expect(requiredSpan).toBeTruthy();
|
expect(requiredSpan).toBeTruthy();
|
||||||
@@ -1061,10 +1151,12 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select a preset
|
// Select a preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-engineer"));
|
await user.click(screen.getByTestId("preset-engineer"));
|
||||||
|
|
||||||
// Preset advances to step 1, go back to step 0
|
// Preset advances to step 1, go back to step 0
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// The * required indicator should NOT be visible when a preset is selected
|
// The * required indicator should NOT be visible when a preset is selected
|
||||||
const nameLabel = screen.getByText("Name", { selector: "label" });
|
const nameLabel = screen.getByText("Name", { selector: "label" });
|
||||||
@@ -1081,13 +1173,15 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select a preset (this fills the name and advances to step 1)
|
// Select a preset (this fills the name and advances to step 1)
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-ceo"));
|
await user.click(screen.getByTestId("preset-ceo"));
|
||||||
|
|
||||||
// Go back to step 0
|
// Go back to step 0
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// Clear the name field
|
// Clear the name field
|
||||||
const nameInput = screen.getByLabelText(/Name/) as HTMLInputElement;
|
const nameInput = getStepZeroField(/Name/) as HTMLInputElement;
|
||||||
await user.clear(nameInput);
|
await user.clear(nameInput);
|
||||||
expect(nameInput.value).toBe("");
|
expect(nameInput.value).toBe("");
|
||||||
|
|
||||||
@@ -1115,6 +1209,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the QA Engineer preset
|
// Click the QA Engineer preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-qa-engineer"));
|
await user.click(screen.getByTestId("preset-qa-engineer"));
|
||||||
|
|
||||||
// Navigate to summary and create
|
// Navigate to summary and create
|
||||||
@@ -1143,13 +1238,15 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select a preset (advances to step 1)
|
// Select a preset (advances to step 1)
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-engineer"));
|
await user.click(screen.getByTestId("preset-engineer"));
|
||||||
|
|
||||||
// Go back to step 0 to override instructions
|
// Go back to step 0 to override instructions
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// Override the instructionsText manually
|
// Override the instructionsText manually
|
||||||
const instructionsTextarea = screen.getByLabelText(/Inline Instructions/) as HTMLTextAreaElement;
|
const instructionsTextarea = getStepZeroField(/Inline Instructions/) as HTMLTextAreaElement;
|
||||||
await user.clear(instructionsTextarea);
|
await user.clear(instructionsTextarea);
|
||||||
await user.type(instructionsTextarea, "My custom instructions");
|
await user.type(instructionsTextarea, "My custom instructions");
|
||||||
|
|
||||||
@@ -1178,17 +1275,19 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Click the CTO preset
|
// Click the CTO preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-cto"));
|
await user.click(screen.getByTestId("preset-cto"));
|
||||||
|
|
||||||
// Go back to step 0 to verify fields
|
// Go back to step 0 to verify fields
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// Verify soul was populated
|
// Verify soul was populated
|
||||||
const soulTextarea = screen.getByLabelText(/Soul/) as HTMLTextAreaElement;
|
const soulTextarea = getStepZeroField(/Soul/) as HTMLTextAreaElement;
|
||||||
expect(soulTextarea.value).toContain("pragmatic technologist");
|
expect(soulTextarea.value).toContain("pragmatic technologist");
|
||||||
|
|
||||||
// Verify instructionsText was populated
|
// Verify instructionsText was populated
|
||||||
const instructionsTextarea = screen.getByLabelText(/Inline Instructions/) as HTMLTextAreaElement;
|
const instructionsTextarea = getStepZeroField(/Inline Instructions/) as HTMLTextAreaElement;
|
||||||
expect(instructionsTextarea.value).toContain("Evaluate technology choices");
|
expect(instructionsTextarea.value).toContain("Evaluate technology choices");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1201,23 +1300,27 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select CEO preset
|
// Select CEO preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-ceo"));
|
await user.click(screen.getByTestId("preset-ceo"));
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
|
||||||
// Verify CEO soul is set
|
// Verify CEO soul is set
|
||||||
let soulTextarea = screen.getByLabelText(/Soul/) as HTMLTextAreaElement;
|
await openCustomTab(user);
|
||||||
|
let soulTextarea = getStepZeroField(/Soul/) as HTMLTextAreaElement;
|
||||||
expect(soulTextarea.value).toContain("strategic leader");
|
expect(soulTextarea.value).toContain("strategic leader");
|
||||||
|
|
||||||
// Select a different preset (DevOps Engineer)
|
// Select a different preset (DevOps Engineer)
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-devops-engineer"));
|
await user.click(screen.getByTestId("preset-devops-engineer"));
|
||||||
await user.click(screen.getByText("Back"));
|
await user.click(screen.getByText("Back"));
|
||||||
|
await openCustomTab(user);
|
||||||
|
|
||||||
// Verify soul updated to DevOps
|
// Verify soul updated to DevOps
|
||||||
soulTextarea = screen.getByLabelText(/Soul/) as HTMLTextAreaElement;
|
soulTextarea = getStepZeroField(/Soul/) as HTMLTextAreaElement;
|
||||||
expect(soulTextarea.value).toContain("infrastructure-minded engineer");
|
expect(soulTextarea.value).toContain("infrastructure-minded engineer");
|
||||||
|
|
||||||
// Verify instructions updated
|
// Verify instructions updated
|
||||||
const instructionsTextarea = screen.getByLabelText(/Inline Instructions/) as HTMLTextAreaElement;
|
const instructionsTextarea = getStepZeroField(/Inline Instructions/) as HTMLTextAreaElement;
|
||||||
expect(instructionsTextarea.value).toContain("rollback plan");
|
expect(instructionsTextarea.value).toContain("rollback plan");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1230,6 +1333,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Select a preset
|
// Select a preset
|
||||||
|
await openPresetTab(user);
|
||||||
await user.click(screen.getByTestId("preset-cto"));
|
await user.click(screen.getByTestId("preset-cto"));
|
||||||
|
|
||||||
// Close the dialog — wait for the state updates to flush before unmounting
|
// Close the dialog — wait for the state updates to flush before unmounting
|
||||||
@@ -1245,10 +1349,10 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledTimes(2));
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledTimes(2));
|
||||||
|
|
||||||
// Soul and instructionsText should be empty
|
// Soul and instructionsText should be empty
|
||||||
const soulTextarea = screen.getByLabelText(/Soul/) as HTMLTextAreaElement;
|
const soulTextarea = getStepZeroField(/Soul/) as HTMLTextAreaElement;
|
||||||
expect(soulTextarea.value).toBe("");
|
expect(soulTextarea.value).toBe("");
|
||||||
|
|
||||||
const instructionsTextarea = screen.getByLabelText(/Inline Instructions/) as HTMLTextAreaElement;
|
const instructionsTextarea = getStepZeroField(/Inline Instructions/) as HTMLTextAreaElement;
|
||||||
expect(instructionsTextarea.value).toBe("");
|
expect(instructionsTextarea.value).toBe("");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1268,7 +1372,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -1300,7 +1404,7 @@ describe("NewAgentDialog", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -1327,7 +1431,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1
|
// Navigate to step 1
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -1344,7 +1448,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1 and add a skill
|
// Navigate to step 1 and add a skill
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -1367,7 +1471,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate to step 1
|
// Navigate to step 1
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|
||||||
@@ -1396,7 +1500,7 @@ describe("NewAgentDialog", () => {
|
|||||||
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
await waitFor(() => expect(mockFetchModels).toHaveBeenCalledOnce());
|
||||||
|
|
||||||
// Navigate through steps without adding skills
|
// Navigate through steps without adding skills
|
||||||
const nameInput = screen.getByLabelText(/Name/);
|
const nameInput = getStepZeroField(/Name/);
|
||||||
await user.type(nameInput, "Test Agent");
|
await user.type(nameInput, "Test Agent");
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
await user.click(screen.getByText("Next"));
|
await user.click(screen.getByText("Next"));
|
||||||
|
|||||||
@@ -29537,9 +29537,52 @@ html .column.drag-over * {
|
|||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tabs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tab {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--card);
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
font-size: calc(var(--space-sm) + var(--space-xs));
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast), box-shadow var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tab:hover {
|
||||||
|
border-color: var(--todo);
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tab:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--todo);
|
||||||
|
box-shadow: var(--focus-ring-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tab.active {
|
||||||
|
border-color: var(--todo);
|
||||||
|
color: var(--text);
|
||||||
|
background: color-mix(in srgb, var(--todo) 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tab-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-role-grid {
|
.agent-role-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(calc(var(--space-xl) * 3 + var(--space-sm)), 1fr));
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29684,10 +29727,10 @@ html .column.drag-over * {
|
|||||||
|
|
||||||
.agent-presets-grid {
|
.agent-presets-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(calc(var(--space-xl) * 5 + var(--space-lg) + var(--space-xs)), 1fr));
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
margin-bottom: var(--space-lg);
|
margin-bottom: var(--space-lg);
|
||||||
max-height: 400px;
|
max-height: calc(var(--space-xl) * 16 + var(--space-lg));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29710,6 +29753,12 @@ html .column.drag-over * {
|
|||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-preset-card:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--todo);
|
||||||
|
box-shadow: var(--focus-ring-strong);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-preset-card.selected {
|
.agent-preset-card.selected {
|
||||||
border-color: var(--todo);
|
border-color: var(--todo);
|
||||||
background: color-mix(in srgb, var(--todo) 12%, transparent);
|
background: color-mix(in srgb, var(--todo) 12%, transparent);
|
||||||
@@ -34710,6 +34759,17 @@ html .column.drag-over * {
|
|||||||
height: calc(var(--space-xs) - var(--space-xs) * 0.25);
|
height: calc(var(--space-xs) - var(--space-xs) * 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tabs {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: calc(var(--space-sm) - var(--space-xs) * 0.5);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-dialog-tab {
|
||||||
|
min-height: calc(var(--space-lg) * 2 + var(--space-xs));
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
/* Agent Import specific */
|
/* Agent Import specific */
|
||||||
.agent-import-file-upload {
|
.agent-import-file-upload {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -771,7 +771,7 @@ describe("POST /api/projects route handler", () => {
|
|||||||
expect((res.body as { error?: string }).error).toContain("Git clone failed");
|
expect((res.body as { error?: string }).error).toContain("Git clone failed");
|
||||||
expect(mockRegisterProject).not.toHaveBeenCalled();
|
expect(mockRegisterProject).not.toHaveBeenCalled();
|
||||||
expect(mockFsRm).toHaveBeenCalledWith("/tmp/broken-clone", { recursive: true, force: true });
|
expect(mockFsRm).toHaveBeenCalledWith("/tmp/broken-clone", { recursive: true, force: true });
|
||||||
});
|
}, 15_000);
|
||||||
|
|
||||||
it("rejects clone mode when destination directory is non-empty", async () => {
|
it("rejects clone mode when destination directory is non-empty", async () => {
|
||||||
const store = new MockStoreForRoutes();
|
const store = new MockStoreForRoutes();
|
||||||
|
|||||||
Reference in New Issue
Block a user