mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
Three remaining e2e failures after the global-setup tour-completed fix all came from cross-project sharing — specs that target desktop-only DOM (the `aside[aria-label="Sidebar"]` logo + Achievements link) were running in the chromium-mobile profile too, where the sidebar is `hidden md:flex` by design. Added `test.skip(viewport.width < 768, ...)` guards so each spec only runs in the project that can satisfy its contract. The fourth failure was the `/api/dashboard/widgets` endpoint that the A5 tile-strip work added since `onboarding-flicker.spec.ts` was written. Without a stub the unauthenticated dashboard 500s on this query and React Query's error boundary swallows the page render before `<GettingStartedChecklist>` mounts. Added the stub to both flicker specs (complete + incomplete-onboarding paths).
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
||
|
||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||
|
||
/**
|
||
* v1.4.15 phase-B4 — /achievements navigation smoke.
|
||
*
|
||
* The page itself has unit-test coverage for the locked-vs-unlocked
|
||
* card render; this spec answers the higher-level "can the user reach
|
||
* the page from the global nav?" question. We mock
|
||
* `/api/gamification/achievements` so the assertion does not depend on
|
||
* the seed user actually having any achievement data — the routing +
|
||
* sidebar wiring is what matters here, not the data freshness.
|
||
*/
|
||
test.describe("achievements page is reachable from the sidebar", () => {
|
||
test.use({ storageState: STORAGE_STATE_PATH });
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.route("**/api/gamification/achievements", (route) =>
|
||
route.fulfill({
|
||
status: 200,
|
||
contentType: "application/json",
|
||
body: JSON.stringify({
|
||
data: {
|
||
summary: {
|
||
unlockedCount: 0,
|
||
totalCount: 38,
|
||
earnedPoints: 0,
|
||
totalPoints: 9999,
|
||
completionPercent: 0,
|
||
nextAchievement: null,
|
||
},
|
||
achievements: [
|
||
{
|
||
id: "intake-total-1",
|
||
metric: "totalTakenIntakes",
|
||
category: "medication",
|
||
titleKey: "achievements.badges.intakeTotal1.title",
|
||
descriptionKey: "achievements.badges.intakeTotal1.description",
|
||
icon: "Pill",
|
||
format: "count",
|
||
target: 1,
|
||
current: 0,
|
||
points: 8,
|
||
unlocked: false,
|
||
progressPercent: 0,
|
||
completedAt: null,
|
||
},
|
||
],
|
||
metrics: {},
|
||
},
|
||
error: null,
|
||
}),
|
||
}),
|
||
);
|
||
});
|
||
|
||
test("desktop sidebar exposes the achievements link", async ({
|
||
page,
|
||
viewport,
|
||
}) => {
|
||
// The sidebar nav is hidden on small viewports (`md:flex`). The
|
||
// chromium-mobile profile (Pixel 5, 393 px wide) cannot satisfy
|
||
// this contract by design — skip there so the spec only runs in
|
||
// the chromium-desktop project (1280×720). Without this guard
|
||
// every push fails 1 / 4 specs purely because the same suite is
|
||
// sharded across both projects.
|
||
test.skip(
|
||
(viewport?.width ?? 0) < 768,
|
||
"sidebar is hidden on mobile by design (md:flex breakpoint)",
|
||
);
|
||
|
||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||
// Match by aria-label so we don't accidentally pick up a tooltip.
|
||
const sidebar = page.locator('aside[aria-label="Sidebar"]');
|
||
await expect(sidebar).toBeVisible();
|
||
await sidebar.getByRole("link", { name: "Achievements" }).click();
|
||
await expect(page).toHaveURL(/\/achievements\b/);
|
||
// Page title from the loaded route. The unit test guards the
|
||
// grouped + locked-card render shape; here we only assert the route
|
||
// mounted and rendered the heading.
|
||
await expect(
|
||
page.getByRole("heading", { name: "Achievements" }),
|
||
).toBeVisible({ timeout: 5000 });
|
||
});
|
||
});
|