chore: upgrade Node.js runtime to v24 across codebase #419

Merged
jason.woltje merged 438 commits from fix/auth-frontend-remediation into main 2026-02-17 01:04:47 +00:00
3 changed files with 85 additions and 3 deletions
Showing only changes of commit 3d9edf4141 - Show all commits

View File

@@ -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();
});
});
});

View File

@@ -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);

View File

@@ -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" }}>