From 96dbe0920d535e1bc29d4fd90d36ac2ae57166a6 Mon Sep 17 00:00:00 2001 From: Julie Muzina Date: Thu, 29 Jan 2026 15:00:17 -0500 Subject: [PATCH] fix(MainTable): Tables can be sorted by keyboard --- src/components/MainTable/MainTable.test.tsx | 64 +++++++++++++++++++-- src/components/MainTable/MainTable.tsx | 2 +- src/components/TableHeader/TableHeader.tsx | 14 ++++- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/components/MainTable/MainTable.test.tsx b/src/components/MainTable/MainTable.test.tsx index ff3a70292..f98b55993 100644 --- a/src/components/MainTable/MainTable.test.tsx +++ b/src/components/MainTable/MainTable.test.tsx @@ -197,7 +197,9 @@ describe("MainTable", () => { ); await userEvent.click( - screen.getByRole("columnheader", { name: "Status" }), + within(screen.getByRole("columnheader", { name: "Status" })).getByRole( + "button", + ), ); // The status should now be ascending. expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( @@ -213,7 +215,9 @@ describe("MainTable", () => { ); await userEvent.click( - screen.getByRole("columnheader", { name: "Status" }), + within(screen.getByRole("columnheader", { name: "Status" })).getByRole( + "button", + ), ); // The status should now be descending. expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( @@ -229,7 +233,9 @@ describe("MainTable", () => { ); await userEvent.click( - screen.getByRole("columnheader", { name: "Status" }), + within(screen.getByRole("columnheader", { name: "Status" })).getByRole( + "button", + ), ); // The status be back to the original order. expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( @@ -249,7 +255,9 @@ describe("MainTable", () => { render(); const rowItems = screen.getAllByRole("row"); await userEvent.click( - screen.getByRole("columnheader", { name: "Status" }), + within(screen.getByRole("columnheader", { name: "Status" })).getByRole( + "button", + ), ); const expectedOrder = ["Idle", "Ready", "Waiting"]; @@ -260,6 +268,14 @@ describe("MainTable", () => { ); } + // Non-sortable columns don't have sort buttons. + expect( + within(screen.getByRole("columnheader", { name: "RAM" })).queryByRole( + "button", + ), + ).not.toBeInTheDocument(); + + // Clicking a column header, not a sort button. await userEvent.click(screen.getByRole("columnheader", { name: "RAM" })); // The status should not change for (let i = 1; i < 4; i++) { @@ -277,6 +293,46 @@ describe("MainTable", () => { } }); + it("can be sorted with the keyboard", async () => { + render(); + const rowItems = screen.getAllByRole("row"); + // Check the initial status order. + expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( + "Ready", + ); + + expect(within(rowItems[2]).getByRole("rowheader").textContent).toBe( + "Waiting", + ); + + expect(within(rowItems[3]).getByRole("rowheader").textContent).toBe( + "Idle", + ); + + await userEvent.tab(); + expect( + within(screen.getByRole("columnheader", { name: "Status" })).getByRole( + "button", + ), + ).toHaveFocus(); + await userEvent.keyboard("{Enter}"); + // The status should now be ascending. + expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( + "Idle", + ); + // You should also be able to sort with the Spacebar. + await userEvent.keyboard(" "); + // The status should now be descending. + expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( + "Waiting", + ); + await userEvent.keyboard("{Enter}"); + // The status be back to the original order. + expect(within(rowItems[1]).getByRole("rowheader").textContent).toBe( + "Ready", + ); + }); + it("can set a default sort", () => { render( void; }, HTMLProps >; @@ -19,11 +21,21 @@ export type Props = PropsWithSpread< const TableHeader = ({ children, sort, + onSort, ...props }: Props): React.JSX.Element => { + const headerContents = () => + sort && onSort ? ( + + ) : ( + children + ); + return ( - {children} + {headerContents()} ); };