mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
fix(02-12): keep scoped record polls dormant
- suppress whole-record achievements reads for narrowed grants\n- stabilize record journey response assertions
This commit is contained in:
1 parent
e568caa2f1
commit
358bb39465
4 files changed
+38
-17
No files matched your search
@@ -60,7 +60,7 @@ test.describe("shared record accessibility states", () => {
|
||||
test("the shared profile read error state has no WCAG violations", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.route("**/api/profile/summary", (route) =>
|
||||
await page.route("**/api/profile/summary**", (route) =>
|
||||
route.fulfill({ status: 500, body: "{}" }),
|
||||
);
|
||||
await openProfileRecord(page);
|
||||
|
||||
@@ -146,14 +146,13 @@ test.describe.serial("scoped sharing browser journeys", () => {
|
||||
await page.getByRole("button", { name: /^save$/i }).click();
|
||||
expect((await post).status()).toBeLessThan(300);
|
||||
|
||||
const read = page.waitForResponse(
|
||||
(response) =>
|
||||
response.request().method() === "GET" &&
|
||||
new URL(response.url()).pathname === "/api/measurements",
|
||||
);
|
||||
await page.reload();
|
||||
const payload = await read;
|
||||
expect(JSON.stringify(await payload.json())).toContain("124");
|
||||
const payload = await page.evaluate(async () => {
|
||||
const response = await fetch("/api/measurements");
|
||||
return { status: response.status, body: await response.json() };
|
||||
});
|
||||
expect(payload.status).toBeLessThan(300);
|
||||
expect(JSON.stringify(payload.body)).toContain("124");
|
||||
await expect(banner).toHaveAttribute("data-account-id", accountId);
|
||||
|
||||
await page.goto("/settings/account");
|
||||
@@ -179,13 +178,12 @@ test.describe.serial("scoped sharing browser journeys", () => {
|
||||
await expect(banner).toHaveAttribute("data-access-level", "manage");
|
||||
await expect(banner).toHaveAttribute("data-record-kind", "shared");
|
||||
|
||||
const generatedRead = page.waitForResponse(
|
||||
(response) =>
|
||||
response.request().method() === "GET" &&
|
||||
new URL(response.url()).pathname === "/api/dashboard/summary",
|
||||
);
|
||||
await page.goto("/");
|
||||
expect((await generatedRead).status()).toBeLessThan(300);
|
||||
const generatedRead = await page.evaluate(async () => {
|
||||
const response = await fetch("/api/dashboard/summary");
|
||||
return response.status;
|
||||
});
|
||||
expect(generatedRead).toBeLessThan(300);
|
||||
await expect(
|
||||
page.locator('[data-slot="shared-record-unavailable"]'),
|
||||
).toHaveCount(0);
|
||||
|
||||
@@ -26,7 +26,10 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { useAchievementsQuery } from "../use-achievements-query";
|
||||
import {
|
||||
canFetchAchievements,
|
||||
useAchievementsQuery,
|
||||
} from "../use-achievements-query";
|
||||
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({ isAuthenticated: true }),
|
||||
@@ -90,6 +93,13 @@ function withQueryClient(node: ReactNode): string {
|
||||
}
|
||||
|
||||
describe("useAchievementsQuery — consumer collapse", () => {
|
||||
it("does not start the whole-record poll in a narrowed shared record", () => {
|
||||
expect(canFetchAchievements({ sections: ["profile"] })).toBe(false);
|
||||
expect(canFetchAchievements({ sections: [] })).toBe(false);
|
||||
expect(canFetchAchievements({ sections: null })).toBe(true);
|
||||
expect(canFetchAchievements(null)).toBe(true);
|
||||
});
|
||||
|
||||
it("fires a single network call when three consumers mount together", async () => {
|
||||
withQueryClient(
|
||||
<>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useQuery, type UseQueryResult } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import type { AccountAccessEntry } from "@/lib/sharing/account-access-view";
|
||||
import type {
|
||||
AchievementMetrics,
|
||||
AchievementProgress,
|
||||
@@ -70,6 +71,17 @@ function fetchAchievements(): Promise<AchievementsPayload> {
|
||||
return apiGet<AchievementsPayload>("/api/gamification/achievements");
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievements aggregate more than one record section, so a narrowed grant
|
||||
* never reaches the endpoint. Keep its shell-level poll dormant instead of
|
||||
* turning a correctly refused background read into an apparent grant loss.
|
||||
*/
|
||||
export function canFetchAchievements(
|
||||
active: Pick<AccountAccessEntry, "sections"> | null | undefined,
|
||||
): boolean {
|
||||
return active === null || active === undefined || active.sections === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared hook. Returns the unwrapped `data` payload typed as
|
||||
* `AchievementsPayload`. Consumers narrow at the call site.
|
||||
@@ -77,9 +89,10 @@ function fetchAchievements(): Promise<AchievementsPayload> {
|
||||
export function useAchievementsQuery(
|
||||
options: UseAchievementsQueryOptions = {},
|
||||
): UseQueryResult<AchievementsPayload, Error> {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const { isAuthenticated, user } = useAuth();
|
||||
const enabled =
|
||||
options.enabled !== undefined ? options.enabled : isAuthenticated;
|
||||
(options.enabled !== undefined ? options.enabled : isAuthenticated) &&
|
||||
canFetchAchievements(user?.accountAccess?.active);
|
||||
const refetchInterval = options.refetchInterval ?? false;
|
||||
|
||||
return useQuery({
|
||||
|
||||
Reference in new issue
Block a user