diff --git a/src/pages/Dump.tsx b/src/pages/Dump.tsx index 681b44f4..2e07f656 100644 --- a/src/pages/Dump.tsx +++ b/src/pages/Dump.tsx @@ -79,7 +79,7 @@ export function Dump(): React.JSX.Element { const navigate = useNavigate(); // Taken once, since it stays in the history entry, where a reload or Back // would show that snapshot forever. It is cleared from there below. - const [handedOver] = useState( + const [handedOver, setHandedOver] = useState( () => (location.state as { dump?: Dump } | null)?.dump ?? null, ); const preloaded = isAt(handedOver, username, slug) ? handedOver : null; @@ -95,6 +95,19 @@ export function Dump(): React.JSX.Element { preloaded ? { status: "loaded", dump: preloaded } : { status: "loading" }, ); const loadedDump = dumpState.status === "loaded" ? dumpState.dump : null; + // The path last rendered. One that names another dump, as a related dump's + // link does, drops the dump on screen in the same render: left for the + // fetch below to drop, useCanonicalPath would first put its path back in + // the URL. The dump handed over goes with it, so that coming back to its + // path fetches it. + const [target, setTarget] = useState({ username, slug }); + if (target.username !== username || target.slug !== slug) { + setTarget({ username, slug }); + if (!isAt(loadedDump, username, slug)) { + setHandedOver(null); + setDumpState({ status: "loading" }); + } + } useCanonicalPath(loadedDump && dumpUrl(loadedDump)); // The dump on screen, which a path it was just given, by useCanonicalPath or // a slug edited elsewhere, needn't fetch again. diff --git a/src/test/pages/Dump.test.tsx b/src/test/pages/Dump.test.tsx index 92c6294f..924bb67c 100644 --- a/src/test/pages/Dump.test.tsx +++ b/src/test/pages/Dump.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, test, vi } from "vitest"; import { act, screen, waitFor, within } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; -import { Route } from "react-router"; +import { Link, Route } from "react-router"; import { Dump } from "../../pages/Dump.tsx"; import { DumpEdit } from "../../pages/DumpEdit.tsx"; import { RestrictedLoggedIn } from "../../pages/RestrictedLoggedIn.tsx"; @@ -225,6 +225,73 @@ describe("Dump page", () => { expect(await screen.findByText("Best of")).toBeInTheDocument(); }); + test("a related dump's link leads to that dump", async () => { + const first = dumpRoutes(); + const second = makeDump({ + id: "d2", + slug: "next-find", + title: "Next find", + }); + route( + "GET /api/users/:u/dumps/:slug", + ({ params }) => wire(params.slug === second.slug ? second : first), + ); + route( + "GET /api/dumps/:id/related", + ({ params }) => wire(params.id === first.id ? [second] : []), + ); + const { user } = openDump(); + await user.click(await screen.findByRole("link", { name: "Next find" })); + expect(await screen.findByRole("heading", { name: "Next find", level: 1 })) + .toBeInTheDocument(); + expect(currentPath()).toBe("/@owner/dumps/next-find"); + // Its parts are the new dump's. + await waitFor(() => + expect(screen.queryByRole("heading", { name: "Related" })).not + .toBeInTheDocument() + ); + }); + + test("a dump handed over is fetched when come back to", async () => { + const first = dumpRoutes(); + const second = makeDump({ + id: "d2", + slug: "next-find", + title: "Next find", + }); + route( + "GET /api/users/:u/dumps/:slug", + ({ params }) => wire(params.slug === second.slug ? second : first), + ); + route( + "GET /api/dumps/:id/related", + ({ params }) => wire([params.id === first.id ? second : first]), + ); + const user = userEvent.setup(); + renderApp(, { + route: "/start", + path: "/:handle/dumps/:slug", + extraRoutes: ( + open + } + /> + ), + }); + FakeWebSocket.last.welcome(); + await user.click(screen.getByRole("link", { name: "open" })); + await user.click(await screen.findByRole("link", { name: "Next find" })); + await user.click(await screen.findByRole("link", { name: "A find" })); + expect(await screen.findByRole("heading", { name: "A find", level: 1 })) + .toBeInTheDocument(); + expect(calls("GET /api/users/:u/dumps/:slug").map((c) => c.path)).toEqual([ + "/api/users/@owner/dumps/next-find", + "/api/users/@owner/dumps/a-find", + ]); + }); + test("a live update of this dump is applied", async () => { const d = dumpRoutes(); const { socket } = openDump();