mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
Merge branch 'feat/v144-settings' into release/v1.44.0
# Conflicts: # src/components/settings/privacy-section.tsx
This commit is contained in:
commit
e1b70537cc
129 files changed
+5138
-3169
No files matched your search
@@ -807,12 +807,14 @@ test.describe("Coach page frame", () => {
|
||||
const popover = page.locator('[data-slot="coach-settings-popover"]');
|
||||
await expect(popover).toBeVisible();
|
||||
await expect(popover).toHaveAttribute("role", "dialog");
|
||||
// The model is chosen in Settings > AI; the overlay shows what is in
|
||||
// effect and links there.
|
||||
await expect(
|
||||
popover.locator('[data-slot="coach-quick-provider"]'),
|
||||
).toHaveValue("local");
|
||||
).toHaveAttribute("data-value", "local");
|
||||
await expect(
|
||||
popover.locator('[data-slot="coach-quick-model"]'),
|
||||
).toHaveValue("qwen2.5");
|
||||
).toHaveAttribute("data-value", "qwen2.5");
|
||||
await expect(
|
||||
popover.locator('[data-slot="coach-sources-window"]'),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -270,21 +270,6 @@ async function saveCard(
|
||||
return (await settled).status();
|
||||
}
|
||||
|
||||
/** Flip a channel card's enable switch and wait for the route to answer. */
|
||||
async function toggleCard(
|
||||
page: Page,
|
||||
switchId: string,
|
||||
endpoint: string,
|
||||
): Promise<number> {
|
||||
const settled = page.waitForResponse(
|
||||
(res) =>
|
||||
new URL(res.url()).pathname === endpoint &&
|
||||
res.request().method() === "PUT",
|
||||
);
|
||||
await page.locator(switchId).click();
|
||||
return (await settled).status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure both channels the way a person would: email on with a recipient,
|
||||
* ntfy saved and left off. Everything here happens on the settings surface.
|
||||
@@ -292,11 +277,10 @@ async function toggleCard(
|
||||
async function configureChannels(page: Page): Promise<void> {
|
||||
await openChannels(page);
|
||||
|
||||
// The enable switch is a field of the form: it saves with the one Save.
|
||||
await fillField(page, "#email-recipient", EMAIL_RECIPIENT);
|
||||
await page.locator("#email-toggle").click();
|
||||
expect(await saveCard(page, "#email", "/api/settings/email")).toBe(200);
|
||||
expect(await toggleCard(page, "#email-toggle", "/api/settings/email")).toBe(
|
||||
200,
|
||||
);
|
||||
await expect(page.locator("#email-toggle")).toHaveAttribute(
|
||||
"data-state",
|
||||
"checked",
|
||||
|
||||
@@ -58,11 +58,13 @@ const SETTINGS_ROUTES = [
|
||||
"/settings/api",
|
||||
"/settings/mcp",
|
||||
"/settings/advanced",
|
||||
"/settings/preview",
|
||||
"/settings/about",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* The Layout hub's seven children. They are the routes whose header block
|
||||
* The Arrangement hub's children, plus (v1.44) the module pages under
|
||||
* Modules, which use the same back-link header. They are the routes whose header block
|
||||
* changed MOST — the shell used to hand-roll a `space-y-6` between the
|
||||
* back-link and the title, and `PageHeader`'s documented contract is
|
||||
* `space-y-1.5`. Nothing measured them before, so the step went from 24 px
|
||||
@@ -73,10 +75,12 @@ const LAYOUT_CHILD_ROUTES = [
|
||||
"/settings/layout/dashboard",
|
||||
"/settings/layout/insights",
|
||||
"/settings/layout/medications",
|
||||
"/settings/layout/mood",
|
||||
"/settings/layout/labs",
|
||||
"/settings/layout/illness",
|
||||
"/settings/layout/vorsorge",
|
||||
"/settings/modules/medications",
|
||||
"/settings/modules/mood",
|
||||
"/settings/modules/labs",
|
||||
] as const;
|
||||
|
||||
/** The `space-y-1.5` between a back-link and the title it sits above. */
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { expect, test } from "./setup/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* Settings navigation at 390 px: one button names the current section and
|
||||
* opens a sheet with every section under its group. It replaced a strip of
|
||||
* every section as chips, where most of Settings sat off screen.
|
||||
*/
|
||||
test.describe("settings navigation on a phone", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test("names the section, opens the grouped list and moves on a tap", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/settings/account", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const trigger = page.locator('[data-slot="settings-mobile-nav-trigger"]');
|
||||
await expect(trigger).toBeVisible({ timeout: 15_000 });
|
||||
await expect(trigger).toContainText("Account");
|
||||
|
||||
// The click can land before hydration; retry until the sheet opens.
|
||||
const list = page.locator('[data-slot="settings-mobile-nav-list"]');
|
||||
await expect(async () => {
|
||||
await trigger.click();
|
||||
await expect(list).toBeVisible({ timeout: 2_000 });
|
||||
}).toPass({ timeout: 15_000 });
|
||||
|
||||
for (const group of ["Account", "Tracking", "Notifications", "System"]) {
|
||||
await expect(
|
||||
list.getByRole("heading", { name: group, exact: true }),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
await list.getByRole("link", { name: "Security" }).click();
|
||||
await expect(page).toHaveURL(/\/settings\/security$/);
|
||||
await expect(list).toHaveCount(0);
|
||||
await expect(trigger).toContainText("Security");
|
||||
|
||||
// Nothing scrolls sideways.
|
||||
const overflow = await page.evaluate(
|
||||
() => document.documentElement.scrollWidth - window.innerWidth,
|
||||
);
|
||||
expect(overflow).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
@@ -33,7 +33,7 @@ const ROUTES = [
|
||||
// one-scroll-floor assertion below).
|
||||
"/settings/layout/insights",
|
||||
"/settings/layout/medications",
|
||||
"/settings/layout/mood",
|
||||
"/settings/modules/mood",
|
||||
"/settings/modules",
|
||||
"/settings/account",
|
||||
"/settings/notifications",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
* Issue #490 — client-rendered timestamps follow the PROFILE timezone.
|
||||
*
|
||||
* `/api/auth/me` is mocked with an `Asia/Manila` profile; the sessions
|
||||
* card on /settings/privacy renders `lastActiveAt` through
|
||||
* card on /settings/security renders `lastActiveAt` through
|
||||
* `useFormatters()`, which reads the localStorage timezone mirror that
|
||||
* `fetchMe` fills from the mocked payload. A fixed UTC instant must
|
||||
* therefore paint the MANILA wall clock (14:30), never the Berlin
|
||||
@@ -75,7 +75,7 @@ test.describe("profile-timezone display (#490)", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto("/settings/privacy", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/settings/security", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// The sessions card is collapsed by default — expand it first. The
|
||||
// toggle's onClick can be dropped if Playwright clicks before React has
|
||||
|
||||
Reference in new issue
Block a user