All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Fixes all 542 ESLint problems in the web package to achieve 0 errors and 0 warnings. Changes: - Fixed 144 issues: nullish coalescing, return types, unused variables - Fixed 118 issues: unnecessary conditions, type safety, template literals - Fixed 79 issues: non-null assertions, unsafe assignments, empty functions - Fixed 67 issues: explicit return types, promise handling, enum comparisons - Fixed 45 final warnings: missing return types, optional chains - Fixed 25 typecheck-related issues: async/await, type assertions, formatting - Fixed JSX.Element namespace errors across 90+ files All Quality Rails violations resolved. Lint and typecheck both pass with 0 problems. Files modified: 118 components, tests, hooks, and utilities Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
229 lines
8.0 KiB
TypeScript
229 lines
8.0 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
import { describe, it, expect } from "vitest";
|
|
import { render, screen } from "@testing-library/react";
|
|
import { TaskItem } from "./TaskItem";
|
|
import { TaskStatus, TaskPriority, type Task } from "@mosaic/shared";
|
|
|
|
describe("TaskItem", (): void => {
|
|
const baseTask: Task = {
|
|
id: "task-1",
|
|
title: "Test task",
|
|
description: "Task description",
|
|
status: TaskStatus.IN_PROGRESS,
|
|
priority: TaskPriority.MEDIUM,
|
|
dueDate: new Date("2026-01-29"),
|
|
creatorId: "user-1",
|
|
assigneeId: "user-1",
|
|
workspaceId: "workspace-1",
|
|
projectId: null,
|
|
parentId: null,
|
|
sortOrder: 0,
|
|
metadata: {},
|
|
completedAt: null,
|
|
createdAt: new Date("2026-01-28"),
|
|
updatedAt: new Date("2026-01-28"),
|
|
};
|
|
|
|
it("should render task title", (): void => {
|
|
render(<TaskItem task={baseTask} />);
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render task description when present", (): void => {
|
|
render(<TaskItem task={baseTask} />);
|
|
expect(screen.getByText("Task description")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show status indicator for active task", (): void => {
|
|
render(<TaskItem task={{ ...baseTask, status: TaskStatus.IN_PROGRESS }} />);
|
|
expect(screen.getByText("🟢")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show status indicator for not started task", (): void => {
|
|
render(<TaskItem task={{ ...baseTask, status: TaskStatus.NOT_STARTED }} />);
|
|
expect(screen.getByText("⚪")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show status indicator for paused task", (): void => {
|
|
render(<TaskItem task={{ ...baseTask, status: TaskStatus.PAUSED }} />);
|
|
expect(screen.getByText("⏸️")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display priority badge", (): void => {
|
|
render(<TaskItem task={{ ...baseTask, priority: TaskPriority.HIGH }} />);
|
|
expect(screen.getByText("High priority")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should not use demanding language", (): void => {
|
|
const { container } = render(<TaskItem task={baseTask} />);
|
|
const text = container.textContent;
|
|
expect(text).not.toMatch(/overdue/i);
|
|
expect(text).not.toMatch(/urgent/i);
|
|
expect(text).not.toMatch(/must/i);
|
|
expect(text).not.toMatch(/critical/i);
|
|
});
|
|
|
|
it("should show 'Target passed' for past due dates", (): void => {
|
|
const pastTask = {
|
|
...baseTask,
|
|
dueDate: new Date("2026-01-27"), // Past date
|
|
};
|
|
render(<TaskItem task={pastTask} />);
|
|
expect(screen.getByText(/target passed/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show 'Approaching target' for near due dates", (): void => {
|
|
const soonTask = {
|
|
...baseTask,
|
|
dueDate: new Date(Date.now() + 12 * 60 * 60 * 1000), // 12 hours from now
|
|
};
|
|
render(<TaskItem task={soonTask} />);
|
|
expect(screen.getByText(/approaching target/i)).toBeInTheDocument();
|
|
});
|
|
|
|
describe("error states", (): void => {
|
|
it("should handle task with missing title", (): void => {
|
|
const taskWithoutTitle = {
|
|
...baseTask,
|
|
title: "",
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithoutTitle} />);
|
|
// Should render without crashing, even with empty title
|
|
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with missing description", (): void => {
|
|
const taskWithoutDescription = {
|
|
...baseTask,
|
|
description: null,
|
|
};
|
|
|
|
render(<TaskItem task={taskWithoutDescription} />);
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
// Description paragraph should not be rendered when null
|
|
expect(screen.queryByText("Task description")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with invalid status", (): void => {
|
|
const taskWithInvalidStatus = {
|
|
...baseTask,
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
status: "invalid-status" as any,
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithInvalidStatus} />);
|
|
// Should render without crashing even with invalid status
|
|
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with invalid priority", (): void => {
|
|
const taskWithInvalidPriority = {
|
|
...baseTask,
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
priority: "invalid-priority" as any,
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithInvalidPriority} />);
|
|
// Should render without crashing even with invalid priority
|
|
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with missing dueDate", (): void => {
|
|
const taskWithoutDueDate = {
|
|
...baseTask,
|
|
dueDate: null,
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
render(<TaskItem task={taskWithoutDueDate as any} />);
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with invalid dueDate", (): void => {
|
|
const taskWithInvalidDate = {
|
|
...baseTask,
|
|
dueDate: new Date("invalid-date"),
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithInvalidDate} />);
|
|
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with very long title", (): void => {
|
|
const longTitle = "A".repeat(500);
|
|
const taskWithLongTitle = {
|
|
...baseTask,
|
|
title: longTitle,
|
|
};
|
|
|
|
render(<TaskItem task={taskWithLongTitle} />);
|
|
expect(screen.getByText(longTitle)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with special characters in title", (): void => {
|
|
const taskWithSpecialChars = {
|
|
...baseTask,
|
|
title: '<img src="x" onerror="alert(1)">',
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithSpecialChars} />);
|
|
// Should render escaped HTML entities, not execute
|
|
// React escapes to <img... > which is safe
|
|
expect(container.innerHTML).toContain("<img");
|
|
expect(container.innerHTML).not.toContain("<img src=");
|
|
// Text should be displayed as-is
|
|
expect(screen.getByText(/<img src="x" onerror="alert\(1\)">/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with HTML in description", (): void => {
|
|
const taskWithHtmlDesc = {
|
|
...baseTask,
|
|
description: '<b>Bold text</b><script>alert("xss")</script>',
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithHtmlDesc} />);
|
|
// Should render as text, not HTML - React escapes by default
|
|
expect(container.innerHTML).not.toContain("<script>");
|
|
// Text should be displayed as-is
|
|
expect(screen.getByText(/Bold text/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with missing required IDs", (): void => {
|
|
const taskWithMissingIds = {
|
|
...baseTask,
|
|
id: "",
|
|
workspaceId: "",
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={taskWithMissingIds} />);
|
|
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with extremely old due date", (): void => {
|
|
const veryOldTask = {
|
|
...baseTask,
|
|
dueDate: new Date("1970-01-01"),
|
|
};
|
|
|
|
render(<TaskItem task={veryOldTask} />);
|
|
expect(screen.getByText(/target passed/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle task with far future due date", (): void => {
|
|
const farFutureTask = {
|
|
...baseTask,
|
|
dueDate: new Date("2099-12-31"),
|
|
};
|
|
|
|
const { container } = render(<TaskItem task={farFutureTask} />);
|
|
expect(container.querySelector(".bg-white")).toBeInTheDocument();
|
|
expect(screen.getByText("Test task")).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|