+ {featured ? (
+
+
+ Best match
+
+ ) : null}
{rank != null ? (
diff --git a/src/lib/therapy-ranking.ts b/src/lib/therapy-ranking.ts
index 0b2ce340b2..93ac8a6c94 100644
--- a/src/lib/therapy-ranking.ts
+++ b/src/lib/therapy-ranking.ts
@@ -16,6 +16,11 @@ const normalize = (value: string | null | undefined) =>
.replace(/\s+/g, " ")
.trim();
+/** True only when the scorer can derive at least one searchable token. */
+export function hasSearchableTherapyQuery(query: string | null | undefined): boolean {
+ return Boolean(normalize(query));
+}
+
/**
* The single Therapy ranking contract used by both the dedicated catalogue and
* universal discovery. Keeping one scorer prevents a search handoff from
diff --git a/tests/recommend-screen.dom.test.tsx b/tests/recommend-screen.dom.test.tsx
index c1a74bd199..8a724c944a 100644
--- a/tests/recommend-screen.dom.test.tsx
+++ b/tests/recommend-screen.dom.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen } from "@testing-library/react";
+import { render, screen, within } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import type { Therapy } from "@/components/therapy-compass/data/types";
@@ -83,6 +83,7 @@ describe("Recommend screen", () => {
const cards = document.querySelectorAll("[data-therapy-result-card]");
expect(cards).toHaveLength(2);
expect(cards[0]).toHaveAttribute("data-therapy-result-featured");
+ expect(within(cards[0] as HTMLElement).getByText("Best match")).toBeInTheDocument();
const featuredOpen = cards[0]?.querySelector('[aria-label="Open record"]');
expect(featuredOpen).toHaveClass("bg-[color:var(--command)]");
expect(screen.getAllByText("WHY MATCHED").length).toBeGreaterThan(0);
diff --git a/tests/therapy-compass-search-filters.dom.test.tsx b/tests/therapy-compass-search-filters.dom.test.tsx
index 6a2f6de792..4d6b70c795 100644
--- a/tests/therapy-compass-search-filters.dom.test.tsx
+++ b/tests/therapy-compass-search-filters.dom.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen } from "@testing-library/react";
+import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
@@ -50,6 +50,18 @@ vi.mock("@/components/therapy-compass/bindings", () => ({
toggleBriefOnly,
clearSearch,
clearSearchFilters,
+ isInCompare: () => false,
+ open: vi.fn(),
+ openSheet: vi.fn(),
+ toggleCompare: vi.fn(),
+ }),
+}));
+
+vi.mock("@/components/account-data-provider", () => ({
+ useAccountData: () => ({
+ isAuthenticated: false,
+ isSaved: () => false,
+ setFavourite: vi.fn(),
}),
}));
@@ -183,4 +195,42 @@ describe("therapy-compass search filter contract adoption", () => {
await user.click(screen.getByTestId("therapy-filter-trigger-phone"));
expect(screen.queryByTestId("therapy-filter-panel-clear")).not.toBeInTheDocument();
});
+
+ it("visually identifies only the first ranked result as the best match", () => {
+ bindingsState.query = "anxiety";
+ bindingsState.queryMatches = [therapy()];
+ bindingsState.searchResults = [
+ therapy({ slug: "first", name: "First ranked therapy" }),
+ therapy({ slug: "second", name: "Second ranked therapy" }),
+ ];
+
+ render();
+
+ const cards = document.querySelectorAll("[data-therapy-result-card]");
+ expect(cards).toHaveLength(2);
+ expect(cards[0]).toHaveAttribute("data-therapy-result-featured");
+ expect(cards[1]).not.toHaveAttribute("data-therapy-result-featured");
+ expect(within(cards[0] as HTMLElement).getByText("Best match")).toBeInTheDocument();
+ expect(within(cards[1] as HTMLElement).queryByText("Best match")).not.toBeInTheDocument();
+ });
+
+ it("does not imply a best match when the unqueried catalogue is alphabetical", () => {
+ bindingsState.searchResults = [therapy({ slug: "first" })];
+
+ render();
+
+ expect(document.querySelector("[data-therapy-result-featured]")).toBeNull();
+ expect(screen.queryByText("Best match")).not.toBeInTheDocument();
+ });
+
+ it("does not imply a best match for a punctuation-only query", () => {
+ bindingsState.query = "!!!";
+ bindingsState.queryMatches = [therapy({ slug: "first" }), therapy({ slug: "second" })];
+ bindingsState.searchResults = [therapy({ slug: "first" }), therapy({ slug: "second" })];
+
+ render();
+
+ expect(document.querySelector("[data-therapy-result-featured]")).toBeNull();
+ expect(screen.queryByText("Best match")).not.toBeInTheDocument();
+ });
});
diff --git a/tests/ui-route-coverage.spec.ts b/tests/ui-route-coverage.spec.ts
index 57703d9c27..c119ecb01f 100644
--- a/tests/ui-route-coverage.spec.ts
+++ b/tests/ui-route-coverage.spec.ts
@@ -327,6 +327,9 @@ test.describe("previously uncovered production routes", () => {
const card = page.locator("[data-therapy-result-card]").first();
await expect(card).toBeVisible({ timeout: 30_000 });
+ await expect(card).toHaveAttribute("data-therapy-result-featured", "");
+ await expect(card.getByText("Best match", { exact: true })).toBeVisible();
+ await expect(page.locator("[data-therapy-result-highlight]")).toHaveCount(1);
for (const width of [320, 390, 639, 768, 1440, 1920]) {
await page.setViewportSize({ width, height: width < 768 ? 844 : 900 });
@@ -334,6 +337,7 @@ test.describe("previously uncovered production routes", () => {
const layout = await card.evaluate((element) => {
const bounds = element.getBoundingClientRect();
+ const borderLeft = Number.parseFloat(getComputedStyle(element).borderLeftWidth);
const copy = element.querySelector("[data-therapy-result-copy]")!.getBoundingClientRect();
const evidence = element.querySelector("[data-therapy-result-evidence]")!.getBoundingClientRect();
const actions = element.querySelector("[data-therapy-result-actions]")!;
@@ -349,6 +353,7 @@ test.describe("previously uncovered production routes", () => {
});
return {
card: { left: bounds.left, right: bounds.right },
+ borderLeft,
copyLeft: copy.left,
evidence: { left: evidence.left, right: evidence.right },
buttons,
@@ -356,9 +361,12 @@ test.describe("previously uncovered production routes", () => {
});
if (width < 640) {
- expect(Math.abs(layout.evidence.left - layout.card.left), `${width}px evidence left edge`).toBeLessThanOrEqual(
- 1,
- );
+ // Featured cards carry the intentional 3px best-match accent edge.
+ // The evidence panel remains full-bleed inside that border.
+ expect(
+ Math.abs(layout.evidence.left - (layout.card.left + layout.borderLeft)),
+ `${width}px evidence left edge`,
+ ).toBeLessThanOrEqual(1);
expect(
Math.abs(layout.card.right - layout.evidence.right),
`${width}px evidence right edge`,