Files
siftlode/frontend/src/features/navigation/navigation.dom.test.ts
T
peter 63a0fecc8b refactor(frontend): the flat components/ folder moves into shell/, modules/<page>/ and features/<name>/
A pure move (git mv, history kept): 333 files leave components/, which now holds only the platform
layer (ui/) and the legal pages. The shell is the frame (App, header, nav rail, palette, boot); each
page lives in modules/<page>/ with its rail, store and tests; what several pages share sits in
features/<name>/; the shared-link page is watch/. Domain-free helpers went straight to ui/
(ViewSwitcher, FilterRail, the confirm dialog, the language switcher, the copy rows).

Every relative specifier was rewritten (imports, dynamic imports, vi.mock, new URL), and the
contract tests that scan sources now scan the whole app source, not only components/ — each glob
was counted before and after so none went green over fewer files. The locale-format rule matched
`/src/components/` only, so the move would have taken ~300 files out from under its i18n-singleton
check; it now covers every folder that holds components, with a test per folder.
2026-10-22 23:12:45 +02:00

130 lines
5.0 KiB
TypeScript

// @vitest-environment jsdom
//
// NavigationProvider's URL-derived seeds, backfilling a code review's deferred coverage:
// • loadInitialPage — the bookmark deep-link precedence (?page > ?v > ?pl > filters/?chan > the
// account's stored landing page > feed).
// • channelViewFrom — the shared _chan/?chan resolver behind BOTH the channelView seed and the
// onPop handler: a popstate landing on a URL-only bookmark entry must restore the
// channel from ?chan, not close it.
// Only getAccountRaw is stubbed (the page the account left open); everything else is real. Whether
// to start on Home is the account's preference, handed in.
import { describe, it, expect, beforeEach, vi } from "vitest";
const { stored } = vi.hoisted(() => ({ stored: {} as Record<string, string | null> }));
vi.mock("../../lib/storage", async (importOriginal) => {
const actual = await importOriginal<typeof import("../../lib/storage")>();
return { ...actual, getAccountRaw: (k: string) => stored[k] ?? null };
});
import { loadInitialPage, channelViewFrom } from "./navigationInit";
import { LS } from "../../lib/storage";
const setUrl = (search: string) => window.history.replaceState(null, "", search || "/");
let startOnHome = true;
beforeEach(() => {
setUrl("/");
startOnHome = true;
for (const k of Object.keys(stored)) delete stored[k];
});
describe("loadInitialPage precedence", () => {
it("?page wins and is validated", () => {
setUrl("?page=stats");
expect(loadInitialPage(startOnHome)).toBe("stats");
});
it("an invalid ?page falls back to feed", () => {
setUrl("?page=bogus");
expect(loadInitialPage(startOnHome)).toBe("feed");
});
it("?v implies the plex module", () => {
setUrl("?v=show:1");
expect(loadInitialPage(startOnHome)).toBe("plex");
});
it("?pl implies playlists", () => {
setUrl("?pl=3");
expect(loadInitialPage(startOnHome)).toBe("playlists");
});
it.each(["?folder=4", "?item=12", "?folder=all&item=12"])(
"%s implies the Files page (a folder, or a file to arrive at)",
(search) => {
setUrl(search);
expect(loadInitialPage(startOnHome)).toBe("files");
},
);
it("a filter param implies the feed", () => {
setUrl("?tags=1,2");
expect(loadInitialPage(startOnHome)).toBe("feed");
});
it("?chan (the channel overlay) implies the feed", () => {
setUrl("?chan=UC1");
expect(loadInitialPage(startOnHome)).toBe("feed");
});
it("?page beats a lower-precedence param", () => {
setUrl("?page=stats&v=show:1");
expect(loadInitialPage(startOnHome)).toBe("stats");
});
it("?v beats ?pl and filters", () => {
setUrl("?v=show:1&pl=3&tags=1");
expect(loadInitialPage(startOnHome)).toBe("plex");
});
// A bare URL lands on Home, OVER the remembered module. Landing on
// whatever you happened to close the app on is precisely the habit the anti-paralysis surface
// exists to replace, so the remembered page only wins once the account opts out.
it("a bare URL lands on Home even when another module is remembered", () => {
stored[LS.page] = "plex";
expect(loadInitialPage(startOnHome)).toBe("home");
});
it("opting out restores the account's stored landing page", () => {
startOnHome = false;
stored[LS.page] = "plex";
expect(loadInitialPage(startOnHome)).toBe("plex");
});
// The Downloads page is gone; what it held is in Files. A bookmark and a remembered page both land
// there instead of on the feed.
it("a retired page lands where its content went — linked or remembered", () => {
setUrl("?page=downloads");
expect(loadInitialPage(startOnHome)).toBe("files");
setUrl("/");
startOnHome = false;
stored[LS.page] = "downloads";
expect(loadInitialPage(startOnHome)).toBe("files");
});
it("opted out with no / invalid stored page still defaults to feed", () => {
startOnHome = false;
expect(loadInitialPage(startOnHome)).toBe("feed");
stored[LS.page] = "nope";
expect(loadInitialPage(startOnHome)).toBe("feed");
});
it("a deep link still beats Home", () => {
setUrl("?pl=3");
expect(loadInitialPage(startOnHome)).toBe("playlists");
});
});
describe("channelViewFrom", () => {
it("reads _chan (+ _chanName) from an in-app entry", () => {
expect(channelViewFrom({ _chan: "UC1", _chanName: "Chan" }, "")).toEqual({
id: "UC1",
name: "Chan",
});
});
it("_chan without a name yields an undefined name", () => {
expect(channelViewFrom({ _chan: "UC1" }, "")).toEqual({ id: "UC1", name: undefined });
});
it("falls back to the ?chan URL param when the entry has no _chan (fresh bookmark)", () => {
expect(channelViewFrom(null, "?chan=UC9")).toEqual({ id: "UC9", name: undefined });
});
it("prefers _chan over the URL param", () => {
expect(channelViewFrom({ _chan: "UCstate" }, "?chan=UCurl")).toEqual({
id: "UCstate",
name: undefined,
});
});
it("returns null when neither is present (channel closed)", () => {
expect(channelViewFrom({}, "")).toBeNull();
expect(channelViewFrom(null, "/")).toBeNull();
});
});