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.
46 lines
1.6 KiB
TypeScript
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();
|
|
});
|
|
});
|