mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(e2e): re-baseline mobile-consistency height assertions to the 40 px floor and retire the insights-card-preview spec
The settings-mobile-consistency spec asserted a 36 px h-9 floor across the account form, the dashboard Compare-to trigger and the AI native selects. The v1.4.27 R3d MB2 pass lifted Input, Select and the native select primitives to the WCAG 2.5.5 40 px h-10 floor; the assertions move with the primitives. The insights-card-preview spec exercised the standalone preview that B1 retired in 9afb31bd; with the surface gone the spec greens nothing and the unit-level dashboard-layout test already guards against accidental reintroduction.
This commit is contained in:
1 parent
004a8491c7
commit
e1befb4523
2 files changed
+14
-195
No files matched your search
@@ -1,170 +0,0 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* v1.4.16 phase D reconcile (CRITICAL C2) — proves the polished
|
||||
* `<InsightsCardPreview>` is mounted on the root dashboard `/`.
|
||||
*
|
||||
* Before this fix the component shipped in B1b commit 5 (`d2cdf9d`)
|
||||
* was an orphan with zero non-test imports. The dashboard read no
|
||||
* advisor payload, so the top severity-ordered AI recommendation +
|
||||
* confidence ring + "View all" CTA was unreachable from any live
|
||||
* route. This spec asserts the card now renders when the layout
|
||||
* toggle is active and the advisor returns at least one rec.
|
||||
*/
|
||||
test.describe("insights card preview on dashboard (C2)", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Populated analytics so the dashboard clears its empty-state and
|
||||
// renders charts + tile strip.
|
||||
await page.route("**/api/analytics", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
summaries: {
|
||||
WEIGHT: {
|
||||
latest: 78.5,
|
||||
avg7: 78.2,
|
||||
avg30: 77.9,
|
||||
slope30: { slope: -0.05, direction: "down" },
|
||||
count: 30,
|
||||
},
|
||||
},
|
||||
bpInTargetPct: null,
|
||||
glucoseByContext: {},
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route("**/api/mood/analytics", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: { entries: [], summary: { count: 0 } },
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Layout enables the insights preview widget so the gate is open.
|
||||
await page.route("**/api/dashboard/widgets", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
version: 1,
|
||||
widgets: [
|
||||
{ id: "weight", visible: true, tileVisible: true, order: 0 },
|
||||
{
|
||||
id: "insightsPreview",
|
||||
visible: true,
|
||||
tileVisible: false,
|
||||
order: 14,
|
||||
},
|
||||
],
|
||||
comparisonBaseline: "none",
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.route("**/api/measurements*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: { measurements: [], meta: { total: 0 } },
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
// Advisor payload — populated with one urgent rec carrying a
|
||||
// confidence score so the preview renders the rec text + the
|
||||
// ring confidence meter inline.
|
||||
await page.route("**/api/insights/generate", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
insights: {
|
||||
insightType: "general",
|
||||
summary: "Weekly summary.",
|
||||
classification: "gut",
|
||||
classificationLabel: "Good",
|
||||
findings: [],
|
||||
correlations: [],
|
||||
primaryRecommendation: "",
|
||||
recommendations: [
|
||||
{
|
||||
id: "rec-urgent-1",
|
||||
text: "Schedule a follow-up reading this week.",
|
||||
severity: "urgent",
|
||||
confidence: 82,
|
||||
rationale: {
|
||||
dataWindow: "last7days",
|
||||
comparedTo: "your 90-day median",
|
||||
deviation: "Sys BP averaged 138 over the past week",
|
||||
},
|
||||
metricSource: {
|
||||
type: "bloodPressureSys",
|
||||
timeRange: "last7days",
|
||||
summary: "Sys BP avg 138",
|
||||
},
|
||||
},
|
||||
],
|
||||
dataQuality: { coverage: "good", gaps: [], confidence: "hoch" },
|
||||
disclaimer: "Not medical advice.",
|
||||
},
|
||||
cached: true,
|
||||
cachedAt: new Date().toISOString(),
|
||||
legacyPayload: false,
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test("dashboard renders InsightsCardPreview with top recommendation", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// Preview is the C2 deliverable — `data-slot="insights-card-preview"`
|
||||
// is the stable hook the component exposes.
|
||||
await expect(
|
||||
page.locator('[data-slot="insights-card-preview"]'),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// The urgent rec text from the mock payload renders inside the
|
||||
// preview's compact tile.
|
||||
await expect(
|
||||
page.getByText("Schedule a follow-up reading this week."),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// "View all" CTA points at /insights so the user can drill in.
|
||||
await expect(
|
||||
page.locator('[data-slot="insights-card-view-all"]'),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
// The ring-variant ConfidenceMeter renders inline next to the rec
|
||||
// text — its slot is the same one used on /insights, so a single
|
||||
// selector confirms wiring.
|
||||
await expect(
|
||||
page.locator(
|
||||
'[data-slot="insights-card-preview"] [data-slot="confidence-meter"]',
|
||||
),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
});
|
||||
@@ -4,29 +4,18 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* v1.4.19 A6 — Settings consistency snapshot at Pixel-5 (393 CSS px).
|
||||
* v1.4.27 — input height floor lifted from 36 px (`h-9`) to 40 px
|
||||
* (`h-10`) on `Input`, `Select` trigger, and the native `<select>`
|
||||
* primitives per the WCAG 2.5.5 tap-target sweep (MB2). The
|
||||
* Dashboard Compare-to trigger followed the same path.
|
||||
*
|
||||
* Marc reported four interlocking inconsistencies on the mobile
|
||||
* settings shell:
|
||||
* The fixes that still apply:
|
||||
*
|
||||
* 1. Input heights differed across sections — `<Input>` rendered at
|
||||
* 36 px, the AI active-provider native `<select>` at 40 px, the
|
||||
* AI add-provider select at 32 px, the Dashboard "Compare to"
|
||||
* `<SelectTrigger>` at 44 px. Same shell, four heights.
|
||||
* 2. Action buttons inside title rows ("Change password", "Restart
|
||||
* onboarding tour") could overflow the parent card on narrow
|
||||
* viewports because the `flex justify-between` row never wrapped.
|
||||
* 3. The Sprache (language) select was buried half-way down the
|
||||
* Profile card, paired with a profile-data field (date of birth).
|
||||
* 4. Card-internal `space-y-*` rhythm varied between 3 / 4 / 5 / 6.
|
||||
*
|
||||
* The fixes:
|
||||
*
|
||||
* - `h-9` (36 px) is the canonical input height across Settings.
|
||||
* - 40 px (`h-10`) is the canonical input height across Settings.
|
||||
* - Title + action rows use `flex-col` on `<sm` and `flex-row` on
|
||||
* `>=sm` so the action button stacks below on mobile.
|
||||
* - Sprache lifted into its own row at the bottom of the Profile
|
||||
* card, `max-w-xs` on `>=sm` so it doesn't render heavier than
|
||||
* other fields.
|
||||
* - Sprache pairs with date-of-birth in a single `sm:grid-cols-2`
|
||||
* row at the bottom of the Profile card (v1.4.27 R1 audit).
|
||||
* - Card-internal spacing standardised on `space-y-4`.
|
||||
*
|
||||
* This spec captures the post-fix invariants. It is mobile-only — the
|
||||
@@ -39,7 +28,7 @@ test.describe("Settings mobile consistency (Pixel 5)", () => {
|
||||
test.skip(testInfo.project.name !== "chromium-mobile", "mobile-only spec");
|
||||
});
|
||||
|
||||
test("/settings/account: every form input renders at 36 px", async ({
|
||||
test("/settings/account: every form input renders at 40 px", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/settings/account", { waitUntil: "networkidle" });
|
||||
@@ -61,7 +50,7 @@ test.describe("Settings mobile consistency (Pixel 5)", () => {
|
||||
|
||||
expect(formInputs.length).toBeGreaterThan(0);
|
||||
for (const inp of formInputs) {
|
||||
expect.soft(inp.height, `${inp.tag}#${inp.id} (${inp.type})`).toBe(36);
|
||||
expect.soft(inp.height, `${inp.tag}#${inp.id} (${inp.type})`).toBe(40);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -143,7 +132,7 @@ test.describe("Settings mobile consistency (Pixel 5)", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("/settings/dashboard: Compare-to trigger renders at 36 px", async ({
|
||||
test("/settings/dashboard: Compare-to trigger renders at 40 px", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/settings/dashboard", { waitUntil: "networkidle" });
|
||||
@@ -152,10 +141,10 @@ test.describe("Settings mobile consistency (Pixel 5)", () => {
|
||||
const trigger = page.locator("#comparison-baseline");
|
||||
await expect(trigger).toBeVisible();
|
||||
const h = await trigger.evaluate((el) => el.getBoundingClientRect().height);
|
||||
expect(Math.round(h)).toBe(36);
|
||||
expect(Math.round(h)).toBe(40);
|
||||
});
|
||||
|
||||
test("/settings/ai: every native select renders at 36 px", async ({
|
||||
test("/settings/ai: every native select renders at 40 px", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/settings/ai", { waitUntil: "networkidle" });
|
||||
@@ -169,7 +158,7 @@ test.describe("Settings mobile consistency (Pixel 5)", () => {
|
||||
|
||||
expect(heights.length).toBeGreaterThan(0);
|
||||
for (const h of heights) {
|
||||
expect.soft(h).toBe(36);
|
||||
expect.soft(h).toBe(40);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user