Files
siftlode/frontend/src/lib/useAccountRehydrate.dom.test.ts
T
peter 726c382524 refactor(account): address account-pin re-hydrate review — extract module, harden guards
Fixes the 5 findings from /code-review high on the account-pin work:
- Extract the reactive-account primitives (ACTIVE_ACCOUNT_KEY, activeAccountId,
  notifyActiveAccountChange, useActiveAccountId) into a new dependency-free
  lib/activeAccount.ts, breaking the storage.ts <-> useAccountRehydrate.ts import cycle.
  storage.ts / api/core.ts / useAccountRehydrate.ts now import from it.
- NavigationProvider: the page re-hydrate now PRESERVES existing history.state markers
  (spread, not overwrite), and openChannel/enterYtSearch markDirty so a sub-view opened
  before the account pins isn't clobbered by the page override.
- useAccountRehydrate: reset the dirty flag after each account transition, so a future
  non-reloading account switch can't skip the re-hydrate and leak the prior account's state.
- Doc-link useAccountRehydrate to the pre-existing useEffect([me?.id]) server-adopt pattern.
- Tests: model a mutable pinned account and add useSortState integration tests for the
  null->id re-hydrate + the markDirty no-clobber guard (vitest 115->117).

gate-green: vite build + vitest 117 + eslint-0 + knip + prettier. Self-E2E'd on a fresh
tab: still lands on Plex with Movies/In-progress/Year restored, zero console errors.
2026-08-14 03:12:50 +02:00

46 lines
1.6 KiB
TypeScript

// @vitest-environment jsdom
//
// useAccountRehydrate re-reads per-account state when the tab's account is pinned after mount (first
// load). useActiveAccountId is mocked with a plain variable so the transition can be driven by hand.
import { describe, it, expect, beforeEach, vi } from "vitest";
import { renderHook, act } from "@testing-library/react";
let account: number | null = null;
vi.mock("./activeAccount", () => ({
useActiveAccountId: () => account,
}));
import { useAccountRehydrate } from "./useAccountRehydrate";
describe("useAccountRehydrate", () => {
beforeEach(() => {
account = null;
});
it("runs rehydrate when the account transitions from null to a value", () => {
const rehydrate = vi.fn();
const { rerender } = renderHook(() => useAccountRehydrate(rehydrate));
expect(rehydrate).not.toHaveBeenCalled(); // mount with no account: no transition
account = 1;
act(() => rerender());
expect(rehydrate).toHaveBeenCalledTimes(1);
});
it("does not run when the account id is already known and stable", () => {
account = 1;
const rehydrate = vi.fn();
const { rerender } = renderHook(() => useAccountRehydrate(rehydrate));
act(() => rerender());
expect(rehydrate).not.toHaveBeenCalled();
});
it("skips rehydrate when marked dirty before the account pins (no clobber of a user edit)", () => {
const rehydrate = vi.fn();
const { result, rerender } = renderHook(() => useAccountRehydrate(rehydrate));
act(() => result.current()); // markDirty — the owning setter ran pre-pin
account = 1;
act(() => rerender());
expect(rehydrate).not.toHaveBeenCalled();
});
});