From f8fcc492536d10f96263cb900751d7e6c7268074 Mon Sep 17 00:00:00 2001 From: The Doom Lab Date: Fri, 9 Oct 2026 01:12:27 +0200 Subject: [PATCH] Make task and project names buttons in the all tasks table (#547) Co-Authored-By: Claude Sonnet 5.5 --- .../tables/columns/AllTasksColumns.test.tsx | 63 +++++++++++++++++++ src/tasks/tables/columns/AllTasksColumns.tsx | 27 +++++++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/tasks/tables/columns/AllTasksColumns.test.tsx diff --git a/src/tasks/tables/columns/AllTasksColumns.test.tsx b/src/tasks/tables/columns/AllTasksColumns.test.tsx new file mode 100644 index 00000000..ec855cac --- /dev/null +++ b/src/tasks/tables/columns/AllTasksColumns.test.tsx @@ -0,0 +1,63 @@ +import React from "react" +import { describe, expect, test, vi } from "vitest" +import { cleanup, render, screen } from "@testing-library/react" + +// The generated route helpers only exist after a Blitz build, so stand in for the two used here +vi.mock("@blitzjs/next", () => ({ + Routes: { + ShowTaskPage: ({ projectId, taskId }: { projectId: number; taskId: number }) => + `/projects/${projectId}/tasks/${taskId}`, + ShowProjectPage: ({ projectId }: { projectId: number }) => `/projects/${projectId}`, + }, +})) + +// Next's Link needs a router to render; a plain anchor is enough to check where it points +vi.mock("next/link", () => ({ + default: ({ href, children, ...rest }: any) => ( + + {children} + + ), +})) + +import { AllTasksColumns } from "./AllTasksColumns" + +const row = { + name: "Collect data", + projectName: "Big Study", + view: { taskId: 3, projectId: 9 }, +} + +const renderCell = (accessorKey: string, value: string) => { + const column: any = AllTasksColumns.find((col: any) => col.accessorKey === accessorKey) + render(column.cell({ getValue: () => value, row: { original: row } })) +} + +describe("AllTasksColumns", () => { + test("the task name links to the task page", () => { + renderCell("name", "Collect data") + expect(screen.getByRole("link", { name: "Collect data" }).getAttribute("href")).toBe( + "/projects/9/tasks/3" + ) + }) + + test("they are buttons: primary for the task, secondary for the project", () => { + renderCell("name", "Collect data") + expect(screen.getByRole("link", { name: "Collect data" }).className).toContain("btn-primary") + cleanup() + renderCell("projectName", "Big Study") + expect(screen.getByRole("link", { name: "Big Study" }).className).toContain("btn-secondary") + }) + + test("a long name is shortened on the button but kept in full as a tooltip", () => { + const longName = "A very long task name that would not fit" + renderCell("name", longName) + const link = screen.getByRole("link", { name: "A very long task nam..." }) + expect(link.getAttribute("title")).toBe(longName) + }) + + test("the project name links to the project page", () => { + renderCell("projectName", "Big Study") + expect(screen.getByRole("link", { name: "Big Study" }).getAttribute("href")).toBe("/projects/9") + }) +}) diff --git a/src/tasks/tables/columns/AllTasksColumns.tsx b/src/tasks/tables/columns/AllTasksColumns.tsx index 0cbabf96..d1ecb1ae 100644 --- a/src/tasks/tables/columns/AllTasksColumns.tsx +++ b/src/tasks/tables/columns/AllTasksColumns.tsx @@ -10,6 +10,9 @@ import { createDateTextFilter } from "src/core/utils/tableFilters" // Column helper const columnHelperAll = createColumnHelper() +// Long names are shortened so the button keeps its height (same rule as the project tasks table) +const truncateName = (name: string) => (name.length > 20 ? `${name.slice(0, 20)}...` : name) + const dueDateTextFilter = createDateTextFilter({ emptyLabel: "no due date" }) // ColumnDefs @@ -17,7 +20,19 @@ export const AllTasksColumns = [ columnHelperAll.accessor("name", { enableColumnFilter: true, enableSorting: true, - cell: (info) => {info.getValue()}, + cell: (info) => { + const { taskId, projectId } = info.row.original.view + const name = info.getValue() + return ( + + {truncateName(name)} + + ) + }, header: "Name", meta: { filterVariant: "text", @@ -26,7 +41,15 @@ export const AllTasksColumns = [ columnHelperAll.accessor("projectName", { enableColumnFilter: true, enableSorting: true, - cell: (info) => {info.getValue()}, + cell: (info) => ( + + {truncateName(info.getValue())} + + ), header: "Project", meta: { filterVariant: "select",