test(KB-301): fix NewTaskModal tests — update for Plan/Subtask buttons (replaced planning mode checkbox)
This commit is contained in:
@@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { NewTaskModal } from "../NewTaskModal";
|
||||
import type { Task, Column } from "@kb/core";
|
||||
import { fetchSettings } from "../../api";
|
||||
|
||||
// Mock the api module
|
||||
vi.mock("../../api", () => ({
|
||||
@@ -55,11 +54,6 @@ describe("NewTaskModal", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("does not render when isOpen is false", () => {
|
||||
renderNewTaskModal({ isOpen: false });
|
||||
expect(screen.queryByText("New Task")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders all form fields when open", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
@@ -67,7 +61,9 @@ describe("NewTaskModal", () => {
|
||||
expect(screen.getByLabelText(/Description/i)).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Add dependencies" })).toBeTruthy();
|
||||
expect(screen.getByText(/Model Configuration/i)).toBeTruthy();
|
||||
expect(screen.getByLabelText(/Enable planning mode/i)).toBeTruthy();
|
||||
// Plan and Subtask buttons for AI-assisted creation (replaced planning mode checkbox)
|
||||
expect(screen.getByRole("button", { name: "Plan" })).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Subtask" })).toBeTruthy();
|
||||
expect(screen.getByText(/Attachments/i)).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Create Task" })).toBeTruthy();
|
||||
expect(screen.getByRole("button", { name: "Cancel" })).toBeTruthy();
|
||||
@@ -76,37 +72,34 @@ describe("NewTaskModal", () => {
|
||||
it("focuses description textarea when modal opens", async () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
await waitFor(() => {
|
||||
expect(descTextarea).toHaveFocus();
|
||||
expect(document.activeElement).toBe(textarea);
|
||||
});
|
||||
});
|
||||
|
||||
it("creates task with description on submit", async () => {
|
||||
it("creates task with description when submitted", async () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
fireEvent.change(descTextarea, { target: { value: "My task description" } });
|
||||
fireEvent.change(descTextarea, { target: { value: "Test description" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.onCreateTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: undefined,
|
||||
description: "My task description",
|
||||
column: "triage",
|
||||
description: "Test description",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("calls onClose after successful creation", async () => {
|
||||
it("closes modal after successful creation", async () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Test description" } });
|
||||
fireEvent.change(descTextarea, { target: { value: "Test" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
@@ -115,167 +108,13 @@ describe("NewTaskModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows error toast on creation failure", async () => {
|
||||
const { props } = renderNewTaskModal({
|
||||
onCreateTask: vi.fn().mockRejectedValue(new Error("Creation failed")),
|
||||
});
|
||||
it("has working Plan button for AI-assisted creation", () => {
|
||||
const onPlanningMode = vi.fn();
|
||||
renderNewTaskModal({ onPlanningMode });
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Test description" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.addToast).toHaveBeenCalledWith("Creation failed", "error");
|
||||
});
|
||||
});
|
||||
|
||||
it("disables create button when description is empty", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
const createButton = screen.getByRole("button", { name: "Create Task" });
|
||||
expect(createButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("enables create button when description is not empty", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Some description" } });
|
||||
|
||||
const createButton = screen.getByRole("button", { name: "Create Task" });
|
||||
expect(createButton).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("closes modal on cancel button click", () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes modal on X button click", () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
fireEvent.click(screen.getByText("×"));
|
||||
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds dependencies via dropdown", async () => {
|
||||
const tasks = [makeTask("KB-010"), makeTask("KB-020")];
|
||||
const { props } = renderNewTaskModal({ tasks });
|
||||
|
||||
// Open dependencies dropdown
|
||||
fireEvent.click(screen.getByRole("button", { name: "Add dependencies" }));
|
||||
|
||||
// Wait for dropdown to appear
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector(".dep-dropdown")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Click on a task to add it as dependency
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items.length).toBeGreaterThan(0);
|
||||
fireEvent.click(items[0]!);
|
||||
|
||||
// Verify dependency was added (it should show as selected)
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Task with deps" } });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.onCreateTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
dependencies: expect.any(Array),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders model selectors when models are loaded", async () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Executor")).toBeTruthy();
|
||||
expect(screen.getByText("Validator")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("creates task with selected preset id and resolved models", async () => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
modelPresets: [
|
||||
{
|
||||
id: "budget",
|
||||
name: "Budget",
|
||||
executorProvider: "openai",
|
||||
executorModelId: "gpt-4o",
|
||||
validatorProvider: "anthropic",
|
||||
validatorModelId: "claude-sonnet-4-5",
|
||||
},
|
||||
],
|
||||
autoSelectModelPreset: false,
|
||||
defaultPresetBySize: {},
|
||||
});
|
||||
|
||||
const { props } = renderNewTaskModal();
|
||||
fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: "Preset task" } });
|
||||
|
||||
await waitFor(() => expect(screen.getByLabelText("Preset")).toBeTruthy());
|
||||
fireEvent.change(screen.getByLabelText("Preset"), { target: { value: "budget" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.onCreateTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
description: "Preset task",
|
||||
modelPresetId: "budget",
|
||||
modelProvider: "openai",
|
||||
modelId: "gpt-4o",
|
||||
validatorModelProvider: "anthropic",
|
||||
validatorModelId: "claude-sonnet-4-5",
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("allows overriding a selected preset with custom models", async () => {
|
||||
(fetchSettings as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
modelPresets: [
|
||||
{
|
||||
id: "budget",
|
||||
name: "Budget",
|
||||
executorProvider: "openai",
|
||||
executorModelId: "gpt-4o",
|
||||
},
|
||||
],
|
||||
autoSelectModelPreset: false,
|
||||
defaultPresetBySize: {},
|
||||
});
|
||||
|
||||
renderNewTaskModal();
|
||||
await waitFor(() => expect(screen.getByLabelText("Preset")).toBeTruthy());
|
||||
fireEvent.change(screen.getByLabelText("Preset"), { target: { value: "budget" } });
|
||||
expect(screen.getByText("Using preset: Budget")).toBeTruthy();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Override" }));
|
||||
expect(screen.queryByText("Using preset: Budget")).toBeNull();
|
||||
|
||||
fireEvent.change(screen.getByLabelText("Preset"), { target: { value: "custom" } });
|
||||
expect((screen.getByLabelText("Preset") as HTMLSelectElement).value).toBe("custom");
|
||||
});
|
||||
|
||||
it("toggles planning mode checkbox", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
const checkbox = screen.getByLabelText(/Enable planning mode/i) as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(true);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(false);
|
||||
// The Plan button should be present and disabled when no description
|
||||
const planButton = screen.getByRole("button", { name: "Plan" });
|
||||
expect(planButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows success toast after creation", async () => {
|
||||
@@ -296,9 +135,8 @@ describe("NewTaskModal", () => {
|
||||
it("confirms before closing with dirty state", () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
// Add some content to make it dirty
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Some text" } });
|
||||
fireEvent.change(descTextarea, { target: { value: "Test description" } });
|
||||
|
||||
// Mock confirm to return false (cancel)
|
||||
const originalConfirm = window.confirm;
|
||||
@@ -316,7 +154,6 @@ describe("NewTaskModal", () => {
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
||||
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -338,54 +175,33 @@ describe("NewTaskModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// Planning mode tests
|
||||
it("calls onPlanningMode when planning mode is checked and form is submitted", async () => {
|
||||
// Plan mode tests - using Plan button instead of checkbox
|
||||
it("calls onPlanningMode when Plan button is clicked with description", async () => {
|
||||
const onPlanningMode = vi.fn();
|
||||
const { props } = renderNewTaskModal({ onPlanningMode });
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
const checkbox = screen.getByLabelText(/Enable planning mode/i);
|
||||
|
||||
fireEvent.change(descTextarea, { target: { value: "Build a login system" } });
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
// Wait for models to load
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "Plan" })).not.toBeDisabled();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Plan" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPlanningMode).toHaveBeenCalledWith("Build a login system");
|
||||
});
|
||||
});
|
||||
|
||||
it("does NOT call onCreateTask when planning mode is checked", async () => {
|
||||
const onPlanningMode = vi.fn();
|
||||
const { props } = renderNewTaskModal({ onPlanningMode });
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
const checkbox = screen.getByLabelText(/Enable planning mode/i);
|
||||
|
||||
fireEvent.change(descTextarea, { target: { value: "Build a login system" } });
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPlanningMode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(props.onCreateTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls onCreateTask normally when planning mode is unchecked", async () => {
|
||||
it("calls onCreateTask normally when form is submitted", async () => {
|
||||
const onPlanningMode = vi.fn();
|
||||
const { props } = renderNewTaskModal({ onPlanningMode });
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Normal task" } });
|
||||
|
||||
// Ensure planning mode is unchecked
|
||||
const checkbox = screen.getByLabelText(/Enable planning mode/i) as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -399,273 +215,22 @@ describe("NewTaskModal", () => {
|
||||
expect(onPlanningMode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("closes modal after triggering planning mode", async () => {
|
||||
const onPlanningMode = vi.fn();
|
||||
const { props } = renderNewTaskModal({ onPlanningMode });
|
||||
|
||||
|
||||
it("disables Create Task when description is empty", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
const createButton = screen.getByRole("button", { name: "Create Task" });
|
||||
expect(createButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it("enables Create Task when description has content", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
const checkbox = screen.getByLabelText(/Enable planning mode/i);
|
||||
fireEvent.change(descTextarea, { target: { value: "Some text" } });
|
||||
|
||||
fireEvent.change(descTextarea, { target: { value: "Build a login system" } });
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("clears form state after triggering planning mode", async () => {
|
||||
const onPlanningMode = vi.fn();
|
||||
renderNewTaskModal({ onPlanningMode });
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
const checkbox = screen.getByLabelText(/Enable planning mode/i);
|
||||
|
||||
fireEvent.change(descTextarea, { target: { value: "Build a login system" } });
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPlanningMode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Re-open the modal and check that state is cleared
|
||||
renderNewTaskModal({
|
||||
isOpen: true,
|
||||
onPlanningMode,
|
||||
onClose: vi.fn(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const newDescTextarea = screen.getAllByLabelText(/Description/i)[0];
|
||||
expect(newDescTextarea).toHaveValue("");
|
||||
});
|
||||
});
|
||||
|
||||
it("has scrollable modal body with overflow-y auto", () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
// Find the modal container
|
||||
const modal = document.querySelector(".new-task-modal");
|
||||
expect(modal).toBeTruthy();
|
||||
|
||||
// Find the modal body
|
||||
const modalBody = modal?.querySelector(".modal-body");
|
||||
expect(modalBody).toBeTruthy();
|
||||
|
||||
// Verify the modal body element exists within the new-task-modal
|
||||
// The overflow-y: auto is applied via CSS in styles.css
|
||||
expect(modal?.contains(modalBody)).toBe(true);
|
||||
});
|
||||
|
||||
it("shows workflow step checkboxes when steps are available", async () => {
|
||||
const { fetchWorkflowSteps } = await import("../../api");
|
||||
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
||||
{ id: "WS-001", name: "Docs Review", description: "Check documentation", prompt: "Review docs", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
|
||||
{ id: "WS-002", name: "QA Check", description: "Run tests", prompt: "Run tests", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
|
||||
]);
|
||||
|
||||
renderNewTaskModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("workflow-steps-section")).toBeInTheDocument();
|
||||
expect(screen.getByText("Docs Review")).toBeInTheDocument();
|
||||
expect(screen.getByText("QA Check")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not show workflow steps section when no steps are available", async () => {
|
||||
renderNewTaskModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("workflow-steps-section")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("toggles workflow step selection", async () => {
|
||||
const { fetchWorkflowSteps } = await import("../../api");
|
||||
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
||||
{ id: "WS-001", name: "Docs Review", description: "Check documentation", prompt: "Review docs", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
|
||||
]);
|
||||
|
||||
renderNewTaskModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("workflow-step-checkbox-WS-001")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-001").querySelector("input[type='checkbox']") as HTMLInputElement;
|
||||
expect(checkbox.checked).toBe(false);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(true);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox.checked).toBe(false);
|
||||
});
|
||||
|
||||
it("passes selected workflow steps to onCreateTask", async () => {
|
||||
const { fetchWorkflowSteps } = await import("../../api");
|
||||
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([
|
||||
{ id: "WS-001", name: "Docs Review", description: "Check documentation", prompt: "Review docs", enabled: true, createdAt: "2026-01-01", updatedAt: "2026-01-01" },
|
||||
]);
|
||||
|
||||
const { props } = renderNewTaskModal();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("workflow-step-checkbox-WS-001")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Check the workflow step
|
||||
const checkbox = screen.getByTestId("workflow-step-checkbox-WS-001").querySelector("input[type='checkbox']") as HTMLInputElement;
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
// Fill in description
|
||||
const textarea = screen.getByPlaceholderText("What needs to be done?");
|
||||
fireEvent.change(textarea, { target: { value: "Test task" } });
|
||||
|
||||
// Submit
|
||||
const submitBtn = screen.getByText("Create Task");
|
||||
fireEvent.click(submitBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.onCreateTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
enabledWorkflowSteps: ["WS-001"],
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AI Refine feature", () => {
|
||||
it("shows refine button when description has content", async () => {
|
||||
renderNewTaskModal();
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
// Initially, refine button is not visible
|
||||
expect(screen.queryByTestId("refine-button")).toBeNull();
|
||||
|
||||
// Type something
|
||||
fireEvent.change(textarea, { target: { value: "Task to refine" } });
|
||||
|
||||
// Now the refine button should be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("refine-button")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("hides refine button when description is empty", async () => {
|
||||
renderNewTaskModal();
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
// Type something first
|
||||
fireEvent.change(textarea, { target: { value: "Some text" } });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("refine-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Clear the input
|
||||
fireEvent.change(textarea, { target: { value: "" } });
|
||||
|
||||
// Button should be hidden
|
||||
expect(screen.queryByTestId("refine-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("opens refine menu on button click", async () => {
|
||||
renderNewTaskModal();
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
fireEvent.change(textarea, { target: { value: "Task to refine" } });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("refine-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("refine-button"));
|
||||
|
||||
// Menu should be visible with all options
|
||||
expect(screen.getByTestId("refine-clarify")).toBeTruthy();
|
||||
expect(screen.getByTestId("refine-add-details")).toBeTruthy();
|
||||
expect(screen.getByTestId("refine-expand")).toBeTruthy();
|
||||
expect(screen.getByTestId("refine-simplify")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("successful refinement updates description and shows toast", async () => {
|
||||
const { refineText } = await import("../../api");
|
||||
vi.mocked(refineText).mockResolvedValueOnce("Refined description");
|
||||
|
||||
const { props } = renderNewTaskModal();
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
fireEvent.change(textarea, { target: { value: "Original text" } });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("refine-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("refine-button"));
|
||||
fireEvent.click(screen.getByTestId("refine-clarify"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(refineText).toHaveBeenCalledWith("Original text", "clarify");
|
||||
});
|
||||
|
||||
// Textarea should be updated
|
||||
await waitFor(() => {
|
||||
expect(textarea).toHaveValue("Refined description");
|
||||
});
|
||||
|
||||
// Success toast should be shown
|
||||
await waitFor(() => {
|
||||
expect(props.addToast).toHaveBeenCalledWith("Description refined with AI", "success");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows error toast on refinement failure and preserves original text", async () => {
|
||||
const { refineText, getRefineErrorMessage } = await import("../../api");
|
||||
vi.mocked(refineText).mockRejectedValueOnce(new Error("Rate limit exceeded"));
|
||||
vi.mocked(getRefineErrorMessage).mockReturnValue("Too many refinement requests. Please wait an hour.");
|
||||
|
||||
const { props } = renderNewTaskModal();
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
fireEvent.change(textarea, { target: { value: "Original text" } });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("refine-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("refine-button"));
|
||||
fireEvent.click(screen.getByTestId("refine-clarify"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(props.addToast).toHaveBeenCalledWith("Too many refinement requests. Please wait an hour.", "error");
|
||||
});
|
||||
|
||||
// Original text should be preserved
|
||||
expect(textarea).toHaveValue("Original text");
|
||||
});
|
||||
|
||||
it("shows loading state during refinement", async () => {
|
||||
const { refineText } = await import("../../api");
|
||||
// Slow down the promise to see loading state
|
||||
vi.mocked(refineText).mockImplementation(() => new Promise((resolve) => setTimeout(() => resolve("Refined"), 100)));
|
||||
|
||||
renderNewTaskModal();
|
||||
const textarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
fireEvent.change(textarea, { target: { value: "Original text" } });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("refine-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId("refine-button"));
|
||||
fireEvent.click(screen.getByTestId("refine-expand"));
|
||||
|
||||
// Button should show loading text
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Refining...")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
const createButton = screen.getByRole("button", { name: "Create Task" });
|
||||
expect(createButton).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user