import type { ComponentProps } from "react"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { InlineCreateCard } from "../InlineCreateCard"; import type { Task, Column } from "@fusion/core"; import { fetchModels, fetchSettings } from "../../api"; import type { ModelInfo } from "../../api"; // Mock lucide-react vi.mock("lucide-react", () => ({ Brain: () => null, Cpu: () => null, Link: () => null, Search: () => null, Sparkles: () => null, Terminal: () => null, Lightbulb: () => null, ListTree: () => null, Zap: () => null, ChevronDown: () => null, ChevronUp: () => null, })); // Mock the api module vi.mock("../../api", () => ({ fetchModels: vi.fn().mockResolvedValue([]), fetchSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, }), uploadAttachment: vi.fn(), })); const MOCK_MODELS: ModelInfo[] = [ { provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", reasoning: true, contextWindow: 200_000, }, { provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: true, contextWindow: 128_000, }, ]; function createMockTask(overrides: Partial = {}): Task { return { id: "FN-001", title: "Test task", description: "Task description", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", ...overrides, }; } function renderCard( tasks: Task[] = [], overrides: Partial> = {}, ) { const props: ComponentProps = { tasks, onSubmit: vi.fn().mockResolvedValue({ id: "FN-001" } as Task), onCancel: vi.fn(), addToast: vi.fn(), availableModels: MOCK_MODELS, ...overrides, }; const result = render(); return { ...result, props }; } function openModelPanel() { fireEvent.click(screen.getByRole("button", { name: /Models/i })); } function chooseModel(label: "Executor Model" | "Validator Model", optionText: string) { fireEvent.click(screen.getByRole("button", { name: label })); fireEvent.click(screen.getByText(optionText)); } // Helper to expand the InlineCreateCard by clicking the toggle button function expandInlineCreate() { const toggleButton = screen.getByTestId("inline-create-toggle"); fireEvent.click(toggleButton); } beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); vi.mocked(fetchModels).mockResolvedValue(MOCK_MODELS); vi.mocked(fetchSettings).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, }); }); describe("InlineCreateCard toggle button", () => { it("toggle button expands the view", () => { renderCard(); const toggleButton = screen.getByTestId("inline-create-toggle"); const textarea = screen.getByPlaceholderText("What needs to be done?"); // Initially, footer controls are not visible expect(document.querySelector(".inline-create-footer")).toBeNull(); expect(document.querySelector(".inline-create-card")?.classList.contains("inline-create-card--collapsed")).toBe(true); expect(document.querySelector(".inline-create-card")?.className).toContain("inline-create-card"); expect(toggleButton.getAttribute("aria-expanded")).toBe("false"); expect(toggleButton.getAttribute("aria-controls")).toBeNull(); expect(textarea.getAttribute("aria-controls")).toBeNull(); // Click toggle to expand expandInlineCreate(); // Now footer controls should be visible expect(document.querySelector(".inline-create-footer")).toBeTruthy(); expect(document.querySelector(".inline-create-card")?.classList.contains("inline-create-card--expanded")).toBe(true); expect(screen.getByText(/Deps/)).toBeTruthy(); expect(screen.getByRole("button", { name: /Models/i })).toBeTruthy(); expect(screen.getByTestId("plan-button")).toBeTruthy(); expect(screen.getByTestId("subtask-button")).toBeTruthy(); expect(screen.getByRole("button", { name: /Save/i })).toBeTruthy(); expect(toggleButton.getAttribute("aria-expanded")).toBe("true"); expect(toggleButton.getAttribute("aria-controls")).toBe("inline-create-controls"); expect(textarea.getAttribute("aria-controls")).toBe("inline-create-controls"); }); it("toggle button collapses the view when expanded", () => { renderCard(); const toggleButton = screen.getByTestId("inline-create-toggle"); // Expand first expandInlineCreate(); expect(document.querySelector(".inline-create-footer")).toBeTruthy(); expect(toggleButton.getAttribute("aria-expanded")).toBe("true"); // Click toggle again to collapse expandInlineCreate(); // Footer should be hidden expect(document.querySelector(".inline-create-footer")).toBeNull(); expect(document.querySelector(".inline-create-card")?.classList.contains("inline-create-card--collapsed")).toBe(true); expect(toggleButton.getAttribute("aria-expanded")).toBe("false"); }); it("maintains the collapsed and expanded styling contract on the inline create container", () => { renderCard(); const card = document.querySelector(".inline-create-card"); expect(card?.classList.contains("inline-create-card--collapsed")).toBe(true); expect(card?.classList.contains("inline-create-card--expanded")).toBe(false); expect(document.querySelector(".inline-create-footer")).toBeNull(); expandInlineCreate(); expect(card?.classList.contains("inline-create-card--expanded")).toBe(true); expect(card?.classList.contains("inline-create-card--collapsed")).toBe(false); expect(document.querySelector(".inline-create-footer")).toBeTruthy(); }); it("does NOT expand on focus", () => { renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); // Focus should not expand the card textarea.focus(); expect(document.querySelector(".inline-create-footer")).toBeNull(); }); it("calls onCancel on blur when collapsed and empty", () => { const { props } = renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).toHaveBeenCalledTimes(1); }); it("does not cancel on blur when collapsed and has content", () => { const { props } = renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.change(textarea, { target: { value: "Keep collapsed draft" } }); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).not.toHaveBeenCalled(); }); it("calls onCancel on blur when expanded and empty", () => { const { props } = renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); expandInlineCreate(); expect(document.querySelector(".inline-create-footer")).toBeTruthy(); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).toHaveBeenCalledTimes(1); }); it("does not cancel on blur when expanded and has content", () => { const { props } = renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); expandInlineCreate(); fireEvent.change(textarea, { target: { value: "Keep drafting" } }); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).not.toHaveBeenCalled(); expect(document.querySelector(".inline-create-footer")).toBeTruthy(); }); }); describe("InlineCreateCard Escape key behavior", () => { it("calls onCancel when Escape is pressed", () => { const { props } = renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.keyDown(textarea, { key: "Escape" }); expect(props.onCancel).toHaveBeenCalledTimes(1); }); it("closes dropdowns on first Escape, cancels on second", () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); // Open a dropdown fireEvent.click(screen.getByText(/Deps/)); expect(document.querySelector(".dep-dropdown")).toBeTruthy(); // First Escape closes dropdown fireEvent.keyDown(textarea, { key: "Escape" }); expect(document.querySelector(".dep-dropdown")).toBeNull(); expect(props.onCancel).not.toHaveBeenCalled(); }); }); describe("InlineCreateCard dep-dropdown focus retention", () => { const testTasks: Task[] = [ createMockTask({ id: "FN-010", title: "Task A", description: "First task" }), ]; it("dep-dropdown-item mouseDown calls preventDefault to retain focus", () => { renderCard(testTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const item = document.querySelector(".dep-dropdown-item") as HTMLElement; expect(item).toBeTruthy(); const prevented = !fireEvent.mouseDown(item); expect(prevented).toBe(true); }); }); describe("InlineCreateCard model selector", () => { it("opens and closes the model disclosure dropdown", () => { renderCard(); expandInlineCreate(); openModelPanel(); expect(screen.getByText("Executor Model")).toBeTruthy(); expect(screen.getByText("Validator Model")).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: /Models/i })); expect(screen.queryByText("Executor Model")).toBeNull(); }); it("renders the shared model dropdown in the portal layer from the inline create surface", async () => { renderCard(); expandInlineCreate(); openModelPanel(); fireEvent.click(screen.getByRole("button", { name: "Executor Model" })); const portal = await screen.findByTestId("model-combobox-portal"); expect(portal).toBeTruthy(); expect(portal.classList.contains("model-combobox-dropdown--portal")).toBe(true); expect(document.body.contains(portal)).toBe(true); }); it("updates executor selection and shows the selected model badge", () => { renderCard(); expandInlineCreate(); openModelPanel(); chooseModel("Executor Model", "Claude Sonnet 4.5"); expect(screen.getByText("anthropic/claude-sonnet-4-5")).toBeTruthy(); }); it("updates validator selection and shows the selected model badge", () => { renderCard(); expandInlineCreate(); openModelPanel(); chooseModel("Validator Model", "GPT-4o"); expect(screen.getByText("openai/gpt-4o")).toBeTruthy(); }); it("clears the model selection when Use default is chosen", () => { renderCard(); expandInlineCreate(); openModelPanel(); chooseModel("Executor Model", "Claude Sonnet 4.5"); expect(screen.getByText("anthropic/claude-sonnet-4-5")).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Executor Model" })); const defaultOption = document.querySelector(".model-combobox-dropdown .model-combobox-option") as HTMLElement; expect(defaultOption).toBeTruthy(); fireEvent.click(defaultOption); expect(screen.getAllByText("Using default")).toHaveLength(2); }); it("omits model fields from the submit payload after clearing back to default", async () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.change(textarea, { target: { value: "Task using defaults again" } }); openModelPanel(); chooseModel("Executor Model", "Claude Sonnet 4.5"); fireEvent.click(screen.getByRole("button", { name: "Executor Model" })); const defaultOption = document.querySelector(".model-combobox-dropdown .model-combobox-option") as HTMLElement; expect(defaultOption).toBeTruthy(); fireEvent.click(defaultOption); fireEvent.click(screen.getByRole("button", { name: "Save" })); await waitFor(() => { expect(props.onSubmit).toHaveBeenCalledWith( expect.objectContaining({ description: "Task using defaults again", modelProvider: undefined, modelId: undefined, validatorModelProvider: undefined, validatorModelId: undefined, }), ); }); }); it("includes selected models in the submit payload", async () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.change(textarea, { target: { value: "Task with model overrides" } }); openModelPanel(); chooseModel("Executor Model", "Claude Sonnet 4.5"); chooseModel("Validator Model", "GPT-4o"); fireEvent.click(screen.getByRole("button", { name: "Save" })); await waitFor(() => { expect(props.onSubmit).toHaveBeenCalledWith( expect.objectContaining({ description: "Task with model overrides", modelProvider: "anthropic", modelId: "claude-sonnet-4-5", validatorModelProvider: "openai", validatorModelId: "gpt-4o", }), ); }); }); it("does NOT call onCancel when focus leaves while the model dropdown is open", () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); openModelPanel(); textarea.focus(); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).not.toHaveBeenCalled(); }); it("does NOT call onCancel when expanded, empty, and a dropdown is open", () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.click(screen.getByText(/Deps/)); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).not.toHaveBeenCalled(); }); it("does NOT call onCancel when focus leaves while the preset dropdown is open", () => { vi.mocked(fetchSettings).mockResolvedValueOnce({ modelPresets: [{ id: "budget", name: "Budget", executorProvider: "anthropic", executorModelId: "claude-sonnet-4-5" }], autoSelectModelPreset: false, defaultPresetBySize: {}, }); const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.click(screen.getByRole("button", { name: /Preset/i })); textarea.focus(); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).not.toHaveBeenCalled(); }); it.skip("includes selected preset id in the submit payload", async () => { vi.mocked(fetchSettings).mockResolvedValueOnce({ modelPresets: [{ id: "budget", name: "Budget", executorProvider: "anthropic", executorModelId: "claude-sonnet-4-5", validatorProvider: "openai", validatorModelId: "gpt-4o" }], autoSelectModelPreset: false, defaultPresetBySize: {}, }); const { props } = renderCard([], { availableModels: undefined }); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.change(textarea, { target: { value: "Task with preset" } }); fireEvent.click(await screen.findByRole("button", { name: /Preset/i })); fireEvent.click(await screen.findByRole("button", { name: "Budget" })); fireEvent.click(await screen.findByRole("button", { name: /Save/i })); await waitFor(() => { expect(props.onSubmit).toHaveBeenCalledWith(expect.objectContaining({ description: "Task with preset", modelPresetId: "budget", modelProvider: "anthropic", modelId: "claude-sonnet-4-5", validatorModelProvider: "openai", validatorModelId: "gpt-4o", })); }); }); it("calls onCancel after a model override is selected and focus leaves the card while empty", () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); openModelPanel(); chooseModel("Executor Model", "Claude Sonnet 4.5"); fireEvent.click(screen.getByRole("button", { name: /1 model/i })); textarea.focus(); fireEvent.focusOut(textarea, { relatedTarget: null }); expect(props.onCancel).toHaveBeenCalledTimes(1); }); it("prevents default on model option mouseDown to retain focus while selecting", () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); textarea.focus(); openModelPanel(); fireEvent.click(screen.getByRole("button", { name: "Executor Model" })); const option = screen.getByText("Claude Sonnet 4.5"); const prevented = !fireEvent.mouseDown(option); fireEvent.click(option); expect(prevented).toBe(true); expect(props.onCancel).not.toHaveBeenCalled(); expect(screen.getByText("anthropic/claude-sonnet-4-5")).toBeTruthy(); }); it("uses parent-provided models without fetching again", () => { renderCard(); expect(fetchModels).not.toHaveBeenCalled(); }); it("fetches models when parent-provided models are omitted", async () => { renderCard([], { availableModels: undefined }); await waitFor(() => { expect(fetchModels).toHaveBeenCalledTimes(1); }); }); it("shows an error state and retries model loading", async () => { vi.mocked(fetchModels) .mockRejectedValueOnce(new Error("no auth")) .mockResolvedValueOnce(MOCK_MODELS); renderCard([], { availableModels: undefined }); expandInlineCreate(); openModelPanel(); await waitFor(() => { expect(screen.getByText("Failed to load models.")).toBeTruthy(); }); fireEvent.click(screen.getByRole("button", { name: "Retry" })); await waitFor(() => { expect(screen.queryByText("Failed to load models.")).toBeNull(); }); expect(fetchModels).toHaveBeenCalledTimes(2); }); }); describe("InlineCreateCard dependency dropdown sort order", () => { const scrambledTasks: Task[] = [ createMockTask({ id: "FN-001", title: "Oldest", description: "First", createdAt: "2026-01-01T00:00:00Z" }), createMockTask({ id: "FN-003", title: "Newest", description: "Third", createdAt: "2026-03-01T00:00:00Z" }), createMockTask({ id: "FN-002", title: "Middle", description: "Second", createdAt: "2026-02-01T00:00:00Z" }), ]; it("renders dependency dropdown items sorted newest-first by createdAt", () => { renderCard(scrambledTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const items = document.querySelectorAll(".dep-dropdown-item"); expect(items).toHaveLength(3); const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent); expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]); }); it("preserves newest-first sort order when a search filter is applied", () => { renderCard(scrambledTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const input = document.querySelector(".dep-dropdown-search") as HTMLInputElement; fireEvent.change(input, { target: { value: "FN-00" } }); const items = document.querySelectorAll(".dep-dropdown-item"); expect(items).toHaveLength(3); const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent); expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]); }); }); describe("InlineCreateCard dependency dropdown sort with identical timestamps", () => { const sameTimeTasks: Task[] = [ createMockTask({ id: "FN-001", title: "First", description: "First task" }), createMockTask({ id: "FN-002", title: "Second", description: "Second task" }), createMockTask({ id: "FN-003", title: "Third", description: "Third task" }), ]; it("renders tasks with identical createdAt sorted newest-ID-first (descending numeric ID)", () => { renderCard(sameTimeTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const items = document.querySelectorAll(".dep-dropdown-item"); expect(items).toHaveLength(3); const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent); expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]); }); it("preserves newest-ID-first order when search filter is applied with identical timestamps", () => { renderCard(sameTimeTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const input = document.querySelector(".dep-dropdown-search") as HTMLInputElement; fireEvent.change(input, { target: { value: "FN-00" } }); const items = document.querySelectorAll(".dep-dropdown-item"); expect(items).toHaveLength(3); const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent); expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]); }); }); describe("InlineCreateCard dependency dropdown search", () => { const testTasks: Task[] = [ createMockTask({ id: "FN-001", title: "Fix login", description: "Login page broken", createdAt: "2026-01-01T00:00:00Z" }), createMockTask({ id: "FN-002", title: "Add dark mode", description: "Theme support", createdAt: "2026-02-01T00:00:00Z" }), createMockTask({ id: "FN-003", title: "Refactor API", description: "Clean up endpoints", createdAt: "2026-03-01T00:00:00Z" }), ]; it("shows search input when dropdown is opened", () => { renderCard(testTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const input = document.querySelector(".dep-dropdown-search") as HTMLInputElement; expect(input).toBeTruthy(); expect(input.placeholder).toBe("Search tasks…"); }); it("filters tasks by search term", () => { renderCard(testTasks); expandInlineCreate(); fireEvent.click(screen.getByText(/Deps/)); const input = document.querySelector(".dep-dropdown-search") as HTMLInputElement; fireEvent.change(input, { target: { value: "dark" } }); const items = document.querySelectorAll(".dep-dropdown-item"); expect(items).toHaveLength(1); expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("FN-002"); }); }); describe("InlineCreateCard Plan and Subtask buttons", () => { it("renders Plan and Subtask buttons disabled when description is empty", () => { renderCard(); expandInlineCreate(); const planButton = screen.getByTestId("plan-button") as HTMLButtonElement; const subtaskButton = screen.getByTestId("subtask-button") as HTMLButtonElement; expect(planButton.disabled).toBe(true); expect(subtaskButton.disabled).toBe(true); }); it("enables Plan and Subtask buttons when description is entered", () => { renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.change(textarea, { target: { value: "Test task" } }); const planButton = screen.getByTestId("plan-button") as HTMLButtonElement; const subtaskButton = screen.getByTestId("subtask-button") as HTMLButtonElement; expect(planButton.disabled).toBe(false); expect(subtaskButton.disabled).toBe(false); }); it("calls onPlanningMode and collapses after Plan clicked", () => { const onPlanningMode = vi.fn(); renderCard([], { onPlanningMode }); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?") as HTMLTextAreaElement; fireEvent.change(textarea, { target: { value: "Plan this task" } }); fireEvent.click(screen.getByTestId("plan-button")); expect(onPlanningMode).toHaveBeenCalledWith("Plan this task"); expect(textarea.value).toBe(""); expect(screen.getByTestId("inline-create-toggle").getAttribute("aria-expanded")).toBe("false"); expect(document.querySelector(".inline-create-footer")).toBeNull(); }); it("calls onSubtaskBreakdown and collapses after Subtask clicked", () => { const onSubtaskBreakdown = vi.fn(); renderCard([], { onSubtaskBreakdown }); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?") as HTMLTextAreaElement; fireEvent.change(textarea, { target: { value: "Break this down" } }); fireEvent.click(screen.getByTestId("subtask-button")); expect(onSubtaskBreakdown).toHaveBeenCalledWith("Break this down"); expect(textarea.value).toBe(""); expect(screen.getByTestId("inline-create-toggle").getAttribute("aria-expanded")).toBe("false"); expect(document.querySelector(".inline-create-footer")).toBeNull(); }); it("shows toast when Plan clicked with empty description (via direct handler call)", () => { const addToast = vi.fn(); const onPlanningMode = vi.fn(); renderCard([], { addToast, onPlanningMode }); expandInlineCreate(); // When no description, button is disabled - verify that behavior const planButton = screen.getByTestId("plan-button") as HTMLButtonElement; expect(planButton.disabled).toBe(true); // The handler validation exists but can't be triggered via click when disabled // The disabled state is the primary UX protection }); it("shows toast when Subtask clicked with empty description (via direct handler call)", () => { const addToast = vi.fn(); const onSubtaskBreakdown = vi.fn(); renderCard([], { addToast, onSubtaskBreakdown }); expandInlineCreate(); // When no description, button is disabled - verify that behavior const subtaskButton = screen.getByTestId("subtask-button") as HTMLButtonElement; expect(subtaskButton.disabled).toBe(true); // The handler validation exists but can't be triggered via click when disabled // The disabled state is the primary UX protection }); }); describe("InlineCreateCard localStorage persistence", () => { beforeEach(() => { // Clear localStorage before each test localStorage.clear(); }); afterEach(() => { localStorage.clear(); }); it("restores description from localStorage on mount", () => { // Pre-populate localStorage localStorage.setItem("kb-inline-create-text", "Saved draft description"); renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); // Should restore the saved description expect((textarea as HTMLTextAreaElement).value).toBe("Saved draft description"); }); it("updates localStorage when typing", async () => { renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); fireEvent.change(textarea, { target: { value: "Typing this task" } }); // Wait for the useEffect to run await waitFor(() => { expect(localStorage.getItem("kb-inline-create-text")).toBe("Typing this task"); }); }); it("clears textarea and localStorage after successful task creation", async () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?") as HTMLTextAreaElement; // Type something to set localStorage fireEvent.change(textarea, { target: { value: "Task to create" } }); await waitFor(() => { expect(localStorage.getItem("kb-inline-create-text")).toBe("Task to create"); }); // Submit the task by clicking the Save button fireEvent.click(screen.getByRole("button", { name: /Save/i })); await waitFor(() => { expect(props.onSubmit).toHaveBeenCalled(); }); expect(textarea.value).toBe(""); expect(screen.getByTestId("inline-create-toggle").getAttribute("aria-expanded")).toBe("false"); // localStorage should be cleared expect(localStorage.getItem("kb-inline-create-text")).toBeNull(); }); it("allows immediately re-expanding after successful submit", async () => { const { props } = renderCard(); expandInlineCreate(); const textarea = screen.getByPlaceholderText("What needs to be done?") as HTMLTextAreaElement; const toggleButton = screen.getByTestId("inline-create-toggle"); fireEvent.change(textarea, { target: { value: "Task to create" } }); fireEvent.click(screen.getByRole("button", { name: /Save/i })); await waitFor(() => { expect(props.onSubmit).toHaveBeenCalled(); }); fireEvent.click(toggleButton); expect(toggleButton.getAttribute("aria-expanded")).toBe("true"); expect(document.querySelector(".inline-create-footer")).toBeTruthy(); }); it("clears localStorage when cancelling via Escape key", async () => { const { props } = renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); // Type something to set localStorage fireEvent.change(textarea, { target: { value: "Draft to cancel" } }); await waitFor(() => { expect(localStorage.getItem("kb-inline-create-text")).toBe("Draft to cancel"); }); // Press Escape to cancel fireEvent.keyDown(textarea, { key: "Escape" }); // onCancel should be called await waitFor(() => { expect(props.onCancel).toHaveBeenCalled(); }); // localStorage should be cleared expect(localStorage.getItem("kb-inline-create-text")).toBeNull(); }); it("starts with empty description when localStorage is empty", () => { renderCard(); const textarea = screen.getByPlaceholderText("What needs to be done?"); expect((textarea as HTMLTextAreaElement).value).toBe(""); }); });