mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
Output of `node scripts/strip-planning-tags.mjs --write`: 3,157 comment lines in 1,308 files lose the work-item identifier at their head. Release versions and issue references stay; nothing outside comment text changes, which the script checks per file against a comment-free printout. healthlog/no-planning-tags is enabled at error level with this commit, so the tree it lands on is clean.
137 lines
4.9 KiB
TypeScript
137 lines
4.9 KiB
TypeScript
import { expect, test } from "./setup/test";
|
|
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
|
|
|
/**
|
|
* v1.4.27 — public surfaces.
|
|
*
|
|
* Three contracts under test, all reachable without a session:
|
|
*
|
|
* 1. `/about` — added to `PUBLIC_PATHS` in `src/proxy.ts` for the
|
|
* GeoLite2 CC BY-SA 4.0 attribution. A logged-out visitor must
|
|
* load the page (HTTP 200) and see the attribution copy.
|
|
* 2. `/this-route-does-not-exist` — the proxy keeps an anonymous visitor
|
|
* behind the login boundary, while an authenticated visitor reaches
|
|
* Next.js's branded `src/app/not-found.tsx`.
|
|
* 3. `/privacy` — the page mounts a collapsible `<details>` TOC
|
|
* (`data-slot="privacy-toc"`); clicking the summary opens it
|
|
* and the anchor links inside navigate to the right section.
|
|
*
|
|
* Specs intentionally do NOT use the authenticated storage state —
|
|
* the test suite needs to prove the routes work for first-time
|
|
* visitors and App-Store reviewers.
|
|
*/
|
|
test.describe("v1.4.27 — public pages", () => {
|
|
// Force no storage state so we run as an anonymous visitor.
|
|
test.use({ storageState: { cookies: [], origins: [] } });
|
|
|
|
test("/about returns 200 and renders the GeoLite2 attribution", async ({
|
|
page,
|
|
}) => {
|
|
const response = await page.goto("/about", {
|
|
waitUntil: "domcontentloaded",
|
|
});
|
|
expect(response?.status()).toBe(200);
|
|
// No bounce to the login page.
|
|
expect(page.url()).toMatch(/\/about(\?|$)/);
|
|
|
|
await expect(
|
|
page.getByRole("heading", { name: /about/i }).first(),
|
|
).toBeVisible();
|
|
|
|
// GeoLite2 attribution is the load-bearing reason this page exists.
|
|
await expect(page.getByText(/GeoLite2/).first()).toBeVisible();
|
|
await expect(page.getByText(/MaxMind/).first()).toBeVisible();
|
|
// Both licences, because naming only the Creative Commons one understates
|
|
// the terms: the GeoLite EULA is what governs and CC BY-SA 4.0 rides
|
|
// inside it.
|
|
await expect(
|
|
page.getByText(/GeoLite End User License Agreement/i),
|
|
).toBeVisible();
|
|
await expect(page.getByText(/Attribution-ShareAlike 4\.0/i)).toBeVisible();
|
|
});
|
|
|
|
test("/this-route-does-not-exist keeps an anonymous visitor behind login", async ({
|
|
page,
|
|
}) => {
|
|
const response = await page.goto("/this-route-does-not-exist", {
|
|
waitUntil: "domcontentloaded",
|
|
});
|
|
// `page.goto` follows the proxy's 307 and returns the final login response.
|
|
expect(response?.status()).toBe(200);
|
|
await expect(page).toHaveURL(/\/auth\/login(?:\?|$)/);
|
|
await expect(
|
|
page.getByRole("button", { name: /sign in with passkey/i }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("/privacy renders the TOC and the anchor links jump to sections", async ({
|
|
page,
|
|
}) => {
|
|
const response = await page.goto("/privacy", {
|
|
waitUntil: "domcontentloaded",
|
|
});
|
|
expect(response?.status()).toBe(200);
|
|
|
|
const toc = page.locator('[data-slot="privacy-toc"]');
|
|
await expect(toc).toBeVisible();
|
|
|
|
// <details> defaults to closed. Toggling the summary opens it.
|
|
const isOpenBefore = await toc.evaluate(
|
|
(el) => (el as HTMLDetailsElement).open,
|
|
);
|
|
expect(isOpenBefore).toBe(false);
|
|
|
|
await toc.locator("summary").click();
|
|
const isOpenAfter = await toc.evaluate(
|
|
(el) => (el as HTMLDetailsElement).open,
|
|
);
|
|
expect(isOpenAfter).toBe(true);
|
|
|
|
// Clicking a TOC anchor scrolls the matching section into view.
|
|
// We assert the URL hash settles and the target section's heading
|
|
// is visible inside the viewport.
|
|
const intro = toc.locator('a[href="#intro"]').first();
|
|
await expect(intro).toBeVisible();
|
|
await intro.click();
|
|
|
|
await expect.poll(() => page.url()).toMatch(/#intro$/);
|
|
|
|
const introSection = page.locator("#intro");
|
|
await expect(introSection).toBeVisible();
|
|
// scroll-mt-28 leaves the section clear of the sticky header —
|
|
// assert the section's top is inside the viewport.
|
|
const introTop = await introSection.evaluate(
|
|
(el) => el.getBoundingClientRect().top,
|
|
);
|
|
expect(introTop).toBeGreaterThanOrEqual(0);
|
|
expect(introTop).toBeLessThan(800);
|
|
});
|
|
});
|
|
|
|
test.describe("v1.4.27 — authenticated not-found page", () => {
|
|
test.use({ storageState: STORAGE_STATE_PATH });
|
|
|
|
test("/this-route-does-not-exist renders the branded 404 page", async ({
|
|
page,
|
|
}) => {
|
|
const response = await page.goto("/this-route-does-not-exist", {
|
|
waitUntil: "domcontentloaded",
|
|
});
|
|
expect(response?.status()).toBe(404);
|
|
expect(page.url()).not.toMatch(/\/auth\/login/);
|
|
|
|
await expect(page.getByText(/^404$/i)).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", {
|
|
name: /this page doesn't exist|diese seite existiert nicht/i,
|
|
}),
|
|
).toBeVisible();
|
|
|
|
const back = page.getByRole("link", {
|
|
name: /back to dashboard|zurück zum dashboard/i,
|
|
});
|
|
await expect(back).toBeVisible();
|
|
await expect(back).toHaveAttribute("href", "/");
|
|
});
|
|
});
|