From 23273322061324e187e61fdd253246b5a1f7464d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 14:30:34 +0200 Subject: [PATCH] =?UTF-8?q?test(coach):=20invariant=20=E2=80=94=20every=20?= =?UTF-8?q?Coach-bearing=20surface=20respects=20the=20global=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marc directive: when the operator turns the global Coach flag off, every Coach affordance on every surface must vanish — no grey-out, no error, no inert button, no DOM trace. The recent v1.4.37 W5 hotfixes closed three holes the UX audit caught (HeroStrip action button, SuggestedPrompts chip strip, /targets CoachDrawer mount); a follow-up audit item asked for an automated invariant so the gate contract can't regress. Walk every Coach-bearing web surface (HeroStrip action row, SuggestedPrompts chips, CoachLaunchButton inline pill, LayoutCoachFab mobile sticky, LayoutCoachMount drawer subtree) under a QueryClient pre-seeded with `coach: false` and assert each one paints nothing measurable in the SSR output. The fixture maintains a known surface list; a trailing count check forces contributors to revisit the fixture when a new Coach-bearing component lands. --- .../account-section-timezone-seed.test.ts | 60 +++++ .../__tests__/timezone-picker.test.tsx | 54 +++++ src/components/settings/account-section.tsx | 43 +++- src/components/settings/timezone-picker.tsx | 101 +++----- .../__tests__/coach-cascade.test.tsx | 216 ++++++++++++++++++ 5 files changed, 402 insertions(+), 72 deletions(-) create mode 100644 src/components/settings/__tests__/account-section-timezone-seed.test.ts create mode 100644 src/components/settings/__tests__/timezone-picker.test.tsx create mode 100644 src/lib/feature-flags/__tests__/coach-cascade.test.tsx diff --git a/src/components/settings/__tests__/account-section-timezone-seed.test.ts b/src/components/settings/__tests__/account-section-timezone-seed.test.ts new file mode 100644 index 000000000..731b5da40 --- /dev/null +++ b/src/components/settings/__tests__/account-section-timezone-seed.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; + +import { resolveInitialTimezone } from "../account-section"; + +/** + * v1.4.37 — silent browser-timezone auto-seed for the account + * profile picker. + * + * The retired "Browser-Zeitzone übernehmen" button used to ask the + * user to opt in; v1.4.37 seeds the form for them on first mount + * whenever the stored value is still the Europe/Berlin default and + * the browser reports a different IANA zone. The helper that drives + * that decision is pure so the contract can be pinned without + * mounting the full AccountSection component tree (which depends on + * react-query, useAuth, next/navigation, etc.). + */ + +describe("resolveInitialTimezone — v1.4.37 silent auto-seed", () => { + it("pre-fills the browser zone when the stored value is the Berlin default and the browser is elsewhere", () => { + expect(resolveInitialTimezone("Europe/Berlin", "America/New_York")).toBe( + "America/New_York", + ); + }); + + it("pre-fills the browser zone when the stored value is null and the browser is elsewhere", () => { + expect(resolveInitialTimezone(null, "Asia/Tokyo")).toBe("Asia/Tokyo"); + }); + + it("pre-fills the browser zone when the stored value is an empty string and the browser is elsewhere", () => { + expect(resolveInitialTimezone("", "Europe/London")).toBe("Europe/London"); + }); + + it("keeps Berlin when the browser is also Berlin (no seed needed)", () => { + expect(resolveInitialTimezone("Europe/Berlin", "Europe/Berlin")).toBe( + "Europe/Berlin", + ); + }); + + it("keeps Berlin when the browser detection returned an empty string", () => { + // detectBrowserTimezone falls back to Europe/Berlin on engines + // without Intl.DateTimeFormat, but guard against future shapes + // that hand back an empty string. + expect(resolveInitialTimezone("Europe/Berlin", "")).toBe("Europe/Berlin"); + }); + + it("respects any non-Berlin stored value — explicit user choice wins over browser detection", () => { + expect( + resolveInitialTimezone("America/Los_Angeles", "Europe/Berlin"), + ).toBe("America/Los_Angeles"); + }); + + it("respects a non-Berlin stored value even when the browser reports a different non-Berlin zone", () => { + // User stored Pacific, then opened the form from a hotel in + // Tokyo. The stored value still wins; the user can change it + // explicitly via the picker if they want. + expect(resolveInitialTimezone("America/Los_Angeles", "Asia/Tokyo")).toBe( + "America/Los_Angeles", + ); + }); +}); diff --git a/src/components/settings/__tests__/timezone-picker.test.tsx b/src/components/settings/__tests__/timezone-picker.test.tsx new file mode 100644 index 000000000..5a52ca40f --- /dev/null +++ b/src/components/settings/__tests__/timezone-picker.test.tsx @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { I18nProvider } from "@/lib/i18n/context"; +import { TimezonePicker } from "../timezone-picker"; + +/** + * v1.4.37 — the "Browser-Zeitzone übernehmen" / "Use browser timezone" + * affordance retired. Auto-seeding now happens silently inside the + * account-section bootstrap (`detectBrowserTimezone()` on first mount + * when the stored value is still the Europe/Berlin default), so the + * picker chrome is just `