mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
test(e2e): pin the coach launch spec to the new chat contract
The floating button only appears while a coach nudge is unread, the inline pill is the default launcher on every viewport, and the conversation list navigates to the full view instead of a tray.
This commit is contained in:
1 parent
6067d9176e
commit
4ad17f9de3
1 file changed
+101
-50
+101
-50
@@ -3,36 +3,40 @@ import { expect, test } from "@playwright/test";
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* v1.4.27 R3d MB4 — Coach reachability on `/insights/{slug}` sub-pages
|
||||
* plus the Coach drawer bottom-sheet branch on `<sm`.
|
||||
* Coach launch surfaces on `/insights/{slug}` sub-pages.
|
||||
*
|
||||
* Two contracts under test:
|
||||
* Contracts under test (v1.16.1):
|
||||
*
|
||||
* 1. Every routed Insights sub-page mounts a Coach launch surface.
|
||||
* The layout renders a sticky-bottom FAB
|
||||
* (`data-slot="coach-launch-fab"`, visible on `<lg`); v1.8.6 moved
|
||||
* the per-page action into the sub-page header as an icon-only
|
||||
* button (`data-slot="coach-launch-icon"`, mounted by the shell at
|
||||
* heading height across breakpoints). The spec asserts the FAB
|
||||
* renders on Pixel-5 and the header icon renders on Desktop Chrome.
|
||||
* 1. The floating bubble (`data-slot="coach-nudge-bubble"`) renders
|
||||
* ONLY while an unread Coach-initiated nudge exists
|
||||
* (`GET /api/insights/coach/nudge-status`). By default no bubble
|
||||
* mounts on any viewport; the everyday entry point is the inline
|
||||
* launch the sub-page shell mounts in the header
|
||||
* (`data-slot="coach-launch-icon"`, visible across breakpoints).
|
||||
*
|
||||
* 2. Opening the Coach drawer on `<sm` mounts it as a bottom-sheet
|
||||
* (`data-variant="bottom-sheet"`). On `>=sm` it mounts as a
|
||||
* right-side sheet (`data-variant="side-sheet"`).
|
||||
* 2. Clicking the inline launch opens the Coach drawer — as a
|
||||
* bottom-sheet (`data-variant="bottom-sheet"`) on `<sm`, as a
|
||||
* right-side sheet (`data-variant="side-sheet"`) on `>=sm`.
|
||||
*
|
||||
* 3. With an unread nudge the bubble mounts and clicking it
|
||||
* navigates to the full-page chat at `/insights/coach`.
|
||||
*
|
||||
* 4. The drawer's "Conversations" button no longer opens an in-panel
|
||||
* tray; it hands off to the full-page route `/insights/coach`.
|
||||
*
|
||||
* The three sub-pages covered (blutdruck, gewicht, schlaf) span the
|
||||
* empty-state branch (no data → CTA + Coach launch) and the populated
|
||||
* branch (with data → full chart + Coach launch beneath).
|
||||
* branch (with data → full chart + Coach launch in the header).
|
||||
*/
|
||||
test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => {
|
||||
test.describe("Coach launch surfaces on insights sub-pages", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Populated analytics so each sub-page paints the data branch
|
||||
// (`hasMetricData` true) — the Coach button is mounted at the
|
||||
// bottom of the data tree.
|
||||
// v1.4.39.3 — regex form matches the slim slice the v1.4.39.2
|
||||
// dashboard split fires alongside the thick request.
|
||||
// (`hasMetricData` true) — the inline Coach launch is mounted in
|
||||
// the sub-page header by the shell.
|
||||
// Regex form matches the slim slice the dashboard split fires
|
||||
// alongside the thick request.
|
||||
await page.route(/\/api\/analytics(\?|$)/, (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
@@ -103,35 +107,50 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => {
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Default nudge state: nothing unread. Registered AFTER the
|
||||
// generic `**-status*` mock so it wins for the nudge endpoint
|
||||
// (Playwright matches routes in reverse registration order).
|
||||
await page.route("**/api/insights/coach/nudge-status*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: { nudgedAt: null, unread: false },
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
for (const slug of ["blutdruck", "gewicht", "schlaf"] as const) {
|
||||
test(`/insights/${slug} mounts the Coach launch button on Pixel 5 (FAB branch)`, async ({
|
||||
test(`/insights/${slug} mounts the inline Coach launch and no bubble by default on Pixel 5`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.skip(
|
||||
testInfo.project.name !== "chromium-mobile",
|
||||
"Pixel 5 FAB branch",
|
||||
"mobile inline-launch branch",
|
||||
);
|
||||
|
||||
await page.goto(`/insights/${slug}`, { waitUntil: "domcontentloaded" });
|
||||
|
||||
// The FAB slot must be present and CSS-visible at Pixel 5.
|
||||
const fab = page
|
||||
.locator('[data-slot="coach-launch-fab"]')
|
||||
.first();
|
||||
await expect(fab).toBeVisible({ timeout: 10_000 });
|
||||
// The header launch icon is the everyday entry point — visible
|
||||
// on mobile too since the layout bubble is nudge-gated.
|
||||
const icon = page.locator('[data-slot="coach-launch-icon"]').first();
|
||||
await expect(icon).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// No unread nudge → no floating bubble in the DOM.
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-nudge-bubble"]'),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test(`/insights/${slug} mounts the header Coach icon on Desktop Chrome`, async ({
|
||||
test(`/insights/${slug} mounts the inline Coach launch and no bubble by default on Desktop Chrome`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
// Desktop Chrome viewport is 1280 — `lg` (1024) is matched so the
|
||||
// layout FAB is hidden by `lg:hidden`; the header icon the shell
|
||||
// mounts is the Coach entry on the page itself.
|
||||
test.skip(
|
||||
testInfo.project.name !== "chromium-desktop",
|
||||
"desktop header-icon branch",
|
||||
"desktop inline-launch branch",
|
||||
);
|
||||
|
||||
await page.goto(`/insights/${slug}`, { waitUntil: "domcontentloaded" });
|
||||
@@ -139,12 +158,13 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => {
|
||||
const icon = page.locator('[data-slot="coach-launch-icon"]').first();
|
||||
await expect(icon).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
const fab = page.locator('[data-slot="coach-launch-fab"]').first();
|
||||
await expect(fab).toBeHidden();
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-nudge-bubble"]'),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
}
|
||||
|
||||
test("clicking the FAB opens the Coach drawer as a bottom-sheet on Pixel 5", async ({
|
||||
test("clicking the inline Coach launch opens the drawer as a bottom-sheet on Pixel 5", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.skip(
|
||||
@@ -154,16 +174,16 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => {
|
||||
|
||||
await page.goto("/insights/blutdruck", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const fab = page.locator('[data-slot="coach-launch-fab"]').first();
|
||||
await expect(fab).toBeVisible({ timeout: 10_000 });
|
||||
await fab.click();
|
||||
const icon = page.locator('[data-slot="coach-launch-icon"]').first();
|
||||
await expect(icon).toBeVisible({ timeout: 10_000 });
|
||||
await icon.click();
|
||||
|
||||
const drawer = page.locator('[data-slot="coach-drawer"]');
|
||||
await expect(drawer).toBeVisible({ timeout: 10_000 });
|
||||
await expect(drawer).toHaveAttribute("data-variant", "bottom-sheet");
|
||||
});
|
||||
|
||||
test("clicking the header Coach icon opens the Coach drawer as a side-sheet on Desktop Chrome", async ({
|
||||
test("clicking the inline Coach launch opens the drawer as a side-sheet on Desktop Chrome", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.skip(
|
||||
@@ -182,12 +202,41 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => {
|
||||
await expect(drawer).toHaveAttribute("data-variant", "side-sheet");
|
||||
});
|
||||
|
||||
test("conversation history is collapsed behind a toggle that opens the tray on Desktop Chrome", async ({
|
||||
test("an unread Coach nudge mounts the floating bubble and clicking it opens the full-page chat", async ({
|
||||
page,
|
||||
}) => {
|
||||
// The bubble contract is viewport-independent — the test runs on
|
||||
// both projects. Override the default nudge mock with an unread
|
||||
// one (later registration wins).
|
||||
await page.route("**/api/insights/coach/nudge-status*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: { nudgedAt: "2026-06-09T06:00:00.000Z", unread: true },
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto("/insights/blutdruck", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const bubble = page.locator('[data-slot="coach-nudge-bubble"]');
|
||||
await expect(bubble).toBeVisible({ timeout: 10_000 });
|
||||
await bubble.click();
|
||||
|
||||
await page.waitForURL("**/insights/coach", { timeout: 10_000 });
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-page"]'),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
test("the drawer's Conversations button hands off to the full-page chat on Desktop Chrome", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
// v1.12.0 — the history rail is no longer an always-on left column;
|
||||
// it lives behind the "Conversations" toggle on every viewport and
|
||||
// opens as a tray on demand so the thread keeps the full width.
|
||||
// The in-panel history tray is gone from the drawer; the button
|
||||
// navigates to `/insights/coach` where the conversation list
|
||||
// renders inline on lg+.
|
||||
test.skip(
|
||||
testInfo.project.name !== "chromium-desktop",
|
||||
"exercises the desktop drawer layout",
|
||||
@@ -202,22 +251,24 @@ test.describe("v1.4.27 — Coach reachability on insights sub-pages", () => {
|
||||
const drawer = page.locator('[data-slot="coach-drawer"]');
|
||||
await expect(drawer).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// No inline history column — only the toggle.
|
||||
// The drawer renders no inline history column.
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-drawer-history"]'),
|
||||
).toHaveCount(0);
|
||||
const historyToggle = page.locator(
|
||||
|
||||
const historyButton = page.locator(
|
||||
'[data-slot="coach-drawer-history-tray-trigger"]',
|
||||
);
|
||||
await expect(historyToggle).toBeVisible({ timeout: 10_000 });
|
||||
await expect(historyButton).toBeVisible({ timeout: 10_000 });
|
||||
await historyButton.click();
|
||||
|
||||
// The history rail surfaces only after the toggle is pressed.
|
||||
// No in-panel tray opens — the click navigates to the full view.
|
||||
await page.waitForURL("**/insights/coach", { timeout: 10_000 });
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-page"]'),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-drawer-history-tray"]'),
|
||||
).toHaveCount(0);
|
||||
await historyToggle.click();
|
||||
await expect(
|
||||
page.locator('[data-slot="coach-drawer-history-tray"]'),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user