mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
A point and a calendar cell are small targets; one tap that opened the day opened days by accident. On a touch screen the first tap shows the value again and the tooltip's calm 44 px row opens the day, on every day-linked chart and calendar alike. A date in a list still opens in one tap; a click on a fine pointer still opens directly. The tooltip keeps not gliding into place.
193 lines
7.5 KiB
TypeScript
193 lines
7.5 KiB
TypeScript
import type { Page } from "@playwright/test";
|
|
|
|
import { mockDay } from "./setup/day-mock";
|
|
import { expect, test } from "./setup/test";
|
|
import { MOBILE_ROUTES_STORAGE_STATE_PATH } from "./setup/global-setup";
|
|
import { MOBILE_ROUTES_ANALYTE } from "./setup/mobile-routes-fixture";
|
|
|
|
/**
|
|
* v1.42 — the day view and its doors.
|
|
*
|
|
* The record is the phone-sweep account `globalSetup` seeds: a week of blood
|
|
* pressure, two lab readings, mood and an episode. The day itself
|
|
* (`GET /api/day/{date}`, `GET /api/day/index`) is answered by a route mock in
|
|
* the contract's shape, so the journeys pin the client: which door opens which
|
|
* day, what the history does, and where no door is.
|
|
*
|
|
* - a chart point opens the day beside the chart (desktop);
|
|
* - on a touch screen the first tap shows the value and the tooltip offers
|
|
* the day, the second opens it as a bottom sheet;
|
|
* - a date in a list opens the day while the row keeps its own target;
|
|
* - `?day=` opens the layer on any page, Back and Escape close it, a date in
|
|
* the future is dropped;
|
|
* - stepping to the neighbouring days never adds history entries;
|
|
* - the dashboard's today area offers no door; its charts below do.
|
|
*
|
|
* Stable data attributes only: `day-panel` (+ `data-shell`), `day-link`,
|
|
* `chart-plot[data-day-links]`, `chart-tooltip-open-day`, `day-prev` /
|
|
* `day-next` / `day-close`.
|
|
*/
|
|
|
|
const panel = (page: Page) => page.locator('[data-slot="day-panel"]');
|
|
|
|
function isoDaysAgo(days: number): string {
|
|
const d = new Date(Date.now() - days * 86_400_000);
|
|
return d.toISOString().slice(0, 10);
|
|
}
|
|
|
|
test.describe("the day view", () => {
|
|
test.use({ storageState: MOBILE_ROUTES_STORAGE_STATE_PATH });
|
|
|
|
test.beforeEach(async ({ page, context }, testInfo) => {
|
|
const baseURL = testInfo.project.use.baseURL ?? "http://localhost:3000";
|
|
await context.addCookies([
|
|
{ name: "healthlog-locale", value: "en", url: baseURL },
|
|
]);
|
|
await mockDay(page);
|
|
});
|
|
|
|
test("a chart point opens its day beside the chart; Back closes it", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(testInfo.project.name !== "chromium-desktop", "desktop click");
|
|
await page.setViewportSize({ width: 1440, height: 900 });
|
|
await page.goto("/insights/blood-pressure");
|
|
const plot = page.locator(
|
|
'[data-slot="chart-plot"][data-day-links="true"]',
|
|
);
|
|
await expect(plot).toBeVisible();
|
|
// The plot's right half holds the most recent points.
|
|
const box = (await plot.boundingBox())!;
|
|
await page.mouse.move(box.x + box.width * 0.8, box.y + box.height / 2);
|
|
await page.mouse.click(box.x + box.width * 0.8, box.y + box.height / 2);
|
|
|
|
await expect(panel(page)).toBeVisible();
|
|
await expect(panel(page)).toHaveAttribute("data-shell", "docked");
|
|
// A landmark beside the page, named by its date.
|
|
await expect(panel(page)).toHaveJSProperty("tagName", "ASIDE");
|
|
await expect(page).toHaveURL(/[?&]day=\d{4}-\d{2}-\d{2}/);
|
|
await expect(page.locator('[data-slot="day-focus"]')).toBeVisible();
|
|
// The chart beside it stays usable: the panel is not modal.
|
|
await expect(page.locator('[data-slot="sheet-overlay"]')).toHaveCount(0);
|
|
|
|
await page.goBack();
|
|
await expect(panel(page)).toHaveCount(0);
|
|
await expect(page).not.toHaveURL(/[?&]day=/);
|
|
});
|
|
|
|
test("a date in a list opens the day; the row keeps its own target", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/labs");
|
|
await page
|
|
.locator('[data-slot="lab-list-analyte"]')
|
|
.filter({ hasText: MOBILE_ROUTES_ANALYTE })
|
|
.first()
|
|
.click();
|
|
// The marker's readings, each dated.
|
|
await page.locator('a[href$="/values"]').first().click();
|
|
const link = page.locator('[data-slot="day-link"]:visible').first();
|
|
await expect(link).toBeVisible();
|
|
const day = await link.getAttribute("data-day");
|
|
const before = page.url();
|
|
await link.click();
|
|
await expect(panel(page)).toBeVisible();
|
|
await expect(page.locator('[data-slot="day-view"]')).toHaveAttribute(
|
|
"data-day",
|
|
day!,
|
|
);
|
|
// Same page underneath: the click opened the day, not the reading.
|
|
expect(new URL(page.url()).pathname).toBe(new URL(before).pathname);
|
|
// The reading the person came from sits on top, with the one before it.
|
|
await expect(page.locator('[data-slot="day-focus"]')).toBeVisible();
|
|
});
|
|
|
|
test("?day= opens the layer on any page; Escape closes it", async ({
|
|
page,
|
|
}) => {
|
|
const day = isoDaysAgo(3);
|
|
await page.goto(`/mood?day=${day}`);
|
|
await expect(panel(page)).toBeVisible();
|
|
await expect(page.locator('[data-slot="day-view"]')).toHaveAttribute(
|
|
"data-day",
|
|
day,
|
|
);
|
|
await page.keyboard.press("Escape");
|
|
await expect(panel(page)).toHaveCount(0);
|
|
await expect(page).toHaveURL(/\/mood$/);
|
|
});
|
|
|
|
test("a future or malformed ?day= is dropped without a word", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/mood?day=2999-01-01");
|
|
await expect(page).toHaveURL(/\/mood$/);
|
|
await expect(panel(page)).toHaveCount(0);
|
|
await page.goto("/mood?day=2026-02-30");
|
|
await expect(page).toHaveURL(/\/mood$/);
|
|
await expect(panel(page)).toHaveCount(0);
|
|
});
|
|
|
|
test("stepping between days never adds a history entry", async ({ page }) => {
|
|
await page.goto("/mood");
|
|
await page.locator('[data-slot="day-link"]:visible').first().click();
|
|
await expect(panel(page)).toBeVisible();
|
|
const start = await page.evaluate(() => window.history.length);
|
|
const first = await page
|
|
.locator('[data-slot="day-view"]')
|
|
.getAttribute("data-day");
|
|
for (let i = 0; i < 3; i += 1) {
|
|
await page.locator('[data-slot="day-prev"]').click();
|
|
}
|
|
await expect(page.locator('[data-slot="day-view"]')).not.toHaveAttribute(
|
|
"data-day",
|
|
first!,
|
|
);
|
|
expect(await page.evaluate(() => window.history.length)).toBe(start);
|
|
// One Back leaves the layer, however far it stepped.
|
|
await page.goBack();
|
|
await expect(panel(page)).toHaveCount(0);
|
|
await expect(page).toHaveURL(/\/mood$/);
|
|
});
|
|
|
|
test("on a phone the first tap shows the value, the second opens the day", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
test.skip(testInfo.project.name !== "chromium-mobile", "touch only");
|
|
await page.goto("/insights/blood-pressure");
|
|
const plot = page.locator(
|
|
'[data-slot="chart-plot"][data-day-links="true"]',
|
|
);
|
|
await plot.scrollIntoViewIfNeeded();
|
|
await expect(plot).toBeVisible();
|
|
const box = (await plot.boundingBox())!;
|
|
await page.touchscreen.tap(box.x + box.width * 0.8, box.y + box.height / 2);
|
|
const open = page.locator('[data-slot="chart-tooltip-open-day"]');
|
|
await expect(open).toBeVisible();
|
|
// A tap alone never opens a sheet.
|
|
await expect(panel(page)).toHaveCount(0);
|
|
await open.tap();
|
|
await expect(panel(page)).toBeVisible();
|
|
await expect(panel(page)).toHaveAttribute("data-shell", "bottom");
|
|
// The tapped value heads the day.
|
|
await expect(page.locator('[data-slot="day-focus"]')).toBeVisible();
|
|
});
|
|
|
|
test("the dashboard's today area offers no day door", async ({ page }) => {
|
|
await page.goto("/");
|
|
await expect(
|
|
page.locator('[data-slot="main-content-wrapper"]'),
|
|
).toBeVisible();
|
|
// The hero is today: nothing in it opens another day. (The charts
|
|
// further down do; `day-doors.spec.ts` walks one.)
|
|
await expect(
|
|
page.locator('[data-slot^="today-hero"]').first(),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.locator(
|
|
'[data-slot^="today-hero"] [data-slot="day-link"], [data-slot^="today-hero"] [data-day-links="true"]',
|
|
),
|
|
).toHaveCount(0);
|
|
});
|
|
});
|