feat(#15): implement gantt chart component
- Add milestone support with diamond markers - Implement dependency line rendering with SVG arrows - Add isMilestone property to GanttTask type - Create dependency calculation and visualization - Add comprehensive tests for milestones and dependencies - Add index module tests for exports - Coverage: GanttChart 98.37%, types 91.66%, index 100%
This commit is contained in:
@@ -354,28 +354,36 @@ describe("GanttChart", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dependencies (stretch goal)", () => {
|
||||
describe("Dependencies", () => {
|
||||
it("should render dependency lines when showDependencies is true", () => {
|
||||
const tasks = [
|
||||
createGanttTask({
|
||||
id: "task-1",
|
||||
title: "Foundation",
|
||||
startDate: new Date("2026-02-01"),
|
||||
endDate: new Date("2026-02-10"),
|
||||
}),
|
||||
createGanttTask({
|
||||
id: "task-2",
|
||||
title: "Build on top",
|
||||
startDate: new Date("2026-02-11"),
|
||||
endDate: new Date("2026-02-20"),
|
||||
dependencies: ["task-1"],
|
||||
}),
|
||||
];
|
||||
|
||||
render(<GanttChart tasks={tasks} showDependencies={true} />);
|
||||
|
||||
// Check if dependency visualization exists
|
||||
// Check if dependency SVG exists
|
||||
const chart = screen.getByRole("region", { name: /gantt chart/i });
|
||||
expect(chart).toBeInTheDocument();
|
||||
|
||||
// Specific dependency rendering will depend on implementation
|
||||
// This is a basic check that the prop is accepted
|
||||
// Look for dependency path element
|
||||
const svg = chart.querySelector(".gantt-dependencies");
|
||||
expect(svg).toBeInTheDocument();
|
||||
|
||||
const paths = chart.querySelectorAll(".dependency-line");
|
||||
expect(paths).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should not render dependencies by default", () => {
|
||||
@@ -396,6 +404,138 @@ describe("GanttChart", () => {
|
||||
// Dependencies should not be shown by default
|
||||
const chart = screen.getByRole("region", { name: /gantt chart/i });
|
||||
expect(chart).toBeInTheDocument();
|
||||
|
||||
const svg = chart.querySelector(".gantt-dependencies");
|
||||
expect(svg).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should handle tasks with non-existent dependencies gracefully", () => {
|
||||
const tasks = [
|
||||
createGanttTask({
|
||||
id: "task-1",
|
||||
title: "Task 1",
|
||||
dependencies: ["non-existent-task"],
|
||||
}),
|
||||
];
|
||||
|
||||
render(<GanttChart tasks={tasks} showDependencies={true} />);
|
||||
|
||||
// Should not crash
|
||||
const chart = screen.getByRole("region", { name: /gantt chart/i });
|
||||
expect(chart).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render multiple dependency lines", () => {
|
||||
const tasks = [
|
||||
createGanttTask({
|
||||
id: "task-1",
|
||||
title: "Task 1",
|
||||
startDate: new Date("2026-02-01"),
|
||||
endDate: new Date("2026-02-05"),
|
||||
}),
|
||||
createGanttTask({
|
||||
id: "task-2",
|
||||
title: "Task 2",
|
||||
startDate: new Date("2026-02-01"),
|
||||
endDate: new Date("2026-02-05"),
|
||||
}),
|
||||
createGanttTask({
|
||||
id: "task-3",
|
||||
title: "Task 3",
|
||||
startDate: new Date("2026-02-06"),
|
||||
endDate: new Date("2026-02-10"),
|
||||
dependencies: ["task-1", "task-2"],
|
||||
}),
|
||||
];
|
||||
|
||||
render(<GanttChart tasks={tasks} showDependencies={true} />);
|
||||
|
||||
const chart = screen.getByRole("region", { name: /gantt chart/i });
|
||||
const paths = chart.querySelectorAll(".dependency-line");
|
||||
expect(paths).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Milestones", () => {
|
||||
it("should render milestone as diamond shape", () => {
|
||||
const milestone = createGanttTask({
|
||||
id: "milestone-1",
|
||||
title: "Phase 1 Complete",
|
||||
startDate: new Date("2026-02-15"),
|
||||
endDate: new Date("2026-02-15"),
|
||||
isMilestone: true,
|
||||
});
|
||||
|
||||
render(<GanttChart tasks={[milestone]} />);
|
||||
|
||||
const milestoneElement = screen.getByRole("button", {
|
||||
name: /milestone.*phase 1 complete/i,
|
||||
});
|
||||
expect(milestoneElement).toBeInTheDocument();
|
||||
expect(milestoneElement).toHaveClass("gantt-milestone");
|
||||
});
|
||||
|
||||
it("should handle click on milestone", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTaskClick = vi.fn();
|
||||
|
||||
const milestone = createGanttTask({
|
||||
id: "milestone-1",
|
||||
title: "Milestone Task",
|
||||
isMilestone: true,
|
||||
});
|
||||
|
||||
render(<GanttChart tasks={[milestone]} onTaskClick={onTaskClick} />);
|
||||
|
||||
const milestoneElement = screen.getByRole("button", {
|
||||
name: /milestone.*milestone task/i,
|
||||
});
|
||||
await user.click(milestoneElement);
|
||||
|
||||
expect(onTaskClick).toHaveBeenCalledWith(milestone);
|
||||
});
|
||||
|
||||
it("should support keyboard navigation for milestones", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTaskClick = vi.fn();
|
||||
|
||||
const milestone = createGanttTask({
|
||||
id: "milestone-1",
|
||||
title: "Keyboard Milestone",
|
||||
isMilestone: true,
|
||||
});
|
||||
|
||||
render(<GanttChart tasks={[milestone]} onTaskClick={onTaskClick} />);
|
||||
|
||||
const milestoneElement = screen.getByRole("button", {
|
||||
name: /milestone/i,
|
||||
});
|
||||
|
||||
await user.tab();
|
||||
expect(milestoneElement).toHaveFocus();
|
||||
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onTaskClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should render milestones and regular tasks together", () => {
|
||||
const tasks = [
|
||||
createGanttTask({
|
||||
id: "task-1",
|
||||
title: "Regular Task",
|
||||
isMilestone: false,
|
||||
}),
|
||||
createGanttTask({
|
||||
id: "milestone-1",
|
||||
title: "Milestone",
|
||||
isMilestone: true,
|
||||
}),
|
||||
];
|
||||
|
||||
render(<GanttChart tasks={tasks} />);
|
||||
|
||||
expect(screen.getByRole("button", { name: /gantt bar.*regular task/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /milestone.*milestone/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user