fix(channels): re-hydrate per-account manager state once the account is pinned
The provider tree mounts above <App>, which pins this tab's account only after /api/me resolves — so on a genuinely fresh tab (empty sessionStorage) the manager's sort, filters, view and layout initialised from an unknown account and stayed at defaults until a second reload. Make the active account reactive (a subscribable store + useActiveAccountId via useSyncExternalStore; setActiveAccount/clearActiveAccount notify) and re-read the stored value when the account transitions null→id, via a shared useAccountRehydrate hook. A markDirty guard (called by each owning setter) prevents clobbering a value the user changed in the brief pre-pin window; the effect is keyed on the account id only, so the user's own writes can't loop it. useSortState, useFilterState and ChannelsProvider (view/layout) all adopt it. Affects sort identically — this was a long-standing caveat, not new to R13.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
// @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("./storage", () => ({
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user