Files
HealthLog/src/app/__tests__/targets-responsive.test.tsx
T
Marc-André Bombeck 17b8d8d2c2 chore(release): v1.4.25
Largest feature delta in the v1.4.x line. Insights expands into seven dedicated metric sub-pages; GLP-1 medication tracking lands end-to-end (drug-level chart with Research Mode acknowledgment, EMA titration ladder, cadence + compliance, side-effect taxonomy, pen/vial inventory); cross-source priority becomes a two-axis resolver; per-user timezone threads through ten analytics surfaces; Withings activity + sleep v2 sync; onboarding rebuilt as a nested-route wizard; Personal Records ship end-to-end; Health Score provenance accordion; Coach runs native first-party prompts across six locales with a 1800-assertion refusal-probe matrix; OpenAPI drift gate hard-fails; multi-arch GHCR image. Migrations 0043 through 0060 are additive and forward-only. PROMPT_VERSION 4.24.0 → 4.25.0 with GROUND RULE 9 (refuses dose recommendations) and GROUND RULE 15 (refuses drug-level estimates with MDR + MDCG 2021-24 cites).

See CHANGELOG.md for the full per-section delta.
2026-05-14 23:49:32 +02:00

134 lines
3.6 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
/**
* v1.4.25 W3e — mobile-first responsive layout audit.
*
* Marc directive: "mobile layout must be designed alongside desktop,
* not as an afterthought". This test pins the Tailwind responsive
* utilities so a future refactor cannot quietly collapse the grid
* back to the v1.4.22 `sm:grid-cols-2` and remove the lg three-col
* row.
*
* The three breakpoints we care about:
* • default (<640px): one column, full-width cards
* • sm (640-1023px): two columns
* • lg (1024px+): three columns, slightly wider gutters
*/
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
usePathname: () => "/targets",
}));
const sampleData = {
targets: [
{
type: "WEIGHT",
label: "Weight",
current: 78,
average30: 78,
trend: "stable",
unit: "kg",
range: { min: 60, max: 80 },
classification: { category: "Normal", color: "#50fa7b" },
source: "WHO BMI",
daysInRange7d: 5,
daysLogged7d: 7,
lastMetGoalAt: null,
streakDays: 0,
insufficientData: false,
consistency7d: ["in", "in", "in", "in", "in", "in", "in"],
},
],
pageSummary: {
targetsMetThisWeek: 1,
totalTargets: 1,
streakHighlight: null,
},
bpDiastolic: { current: null, average30: null, range: null },
profile: { heightCm: 180, age: 35, gender: "MALE", glucoseUnit: null },
};
vi.mock("@tanstack/react-query", () => {
const noopClient = {
invalidateQueries: vi.fn(),
setQueryData: vi.fn(),
getQueryData: vi.fn(),
refetchQueries: vi.fn(),
};
return {
useQuery: ({ queryKey }: { queryKey: unknown[] }) => {
if (Array.isArray(queryKey) && queryKey[1] === "provider-chain") {
return {
data: { activeProvider: "openai" },
isLoading: false,
isError: false,
refetch: vi.fn(),
};
}
return {
data: sampleData,
isLoading: false,
isError: false,
refetch: vi.fn(),
};
},
useQueryClient: () => noopClient,
useMutation: () => ({
mutate: vi.fn(),
mutateAsync: vi.fn(),
isPending: false,
reset: vi.fn(),
}),
};
});
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({
user: {
id: "u1",
username: "marc",
email: "marc@example.com",
role: "USER",
},
isAuthenticated: true,
isLoading: false,
refetch: vi.fn(),
}),
}));
import { I18nProvider } from "@/lib/i18n/context";
import TargetsPage from "../targets/page";
function render(locale: "en" | "de" = "en") {
return renderToStaticMarkup(
<I18nProvider initialLocale={locale}>
<TargetsPage />
</I18nProvider>,
);
}
describe("/targets page — mobile-first responsive grid", () => {
it("uses one column by default, two at sm, three at lg", () => {
const html = render();
expect(html).toMatch(/grid-cols-1/);
expect(html).toMatch(/sm:grid-cols-2/);
expect(html).toMatch(/lg:grid-cols-3/);
});
it("uses gap-4 at mobile/sm, gap-6 at lg", () => {
const html = render();
expect(html).toMatch(/\bgap-4\b/);
expect(html).toMatch(/lg:gap-6/);
});
it("renders the Coach CTA full-width on mobile when AI is enabled", () => {
const html = render();
// The TargetCoachButton wraps the Sparkles + "Ask Coach" label;
// its container element should carry the mobile full-width class.
expect(html).toContain("w-full");
expect(html).toContain("sm:w-auto");
});
});