diff --git a/packages/wouter/src/use-hash-location.js b/packages/wouter/src/use-hash-location.js index 130b886..79dee1b 100644 --- a/packages/wouter/src/use-hash-location.js +++ b/packages/wouter/src/use-hash-location.js @@ -51,4 +51,11 @@ export const useHashLocation = ({ ssrPath = "/" } = {}) => [ navigate, ]; -useHashLocation.hrefs = (href) => "#" + href; +// Search stays before the hash, matching `navigate`. A query inside the hash +// (`#/settings?tab=1`) is not a separate search and fails route matching. +useHashLocation.hrefs = (href) => { + const queryAt = href.indexOf("?"); + return queryAt < 0 + ? "#" + href + : href.slice(queryAt) + "#" + href.slice(0, queryAt); +}; diff --git a/packages/wouter/test/use-hash-location.test.tsx b/packages/wouter/test/use-hash-location.test.tsx index 59856f5..3b743eb 100644 --- a/packages/wouter/test/use-hash-location.test.tsx +++ b/packages/wouter/test/use-hash-location.test.tsx @@ -2,7 +2,7 @@ import { test, expect, mock } from "bun:test"; import { renderHook, render, act, fireEvent } from "@testing-library/react"; import { renderToStaticMarkup } from "react-dom/server"; -import { Router, Route, useLocation, Link } from "../src/index.js"; +import { Router, Route, useLocation, useSearch, Link } from "../src/index.js"; import { useHashLocation } from "../src/use-hash-location.js"; import { waitForHashChangeEvent } from "./test-utils"; @@ -377,3 +377,37 @@ test("uses string URLs as hashchange event payload", () => { removeEventListener("hashchange", hashChangeFn); }); + +test("Link href keeps a query before the hash so opening it matches the route", () => { + history.replaceState(null, "", "/app#/home"); + + const { getByRole, unmount } = render( + + Settings + + ); + + const href = getByRole("link", { name: "Settings" }).getAttribute("href"); + expect(href).toBe("?tab=profile#/settings"); + unmount(); + + const opened = new URL(href!, "https://example.test/app"); + history.replaceState(null, "", opened.pathname + opened.search + opened.hash); + + const Settings = () => { + const search = useSearch(); + return <>Settings page {search}; + }; + + const { getByText, queryByText } = render( + + + + + Home + + ); + + expect(queryByText("Home")).not.toBeInTheDocument(); + expect(getByText("Settings page tab=profile")).toBeInTheDocument(); +});