diff --git a/web/src/components/PageBack.test.tsx b/web/src/components/PageBack.test.tsx index 149391e1..c6c8108c 100644 --- a/web/src/components/PageBack.test.tsx +++ b/web/src/components/PageBack.test.tsx @@ -1,7 +1,7 @@ import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router"; -import { describe, expect, it, vi } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ navigate: vi.fn(), @@ -18,6 +18,11 @@ vi.mock("react-router", async () => { import PageBack from "./PageBack"; describe("PageBack", () => { + afterEach(() => { + mocks.navigate.mockClear(); + window.history.replaceState(null, ""); + }); + it("renders a button with the default 'Go back' aria-label", () => { render( @@ -38,8 +43,21 @@ describe("PageBack", () => { expect(screen.getByRole("button", { name: "Return to library" })).toBeInTheDocument(); }); - it("calls navigate(-1) on click", async () => { - mocks.navigate.mockClear(); + it("falls back to the default route when there is no router history", async () => { + render( + + + , + ); + + await userEvent.click(screen.getByRole("button", { name: "Go back" })); + + expect(mocks.navigate).toHaveBeenCalledTimes(1); + expect(mocks.navigate).toHaveBeenCalledWith("/"); + }); + + it("uses browser history when a router history entry is available", async () => { + window.history.replaceState({ idx: 1 }, ""); render( @@ -52,6 +70,20 @@ describe("PageBack", () => { expect(mocks.navigate).toHaveBeenCalledWith(-1); }); + it("uses the explicit target when history preference is disabled", async () => { + window.history.replaceState({ idx: 1 }, ""); + render( + + + , + ); + + await userEvent.click(screen.getByRole("button", { name: "Go back" })); + + expect(mocks.navigate).toHaveBeenCalledTimes(1); + expect(mocks.navigate).toHaveBeenCalledWith("/collections"); + }); + it("applies the documented positioning and glass styling", () => { render( diff --git a/web/src/components/PageBack.tsx b/web/src/components/PageBack.tsx index 345091ce..a2814fd3 100644 --- a/web/src/components/PageBack.tsx +++ b/web/src/components/PageBack.tsx @@ -1,8 +1,10 @@ import { ChevronLeft } from "lucide-react"; -import { useNavigate } from "react-router"; +import { type To, useNavigate } from "react-router"; interface PageBackProps { label?: string; + to?: To; + preferHistory?: boolean; /** * When true, pins the button to the viewport on lg+ so it stays visible * while scrolling. The offset matches the app sidebar (260px) so the @@ -11,16 +13,33 @@ interface PageBackProps { floating?: boolean; } -export default function PageBack({ label = "Go back", floating = false }: PageBackProps) { +export default function PageBack({ + label = "Go back", + to = "/", + preferHistory = true, + floating = false, +}: PageBackProps) { const navigate = useNavigate(); const position = floating ? "absolute top-4 left-2 sm:top-6 lg:fixed lg:left-[268px]" : "absolute top-4 left-2 sm:top-6"; + + function goBack() { + const historyIndex = window.history.state?.idx; + + if (preferHistory && typeof historyIndex === "number" && historyIndex > 0) { + navigate(-1); + return; + } + + navigate(to); + } + return (