fix(settings): gate the Coach tuning on the record's module state

Settings → Coach hid its preference and nudge cards on the raw disableCoach
column, which /me publishes for the signed-in person. Inside a managed or
shared record that answered for the wrong person: a guardian with the Coach
on saw the tuning for a record that has it off, and the other way round.

The cards now read the module map the server resolves for the record on
screen, the same map the navigation and the settings entry already follow.
This commit is contained in:
Marc-André Bombeck committed 2026-10-01 09:50:41 +02:00
1 parent eae46ed535
commit fdbbd3a84a
4 files changed
+56 -9

No files matched your search

@@ -24,6 +24,8 @@ vi.mock("@/hooks/use-auth", () => ({
role: "USER",
timezone: "Europe/Berlin",
disableCoach: authState.disableCoach,
// The server's module map mirrors the record's opt-out.
modules: { coach: !authState.disableCoach },
},
isAuthenticated: true,
isLoading: false,
@@ -34,6 +34,8 @@ vi.mock("@/hooks/use-auth", () => ({
role: "USER",
timezone: "Europe/Berlin",
disableCoach: !coach.available,
// The record's module map, which the section's gate reads.
modules: { coach: coach.available },
},
isAuthenticated: true,
isLoading: false,
@@ -14,6 +14,13 @@ vi.mock("next/navigation", () => ({
usePathname: () => "/settings/coach",
}));
// Past hydration only where a test says so: the gate reads the account once
// mounted, and the pre-hydration pass shows every card.
const mountState = { mounted: false };
vi.mock("@/hooks/use-mounted", () => ({
useMounted: () => mountState.mounted,
}));
vi.mock("@tanstack/react-query", () => ({
useQuery: () => ({ data: null, isLoading: false, refetch: vi.fn() }),
useInfiniteQuery: () => ({ data: undefined, isLoading: false }),
@@ -31,10 +38,18 @@ vi.mock("@/hooks/use-ai-capability", () => ({
}),
}));
const authState: { disableCoach: boolean } = { disableCoach: false };
const authState: {
disableCoach: boolean;
modules: { coach?: boolean } | undefined;
} = { disableCoach: false, modules: undefined };
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({
user: { id: "u1", role: "USER", disableCoach: authState.disableCoach },
user: {
id: "u1",
role: "USER",
disableCoach: authState.disableCoach,
modules: authState.modules,
},
isAuthenticated: true,
isLoading: false,
refetch: vi.fn(),
@@ -83,3 +98,24 @@ describe("<CoachSection> — SSR smoke", () => {
expect(html).not.toContain("settings.coach.activate.");
});
});
describe("<CoachSection> — the tuning follows the record, not the account", () => {
it("hides the nudge when the record has the Coach off, whatever the viewer's own opt-out says", () => {
// A guardian with the Coach on, inside a managed record that has it off:
// `/me` publishes the viewer's own `disableCoach` and the RECORD's module
// map. The record's answer decides.
mountState.mounted = true;
authState.disableCoach = false;
authState.modules = { coach: false };
try {
expect(render()).not.toContain('id="coach-nudge"');
authState.disableCoach = true;
authState.modules = { coach: true };
expect(render()).toContain('id="coach-nudge"');
} finally {
mountState.mounted = false;
authState.disableCoach = false;
authState.modules = undefined;
}
});
});