Files
siftlode/frontend/src/components/navigation.dom.test.ts
T
peter 828dad015c feat(e24): reach the Home surface — the landing toggle, a palette command, Shift+W
S4's reachability half. Home already led the nav order; what was missing was every
way IN other than the rail.

- Settings → Appearance → Startup: "Start on Home", the documented escape hatch for
  the landing override. Deliberately still localStorage and not a server pref —
  `loadInitialPage` answers before /api/me lands, and a landing module decided after
  the first paint is a visible jump. Its two stored values are now ONE exported
  constant, so the writer and the boot-time reader cannot drift on a magic string.
- The command palette gets "Meglepetés" / "Surprise me": it navigates to Home and
  rerolls the hero on arrival. No second "Mit nézzek?" row beside it — the Home
  module row already matches that phrase through its descriptive title.
- A global Shift+W jumps to Home. A chord, not a bare `w`: the Plex player binds bare
  letters and `w` sits on `s`, so a blind reach for the subtitle key would have thrown
  a viewer out of the film.

The palette's request crosses a lazy module boundary, so it is a token that waits
(lib/homeIntent) rather than an event fired into an empty room. Browser-measured:
taken with a compare-and-clear, because StrictMode's double-invoked mount effect
otherwise rerolled twice and landed on the page two picks deep.

HU/EN parity kept; UAT on localdev in both languages, including the empty shelf.
2026-09-08 00:06:34 +02:00

109 lines
4.2 KiB
TypeScript

// @vitest-environment jsdom
//
// NavigationProvider's URL-derived seeds, backfilling the E4 deep-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 (the S1 fix): a popstate landing on a URL-only bookmark entry must restore the
// channel from ?chan, not close it.
// Only getAccountRaw is stubbed (the account's stored landing page); everything else is real.
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, START_ON_HOME } from "./navigationInit";
import { LS } from "../lib/storage";
const setUrl = (search: string) => window.history.replaceState(null, "", search || "/");
beforeEach(() => {
setUrl("/");
for (const k of Object.keys(stored)) delete stored[k];
});
describe("loadInitialPage precedence", () => {
it("?page wins and is validated", () => {
setUrl("?page=stats");
expect(loadInitialPage()).toBe("stats");
});
it("an invalid ?page falls back to feed", () => {
setUrl("?page=bogus");
expect(loadInitialPage()).toBe("feed");
});
it("?v implies the plex module", () => {
setUrl("?v=show:1");
expect(loadInitialPage()).toBe("plex");
});
it("?pl implies playlists", () => {
setUrl("?pl=3");
expect(loadInitialPage()).toBe("playlists");
});
it("a filter param implies the feed", () => {
setUrl("?tags=1,2");
expect(loadInitialPage()).toBe("feed");
});
it("?chan (the channel overlay) implies the feed", () => {
setUrl("?chan=UC1");
expect(loadInitialPage()).toBe("feed");
});
it("?page beats a lower-precedence param", () => {
setUrl("?page=stats&v=show:1");
expect(loadInitialPage()).toBe("stats");
});
it("?v beats ?pl and filters", () => {
setUrl("?v=show:1&pl=3&tags=1");
expect(loadInitialPage()).toBe("plex");
});
// E24 changed this rung: 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] = "downloads";
expect(loadInitialPage()).toBe("home");
});
it("opting out restores the account's stored landing page", () => {
stored[LS.startOnHome] = START_ON_HOME.off;
stored[LS.page] = "downloads";
expect(loadInitialPage()).toBe("downloads");
});
it("opted out with no / invalid stored page still defaults to feed", () => {
stored[LS.startOnHome] = START_ON_HOME.off;
expect(loadInitialPage()).toBe("feed");
stored[LS.page] = "nope";
expect(loadInitialPage()).toBe("feed");
});
it("a deep link still beats Home", () => {
setUrl("?pl=3");
expect(loadInitialPage()).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) — the S1 fix", () => {
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();
});
});