diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index f184cd224..86bb5ba01 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: HealthLog API - version: 1.5.6 + version: 1.6.0 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. diff --git a/e2e/medications-wizard-helpers.ts b/e2e/medications-wizard-helpers.ts index a0386d431..5f4352c2e 100644 --- a/e2e/medications-wizard-helpers.ts +++ b/e2e/medications-wizard-helpers.ts @@ -124,6 +124,23 @@ export async function stubDashboardAnalytics(page: Page): Promise { * redirects to `/medications?new=1` and opens the create dialog). */ export async function openCreateWizard(page: Page): Promise { + // Pin the German locale for the wizard flow. The app default locale is + // English; these specs assert the "Schritt N von M" counter and other + // German label surfaces, which only render when the `healthlog-locale` + // cookie resolves to "de". Both the server layout and the client i18n + // provider read this exact cookie name, so setting it before the first + // navigation makes the dialog render German on first paint. Scoped here + // rather than in the shared auth state so English-asserting specs keep + // the default locale. + await page.context().addCookies([ + { + name: "healthlog-locale", + value: "de", + url: page.url().startsWith("http") + ? new URL(page.url()).origin + : "http://localhost:3000", + }, + ]); await page.goto("/medications/new", { waitUntil: "domcontentloaded" }); await expect( page.locator('[data-slot="medication-wizard-dialog"]'), diff --git a/e2e/setup/global-setup.ts b/e2e/setup/global-setup.ts index e57e6897f..af5dc2844 100644 --- a/e2e/setup/global-setup.ts +++ b/e2e/setup/global-setup.ts @@ -181,7 +181,7 @@ export default async function globalSetup(config: FullConfig): Promise { // already-onboarded ones, so for our pre-seeded completed e2e user // the cookie is absent and the proxy passes the dashboard through. state.cookies = state.cookies.filter((c) => - ["healthlog_session", "healthlog_locale", "hl_onboarding"].includes(c.name), + ["healthlog_session", "healthlog-locale", "hl_onboarding"].includes(c.name), ); await writeFile(STORAGE_STATE_PATH, JSON.stringify(state, null, 2)); await ctx.dispose(); diff --git a/tests/integration/medication-cadence-worker.test.ts b/tests/integration/medication-cadence-worker.test.ts index dbf09a46f..c0adee4e5 100644 --- a/tests/integration/medication-cadence-worker.test.ts +++ b/tests/integration/medication-cadence-worker.test.ts @@ -133,6 +133,26 @@ async function loadCanonical(medicationId: string): Promise<{ * matching `getUserTodayBounds` in `src/lib/timezone.ts` so the test * window matches what the worker would build on that tick. */ +// UTC midnight on the local calendar day that `now` falls within for `tz`. +// Mirrors the column convention for `startsOn`/`endsOn` (stored as +// `...T00:00:00.000Z` on the local calendar day). Deriving the day from +// the same timezone `todayBounds` uses keeps a one-shot's emit day inside +// the checked window regardless of wall-clock time — reading the UTC date +// instead drifts a day late in the UTC evening when the local zone has +// already rolled into the next calendar day. +function localCalendarMidnightUtc(now: Date, tz: string): Date { + const parts = new Intl.DateTimeFormat("en-CA", { + timeZone: tz, + year: "numeric", + month: "2-digit", + day: "2-digit", + }).formatToParts(now); + const y = Number(parts.find((p) => p.type === "year")?.value); + const m = Number(parts.find((p) => p.type === "month")?.value); + const d = Number(parts.find((p) => p.type === "day")?.value); + return new Date(Date.UTC(y, m - 1, d, 0, 0, 0, 0)); +} + function todayBounds(now: Date, tz: string): { start: Date; end: Date } { const parts = new Intl.DateTimeFormat("en-CA", { timeZone: tz, @@ -467,17 +487,7 @@ describe("one-shot lifecycle", () => { it("mints the slot, the intake POST flips medication.active to false", async () => { const prisma = getPrismaClient(); const today = new Date(); - const todayDate = new Date( - Date.UTC( - today.getUTCFullYear(), - today.getUTCMonth(), - today.getUTCDate(), - 0, - 0, - 0, - 0, - ), - ); + const todayDate = localCalendarMidnightUtc(today, "Europe/Berlin"); const med = await prisma.medication.create({ data: { @@ -543,17 +553,7 @@ describe("one-shot lifecycle", () => { it("does NOT deactivate when the intake is skipped", async () => { const prisma = getPrismaClient(); const today = new Date(); - const todayDate = new Date( - Date.UTC( - today.getUTCFullYear(), - today.getUTCMonth(), - today.getUTCDate(), - 0, - 0, - 0, - 0, - ), - ); + const todayDate = localCalendarMidnightUtc(today, "Europe/Berlin"); const med = await prisma.medication.create({ data: {