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();