mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
test(ui): gate the two card-header classes that have to agree
The header fix needs two declarations to hold together: the second grid row is conditional on a description, and CardAction spans the full row set. The second is the trap. `row-span-2` against a single explicit row mints an implicit second row and brings the phantom gap straight back for every header that carries an action but no description. Reverting it broke nothing. Nothing in the suite asserts a `row-span` class, so the regression is DOM geometry the unit tests cannot see, and it would have come back silently at the next tidy-up. This is a literal-class assertion in the shape `assessment-spine-order.test.ts` already uses, because the question has an exact answer. Also finishes the compact-card tier while it is open: three cards on the metric surfaces still carried `gap-1.5` where the assessment cards carry `gap-2`. One tier, one value, no `gap-1.5 py-3` left in the tree.
This commit is contained in:
1 parent
14d0eeeb46
commit
8a2bb70dbd
3 files changed
+65
-3
No files matched your search
@@ -258,7 +258,7 @@ export function MetricStatStrip({
|
||||
data-multi-series="true"
|
||||
role="group"
|
||||
aria-label={groupLabel ?? t("insights.subPage.stats.label")}
|
||||
className="gap-1.5 py-3 md:py-4"
|
||||
className="gap-2 py-3 md:py-4"
|
||||
>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 gap-x-6 gap-y-4 md:grid-cols-2">
|
||||
@@ -291,7 +291,7 @@ export function MetricStatStrip({
|
||||
? `${t("insights.subPage.stats.label")} — ${seriesLabel}`
|
||||
: t("insights.subPage.stats.label")
|
||||
}
|
||||
className="gap-1.5 py-3 md:py-4"
|
||||
className="gap-2 py-3 md:py-4"
|
||||
>
|
||||
<CardContent>
|
||||
<SeriesBlock
|
||||
|
||||
@@ -326,7 +326,7 @@ function TargetReferencePanel({
|
||||
<Card
|
||||
data-slot="metric-target-summary"
|
||||
data-target-type={target.type}
|
||||
className="gap-1.5 py-3 md:py-4"
|
||||
className="gap-2 py-3 md:py-4"
|
||||
>
|
||||
{/* Row 1: the canonical tile header — a single-word "Ziel" / "Target"
|
||||
heading (the range numbers live on the range bar below, so the
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* The Card header reserves its second row only when there is one to fill.
|
||||
*
|
||||
* `CardHeader` is a grid. It used to declare `grid-rows-[auto_auto]`
|
||||
* unconditionally, so a card with a title and no `CardDescription` carried an
|
||||
* empty second track and drew the header's `gap-2` against it. Together with the
|
||||
* Card's own `gap-4 md:gap-6` that made the distance from a heading to its body
|
||||
* 32 px on desktop where 24 px was intended. It was visible, it was reported, and
|
||||
* it affected 45 of the 48 files that mount a `CardHeader`.
|
||||
*
|
||||
* Two class declarations have to agree for the fix to hold:
|
||||
*
|
||||
* 1. the second row is conditional on a description being present, and
|
||||
* 2. `CardAction` spans the full row set rather than a hard-coded two.
|
||||
*
|
||||
* The second one is the trap. `row-span-2` against a single explicit row mints
|
||||
* an implicit second row, which brings the gap straight back for every header
|
||||
* that carries an action but no description. Reverting it produces no test
|
||||
* failure anywhere else in the suite, because nothing asserts a `row-span`
|
||||
* class: the regression is DOM geometry, which the unit tests cannot see.
|
||||
*
|
||||
* So this file is the guard. It is deliberately a literal-class assertion, the
|
||||
* same shape `assessment-spine-order.test.ts` already uses, because the question
|
||||
* has an exact answer and an exact answer belongs in a gate rather than in a
|
||||
* reviewer's memory.
|
||||
*/
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const SOURCE = readFileSync(
|
||||
join(process.cwd(), "src/components/ui/card.tsx"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
describe("CardHeader row reservation", () => {
|
||||
it("reserves the second row only when a description is present", () => {
|
||||
expect(SOURCE).toContain(
|
||||
"has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
||||
);
|
||||
});
|
||||
|
||||
it("never declares the second row unconditionally", () => {
|
||||
// The unconditional form is what drew the phantom gap. It may only ever
|
||||
// appear behind the `has-data-[slot=card-description]:` variant, so the bare
|
||||
// token must not occur on its own.
|
||||
const bare = SOURCE.replace(
|
||||
/has-data-\[slot=card-description\]:grid-rows-\[auto_auto\]/g,
|
||||
"",
|
||||
);
|
||||
expect(bare).not.toContain("grid-rows-[auto_auto]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("CardAction row span", () => {
|
||||
it("spans the full row set rather than a hard-coded two", () => {
|
||||
// `row-span-2` would mint an implicit second row in a description-less
|
||||
// header and resurrect the gap the header fix just removed.
|
||||
expect(SOURCE).toContain("row-span-full");
|
||||
expect(SOURCE).not.toContain("row-span-2");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user