From ed1a2b725686b465a5207908cd1b77037c214a2c Mon Sep 17 00:00:00 2001 From: npeter83 Date: Fri, 14 Aug 2026 04:01:38 +0200 Subject: [PATCH] test(channels): cover the tab-history logic; memoize viewTabs (S2b review) Addresses the 2 applied findings from /code-review high on 735d08c: - Add ChannelsProvider.dom.test.tsx (4 tests): setView pushes a _chTab history entry + persists the tab, re-selecting the current tab pushes nothing, a genuine popstate carrying _chTab restores + re-persists it, and a popstate without _chTab is ignored. Real storage (jsdom) with the account pinned to 1; only NavigationProvider is mocked. vitest 120->124. - Memoize viewTabs (keyed on the translation fn) so it isn't rebuilt on every Channels render. The third finding (consolidate the hand-rolled single-select sync chips with ) is deferred by design into the S2c plan, where MatchToggle will be a shared segmented-control primitive that can also absorb the sync chips. gate-green: tsc + vitest 124 + eslint-0 + knip + prettier. --- frontend/src/components/Channels.tsx | 13 +++- .../components/ChannelsProvider.dom.test.tsx | 73 +++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/ChannelsProvider.dom.test.tsx diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index 53523402..4cf4e809 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { qk } from "../lib/queryKeys"; @@ -432,9 +432,14 @@ export default function Channels() { }, [filters, search, sort, resetPage]); const cardPager = ; - const viewTabs: TabDef[] = ( - ["subscribed", "discovery", "hidden", "blocked"] as ChannelsView[] - ).map((v) => ({ id: v, label: t(`channels.tabs.${v}`) })); + const viewTabs: TabDef[] = useMemo( + () => + (["subscribed", "discovery", "hidden", "blocked"] as ChannelsView[]).map((v) => ({ + id: v, + label: t(`channels.tabs.${v}`), + })), + [t], + ); const tabs = (
({ + useNavigation: () => ({ page: "channels" }), + useNavigationActions: () => ({ setPage: vi.fn() }), +})); + +import { ChannelsProvider, useChannels, useChannelsActions } from "./ChannelsProvider"; + +const wrapper = ({ children }: { children: ReactNode }) => ( + {children} +); +const render = () => renderHook(() => ({ ...useChannels(), ...useChannelsActions() }), { wrapper }); +const VIEW_KEY = "siftlode.channelsView.1"; // account-scoped (accountKey suffixes the pinned id) + +beforeEach(() => { + localStorage.clear(); + sessionStorage.setItem("siftlode.activeAccount", "1"); // pin the tab's account + window.history.replaceState({ sfPage: "channels" }, ""); // setView only owns an entry on this page +}); + +describe("ChannelsProvider tab history", () => { + it("setView pushes a _chTab history entry and persists the tab", () => { + const { result } = render(); + expect(result.current.view).toBe("subscribed"); // default + + act(() => result.current.setView("hidden")); + + expect(result.current.view).toBe("hidden"); + expect(window.history.state).toMatchObject({ sfPage: "channels", _chTab: "hidden" }); + expect(localStorage.getItem(VIEW_KEY)).toBe("hidden"); + }); + + it("does NOT push a new entry when re-selecting the current tab", () => { + const { result } = render(); + const spy = vi.spyOn(window.history, "pushState"); + act(() => result.current.setView("subscribed")); // already the current tab + expect(spy).not.toHaveBeenCalled(); + spy.mockRestore(); + }); + + it("a genuine Back (popstate carrying _chTab) restores and re-persists that tab", () => { + const { result } = render(); + act(() => result.current.setView("blocked")); + expect(result.current.view).toBe("blocked"); + + // Browser Back to an entry stamped with a different tab. + act(() => + window.dispatchEvent(new PopStateEvent("popstate", { state: { _chTab: "discovery" } })), + ); + + expect(result.current.view).toBe("discovery"); + expect(localStorage.getItem(VIEW_KEY)).toBe("discovery"); + }); + + it("ignores a popstate for an entry outside the manager (no _chTab)", () => { + const { result } = render(); + act(() => result.current.setView("hidden")); + act(() => window.dispatchEvent(new PopStateEvent("popstate", { state: { sfPage: "feed" } }))); + expect(result.current.view).toBe("hidden"); // unchanged + }); +});