- Rename @kb/core, @kb/dashboard, @kb/engine to @fusion/* namespace - Update all import statements across 143+ files to use new package names - Update workspace dependencies and root package.json references - Fix bundler configurations (tsup, vite) for new package names - Update test files and fix typecheck issues - Add changeset file documenting the package rename
237 lines
7.9 KiB
TypeScript
237 lines
7.9 KiB
TypeScript
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 "@fusion/core";
|
|
|
|
// Mock the api module
|
|
vi.mock("../../api", () => ({
|
|
uploadAttachment: vi.fn().mockResolvedValue({}),
|
|
fetchModels: vi.fn().mockResolvedValue([
|
|
{ provider: "anthropic", id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", reasoning: true, contextWindow: 200000 },
|
|
{ provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128000 },
|
|
]),
|
|
fetchSettings: vi.fn().mockResolvedValue({
|
|
modelPresets: [],
|
|
autoSelectModelPreset: false,
|
|
defaultPresetBySize: {},
|
|
}),
|
|
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
|
|
refineText: vi.fn(),
|
|
getRefineErrorMessage: vi.fn((err) => err?.message || "Failed to refine text. Please try again."),
|
|
}));
|
|
|
|
function makeTask(id: string): Task {
|
|
return {
|
|
id,
|
|
title: `Task ${id}`,
|
|
description: `Description for ${id}`,
|
|
column: "todo" as Column,
|
|
status: undefined as any,
|
|
steps: [],
|
|
currentStep: 0,
|
|
dependencies: [],
|
|
log: [],
|
|
createdAt: "2026-01-01T00:00:00Z",
|
|
updatedAt: "2026-01-01T00:00:00Z",
|
|
};
|
|
}
|
|
|
|
function renderNewTaskModal(props = {}) {
|
|
const defaultProps = {
|
|
isOpen: true,
|
|
onClose: vi.fn(),
|
|
tasks: [] as Task[],
|
|
onCreateTask: vi.fn().mockResolvedValue({ id: "KB-001" }),
|
|
addToast: vi.fn(),
|
|
};
|
|
const mergedProps = { ...defaultProps, ...props };
|
|
const result = render(<NewTaskModal {...mergedProps} />);
|
|
return { ...result, props: mergedProps };
|
|
}
|
|
|
|
describe("NewTaskModal", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("renders all form fields when open", () => {
|
|
renderNewTaskModal();
|
|
|
|
expect(screen.getByText("New Task")).toBeTruthy();
|
|
expect(screen.getByLabelText(/Description/i)).toBeTruthy();
|
|
expect(screen.getByRole("button", { name: "Add dependencies" })).toBeTruthy();
|
|
expect(screen.getByText(/Model Configuration/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();
|
|
});
|
|
|
|
it("focuses description textarea when modal opens", async () => {
|
|
renderNewTaskModal();
|
|
|
|
const textarea = screen.getByLabelText(/Description/i);
|
|
await waitFor(() => {
|
|
expect(document.activeElement).toBe(textarea);
|
|
});
|
|
});
|
|
|
|
it("creates task with description when submitted", async () => {
|
|
const { props } = renderNewTaskModal();
|
|
|
|
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.onCreateTask).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
description: "Test description",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
it("closes modal after successful creation", async () => {
|
|
const { props } = renderNewTaskModal();
|
|
|
|
const descTextarea = screen.getByLabelText(/Description/i);
|
|
fireEvent.change(descTextarea, { target: { value: "Test" } });
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
|
|
|
await waitFor(() => {
|
|
expect(props.onClose).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it("has working Plan button for AI-assisted creation", () => {
|
|
const onPlanningMode = vi.fn();
|
|
renderNewTaskModal({ onPlanningMode });
|
|
|
|
// 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 () => {
|
|
const { props } = renderNewTaskModal({
|
|
onCreateTask: vi.fn().mockResolvedValue({ id: "KB-042" }),
|
|
});
|
|
|
|
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("Created KB-042", "success");
|
|
});
|
|
});
|
|
|
|
it("confirms before closing with dirty state", () => {
|
|
const { props } = renderNewTaskModal();
|
|
|
|
const descTextarea = screen.getByLabelText(/Description/i);
|
|
fireEvent.change(descTextarea, { target: { value: "Test description" } });
|
|
|
|
// Mock confirm to return false (cancel)
|
|
const originalConfirm = window.confirm;
|
|
window.confirm = vi.fn().mockReturnValue(false);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
|
|
expect(window.confirm).toHaveBeenCalledWith("You have unsaved changes. Discard them?");
|
|
expect(props.onClose).not.toHaveBeenCalled();
|
|
|
|
window.confirm = originalConfirm;
|
|
});
|
|
|
|
it("closes without confirm when state is not dirty", () => {
|
|
const { props } = renderNewTaskModal();
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
expect(props.onClose).toHaveBeenCalled();
|
|
});
|
|
|
|
it("creates task with title undefined by default", async () => {
|
|
const { props } = renderNewTaskModal();
|
|
|
|
const descTextarea = screen.getByLabelText(/Description/i);
|
|
fireEvent.change(descTextarea, { target: { value: "Only description" } });
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
|
|
|
await waitFor(() => {
|
|
expect(props.onCreateTask).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
title: undefined,
|
|
description: "Only description",
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
// 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);
|
|
fireEvent.change(descTextarea, { target: { value: "Build a login system" } });
|
|
|
|
// 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("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" } });
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Create Task" }));
|
|
|
|
await waitFor(() => {
|
|
expect(props.onCreateTask).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
description: "Normal task",
|
|
}),
|
|
);
|
|
});
|
|
|
|
expect(onPlanningMode).not.toHaveBeenCalled();
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
fireEvent.change(descTextarea, { target: { value: "Some text" } });
|
|
|
|
const createButton = screen.getByRole("button", { name: "Create Task" });
|
|
expect(createButton).not.toBeDisabled();
|
|
});
|
|
});
|