Files
HealthLog/e2e/documents-procedure-filter.spec.ts
T
Marc-André Bombeck 0219a85e92 feat(documents): filter the archive by procedure or surgery
The vault's filter row gains a procedure facet beside the condition
facet. It lists the record's procedures and surgeries that hold at least
one live document, headed the way the visits page heads them (the
reason, else the practice, else the kind) with the body site and date
beneath, and narrows the list through the existing `encounterId` filter.
Same control, placement and clear behaviour as the condition facet; the
choice rides the URL as `?encounter=`, and an upload dropped into the
filtered view is filed against the procedure.

The usage read publishes the choices as `linkedProcedures` (additive),
decrypting only the label columns, and returns none when a sharing
grant does not cover the visits section.

On a phone an active facet now gives up label width before the search
field does, and the search keeps room for its icon and padding.
2026-10-04 21:59:22 +02:00

132 lines
4.7 KiB
TypeScript

/**
* Document vault — the procedure filter.
*
* The facet beside the condition filter: it lists the procedures that hold a
* document, labelled the way the visits page heads them, narrows the list to
* one procedure's documents through the real `encounterId` filter, writes the
* choice to the URL (`?encounter=`), comes back from a deep link, and clears
* with the one clear control. Run at a 1440 px desktop and a 390 px phone,
* where the filter row must stay a single line with no sideways scroll.
*
* Fixture: `ensureProcedureFixture` (two procedures over a namespaced trio of
* documents), upserted before every test because the visits spec clears the
* account's visits; the dates sit far outside that spec's ±7-day window.
*/
import type { Page } from "@playwright/test";
import { expect, test } from "./setup/test";
import { STORAGE_STATE_PATH } from "./setup/global-setup";
import {
ensureProcedureFixture,
ensureVaultFixture,
PROCEDURE_DOC_PREFIX,
PROCEDURE_KNEE_ID,
} from "./setup/vault-fixture";
function card(page: Page, n: number) {
return page
.getByRole("button", {
name: `Open ${PROCEDURE_DOC_PREFIX} ${String(n).padStart(3, "0")}`,
})
.first();
}
/** The filter row is one line and never scrolls sideways. */
async function expectOneRow(page: Page) {
const bar = page.locator('[data-slot="document-filter-bar"]');
const row = bar.locator("> div").first();
const box = await row.boundingBox();
expect(box).not.toBeNull();
expect(box!.height).toBeLessThan(48);
const overflow = await page.evaluate(
() => document.documentElement.scrollWidth - window.innerWidth,
);
expect(overflow).toBeLessThanOrEqual(0);
}
async function shot(page: Page, name: string, isMobile: boolean) {
const dir = process.env.E2E_SHOT_DIR;
if (!dir) return;
await page.screenshot({
path: `${dir}/${name}-${isMobile ? "390" : "1440"}.png`,
animations: "disabled",
});
}
test.describe("document vault — procedure filter", () => {
test.use({ storageState: STORAGE_STATE_PATH });
test.beforeAll(async () => {
await ensureVaultFixture();
});
test.beforeEach(async ({ page, isMobile }) => {
await ensureProcedureFixture();
await page.setViewportSize(
isMobile ? { width: 390, height: 844 } : { width: 1440, height: 900 },
);
});
test("picks a procedure, narrows to its documents, and clears", async ({
page,
isMobile,
}) => {
await page.goto(`/documents?q=${PROCEDURE_DOC_PREFIX}`);
await expect(card(page, 3)).toBeVisible();
await expect(card(page, 1)).toBeVisible();
const trigger = page.locator('[data-slot="document-procedure-filter"]');
await expect(trigger).toBeVisible();
await expect(trigger).toHaveAttribute("aria-label", "All procedures");
await expectOneRow(page);
await shot(page, "procedure-filter-idle", isMobile);
await trigger.click();
const menu = page.getByRole("menu");
await expect(menu).toBeVisible();
await expect(menu).toContainText("Procedures and surgeries");
// Headed like the visits page, with the body site and side beneath.
const knee = menu.getByRole("menuitemcheckbox", {
name: /Knee arthroscopy/,
});
await expect(knee).toContainText("Knee · Left");
await expect(
menu.getByRole("menuitemcheckbox", { name: /Appendectomy/ }),
).toBeVisible();
await shot(page, "procedure-filter-menu", isMobile);
await knee.click();
await expect(page).toHaveURL(new RegExp(`encounter=${PROCEDURE_KNEE_ID}`));
await expect(card(page, 1)).toBeVisible();
await expect(card(page, 2)).toBeVisible();
await expect(card(page, 3)).toHaveCount(0);
await expect(trigger).toHaveAttribute("aria-label", "Knee arthroscopy");
// Picked: the label shows at every width, phone included.
await expect(trigger).toContainText("Knee arthroscopy");
await expectOneRow(page);
// The search field never collapses under the facets.
const search = await page
.getByRole("searchbox", { name: "Search documents" })
.boundingBox();
expect(search!.width).toBeGreaterThanOrEqual(48);
await shot(page, "procedure-filter-active", isMobile);
await page.getByRole("button", { name: "Clear filters" }).click();
await expect(page).not.toHaveURL(/encounter=/);
await expect(trigger).toHaveAttribute("aria-label", "All procedures");
});
test("a deep link lands filtered and the facet names the procedure", async ({
page,
}) => {
await page.goto(
`/documents?q=${PROCEDURE_DOC_PREFIX}&encounter=${PROCEDURE_KNEE_ID}`,
);
await expect(card(page, 2)).toBeVisible();
await expect(card(page, 3)).toHaveCount(0);
await expect(
page.locator('[data-slot="document-procedure-filter"]'),
).toHaveAttribute("aria-label", "Knee arthroscopy");
});
});