Hide advanced recipe options under expandable content (#6021)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { Recipe, generateDeepLink, Parameter } from '../../recipe';
|
||||
import { Check, ExternalLink, Play, Save, X } from 'lucide-react';
|
||||
import { Geese } from '../icons/Geese';
|
||||
import Copy from '../icons/Copy';
|
||||
import { Check, Save, X, Play } from 'lucide-react';
|
||||
import { ExtensionConfig } from '../ConfigContext';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
@@ -342,8 +342,17 @@ export default function CreateEditRecipeModal({
|
||||
</h1>
|
||||
<p className="text-textSubtle text-sm">
|
||||
{isCreateMode
|
||||
? 'Create a new recipe to define agent behavior and capabilities.'
|
||||
: "You can edit the recipe below to change the agent's behavior in a new session."}
|
||||
? 'Create a new recipe to define agent behavior and capabilities for reusable chat sessions.'
|
||||
: "You can edit the recipe below to change the agent's behavior in a new session."}{' '}
|
||||
<a
|
||||
href="https://block.github.io/goose/docs/guides/recipes/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-blue-500 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
Learn more
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -263,7 +263,7 @@ export default function CreateRecipeFromSessionModal({
|
||||
className="text-lg font-medium text-textProminent"
|
||||
data-testid="analyzing-title"
|
||||
>
|
||||
Analyzing your conversation...
|
||||
Analyzing your conversation
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-textSubtle text-center max-w-md" data-testid="analysis-stage">
|
||||
|
||||
@@ -111,7 +111,7 @@ Use {{parameter_name}} syntax for any user-provided values.`;
|
||||
className={`w-full h-full min-h-[500px] p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none font-mono text-sm ${
|
||||
error ? 'border-red-500' : 'border-border-subtle'
|
||||
}`}
|
||||
placeholder="Detailed instructions for the AI, hidden from the user..."
|
||||
placeholder="Detailed instructions for the AI, hidden from the user"
|
||||
/>
|
||||
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Parameter } from '../../../recipe';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
import ParameterInput from '../../parameter/ParameterInput';
|
||||
import RecipeActivityEditor from '../RecipeActivityEditor';
|
||||
import JsonSchemaEditor from './JsonSchemaEditor';
|
||||
import InstructionsEditor from './InstructionsEditor';
|
||||
import { Button } from '../../ui/button';
|
||||
import { RecipeFormApi } from './recipeFormSchema';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../../ui/collapsible';
|
||||
import { RecipeFormApi, RecipeFormData } from './recipeFormSchema';
|
||||
|
||||
// Type for field API to avoid linting issues - use any to bypass complex type constraints
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -143,6 +145,15 @@ export function RecipeFormFields({
|
||||
return usedInInstructions || usedInPrompt || usedInActivities;
|
||||
};
|
||||
|
||||
const checkHasAdvancedData = React.useCallback((values: RecipeFormData) => {
|
||||
const hasActivities = Boolean(values.activities && values.activities.length > 0);
|
||||
const hasParameters = Boolean(values.parameters && values.parameters.length > 0);
|
||||
const hasJsonSchema = Boolean(values.jsonSchema && values.jsonSchema.trim());
|
||||
return hasActivities || hasParameters || hasJsonSchema;
|
||||
}, []);
|
||||
|
||||
const [advancedOpen, setAdvancedOpen] = useState(() => checkHasAdvancedData(form.state.values));
|
||||
|
||||
return (
|
||||
<div className="space-y-4" data-testid="recipe-form">
|
||||
{/* Title Field */}
|
||||
@@ -244,12 +255,13 @@ export function RecipeFormFields({
|
||||
className={`w-full p-3 border rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none font-mono text-sm ${
|
||||
field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-subtle'
|
||||
}`}
|
||||
placeholder="Detailed instructions for the AI, hidden from the user..."
|
||||
placeholder="Detailed instructions for the AI, hidden from the user"
|
||||
rows={8}
|
||||
data-testid="instructions-input"
|
||||
/>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
Use {`{{parameter_name}}`} to define parameters that users can fill in
|
||||
Use {`{{parameter_name}}`} to define parameters that can be filled in when running the
|
||||
recipe.
|
||||
</p>
|
||||
{field.state.meta.errors.length > 0 && (
|
||||
<p className="text-red-500 text-sm mt-1">{field.state.meta.errors[0]}</p>
|
||||
@@ -296,7 +308,7 @@ export function RecipeFormFields({
|
||||
updateParametersFromFields();
|
||||
}}
|
||||
className="w-full p-3 border border-border-subtle rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
placeholder="Pre-filled prompt when the recipe starts..."
|
||||
placeholder="Pre-filled prompt when the recipe starts"
|
||||
rows={3}
|
||||
data-testid="prompt-input"
|
||||
/>
|
||||
@@ -304,187 +316,206 @@ export function RecipeFormFields({
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
{/* Activities Field */}
|
||||
<form.Field name="activities">
|
||||
{(field: FormFieldApi<string[]>) => (
|
||||
<div>
|
||||
<RecipeActivityEditor
|
||||
activities={field.state.value}
|
||||
setActivities={(activities) => field.handleChange(activities)}
|
||||
onBlur={updateParametersFromFields}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
{/* Advanced Section - Collapsible */}
|
||||
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen} className="mt-6">
|
||||
<CollapsibleTrigger className="flex items-baseline gap-2 w-full py-3 px-4 bg-bgSubtle hover:bg-bgSubtle/80 rounded-lg transition-colors border border-borderSubtle">
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-textSubtle transition-transform duration-200 flex-shrink-0 relative top-0.5 ${
|
||||
advancedOpen ? 'rotate-0' : '-rotate-90'
|
||||
}`}
|
||||
/>
|
||||
<span className="text-sm font-medium text-textStandard">Advanced Options</span>
|
||||
<span className="text-xs text-textSubtle">Activities, parameters, response schema</span>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
{/* Parameters Field */}
|
||||
<form.Field name="parameters">
|
||||
{(field: FormFieldApi<Parameter[]>) => {
|
||||
const handleAddParameter = () => {
|
||||
if (newParameterName.trim()) {
|
||||
const newParam: Parameter = {
|
||||
key: newParameterName.trim(),
|
||||
description: `Enter value for ${newParameterName.trim()}`,
|
||||
input_type: 'string',
|
||||
requirement: 'required',
|
||||
};
|
||||
field.handleChange([...field.state.value, newParam]);
|
||||
setNewParameterName('');
|
||||
// Expand the newly added parameter by default
|
||||
setExpandedParameters((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(newParam.key);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleAddParameter();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteParameter = (parameterKey: string) => {
|
||||
const updatedParams = field.state.value.filter(
|
||||
(param: Parameter) => param.key !== parameterKey
|
||||
);
|
||||
field.handleChange(updatedParams);
|
||||
// Remove from expanded set if it was expanded
|
||||
setExpandedParameters((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(parameterKey);
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleExpanded = (parameterKey: string) => {
|
||||
setExpandedParameters((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(parameterKey)) {
|
||||
newSet.delete(parameterKey);
|
||||
} else {
|
||||
newSet.add(parameterKey);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-md text-textProminent mb-2 font-bold">Parameters</label>
|
||||
<p className="text-textSubtle text-sm space-y-2 pb-4">
|
||||
Parameters will be automatically detected from {`{{parameter_name}}`} syntax in
|
||||
instructions/prompt/activities or you can manually add them below.
|
||||
</p>
|
||||
|
||||
{/* Add Parameter Input - Always Visible */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={newParameterName}
|
||||
onChange={(e) => setNewParameterName(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Enter parameter name..."
|
||||
className="flex-1 px-3 py-2 border border-border-subtle rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
|
||||
<CollapsibleContent className="mt-4 space-y-4 pl-6 border-l-2 border-borderSubtle ml-2">
|
||||
{/* Activities Field */}
|
||||
<form.Field name="activities">
|
||||
{(field: FormFieldApi<string[]>) => (
|
||||
<div>
|
||||
<RecipeActivityEditor
|
||||
activities={field.state.value}
|
||||
setActivities={(activities) => field.handleChange(activities)}
|
||||
onBlur={updateParametersFromFields}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddParameter}
|
||||
disabled={!newParameterName.trim()}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
Add parameter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{field.state.value.length > 0 &&
|
||||
field.state.value
|
||||
.filter((parameter: Parameter) => parameter.key && parameter.key.trim()) // Filter out empty parameters
|
||||
.map((parameter: Parameter) => {
|
||||
const currentValues = form.state.values;
|
||||
const isUnused = !isParameterUsed(
|
||||
parameter.key,
|
||||
currentValues.instructions,
|
||||
currentValues.prompt,
|
||||
currentValues.activities
|
||||
);
|
||||
|
||||
return (
|
||||
<ParameterInput
|
||||
key={parameter.key}
|
||||
parameter={parameter}
|
||||
isUnused={isUnused}
|
||||
isExpanded={expandedParameters.has(parameter.key)}
|
||||
onToggleExpanded={handleToggleExpanded}
|
||||
onDelete={handleDeleteParameter}
|
||||
onChange={(name, value) => {
|
||||
const updatedParams = field.state.value.map((param: Parameter) =>
|
||||
param.key === name ? { ...param, ...value } : param
|
||||
);
|
||||
field.handleChange(updatedParams);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</form.Field>
|
||||
|
||||
{/* JSON Schema Field */}
|
||||
<form.Field name="jsonSchema">
|
||||
{(field: FormFieldApi<string | undefined>) => (
|
||||
<div>
|
||||
<label className="block text-md text-textProminent mb-2 font-bold">
|
||||
Response JSON Schema
|
||||
</label>
|
||||
<p className="text-textSubtle text-sm space-y-2 pb-4">
|
||||
Define the expected structure of the AI's response using JSON Schema format
|
||||
</p>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setShowJsonSchemaEditor(true)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
>
|
||||
Open Editor
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{field.state.value && field.state.value.trim() && (
|
||||
<div
|
||||
className={`border rounded-lg p-3 bg-background-muted ${
|
||||
field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-subtle'
|
||||
}`}
|
||||
>
|
||||
<pre className="text-xs font-mono text-text-standard whitespace-pre-wrap break-words max-h-32 overflow-y-auto">
|
||||
{field.state.value}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
{field.state.meta.errors.length > 0 && (
|
||||
<p className="text-red-500 text-sm mt-1">{field.state.meta.errors[0]}</p>
|
||||
{/* Parameters Field */}
|
||||
<form.Field name="parameters">
|
||||
{(field: FormFieldApi<Parameter[]>) => {
|
||||
const handleAddParameter = () => {
|
||||
if (newParameterName.trim()) {
|
||||
const newParam: Parameter = {
|
||||
key: newParameterName.trim(),
|
||||
description: `Enter value for ${newParameterName.trim()}`,
|
||||
input_type: 'string',
|
||||
requirement: 'required',
|
||||
};
|
||||
field.handleChange([...field.state.value, newParam]);
|
||||
setNewParameterName('');
|
||||
// Expand the newly added parameter by default
|
||||
setExpandedParameters((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.add(newParam.key);
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleAddParameter();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteParameter = (parameterKey: string) => {
|
||||
const updatedParams = field.state.value.filter(
|
||||
(param: Parameter) => param.key !== parameterKey
|
||||
);
|
||||
field.handleChange(updatedParams);
|
||||
// Remove from expanded set if it was expanded
|
||||
setExpandedParameters((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(parameterKey);
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleExpanded = (parameterKey: string) => {
|
||||
setExpandedParameters((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(parameterKey)) {
|
||||
newSet.delete(parameterKey);
|
||||
} else {
|
||||
newSet.add(parameterKey);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="block text-md text-textProminent mb-2 font-bold">
|
||||
Parameters
|
||||
</label>
|
||||
<p className="text-textSubtle text-sm space-y-2 pb-4">
|
||||
Parameters will be automatically detected from {`{{parameter_name}}`} syntax in
|
||||
instructions/prompt/activities or you can manually add them below.
|
||||
</p>
|
||||
|
||||
{/* Add Parameter Input - Always Visible */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={newParameterName}
|
||||
onChange={(e) => setNewParameterName(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
placeholder="Enter parameter name..."
|
||||
className="flex-1 px-3 py-2 border border-border-subtle rounded-lg bg-background-default text-text-standard focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddParameter}
|
||||
disabled={!newParameterName.trim()}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||
>
|
||||
Add parameter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{field.state.value.length > 0 &&
|
||||
field.state.value
|
||||
.filter((parameter: Parameter) => parameter.key && parameter.key.trim()) // Filter out empty parameters
|
||||
.map((parameter: Parameter) => {
|
||||
const currentValues = form.state.values;
|
||||
const isUnused = !isParameterUsed(
|
||||
parameter.key,
|
||||
currentValues.instructions,
|
||||
currentValues.prompt,
|
||||
currentValues.activities
|
||||
);
|
||||
|
||||
return (
|
||||
<ParameterInput
|
||||
key={parameter.key}
|
||||
parameter={parameter}
|
||||
isUnused={isUnused}
|
||||
isExpanded={expandedParameters.has(parameter.key)}
|
||||
onToggleExpanded={handleToggleExpanded}
|
||||
onDelete={handleDeleteParameter}
|
||||
onChange={(name, value) => {
|
||||
const updatedParams = field.state.value.map((param: Parameter) =>
|
||||
param.key === name ? { ...param, ...value } : param
|
||||
);
|
||||
field.handleChange(updatedParams);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</form.Field>
|
||||
|
||||
{/* JSON Schema Field */}
|
||||
<form.Field name="jsonSchema">
|
||||
{(field: FormFieldApi<string | undefined>) => (
|
||||
<div>
|
||||
<label className="block text-md text-textProminent mb-2 font-bold">
|
||||
Response JSON Schema
|
||||
</label>
|
||||
<p className="text-textSubtle text-sm space-y-2 pb-4">
|
||||
Define the expected structure of the AI's response using JSON Schema format
|
||||
</p>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setShowJsonSchemaEditor(true)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs"
|
||||
>
|
||||
Open Editor
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{field.state.value && field.state.value.trim() && (
|
||||
<div
|
||||
className={`border rounded-lg p-3 bg-background-muted ${
|
||||
field.state.meta.errors.length > 0 ? 'border-red-500' : 'border-border-subtle'
|
||||
}`}
|
||||
>
|
||||
<pre className="text-xs font-mono text-text-standard whitespace-pre-wrap break-words max-h-32 overflow-y-auto">
|
||||
{field.state.value}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{field.state.meta.errors.length > 0 && (
|
||||
<p className="text-red-500 text-sm mt-1">{field.state.meta.errors[0]}</p>
|
||||
)}
|
||||
|
||||
{/* JSON Schema Editor Modal */}
|
||||
<JsonSchemaEditor
|
||||
isOpen={showJsonSchemaEditor}
|
||||
onClose={() => setShowJsonSchemaEditor(false)}
|
||||
value={field.state.value || ''}
|
||||
onChange={(value) => {
|
||||
field.handleChange(value);
|
||||
onJsonSchemaChange?.(value);
|
||||
}}
|
||||
error={
|
||||
field.state.meta.errors.length > 0 ? field.state.meta.errors[0] : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* JSON Schema Editor Modal */}
|
||||
<JsonSchemaEditor
|
||||
isOpen={showJsonSchemaEditor}
|
||||
onClose={() => setShowJsonSchemaEditor(false)}
|
||||
value={field.state.value || ''}
|
||||
onChange={(value) => {
|
||||
field.handleChange(value);
|
||||
onJsonSchemaChange?.(value);
|
||||
}}
|
||||
error={field.state.meta.errors.length > 0 ? field.state.meta.errors[0] : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</form.Field>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,14 @@ import { useForm } from '@tanstack/react-form';
|
||||
import { RecipeFormFields, extractTemplateVariables } from '../RecipeFormFields';
|
||||
import { type RecipeFormData } from '../recipeFormSchema';
|
||||
|
||||
const expandAdvancedSection = async (user: ReturnType<typeof userEvent.setup>) => {
|
||||
const advancedTrigger = screen.getByRole('button', { name: /advanced options/i });
|
||||
const activitiesField = screen.queryByText('Activities');
|
||||
if (!activitiesField) {
|
||||
await user.click(advancedTrigger);
|
||||
}
|
||||
};
|
||||
|
||||
describe('RecipeFormFields', () => {
|
||||
const useTestForm = (initialValues?: Partial<RecipeFormData>) => {
|
||||
const defaultValues: RecipeFormData = {
|
||||
@@ -48,13 +56,19 @@ describe('RecipeFormFields', () => {
|
||||
expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders required form fields', () => {
|
||||
it('renders required form fields', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/description/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/instructions/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/prompt/i)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByRole('button', { name: /advanced options/i })).toBeInTheDocument();
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
expect(screen.getAllByText(/activities/i)[0]).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/parameters/i)[0]).toBeInTheDocument();
|
||||
expect(screen.getByText(/response json schema/i)).toBeInTheDocument();
|
||||
@@ -99,9 +113,12 @@ describe('RecipeFormFields', () => {
|
||||
});
|
||||
|
||||
describe('Parameter Management', () => {
|
||||
it('shows parameter input section', () => {
|
||||
it('shows parameter input section', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
expect(screen.getByPlaceholderText('Enter parameter name...')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /add parameter/i })).toBeInTheDocument();
|
||||
});
|
||||
@@ -110,6 +127,8 @@ describe('RecipeFormFields', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
const parameterInput = screen.getByPlaceholderText('Enter parameter name...');
|
||||
const addButton = screen.getByRole('button', { name: /add parameter/i });
|
||||
|
||||
@@ -160,7 +179,7 @@ describe('RecipeFormFields', () => {
|
||||
render(<TestWrapper />);
|
||||
|
||||
const instructionsInput = screen.getByPlaceholderText(
|
||||
'Detailed instructions for the AI, hidden from the user...'
|
||||
'Detailed instructions for the AI, hidden from the user'
|
||||
);
|
||||
|
||||
// Type instructions with template variables - use paste to avoid curly brace issues
|
||||
@@ -173,6 +192,8 @@ describe('RecipeFormFields', () => {
|
||||
// Just verify the component doesn't crash and the text is there
|
||||
expect(instructionsInput).toHaveValue('Hello {{name}}, please {{action}} the {{item}}');
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Check that the parameter section exists
|
||||
expect(screen.getByText('Parameters')).toBeInTheDocument();
|
||||
expect(
|
||||
@@ -186,6 +207,8 @@ describe('RecipeFormFields', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Add a manual parameter
|
||||
const parameterInput = screen.getByPlaceholderText('Enter parameter name...');
|
||||
const addButton = screen.getByText('Add parameter');
|
||||
@@ -200,9 +223,12 @@ describe('RecipeFormFields', () => {
|
||||
expect(parameterInput).toHaveValue('');
|
||||
});
|
||||
|
||||
it('shows parameter management UI', () => {
|
||||
it('shows parameter management UI', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Check parameter section exists
|
||||
expect(screen.getByText('Parameters')).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText('Enter parameter name...')).toBeInTheDocument();
|
||||
@@ -216,6 +242,8 @@ describe('RecipeFormFields', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Check that activities section exists
|
||||
expect(screen.getByText('Activities')).toBeInTheDocument();
|
||||
expect(screen.getByText('Message')).toBeInTheDocument();
|
||||
@@ -259,7 +287,7 @@ describe('RecipeFormFields', () => {
|
||||
render(<TestComponent />);
|
||||
|
||||
const instructionsInput = screen.getByPlaceholderText(
|
||||
'Detailed instructions for the AI, hidden from the user...'
|
||||
'Detailed instructions for the AI, hidden from the user'
|
||||
);
|
||||
|
||||
// Add instructions with template variables
|
||||
@@ -272,6 +300,8 @@ describe('RecipeFormFields', () => {
|
||||
// Wait a moment for the parameter detection to process
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Check if parameters were detected and added
|
||||
// The parameters should appear as text in the parameter section
|
||||
const parameterSection = screen.getByText('Parameters').closest('div');
|
||||
@@ -408,6 +438,8 @@ describe('RecipeFormFields', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Add a manual parameter
|
||||
const parameterInput = screen.getByPlaceholderText('Enter parameter name...');
|
||||
const addButton = screen.getByText('Add parameter');
|
||||
@@ -617,6 +649,8 @@ describe('RecipeFormFields', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Add a manual parameter
|
||||
const parameterInput = screen.getByPlaceholderText('Enter parameter name...');
|
||||
const addButton = screen.getByText('Add parameter');
|
||||
@@ -648,6 +682,8 @@ describe('RecipeFormFields', () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestWrapper />);
|
||||
|
||||
await expandAdvancedSection(user);
|
||||
|
||||
// Add a parameter and test changing its type
|
||||
const parameterInput = screen.getByPlaceholderText('Enter parameter name...');
|
||||
const addButton = screen.getByText('Add parameter');
|
||||
|
||||
Reference in New Issue
Block a user