Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions src/lib/mode-secondary-navigation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { appModeHomeHref, type AppModeId } from "@/lib/app-modes";
import { appModeHomeHref, appModeSelectionHref, type AppModeId } from "@/lib/app-modes";

export type ModeSecondaryNavigationEntry = {
id: string;
Expand Down Expand Up @@ -34,23 +34,23 @@ export const modeSecondaryNavigationRegistry = {
forms: [],
favourites: [],
differentials: [
{ id: "search", label: "Search", href: appModeHomeHref("differentials", { focus: true }) },
{ id: "search", label: "Search", href: appModeSelectionHref("differentials", { focus: true }) },
{ id: "diagnoses", label: "Diagnoses", href: "/differentials/diagnoses" },
{ id: "presentations", label: "Presentations", href: "/differentials/presentations" },
{ id: "compare", label: "Compare", href: "/differentials/compare" },
],
dsm: [
{ id: "search", label: "Search", href: appModeHomeHref("dsm", { focus: true }) },
{ id: "search", label: "Search", href: appModeSelectionHref("dsm", { focus: true }) },
{ id: "compare", label: "Compare", href: "/dsm/compare" },
],
specifiers: [
{ id: "search", label: "Find", href: appModeHomeHref("specifiers", { focus: true }) },
{ id: "search", label: "Find", href: appModeSelectionHref("specifiers", { focus: true }) },
{ id: "builder", label: "Build", href: "/specifiers/builder" },
{ id: "compare", label: "Compare", href: "/specifiers/compare" },
{ id: "map", label: "Map", href: "/specifiers/map" },
],
formulation: [
{ id: "search", label: "Find", href: appModeHomeHref("formulation", { focus: true }) },
{ id: "search", label: "Find", href: appModeSelectionHref("formulation", { focus: true }) },
{ id: "builder", label: "Build", href: "/formulation/builder" },
{ id: "compare", label: "Compare", href: "/formulation/compare" },
{ id: "map", label: "Map", href: "/formulation/map" },
Expand Down Expand Up @@ -252,10 +252,9 @@ export function modeSecondaryNavigationHref(params: {

if (modeId === "differentials") {
const entries: Array<readonly [string, string]> = query ? [["q", query]] : [];
// Returning to Search with a carried query must reopen the results view
// (`run=1`), not the empty mode home — even when the previous tab lacked run.
if (itemId === "search" && query) entries.push(["run", "1"]);
else if (itemId === "search" && currentSearchParams.get("run") === "1") entries.push(["run", "1"]);
// Search now returns to the shared universal composer. Keep a prior query as
// an editable draft, but deliberately drop `run=1`: that flag would submit
// immediately and the legacy redirect would send the user back here.
// Compare (and other in-mode tabs) reuse URL-backed selection so ticks on
// search survive ModeNav handoff without a second client store.
if (currentSearchParams.get("ids")) {
Expand All @@ -267,7 +266,7 @@ export function modeSecondaryNavigationHref(params: {
if (modeId === "dsm") {
if (itemId === "search" && query) {
return navigationHrefWithParams(
appModeHomeHref("dsm", { query, focus: true, run: currentSearchParams.get("run") === "1" }),
appModeSelectionHref("dsm", { query, focus: true }),
currentSearchParams.get("ids") ? [["ids", currentSearchParams.get("ids") ?? ""]] : [],
);
}
Expand Down
31 changes: 26 additions & 5 deletions tests/mode-secondary-navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ const expectedLabels: Record<AppModeId, string[]> = {
factsheets: ["Topics", "Search"],
};

describe("shared search destinations", () => {
it.each([
["differentials", "/?mode=differentials&focus=1"],
["dsm", "/?mode=dsm&focus=1"],
["specifiers", "/?mode=specifiers&focus=1"],
["formulation", "/?mode=formulation&focus=1"],
] as const)("routes the %s search tab to universal search with its mode selected", (modeId, href) => {
expect(modeSecondaryNavigationRegistry[modeId][0]).toMatchObject({ id: "search", href });
});
});

const cleanLandingPath: Record<AppModeId, string> = {
answer: "/",
documents: "/",
Expand Down Expand Up @@ -145,6 +156,15 @@ describe("mode secondary navigation registry", () => {
});

it("translates compatible workflow selection state into each destination URL", () => {
expect(
modeSecondaryNavigationHref({
modeId: "dsm",
itemId: "search",
href: "/?mode=dsm&focus=1",
currentSearchParams: new URLSearchParams("q=bipolar&run=1&ids=one%2Ctwo"),
}),
).toBe("/?mode=dsm&q=bipolar&focus=1&ids=one%2Ctwo");

expect(
modeSecondaryNavigationHref({
modeId: "specifiers",
Expand Down Expand Up @@ -181,16 +201,17 @@ describe("mode secondary navigation registry", () => {
}),
).toBe("/differentials/presentations?q=confusion&ids=delirium%2Cdementia");

// Search restores the last query and re-opens results even when the prior
// tab URL did not carry run=1 (e.g. Diagnoses / Presentations browse).
// Search returns to the universal composer with the mode selected and the
// prior query as a draft. It must not carry run=1, which would re-open the
// old mode-owned results route through the legacy redirect.
expect(
modeSecondaryNavigationHref({
modeId: "differentials",
itemId: "search",
href: "/differentials?focus=1",
currentSearchParams: new URLSearchParams("q=confusion&ids=delirium"),
href: "/?mode=differentials&focus=1",
currentSearchParams: new URLSearchParams("q=confusion&run=1&ids=delirium"),
}),
).toBe("/differentials?focus=1&q=confusion&run=1&ids=delirium");
).toBe("/?mode=differentials&focus=1&q=confusion&ids=delirium");

// Search is the CURRENT tab on /factsheets/search, so its own link must not
// reset what you are looking at. `run` is carried with the query because
Expand Down
1 change: 1 addition & 0 deletions tests/page-secondary-navigation.dom.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ describe("PageSecondaryNavigation", () => {
expect(bar).toHaveAttribute("aria-label", "Specifiers pages");
expect(screen.getByRole("link", { name: "Compare" })).toHaveAttribute("aria-current", "page");
expect(screen.getByRole("link", { name: "Build" })).toHaveAttribute("href", "/specifiers/builder");
expect(screen.getByRole("link", { name: "Find" })).toHaveAttribute("href", "/?mode=specifiers&focus=1");
// Registry order is load-bearing: only the first two slots survive the
// narrowest band, so Find and Build must be the ones that stay.
expect([...bar.querySelectorAll("li a")].map((link) => link.textContent)).toEqual([
Expand Down
Loading