Files
PodTui/tests/yazi-pages-depth.test.ts
2026-08-07 13:38:32 -04:00

128 lines
4.4 KiB
TypeScript

/**
* yazi-pages-depth.test.ts — task 03 page contract tests.
*
* Every depth-stack tab (Feed / MyShows / Discover / Search / Player /
* Settings) renders through `<YaziPaneRow>` with the parent pane reading the
* previous-depth frame's list (blank placeholder at depth 0). Their `open()`
* action calls `nav.pushDepth(frame)` to drill and the Shell calls
* `nav.popDepth()` on `h`. This file exercises the nav-store contract those
* pages depend on for every depth-tab, asserting the parent-slot data model:
*
* • depth 0 → stack has exactly the root frame (parent pane is blank)
* • drill(l)→ push a child frame; stack length 2, parent = previous list
* • drill(l)→ push again; stack length 3 (Settings sections→items→editor)
* • pop(h) → stack shrinks; parent returns to the previous list
* • pop(h) → back at root; parent is blank again
*
* The visual "blank → list → list → blank" transition is the union of this
* data model (which list each depth renders) with `<YaziPaneRow>`'s null
* placeholder (covered by yazi-pane-row.test.tsx). Tested at the store level
* because the page `open()` closures are not exported and the nav store is
* the shared contract all four pages route through.
*/
import { test, expect } from "bun:test";
import { createRoot } from "solid-js";
import {
createNavigation,
DEPTH_CENTER_PANE,
} from "../src/context/navigation-store";
import { TABS, DEPTH_TABS } from "../src/utils/navigation";
import type { DepthFrame } from "../src/context/NavigationContext";
function withNav(fn: (nav: ReturnType<typeof createNavigation>) => void) {
createRoot((dispose) => {
fn(createNavigation());
dispose();
});
}
/** The depth-tabs that render via <YaziPaneRow> (task 03 conversion). */
const CONVERTED_TABS = [
TABS.FEED,
TABS.MYSHOWS,
TABS.DISCOVER,
TABS.SEARCH,
TABS.PLAYER,
TABS.SETTINGS,
];
for (const tab of CONVERTED_TABS) {
const name = TABS[tab];
test(`${name}: depth 0 → 1 → 2 push/pop keeps the parent-slot contract`, () => {
withNav((nav) => {
nav.setActiveTab(tab);
expect(nav.isDepthTab()).toBe(true);
// depth 0: exactly the root frame → parent pane renders blank.
expect(nav.currentDepth()).toBe(0);
expect(nav.depthStack()).toHaveLength(1);
// drill (l): page open() pushes a child frame — parent becomes
// the previous-depth list.
const child: DepthFrame = {
kind: `${name.toLowerCase()}:child`,
ctx: "c1",
focus: 0,
};
nav.pushDepth(child);
nav.setActivePane(DEPTH_CENTER_PANE);
expect(nav.currentDepth()).toBe(1);
expect(nav.depthStack()).toHaveLength(2);
// the parent (depth 0) frame is still the root; the top is the child.
expect(nav.depthStack()[0]).toBe(nav.depthStack()[0]);
expect(nav.topFrame()).toEqual(child);
// drill again (l): push a second child — parent shows the first
// child's list (the chain Settings exercises: sections→items→editor).
const grandchild: DepthFrame = {
kind: `${name.toLowerCase()}:grand`,
ctx: "g1",
focus: 0,
};
nav.pushDepth(grandchild);
expect(nav.currentDepth()).toBe(2);
expect(nav.depthStack()).toHaveLength(3);
expect(nav.topFrame()).toEqual(grandchild);
// pop (h): back to depth 1 — parent frame is the root, top is child.
expect(nav.popDepth()).toBe(true);
expect(nav.currentDepth()).toBe(1);
expect(nav.depthStack()).toHaveLength(2);
expect(nav.topFrame()).toEqual(child);
// pop (h): back to depth 0 — parent pane is blank again.
expect(nav.popDepth()).toBe(true);
expect(nav.currentDepth()).toBe(0);
expect(nav.depthStack()).toHaveLength(1);
});
});
test(`${name}: pop (h) at depth 0 is a noop (returns false, root kept)`, () => {
withNav((nav) => {
nav.setActiveTab(tab);
expect(nav.currentDepth()).toBe(0);
expect(nav.popDepth()).toBe(false);
expect(nav.currentDepth()).toBe(0);
// the root frame is preserved (parent stays blank, not undefined).
expect(nav.depthStack()).toHaveLength(1);
expect(nav.topFrame()).toBeDefined();
});
});
}
// ── DEPTH_TABS covers exactly the depth-stack pages ────────────────────────
test("DEPTH_TABS is exactly the depth-stack tabs", () => {
expect([...DEPTH_TABS].sort()).toEqual(
[
TABS.FEED,
TABS.MYSHOWS,
TABS.DISCOVER,
TABS.SEARCH,
TABS.PLAYER,
TABS.SETTINGS,
].sort(),
);
});