chore: upgrade Node.js runtime to v24 across codebase #419
@@ -132,4 +132,70 @@ describe("FilterBar", (): void => {
|
||||
// Should show 3 active filters (2 statuses + 1 priority)
|
||||
expect(screen.getByText(/3/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("accessibility (CQ-WEB-11)", (): void => {
|
||||
it("should have aria-label on search input", (): void => {
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} />);
|
||||
const searchInput = screen.getByRole("textbox", { name: /search tasks/i });
|
||||
expect(searchInput).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should have aria-label on date inputs", (): void => {
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} />);
|
||||
expect(screen.getByLabelText(/filter from date/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/filter to date/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should have aria-labels on status filter buttons", (): void => {
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} />);
|
||||
expect(screen.getByRole("button", { name: /status filter/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should have aria-labels on priority filter buttons", (): void => {
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} />);
|
||||
expect(screen.getByRole("button", { name: /priority filter/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should have id and htmlFor associations on status checkboxes", async (): Promise<void> => {
|
||||
const user = userEvent.setup();
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} />);
|
||||
|
||||
// Open status dropdown
|
||||
await user.click(screen.getByRole("button", { name: /status filter/i }));
|
||||
|
||||
// Verify specific status checkboxes have proper id attributes
|
||||
const notStartedCheckbox = screen.getByLabelText(/filter by status: not started/i);
|
||||
expect(notStartedCheckbox).toHaveAttribute("id", "status-filter-NOT_STARTED");
|
||||
|
||||
const inProgressCheckbox = screen.getByLabelText(/filter by status: in progress/i);
|
||||
expect(inProgressCheckbox).toHaveAttribute("id", "status-filter-IN_PROGRESS");
|
||||
|
||||
const completedCheckbox = screen.getByLabelText(/filter by status: completed/i);
|
||||
expect(completedCheckbox).toHaveAttribute("id", "status-filter-COMPLETED");
|
||||
});
|
||||
|
||||
it("should have id and htmlFor associations on priority checkboxes", async (): Promise<void> => {
|
||||
const user = userEvent.setup();
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} />);
|
||||
|
||||
// Open priority dropdown
|
||||
await user.click(screen.getByRole("button", { name: /priority filter/i }));
|
||||
|
||||
// Verify specific priority checkboxes have proper id attributes
|
||||
const lowCheckbox = screen.getByLabelText(/filter by priority: low/i);
|
||||
expect(lowCheckbox).toHaveAttribute("id", "priority-filter-LOW");
|
||||
|
||||
const mediumCheckbox = screen.getByLabelText(/filter by priority: medium/i);
|
||||
expect(mediumCheckbox).toHaveAttribute("id", "priority-filter-MEDIUM");
|
||||
|
||||
const highCheckbox = screen.getByLabelText(/filter by priority: high/i);
|
||||
expect(highCheckbox).toHaveAttribute("id", "priority-filter-HIGH");
|
||||
});
|
||||
|
||||
it("should have aria-label on clear filters button", (): void => {
|
||||
const filtersWithSearch = { search: "test" };
|
||||
render(<FilterBar onFilterChange={mockOnFilterChange} initialFilters={filtersWithSearch} />);
|
||||
expect(screen.getByRole("button", { name: /clear filters/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,6 +112,7 @@ export function FilterBar({
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tasks..."
|
||||
aria-label="Search tasks"
|
||||
value={searchValue}
|
||||
onChange={(e) => {
|
||||
setSearchValue(e.target.value);
|
||||
@@ -141,14 +142,17 @@ export function FilterBar({
|
||||
{Object.values(TaskStatus).map((status) => (
|
||||
<label
|
||||
key={status}
|
||||
htmlFor={`status-filter-${status}`}
|
||||
className="flex items-center px-3 py-2 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
id={`status-filter-${status}`}
|
||||
type="checkbox"
|
||||
checked={filters.status?.includes(status) ?? false}
|
||||
onChange={() => {
|
||||
handleStatusToggle(status);
|
||||
}}
|
||||
aria-label={`Filter by status: ${status.replace(/_/g, " ")}`}
|
||||
className="mr-2"
|
||||
/>
|
||||
{status.replace(/_/g, " ")}
|
||||
@@ -179,14 +183,17 @@ export function FilterBar({
|
||||
{Object.values(TaskPriority).map((priority) => (
|
||||
<label
|
||||
key={priority}
|
||||
htmlFor={`priority-filter-${priority}`}
|
||||
className="flex items-center px-3 py-2 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
id={`priority-filter-${priority}`}
|
||||
type="checkbox"
|
||||
checked={filters.priority?.includes(priority) ?? false}
|
||||
onChange={() => {
|
||||
handlePriorityToggle(priority);
|
||||
}}
|
||||
aria-label={`Filter by priority: ${priority}`}
|
||||
className="mr-2"
|
||||
/>
|
||||
{priority}
|
||||
@@ -201,6 +208,7 @@ export function FilterBar({
|
||||
<input
|
||||
type="date"
|
||||
placeholder="From date"
|
||||
aria-label="Filter from date"
|
||||
value={filters.dateFrom ?? ""}
|
||||
onChange={(e) => {
|
||||
handleFilterChange("dateFrom", e.target.value || undefined);
|
||||
@@ -211,6 +219,7 @@ export function FilterBar({
|
||||
<input
|
||||
type="date"
|
||||
placeholder="To date"
|
||||
aria-label="Filter to date"
|
||||
value={filters.dateTo ?? ""}
|
||||
onChange={(e) => {
|
||||
handleFilterChange("dateTo", e.target.value || undefined);
|
||||
|
||||
@@ -222,7 +222,9 @@ export function ReactFlowEditor({
|
||||
}, [readOnly, selectedNode, onNodeDelete, setNodes, setEdges]);
|
||||
|
||||
// Keyboard shortcuts
|
||||
useEffect((): (() => void) => {
|
||||
useEffect((): (() => void) | undefined => {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent): void => {
|
||||
if (readOnly) return;
|
||||
|
||||
@@ -240,8 +242,13 @@ export function ReactFlowEditor({
|
||||
};
|
||||
}, [readOnly, selectedNode, handleDeleteSelected]);
|
||||
|
||||
const isDark =
|
||||
typeof window !== "undefined" && document.documentElement.classList.contains("dark");
|
||||
// Dark mode detection - must be in state+effect to avoid SSR/hydration mismatch
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
useEffect((): void => {
|
||||
if (typeof window !== "undefined") {
|
||||
setIsDark(document.documentElement.classList.contains("dark"));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`w-full h-full ${className}`} style={{ minHeight: "500px" }}>
|
||||
|
||||
Reference in New Issue
Block a user