import { afterAll, beforeAll, describe, expect, mock, test } from "bun:test"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; import { act, useState } from "react"; import { createRoot, type Root } from "react-dom/client"; import { renderToStaticMarkup } from "react-dom/server"; import { ListRow, RowList, type RowListSort } from "./list-row"; import { LogoTile } from "./logo-tile"; import { RelativeTime } from "./relative-time"; beforeAll(() => { GlobalRegistrator.register(); ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean } ).IS_REACT_ACT_ENVIRONMENT = true; }); afterAll(() => { GlobalRegistrator.unregister(); }); async function mount(node: React.ReactNode): Promise<{ container: HTMLElement; root: Root }> { const container = document.createElement("div"); document.body.append(container); const root = createRoot(container); await act(async () => { root.render(node); }); return { container, root }; } const NOW = new Date("2026-09-26T11:48:00Z"); describe("ListRow", () => { test("the whole row is one button named by the title and the state in words", async () => { const onOpen = mock(() => {}); const { container, root } = await mount( , ); const buttons = container.querySelectorAll("button"); expect(buttons).toHaveLength(1); const button = buttons[0]!; const name = (button.getAttribute("aria-labelledby") ?? "") .split(" ") .map((id) => document.getElementById(id)?.textContent) .join(" "); expect(name).toBe("Linear Needs reconnect"); const description = document.getElementById(button.getAttribute("aria-describedby")!); expect(description?.textContent).toBe("Sign in again to keep using Linear."); await act(async () => button.click()); expect(onOpen).toHaveBeenCalledTimes(1); await act(async () => root.unmount()); container.remove(); }); test("a link row keeps its router click handler, so a plain click never loads the page", async () => { // Regression: /schedules rows passed `href` and `linkProps.onClick` (the // router) but no `onOpen`, and the absent `onOpen` replaced the handler. const go = mock(() => {}); const { container, root } = await mount( { event.preventDefault(); go(); }, }} /> , ); const link = container.querySelector("a[data-row-action]")!; expect(link.getAttribute("href")).toBe("/workspaces/ws-1/schedules?taskId=task-1"); const click = new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 }); await act(async () => { link.dispatchEvent(click); }); expect(go).toHaveBeenCalledTimes(1); expect(click.defaultPrevented).toBe(true); await act(async () => root.unmount()); container.remove(); }); test("a link row runs onOpen first and skips linkProps once onOpen handled the click", async () => { const onOpen = mock((event: React.MouseEvent) => event.preventDefault()); const linkClick = mock(() => {}); const { container, root } = await mount( , ); const link = container.querySelector("a[data-row-action]")!; await act(async () => link.click()); expect(onOpen).toHaveBeenCalledTimes(1); expect(linkClick).not.toHaveBeenCalled(); await act(async () => root.unmount()); container.remove(); }); test("a click on a time inside the row still opens it", async () => { const onOpen = mock(() => {}); const { container, root } = await mount( ]} onOpen={onOpen} /> , ); const time = container.querySelector("time")!; // Rows are the tab stop: times inside them are not. expect(time.hasAttribute("tabindex")).toBe(false); await act(async () => time.click()); expect(onOpen).toHaveBeenCalledTimes(1); await act(async () => root.unmount()); container.remove(); }); test("a disabled row says why and is not a button", () => { const html = renderToStaticMarkup( {}} /> , ); expect(html).not.toContain(" { const html = renderToStaticMarkup( Run now} onOpen={() => {}} /> , ); expect(html).toContain('aria-label="More actions for Check AWS cost anomalies"'); expect(html.match(/