mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(dashboard): mood tile mobile shows number once + label, not twice
The mood-list mobile branch (`md:hidden`) rendered each entry's score
TWICE: once as a large bold digit in the left badge, then again next
to it in the title line as "{score} ({label})". Desktop's table view
only ever showed it once. The redundancy made the row read like
"2 2 (schlecht)" — the badge already supplies the digit, the title
line just needs the textual label.
Drop the duplicate digit from the title line; the row now reads
"[2] Schlecht / 12.05.2026 18:30 / tags". Add `data-testid` hooks
on the row and the score badge so the Pixel-5 Playwright guard at
`e2e/mood-card-mobile.spec.ts` can assert "exactly one occurrence of
the score digit per row" — that contract is what would have caught the
duplicate before merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a967895808
commit
2c227fb0c1
2 files changed
+112
-11
No files matched your search
@@ -0,0 +1,94 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
|
||||
/**
|
||||
* v1.4.15 phase-A3 fix #2 — mood-list mobile card was rendering the score
|
||||
* twice: once in the left badge (large bold digit) and once in the title
|
||||
* line as "{score} ({label})". On desktop the table view only ever showed
|
||||
* one. The fix collapses the title line down to just the localized label
|
||||
* (e.g., "Schlecht"), so the row reads
|
||||
*
|
||||
* [ 2 ] Schlecht
|
||||
* 12.05.2026, 18:30
|
||||
*
|
||||
* The Pixel-5 viewport is the only one that mounted the mobile branch
|
||||
* (`md:hidden`) so the regression was mobile-only — this spec runs on the
|
||||
* `chromium-mobile` project and asserts each `data-testid="mood-row"`
|
||||
* contains exactly one occurrence of its score digit.
|
||||
*/
|
||||
test.describe("mood card mobile layout", () => {
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
test.beforeEach(({}, testInfo) => {
|
||||
test.skip(testInfo.project.name !== "chromium-mobile", "mobile-only spec");
|
||||
});
|
||||
|
||||
test("each mobile row shows the score number exactly once", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Stub the mood-entries endpoint with a deterministic dataset whose
|
||||
// scores are all distinct digits — that lets us reason about
|
||||
// "exactly one occurrence" per row independently of locale or label.
|
||||
await page.route("**/api/mood-entries*", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
entries: [
|
||||
{
|
||||
id: "m1",
|
||||
date: "2026-05-08",
|
||||
mood: "SCHLECHT",
|
||||
score: 2,
|
||||
tags: [],
|
||||
source: "MANUAL",
|
||||
moodLoggedAt: "2026-05-08T08:30:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "m2",
|
||||
date: "2026-05-07",
|
||||
mood: "GUT",
|
||||
score: 4,
|
||||
tags: [],
|
||||
source: "MANUAL",
|
||||
moodLoggedAt: "2026-05-07T19:00:00.000Z",
|
||||
},
|
||||
],
|
||||
meta: { total: 2 },
|
||||
},
|
||||
error: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await page.goto("/mood", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const rows = page.locator('[data-testid="mood-row"]');
|
||||
await expect(rows).toHaveCount(2, { timeout: 10_000 });
|
||||
|
||||
// For each row, the rendered text must contain its score digit
|
||||
// exactly once. The desktop table also shows the digit + label in
|
||||
// one cell, but `md:hidden` hides this branch above the breakpoint —
|
||||
// on the Pixel-5 profile only the mobile branch mounts.
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const row = rows.nth(i);
|
||||
const text = await row.innerText();
|
||||
const expectedScore = i === 0 ? "2" : "4";
|
||||
const occurrences = (
|
||||
text.match(new RegExp(`(?<!\\d)${expectedScore}(?!\\d)`, "g")) ?? []
|
||||
).length;
|
||||
expect(
|
||||
occurrences,
|
||||
`row ${i} should contain "${expectedScore}" exactly once, got ${occurrences} in: ${text}`,
|
||||
).toBe(1);
|
||||
}
|
||||
|
||||
// The big-badge score must still be present (we kept it).
|
||||
const scoreBadges = page.locator('[data-testid="mood-row-score"]');
|
||||
await expect(scoreBadges).toHaveCount(2);
|
||||
await expect(scoreBadges.nth(0)).toHaveText("2");
|
||||
await expect(scoreBadges.nth(1)).toHaveText("4");
|
||||
});
|
||||
});
|
||||
@@ -383,29 +383,36 @@ export function MoodList() {
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* Mobile list */}
|
||||
{/* Mobile list — v1.4.15 phase-A3 fix #2: previously the row
|
||||
rendered the score TWICE on mobile (the big number in the
|
||||
left badge AND a duplicate in the title line: "2 (schlecht)").
|
||||
Desktop's table version only ever showed one. The badge is
|
||||
the visual anchor; next to it the user wants the textual
|
||||
label, not a second copy of the digit. The
|
||||
`data-testid="mood-row"` hook is what the Playwright Pixel-5
|
||||
guard at `e2e/mood-card-mobile.spec.ts` queries to assert
|
||||
"exactly one occurrence of the score per row". */}
|
||||
<div className="space-y-2 md:hidden">
|
||||
{data.entries.map((entry) => (
|
||||
<div
|
||||
key={entry.id}
|
||||
data-testid="mood-row"
|
||||
className="bg-card border-border flex items-center justify-between rounded-lg border p-3"
|
||||
>
|
||||
<div className="flex items-center gap-2.5 overflow-hidden">
|
||||
<div className="bg-muted flex h-8 w-8 shrink-0 items-center justify-center rounded-lg">
|
||||
<span className="text-lg font-bold tabular-nums">
|
||||
<span
|
||||
data-testid="mood-row-score"
|
||||
className="text-lg font-bold tabular-nums"
|
||||
>
|
||||
{entry.score}
|
||||
</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<span className="text-sm font-semibold tabular-nums">
|
||||
{entry.score}{" "}
|
||||
<span className="text-muted-foreground font-normal">
|
||||
(
|
||||
{MOOD_LABEL_KEYS[entry.mood]
|
||||
? t(MOOD_LABEL_KEYS[entry.mood])
|
||||
: entry.mood}
|
||||
)
|
||||
</span>
|
||||
<span className="text-sm font-semibold">
|
||||
{MOOD_LABEL_KEYS[entry.mood]
|
||||
? t(MOOD_LABEL_KEYS[entry.mood])
|
||||
: entry.mood}
|
||||
</span>
|
||||
<p className="text-muted-foreground truncate text-xs">
|
||||
{formatDateTime(entry.moodLoggedAt)}
|
||||
|
||||
Reference in new issue
Block a user