Skip to content
3 changes: 3 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export default defineConfig({
grepInvert: mockupTag,
use: {
...devices["Desktop Chrome"],
contextOptions: {
reducedMotion: "no-preference",
Comment on lines +59 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep reduced motion scoped to the transition check

Because the required Production UI lane runs npm run test:e2e:pr, which selects --project=chromium, setting the Chromium project to reducedMotion: "no-preference" makes every production Chromium spec run with animations enabled again. That contradicts the suite-level guard immediately above, which documents known click races in ui-stress/ui-smoke; a single dock-transition assertion should opt out locally with page.emulateMedia(...) instead of weakening the whole required UI gate.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex fix

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Committed 1034f76 (fix: scope reduced-motion override to dock test) on branch work.
  • Restored the Chromium Playwright project to inherit the suite-wide reduced-motion configuration instead of overriding it with no-preference, keeping production UI specs protected from animation-related click races. playwright.config.tsL45-L60
  • Scoped the animation opt-out to the single bottom search dock transition assertion with page.emulateMedia({ reducedMotion: "no-preference" }), so that test can still verify the transform transition locally. tests/ui-tools.spec.tsL1016-L1022
  • Created PR metadata with the make_pr tool using title fix: scope reduced-motion override to dock test.

Testing

  • npm run workflow:flightplan -- --write-evidence --files playwright.config.ts
  • git diff --check
  • ⚠️ npm run test:focused -- --files tests/ui-tools.spec.ts,playwright.config.ts — focused runner refused test/config path selection and instructed running the full unit suite.
  • ⚠️ npm run test -- --run tests/ui-tools.spec.ts — could not run because node_modules/vitest/vitest.mjs is missing in this workspace; current runtime is Node v20.20.2.

Worktree

  • Current branch: work.
  • Worktree is clean after commit.

View task →

},
...(chromiumExecutablePath ? { launchOptions: { executablePath: chromiumExecutablePath } } : {}),
},
},
Expand Down
67 changes: 30 additions & 37 deletions tests/audit-content-services-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,61 +107,52 @@
const transport = getFormRecord("transport-crisis-form");

expect(transport).not.toBeNull();
expect(transport).toMatchObject({

Check failure on line 110 in tests/audit-content-services-regressions.test.ts

View workflow job for this annotation

GitHub Actions / Unit coverage

[node] tests/audit-content-services-regressions.test.ts > content and services audit regressions > keeps seeded form provenance explicitly unverified and free of invented source facts

AssertionError: expected { Object (slug, title, ...) } to match object { verification: { …(2) }, …(1) } (66 matching properties omitted from actual) - Expected + Received { "source": { - "label": "Transport form workflow entry", - "status": "Local source confirmation required", + "label": "Office of the Chief Psychiatrist WA — approved MHA 2014 forms", + "status": "Source checked", }, "verification": { - "confidence": "Unknown", + "confidence": "Medium", "locallyVerified": false, }, } ❯ tests/audit-content-services-regressions.test.ts:110:23
verification: { locallyVerified: false, confidence: "Medium" },
verification: { locallyVerified: false, confidence: "Unknown" },
source: {
label: "Office of the Chief Psychiatrist WA — approved MHA 2014 forms",
status: "Source checked",
label: "Transport form workflow entry",
status: "Local source confirmation required",
},
Comment thread
BigSimmo marked this conversation as resolved.
});
expect(transport?.source).toHaveProperty("url");
expect(transport?.source).not.toHaveProperty("url");
expect(transport?.source).not.toHaveProperty("published");
expect(transport?.source).toHaveProperty("reviewed");
expect(transport?.source).not.toHaveProperty("reviewed");
expect(transport?.source).not.toHaveProperty("pages");
expect(transport?.source).not.toHaveProperty("pageCount");
expect(transport?.source).not.toHaveProperty("reviewDue");
expect(JSON.stringify(transport?.source)).toMatch(/Office of the Chief Psychiatrist WA — approved MHA 2014 forms/);
expect(formDetailSource).not.toMatch(/Review due/i);
expect(formDetailSource).not.toMatch(/Admission order|Treatment order|5\(2\)/);
expect(formDetailSource).toContain("Pathway navigation is not available yet");
expect(formDetailSource).toContain("Full pathway unavailable");
expect(formDetailSource).toContain(">Source info</");
expect(JSON.stringify(transport?.source)).not.toMatch(/\.pdf\b|\b\d+\s+pages?\b|\bact sections?\b|\bstatutory\b/i);
expect(formDetailSource).not.toMatch(/\.pdf\b|\b\d+\s+pages?\b|\bAct sections?\b|\bReview due\b/i);
expect(formDetailSource).not.toContain("01 May 2026");
expect(formDetailSource).not.toMatch(/\b(?:1A|3A|4A|4B)\b|5\(2\)|Admission order|Treatment order/);
expect(formDetailSource).not.toMatch(
/Pathway navigation is not available yet|Full pathway unavailable|>Source info</,
);
expect(formDetailSource).toContain("No linked full pathway is available for this record.");
expect(normalizedFormDetailSource).toContain(
'label: "Source currency", value: displayText(form.source?.reviewed, "Review locally")',
'...(hasText(form.source?.reviewed) ? [{ icon: CalendarDays, label: "Source review", value: form.source.reviewed.trim() }] : [])',
);

for (const form of formRecords) {
if (form.source?.url) continue;
expect(form.verification?.locallyVerified, form.slug).toBe(false);
expect(form.verification?.confidence, form.slug).toBe("Unknown");
expect(form.source?.status, form.slug).toMatch(/confirmation required|source checked/i);
expect(form.source?.status, form.slug).toMatch(/confirmation required/i);
expect(form.source?.reviewed, form.slug).toBeUndefined();
}

expect(formsSearchSource).toContain("const sourceSnippetCount = 278;");
expect(formsSearchSource).toContain("const taskCount = 8;");
expect(formsSearchSource).toContain("const pathwayCount = 12;");
expect(formsSearchSource).toContain('["Evidence", sourceSnippetCount]');
expect(formsSearchSource).toContain('["Pathways", pathwayCount]');
expect(formsSearchSource).toContain('["Tasks", taskCount]');
expect(formsSearchSource).toContain("PSOLIS");
expect(formsSearchSource).toContain("Source verified");
expect(formsSearchSource).toContain("Official source");
expect(formsSearchSource).toContain("Aligned to MHA 2014");
expect(formsSearchSource).toContain("Open account setup");
expect(formsSearchSource).toContain("View full pathway");
expect(formsSearchSource).toContain("Filter controls are coming soon");
expect(formsSearchSource).toContain("tagToneClass(chipLabel)");
expect(formsSearchSource).toContain("Title or content match");
expect(formsSearchSource).toContain("Content match in related pathway");
expect(formsSearchSource).toContain("Content match in the forms catalogue");
expect(formsSearchSource).toContain("View all forms (");
expect(formsSearchSource).not.toMatch(
/\b(?:1A|3A|4A|4B)\b|Evidence 278|Pathways 12|Tasks 8|PSOLIS|Source verified|Official source|Aligned to MHA|Open account setup|View full pathway|Filter controls are coming soon/,
);
expect(formsSearchSource).toContain("statusToneClass(chip.tone)");
expect(formsSearchSource).toContain("Title or identifier match");
expect(formsSearchSource).toContain("Match in form record details");
expect(formsSearchSource).toContain("Browse all forms");
expect(formsHomeSource).not.toMatch(/Source verified|Open account setup/);
expect(formsHomeSource).not.toMatch(
/Number, pathway, clock|Maker, clock, copies|Browse pathways|Before, current, parallel, after|starter set of MHA 2014 forms|follow a pathway/,
);
expect(formsHomeSource).toContain("need local confirmation");
expect(formsHomeSource).toContain("Source catalogue reviewed");
expect(formsHomeSource).toContain("Local confirmation required");
expect(formsHomeSource).toContain("form records confirmed");
});

it("does not render negative or text-only source statuses as verified", () => {
Expand Down Expand Up @@ -215,12 +206,14 @@
});

it("claims and renders a form source link only when the record has a URL", () => {
expect(normalizedFormDetailSource).toContain("form.source?.url ? (");
expect(normalizedFormDetailSource).toContain(

Check failure on line 209 in tests/audit-content-services-regressions.test.ts

View workflow job for this annotation

GitHub Actions / Unit coverage

[node] tests/audit-content-services-regressions.test.ts > content and services audit regressions > claims and renders a form source link only when the record has a URL

AssertionError: expected '"use client"; import { useRouter } fr…' to contain '{form.source?.url ? "Source link avai…' - Expected + Received - {form.source?.url ? "Source link available" : "No source link available"} + "use client"; import { useRouter } from "next/navigation"; import { ArrowLeft, Bookmark, BookmarkCheck, CalendarDays, CircleCheck, ChevronRight, Clipboard, ClipboardList, Clock3, Download, ExternalLink, FileText, Info, MapPin, Navigation, Phone, Route, Scale, ShieldCheck, Tag, UserRound, X, CircleX, type LucideIcon, } from "lucide-react"; import { useMemo, useState, type ReactNode } from "react"; import { cn, codeText, floatingControl, metadataPill, pageContainer, primaryControl, textMuted, toneDanger, toneInfo, toneNeutral, toneSuccess, toneWarning, } from "@/components/ui-primitives"; import { FormCodeBadge, splitFormCode } from "@/components/forms/form-code-badge"; import { appModeHomeHref } from "@/lib/app-modes"; import { formCatalogDetails, type FormRecord } from "@/lib/form-ranker"; import type { ServiceChipTone, ServiceContact, ServiceCriterion, ServiceSummaryCard } from "@/lib/service-ranker"; import { readSavedRegistrySlugs, savedFormsStorageKey, writeSavedRegistrySlugs } from "@/lib/saved-registry-storage"; const missingText = "Not listed"; function hasText(value: string | null | undefined): value is string { return Boolean(value && value.trim().length > 0); } function displayText(value: string | null | undefined, fallback = missingText) { return hasText(value) ? value.trim() : fallback; } async function copyText(value: string) { if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(value); return; } catch { // Fall through to the legacy selection path for restricted browser contexts. } } const textArea = document.createElement("textarea"); textArea.value = value; textArea.setAttribute("readonly", ""); textArea.style.position = "fixed"; textArea.style.opacity = "0"; document.body.appendChild(textArea); textArea.select(); try { const copied = document.execCommand?.("copy"); if (copied === false) throw new Error("copy command rejected"); } finally { document.body.removeChild(textArea); } } function chipToneClass(tone: ServiceChipTone | null | undefined) { if (tone === "danger") return toneDanger; if (tone === "info") return toneInfo; if (tone === "warning") return toneWarning; if (tone === "success") return toneSuccess; return toneNeutral; } export function sourceToneClass(form: FormRecord) { const status = form.source?.status?.toLowerCase() ?? ""; if (/required|review|unverified|not verified|unchecked|pending|unknown|confirm/.test(status)) return toneWarning; if (form.verification?.locallyVerified === true) return toneSuccess; return toneNeutral; } function formCode(form: FormRecord) { const details = formCatalogDetails(form); if (details?.form) return details.form; if (form.slug.includes("transport")) return "4A"; if (form.slug.includes("capacity")) return "CAP"; if (form.slug.includes("clozapine")) return "CLZ"; if (form.slug.includes("handover")) return "SAFE"; return form.title .split(/\s+/) .map((part) => part[0]) .join("") .slice(0, 4) .toUpperCase(); } function formShortTitle(form: FormRecord) { const details = formCatalogDetails(form); return details?.form ? `Form ${details.form}` : displayText(form.catalogueLabel, "Form"); } function summaryIcon(card: ServiceSummaryCard) { const label = `${card.id} ${card.label} ${card.title}`.toLowerCase(); const Icon = label.includes("clock") ? Clock3 : label.includes("destination") || label.includes("place") || label.includes("route") ? MapPin : label.includes("authority") || label.includes("maker") ? UserRound : label.includes("criteria") || label.includes("threshold") ? Scale : ClipboardList; return <Icon className="h-5 w-5" aria-hidden />; } function summaryCardsFor(form: FormRecord): ServiceSummaryCard[] { if (form.summaryCards?.length) return form.summaryCards.slice(0, 4); return [ { id: "route", label: "Route", title: "Use pathway", detail: form.route }, { id: "eligibility", label: "Eligibility", title: "Patient fi
'{form.source?.url ? "Source link available" : "No source link available"}',
);
expect(normalizedFormDetailSource).toMatch(/\{form\.source\?\.url \? \( <a href=\{form\.source\.url\}/);
expect(normalizedFormDetailSource).toMatch(
/<a href=\{form\.source\.url\} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-10/,
/<a href=\{form\.source\.url\} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-tap/,
);
expect(formDetailSource.match(/Source link pending/g)).toHaveLength(1);
expect(formDetailSource).toContain("Source link pending");
expect(formDetailSource.match(/Source link available/g)).toHaveLength(1);
expect(formDetailSource).not.toContain("Source link pending");
});
});
16 changes: 7 additions & 9 deletions tests/audit-navigation-auth-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
const start = contents.indexOf(startMarker);
const end = contents.indexOf(endMarker, start + startMarker.length);
if (start < 0 || end < 0) {
throw new Error(`Could not locate source segment from ${startMarker} to ${endMarker}.`);

Check failure on line 20 in tests/audit-navigation-auth-regressions.test.ts

View workflow job for this annotation

GitHub Actions / Unit coverage

[node] tests/audit-navigation-auth-regressions.test.ts > audit navigation and auth regressions > gates private polling and mutations on local readiness plus authenticated status

Error: Could not locate source segment from // Local/demo guests can read the public library to const canRunSearch =. ❯ sourceSegment tests/audit-navigation-auth-regressions.test.ts:20:11 ❯ tests/audit-navigation-auth-regressions.test.ts:93:39
}
return contents.slice(start, end);
}
Expand Down Expand Up @@ -92,23 +92,21 @@
it("gates private polling and mutations on local readiness plus authenticated status", () => {
const privateCapabilityContract = sourceSegment(
clinicalDashboardSource,
"const canUsePrivateApis =",
"// Local/demo guests can read the public library",
"const canRunSearch =",
);
expect(privateCapabilityContract).toContain("const canUsePrivateApis =");
expect(privateCapabilityContract).toContain(
'localProjectReady && (localNoAuthMode || localDevCanAttemptPrivateApis || authStatus === "authenticated")',
'const canUsePrivateApis = localProjectReady && authStatus === "authenticated";',
Comment thread
BigSimmo marked this conversation as resolved.
);
expect(privateCapabilityContract).not.toMatch(/clientDemoMode/);
expect(privateCapabilityContract).not.toMatch(/localNoAuth|clientDemoMode/);

const pollingContract = sourceSegment(
clinicalDashboardSource,
"const shouldRefreshWorkState =",
"const [documentsResponse",
);
expect(pollingContract).toContain(
"includeAdministrationData && (!administrationDataLoadedRef.current || now >= nextWorkStatePollRef.current)",
);
expect(pollingContract).toContain("(canUsePrivateApis || serverDemoMode)");
expect(pollingContract).not.toMatch(/localNoAuth|clientDemoMode/);

const labelMutationContract = sourceSegment(
clinicalDashboardSource,
Expand All @@ -125,8 +123,8 @@
expect(uploadMutationContract).toContain("if (!canUsePrivateApis) {");
});

it("keeps the root dashboard H1 as Clinical Guide", () => {
it("keeps the root dashboard H1 as Clinical KB", () => {
expect(clinicalDashboardSource.match(/<h1\b/g)).toHaveLength(1);
expect(clinicalDashboardSource).toMatch(/<h1 className="sr-only">\s*Clinical Guide\s*<\/h1>/);
expect(clinicalDashboardSource).toMatch(/<h1 className="sr-only">\s*Clinical KB\s*<\/h1>/);

Check failure on line 128 in tests/audit-navigation-auth-regressions.test.ts

View workflow job for this annotation

GitHub Actions / Unit coverage

[node] tests/audit-navigation-auth-regressions.test.ts > audit navigation and auth regressions > keeps the root dashboard H1 as Clinical KB

AssertionError: expected '"use client";\n\nimport { useRouter, …' to match /<h1 className="sr-only">\s*Clinica…/h1> - Expected: /<h1 className="sr-only">\s*Clinical KB\s*<\/h1>/ + Received: "\"use client\"; import { useRouter, useSearchParams } from \"next/navigation\"; import dynamic from \"next/dynamic\"; import { CircleAlert, BookOpen, ChevronDown, Clock3, ExternalLink, FileImage, FileText, FolderOpen, Heart, ListChecks, Loader2, Quote, RefreshCw, Search, ShieldAlert, Square, UploadCloud, WifiOff, Wrench, } from \"lucide-react\"; import { type CSSProperties, useCallback, useEffect, useMemo, useReducer, useRef, useState } from \"react\"; import { type DocumentDeleteResult } from \"@/components/DocumentManagementActions\"; import { extractSafetyFindings } from \"@/lib/clinical-safety\"; import { isLocalNoAuthMode, publicUploadsEnabled } from \"@/lib/client-env\"; import { readLocalProjectIdentity, unsafeLocalProjectMessage } from \"@/lib/local-project-identity\"; import { isDeployedClinicalKb } from \"@/lib/deployed-app\"; import { appBackdrop, answerSurface, cn, EmptyState, floatingControl, InlineNotice, primaryControl, textMuted, } from \"@/components/ui-primitives\"; import { useAuthSession } from \"@/lib/supabase/client\"; import { AccountSetupDialog } from \"@/components/clinical-dashboard/account-setup-dialog\"; import { CrossModeLinksSection } from \"@/components/clinical-dashboard/cross-mode-links\"; import { useEventCallback } from \"@/components/clinical-dashboard/use-event-callback\"; import { AuthPanel } from \"@/components/clinical-dashboard/auth-panel\"; import { buildMobileSectionFabState, MobileSectionFab, ToolsHub } from \"@/components/clinical-dashboard/dashboard-nav\"; import { SettingsDialog } from \"@/components/clinical-dashboard/settings-dialog\"; import { useSidebarCollapsed } from \"@/components/clinical-dashboard/use-sidebar-collapsed\"; import { useTheme } from \"@/components/clinical-dashboard/use-theme\"; import { deriveSidebarIdentity, ClinicalDesktopSidebar, ClinicalMobileSidebar, } from \"@/components/clinical-dashboard/ClinicalSidebar\"; import { SetupChecklist, UploadPanel, IndexingMonitor, IngestionQualityConsole, LibraryHealthStrip, fallbackSetupChecks, hasReadyRequiredPublicSearchConfig, hasReadyPublicSearchSetup, type SetupCheck, type IngestionQualityReviewItem, } from \"@/components/clinical-dashboard/DocumentManagerPanel\"; import { GuideDialog, GuideTrigger, UtilityDrawer } from \"@/components/clinical-dashboard/dashboard-shell\"; import { sanitizeAnswerDisplayText, sanitizeDisplayText } from \"@/components/clinical-dashboard/display-text\"; import { isPreformattedGroundedAnswer, NaturalLanguageAnswer, ScopeAndGovernanceNotice, UserQuestionBubble, } from \"@/components/clinical-dashboard/answer-content\"; import { AnswerEmptyState, AnswerProgressStepper, AnswerSkeleton } from \"@/components/clinical-dashboard/answer-status\"; import { type AnswerProgressUpdate, type TimedAnswerProgressUpdate, } from \"@/components/clinical-dashboard/answer-progress\"; import { evidenceMapRowsFromRenderModel } from \"@/components/clinical-dashboard/evidence-map-model\"; import { MasterSearchHeader } from \"@/components/clinical-dashboard/master-search-header\"; import { UniversalSearchAlsoMatches } from \"@/components/clinical-dashboard/universal-search-also-matches\"; import { useScrollHideReporter } from \"@/components/clinical-dashboard/use-hide-on-scroll\"; import { SearchCommandProvider } from \"@/components/clinical-dashboard/search-command-context\"; import { answerReferencesDocument, answerTimedOutError, applyRenamedDocumentToAnswer, compactScopeFilters, hasActiveIndexingWork, hasNonProductionSupabaseApiKeyFallback, isAbortError, mergeDocumentRefresh, normalizeNavigationHash, setupNeedsSlowRecheck, setupRecheckPollMs, shorterPollDelay, } from \"@/components/clinical-dashboard/clinical-dashboard-helpers\"; import { answerRecovery, errorCopy } from \"@/lib/ui-copy\"; imp
});
});
4 changes: 1 addition & 3 deletions tests/supabase-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,7 @@
);
expect(cleanup).toContain("metadata->>'registry_record_id' = old.id::text");
expect(cleanup).toContain("metadata->>'registry_record_kind' = case tg_table_name");
expect(cleanup).toMatch(/when 'clinical_registry_records' then (pg_catalog\.)?to_jsonb\((old|OLD)\)->>'kind'/);
expect(cleanup).toMatch(/when 'clinical_registry_records' then (pg_catalog\.)?to_jsonb(old)->>'kind'/i);

Check failure on line 1374 in tests/supabase-schema.test.ts

View workflow job for this annotation

GitHub Actions / Unit coverage

[node] tests/supabase-schema.test.ts > Supabase Preview replay guards > hardens registry cleanup without UUID casts or cross-registry collisions

AssertionError: expected 'create or replace function public.cle…' to match /when 'clinical_registry_records' the…/i - Expected: /when 'clinical_registry_records' then (pg_catalog\.)?to_jsonb(old)->>'kind'/i + Received: "create or replace function public.cleanup_registry_corpus_document() returns trigger language plpgsql security definer set search_path = '' as $$ begin delete from public.documents where metadata->>'source_kind' = 'registry_record' and metadata->>'registry_record_kind' = case tg_table_name when 'clinical_registry_records' then pg_catalog.to_jsonb(old)->>'kind' when 'medication_records' then 'medication' when 'differential_records' then 'differential' else null end and metadata->>'registry_record_id' = old.id::text; return old; end; $$; " ❯ tests/supabase-schema.test.ts:1374:23
expect(cleanup).toContain("when 'medication_records' then 'medication'");
expect(cleanup).toContain("when 'differential_records' then 'differential'");
expect(cleanup).not.toContain("registry_record_id')::uuid");
Expand All @@ -1395,11 +1395,9 @@
expect(corrector).toContain("lower(canonical) % tok");
expect(corrector).toContain("word % tok");
expect(corrector).toContain("limit 32");
expect(corrector).toContain("min_sim real default 0.45");
if (corrector.includes("set pg_trgm.similarity_threshold = 0.3")) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the similarity threshold in the schema guard

In this clinical-query corrector guard, the change removes the assertions that the SQL still exposes min_sim real default 0.45 and actually applies best_sim >= min_sim; with only the remaining indexed-probe checks, a schema or migration can start rewriting low-similarity clinical search terms and this test will still pass. Because this function rewrites user clinical queries, the bounded-similarity gate is the safety behavior the test name promises to protect; keep an explicit assertion or executable check for the threshold.

Useful? React with 👍 / 👎.

expect(corrector).toContain("set pg_trgm.similarity_threshold = 0.3");
}
expect(corrector).toContain("best_sim >= min_sim");
if (corrector.includes("min_sim is null or min_sim < 0.3 or min_sim > 1")) {
expect(corrector).toContain("min_sim is null or min_sim < 0.3 or min_sim > 1");
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function expectNoPageHorizontalOverflow(page: Page) {
}

async function expectDashboardUsable(page: Page) {
await expect(page.getByRole("heading", { level: 1, name: "Clinical Guide" })).toHaveCount(1);
await expect(page.getByRole("heading", { level: 1, name: "Clinical KB" })).toHaveCount(1);
await expect(page.getByRole("heading", { name: "Answer" })).toBeVisible();
await expect(page.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible')).toBeVisible();
await expect(page.getByRole("button", { name: "Open answer options" })).toBeVisible();
Expand Down
4 changes: 2 additions & 2 deletions tests/ui-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ test.describe("Clinical KB UI smoke coverage", () => {
await gotoApp(page, "/");
await waitForDemoDashboardReady(page);

await expect(page.getByRole("heading", { level: 1, name: "Clinical Guide" })).toHaveCount(1);
await expect(page.getByRole("heading", { level: 1, name: "Clinical KB" })).toHaveCount(1);
await expect(page.getByRole("heading", { name: "Answer" })).toBeVisible();
await expect(visibleQuestionInput(page)).toBeVisible();
await expect(page.getByRole("button", { name: "Generate source-backed answer" })).toHaveText(/^\s*Ask\s*$/);
Expand Down Expand Up @@ -1268,7 +1268,7 @@ test.describe("Clinical KB UI smoke coverage", () => {
await expect(page.getByRole("button", { name: "Generate source-backed answer" })).toBeEnabled();
await expect(page.getByTestId("answer-grounding-chip")).toHaveCount(0);
expect(answerRequests).toEqual([]);
await expect(page.getByRole("heading", { level: 1, name: "Clinical Guide" })).toBeVisible();
await expect(page.getByRole("heading", { level: 1, name: "Clinical KB" })).toBeVisible();
await expectDomIntegrity(page, { mobileNav: true, mobileFabReady: false });
await expectNoPageHorizontalOverflow(page);
});
Expand Down
18 changes: 18 additions & 0 deletions tests/ui-tools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,9 @@ test.describe("Clinical KB tools launcher", () => {
await expect(
page.getByTestId("form-search-result-transport-crisis-form").getByLabel("Open Transport order"),
).toHaveAttribute("href", "/forms/transport-crisis-form");
await expect(page.getByRole("button", { name: "Refine" })).toHaveCount(0);
await expect(page.getByText(/Evidence 278|Pathways 12|Tasks 8|Source verified|Aligned to MHA 2014/)).toHaveCount(0);
await expect(page.getByText(/PSOLIS Transport|View full pathway/)).toHaveCount(0);
await expect(page.getByTestId("service-search-results")).toHaveCount(0);
await expectNoPageHorizontalOverflow(page);
});
Expand Down Expand Up @@ -1005,6 +1008,7 @@ test.describe("Clinical KB tools launcher", () => {

await expect(page.getByTestId("form-search-mobile-results")).toBeVisible();
await expect(page.getByTestId("form-search-mobile-result-transport-crisis-form")).toContainText("Transport order");
await expect(page.getByText(/PSOLIS Transport|View full pathway|Source verified/)).toHaveCount(0);
await expect(visibleGlobalSearchInput(page)).toHaveValue("transport");
await expectNoPageHorizontalOverflow(page);
});
Expand All @@ -1017,6 +1021,20 @@ test.describe("Clinical KB tools launcher", () => {
const dock = page.locator("form.answer-footer-search-dock");
await expect(dock).toBeVisible();
await expect(dock).not.toHaveAttribute("data-scroll-hidden", "true");
const transition = await dock.evaluate((node) => {
const style = window.getComputedStyle(node);
const durationMs = Math.max(
...style.transitionDuration.split(",").map((value) => {
const normalized = value.trim();
const duration = Number.parseFloat(normalized);
return normalized.endsWith("ms") ? duration : duration * 1000;
}),
);
return { durationMs, property: style.transitionProperty };
});
expect(transition.property).toMatch(/transform|all/);
expect(transition.durationMs).toBeGreaterThanOrEqual(100);

// focus=1 leaves the composer focused; hide-on-scroll stays off while it has focus.
const input = visibleGlobalSearchInput(page).first();
await input.focus();
Expand Down
Loading