mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
fix(insights): offer no metric pill inside a switched-to record
Inside a managed child's record, or as a delegate with manage access, the tab strip showed Weight, Pulse, Blood pressure and the other metric pills, and every one of them opened an error. The strip now reads the record's own presence answer, but the metric pages read /api/analytics, which serves only the signed-in account's own record and refuses a switched session. The strip again shows those pills only in the account's own record, as before; mood, medication, workout and ECG pills keep following the presence answer.
This commit is contained in:
1 parent
6e8554fd37
commit
6f8a342edf
2 files changed
+55
-7
No files matched your search
@@ -13,12 +13,25 @@ import { renderToStaticMarkup } from "react-dom/server";
|
||||
*/
|
||||
|
||||
const queries: Array<{ queryKey: readonly unknown[]; enabled?: boolean }> = [];
|
||||
const stripProps: Array<{ onRegenerate?: unknown }> = [];
|
||||
const stripProps: Array<{
|
||||
onRegenerate?: unknown;
|
||||
availability?: { summaries?: unknown; hasMood?: boolean };
|
||||
}> = [];
|
||||
/** What the presence read answers, and the record the session is inside. */
|
||||
const answer: { presence: unknown; active: unknown } = {
|
||||
presence: undefined,
|
||||
active: null,
|
||||
};
|
||||
|
||||
vi.mock("@tanstack/react-query", () => ({
|
||||
useQuery: (options: { queryKey: readonly unknown[]; enabled?: boolean }) => {
|
||||
queries.push(options);
|
||||
return { data: undefined, isLoading: false, isError: false };
|
||||
const presence = options.queryKey.join("/") === "insights/presence";
|
||||
return {
|
||||
data: presence ? answer.presence : undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
};
|
||||
},
|
||||
useMutation: () => ({ mutate: vi.fn(), isPending: false, data: undefined }),
|
||||
useQueryClient: () => ({
|
||||
@@ -26,7 +39,10 @@ vi.mock("@tanstack/react-query", () => ({
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({ isAuthenticated: true, user: { modules: {} } }),
|
||||
useAuth: () => ({
|
||||
isAuthenticated: true,
|
||||
user: { modules: {}, accountAccess: { active: answer.active } },
|
||||
}),
|
||||
}));
|
||||
vi.mock("@/hooks/use-mounted", () => ({ useMounted: () => true }));
|
||||
vi.mock("@/hooks/use-ai-capability", () => ({
|
||||
@@ -41,7 +57,7 @@ vi.mock("@/hooks/use-insights-layout", () => ({
|
||||
},
|
||||
}));
|
||||
vi.mock("@/components/insights/insights-tab-strip", () => ({
|
||||
InsightsTabStrip: (props: { onRegenerate?: unknown }) => {
|
||||
InsightsTabStrip: (props: (typeof stripProps)[number]) => {
|
||||
stripProps.push(props);
|
||||
return null;
|
||||
},
|
||||
@@ -50,7 +66,7 @@ vi.mock("@/components/insights/insights-tab-strip", () => ({
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { InsightsLayoutShell } from "../insights-layout-shell";
|
||||
|
||||
describe("InsightsLayoutShell — what it reads on entry", () => {
|
||||
describe("InsightsLayoutShell: what it reads on entry", () => {
|
||||
it("keeps the regenerate button and sends no briefing read", () => {
|
||||
renderToStaticMarkup(
|
||||
<InsightsLayoutShell>
|
||||
@@ -82,4 +98,33 @@ describe("InsightsLayoutShell — what it reads on entry", () => {
|
||||
expect(sent).toEqual([JSON.stringify(queryKeys.insightsPresence())]);
|
||||
expect(layoutReads).toEqual([true]);
|
||||
});
|
||||
|
||||
it("offers no metric pill inside a switched-to record", () => {
|
||||
// The metric pages read `/api/analytics`, which serves only the caller's
|
||||
// own record; the mood and medication pills read their own routes.
|
||||
answer.presence = {
|
||||
metrics: { WEIGHT: { count: 12 } },
|
||||
hasMood: true,
|
||||
hasMedication: false,
|
||||
hasWorkouts: false,
|
||||
hasEcgRecordings: false,
|
||||
};
|
||||
const render = (active: unknown) => {
|
||||
answer.active = active;
|
||||
stripProps.length = 0;
|
||||
renderToStaticMarkup(
|
||||
<InsightsLayoutShell>
|
||||
<div />
|
||||
</InsightsLayoutShell>,
|
||||
);
|
||||
return stripProps[0]?.availability;
|
||||
};
|
||||
|
||||
expect(render(null)?.summaries).toEqual({ WEIGHT: { count: 12 } });
|
||||
const switched = render({ accountId: "record-2" });
|
||||
expect(switched?.summaries).toBeUndefined();
|
||||
expect(switched?.hasMood).toBe(true);
|
||||
answer.presence = undefined;
|
||||
answer.active = null;
|
||||
});
|
||||
});
|
||||
@@ -72,19 +72,22 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) {
|
||||
// on a first row as well left that page unreachable before data existed.
|
||||
const hasNutrients = user?.modules?.nutrients === true;
|
||||
const hasEcgRecordings = presence?.hasEcgRecordings === true;
|
||||
// Inside a switched-to record the metric pages read `/api/analytics`, which
|
||||
// serves only the caller's own record, so their pills would open an error.
|
||||
const switched = user?.accountAccess?.active != null;
|
||||
|
||||
// Memoised so an unchanged answer keeps the same reference: the strip is
|
||||
// wrapped in `React.memo` and reruns `buildTabs` on a new object.
|
||||
const availability: InsightInputs | undefined = useMemo(() => {
|
||||
if (!isAuthenticated) return undefined;
|
||||
return {
|
||||
summaries: presence?.metrics,
|
||||
summaries: switched ? undefined : presence?.metrics,
|
||||
hasMood: presence?.hasMood === true,
|
||||
hasMedication: presence?.hasMedication === true,
|
||||
hasWorkouts: presence?.hasWorkouts === true,
|
||||
hasNutrients,
|
||||
};
|
||||
}, [isAuthenticated, presence, hasNutrients]);
|
||||
}, [isAuthenticated, presence, hasNutrients, switched]);
|
||||
|
||||
// v1.15.14 — couple the tab strip to the saved insights layout.
|
||||
// A sub-page pill now shows iff its metric has data AND its slug is
|
||||
|
||||
Reference in new issue
Block a user