fix: a signed-out tab no longer asks for saved views

The providers mount for the public landing too, and the saved-views query had no
enabled gate, so every signed-out load logged a 401 for /api/saved-views. The query
now waits for a signed-in account in BootGate's me cache, like WizardProvider.
This commit is contained in:
2026-09-15 23:45:13 +02:00
parent 595490cb98
commit 4e52bdd4ca
2 changed files with 37 additions and 2 deletions
@@ -67,12 +67,19 @@ function Probe() {
);
}
// The views query waits for a signed-in account in the cache (BootGate's `me`).
function signedInClient() {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
qc.setQueryData(qk.me(), { id: ID, preferences: {} });
return qc;
}
async function mountResolved(views: SavedView[]) {
// Fresh array + object copies per call, like a real fetch — so a refetch changes viewsQuery.data's
// identity and re-runs the seed effect (which is what makes the per-source guard actually load-bearing
// in the re-seed test; a shared reference would skip the effect and hide a missing guard).
vi.spyOn(api, "savedViews").mockImplementation(async () => views.map((v) => ({ ...v })));
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const qc = signedInClient();
render(
<QueryClientProvider client={qc}>
<SavedViewsProvider>
@@ -141,7 +148,7 @@ describe("SavedViewsProvider — default-view seeding on first resolve", () => {
calls += 1;
return calls === 1 ? [def] : [def, view({ id: 9, name: "added-later" })];
});
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const qc = signedInClient();
render(
<QueryClientProvider client={qc}>
<SavedViewsProvider>
@@ -174,3 +181,26 @@ describe("SavedViewsProvider — default-view seeding on first resolve", () => {
expect(h.setPlexFilters).not.toHaveBeenCalled();
});
});
describe("SavedViewsProvider — signed-out tab", () => {
it("does NOT ask for saved views while no account is signed in (the landing logged a 401)", async () => {
const spy = vi.spyOn(api, "savedViews").mockResolvedValue([]);
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
qc.setQueryData(qk.me(), null); // BootGate's "nobody"
render(
<QueryClientProvider client={qc}>
<SavedViewsProvider>
<Probe />
</SavedViewsProvider>
</QueryClientProvider>,
);
// Let any fetch the mount would start get going before asserting it never happened.
await act(async () => {
await new Promise((r) => setTimeout(r, 20));
});
expect(spy).not.toHaveBeenCalled();
// …and it asks as soon as an account signs in.
act(() => qc.setQueryData(qk.me(), { id: ID, preferences: {} }));
await waitFor(() => expect(spy).toHaveBeenCalledWith("feed"));
});
});
@@ -155,9 +155,14 @@ export function SavedViewsProvider({ children }: { children: ReactNode }) {
}, [page, feedFilters, plexFilters, setFeedFilters, setPlexFilters]);
const source = cfg.source;
// Passive observer of the account BootGate fetches: the providers mount for a signed-out tab too
// (the public landing), and saved views are per-account, so asking for "nobody's" views only logs
// a 401 on the landing page. Enabled once a signed-in account is cached.
const { data: me } = useQuery({ queryKey: qk.me(), queryFn: api.me, enabled: false });
const viewsQuery = useQuery({
queryKey: qk.savedViews(source),
queryFn: () => api.savedViews(source),
enabled: !!me,
});
const views = useMemo(() => viewsQuery.data ?? [], [viewsQuery.data]);