feat(FN-1438): enable description expansion in task creation forms

- Fix textbox role selector to avoid matching the expand button in TaskForm
- Enable description expansion in create mode for better task input experience
- Update tests in NewTaskModal and TaskForm to cover the new behavior
This commit is contained in:
gsxdsm
2026-04-10 07:08:13 -07:00
parent ccdc836177
commit f7fde6a2d3
3 changed files with 50 additions and 27 deletions

View File

@@ -11,6 +11,8 @@ vi.mock("lucide-react", () => ({
ChevronDown: () => null,
X: () => null,
Bot: () => null,
Maximize2: () => null,
Minimize2: () => null,
}));
// Mock the api module
@@ -72,7 +74,7 @@ describe("NewTaskModal", () => {
renderNewTaskModal();
expect(screen.getByText("New Task")).toBeTruthy();
expect(screen.getByLabelText(/Description/i)).toBeTruthy();
expect(screen.getByRole('textbox')).toBeTruthy();
expect(screen.getByRole("button", { name: "Plan" })).toBeTruthy();
expect(screen.getByRole("button", { name: "Subtask" })).toBeTruthy();
@@ -114,7 +116,7 @@ describe("NewTaskModal", () => {
it("focuses description textarea when modal opens", async () => {
renderNewTaskModal();
const textarea = screen.getByLabelText(/Description/i);
const textarea = screen.getByRole('textbox');
await waitFor(() => {
expect(document.activeElement).toBe(textarea);
});
@@ -123,7 +125,7 @@ describe("NewTaskModal", () => {
it("creates task with description when submitted", async () => {
const { props } = renderNewTaskModal();
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Test description" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -140,7 +142,7 @@ describe("NewTaskModal", () => {
it("closes modal after successful creation", async () => {
const { props } = renderNewTaskModal();
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Test" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -164,7 +166,7 @@ describe("NewTaskModal", () => {
onCreateTask: vi.fn().mockResolvedValue({ id: "FN-042" }),
});
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Test description" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -177,7 +179,7 @@ describe("NewTaskModal", () => {
it("confirms before closing with dirty state", () => {
const { props } = renderNewTaskModal();
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Test description" } });
// Mock confirm to return false (cancel)
@@ -202,7 +204,7 @@ describe("NewTaskModal", () => {
it("creates task with title undefined by default", async () => {
const { props } = renderNewTaskModal();
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Only description" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -222,7 +224,7 @@ describe("NewTaskModal", () => {
const onPlanningMode = vi.fn();
const { props } = renderNewTaskModal({ onPlanningMode });
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Build a login system" } });
// Wait for models to load
@@ -241,7 +243,7 @@ describe("NewTaskModal", () => {
const onPlanningMode = vi.fn();
const { props } = renderNewTaskModal({ onPlanningMode });
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Normal task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -269,7 +271,7 @@ describe("NewTaskModal", () => {
it("enables Create Task when description has content", () => {
renderNewTaskModal();
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Some text" } });
const createButton = screen.getByRole("button", { name: "Create Task" });
@@ -281,7 +283,7 @@ describe("NewTaskModal", () => {
it("omits modelPresetId from payload when in default mode", async () => {
const { props } = renderNewTaskModal();
const descTextarea = screen.getByLabelText(/Description/i);
const descTextarea = screen.getByRole('textbox');
fireEvent.change(descTextarea, { target: { value: "Default mode task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -315,7 +317,7 @@ describe("NewTaskModal", () => {
});
// Type a description
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Preset task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Preset task" } });
// Select the preset
const select = document.getElementById("model-preset") as HTMLSelectElement;
@@ -356,7 +358,7 @@ describe("NewTaskModal", () => {
});
// Type a description
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Custom task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Custom task" } });
// Select a preset first
const select = document.getElementById("model-preset") as HTMLSelectElement;
@@ -395,7 +397,7 @@ describe("NewTaskModal", () => {
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-001").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(checkbox);
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Task with workflow step" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Task with workflow step" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
@@ -428,7 +430,7 @@ describe("NewTaskModal", () => {
fireEvent.click(checkbox2);
// Type description and submit
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Ordered task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Ordered task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
@@ -467,7 +469,7 @@ describe("NewTaskModal", () => {
fireEvent.click(screen.getByTestId("workflow-step-move-up-WS-002"));
// Type description and submit
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Reordered task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Reordered task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
@@ -496,7 +498,7 @@ describe("NewTaskModal", () => {
});
// Don't interact with workflow steps at all
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "No interaction task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "No interaction task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
@@ -530,7 +532,7 @@ describe("NewTaskModal", () => {
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-001").querySelector('input[type="checkbox"]') as HTMLInputElement;
fireEvent.click(checkbox);
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Deselected task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Deselected task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
@@ -564,7 +566,7 @@ describe("NewTaskModal", () => {
// Don't modify the selection — just submit.
// Since user hasn't explicitly changed steps, the explicitlySet flag is false,
// so the modal sends undefined (backend applies its own defaults)
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Auto-selected task" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Auto-selected task" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
await waitFor(() => {
@@ -648,7 +650,7 @@ describe("NewTaskModal", () => {
const { props } = renderNewTaskModal();
// Type description
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Task with agent" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Task with agent" } });
// Open agent picker and select agent
fireEvent.click(screen.getByTestId("new-task-agent-button"));
@@ -674,7 +676,7 @@ describe("NewTaskModal", () => {
it("omits assignedAgentId from payload when no agent is selected", async () => {
const { props } = renderNewTaskModal();
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Task without agent" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Task without agent" } });
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
@@ -696,7 +698,7 @@ describe("NewTaskModal", () => {
const { props } = renderNewTaskModal();
// Type description
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Task with agent" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Task with agent" } });
// Open agent picker and select agent
fireEvent.click(screen.getByTestId("new-task-agent-button"));
@@ -766,7 +768,7 @@ describe("NewTaskModal", () => {
renderNewTaskModal();
// Type description
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Task with agent" } });
fireEvent.change(screen.getByRole('textbox'), { target: { value: "Task with agent" } });
// Open agent picker and select agent
fireEvent.click(screen.getByTestId("new-task-agent-button"));