feat(HAI-051): add column flash animation on count increase

- Create useFlashOnIncrease hook to detect count increases
- Wire useFlashOnIncrease hook into Column component
- Add CSS flash animation keyframes and styles
- Add unit tests for useFlashOnIncrease hook
- Add Column component flash behavior tests
This commit is contained in:
Dustin Byrne
2026-03-25 23:31:16 -04:00
parent ae2f9cfaea
commit 133fc59297
5 changed files with 201 additions and 1 deletions

View File

@@ -0,0 +1,75 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { Column } from "../Column";
import type { Task, Column as ColumnType } from "@hai/core";
// Mock child components to keep tests focused on the Column badge behavior
vi.mock("../TaskCard", () => ({
TaskCard: ({ task }: { task: Task }) => <div data-testid={`task-${task.id}`} />,
}));
vi.mock("../WorktreeGroup", () => ({
WorktreeGroup: () => <div />,
}));
vi.mock("../InlineCreateCard", () => ({
InlineCreateCard: () => <div />,
}));
vi.mock("lucide-react", () => ({
Link: () => null,
Clock: () => null,
}));
function makeTask(id: string): Task {
return {
id,
title: `Task ${id}`,
column: "triage" as ColumnType,
status: undefined as any,
steps: [],
dependencies: [],
description: "",
created: new Date().toISOString(),
updated: new Date().toISOString(),
};
}
const defaultProps = {
column: "triage" as ColumnType,
allTasks: [] as Task[],
maxConcurrent: 2,
onMoveTask: vi.fn().mockResolvedValue({} as Task),
onOpenDetail: vi.fn(),
addToast: vi.fn(),
};
describe("Column count-flash", () => {
it("does not apply count-flash class on initial render", () => {
const tasks = [makeTask("HAI-001")];
render(<Column {...defaultProps} tasks={tasks} />);
const badge = screen.getByText("1");
expect(badge.className).toContain("column-count");
expect(badge.className).not.toContain("count-flash");
});
it("applies count-flash class when task count increases", () => {
const tasks = [makeTask("HAI-001")];
const { rerender } = render(<Column {...defaultProps} tasks={tasks} />);
const moreTasks = [makeTask("HAI-001"), makeTask("HAI-002")];
rerender(<Column {...defaultProps} tasks={moreTasks} />);
const badge = screen.getByText("2");
expect(badge.className).toContain("count-flash");
});
it("does not apply count-flash class when task count decreases", () => {
const tasks = [makeTask("HAI-001"), makeTask("HAI-002")];
const { rerender } = render(<Column {...defaultProps} tasks={tasks} />);
const fewerTasks = [makeTask("HAI-001")];
rerender(<Column {...defaultProps} tasks={fewerTasks} />);
const badge = screen.getByText("1");
expect(badge.className).not.toContain("count-flash");
});
});