Files
siftlode/frontend/src/lib/pageKeys.dom.test.tsx
T
peter 078194731b feat(files): the Downloads page goes — the tray, a Storage tab and a Downloads settings tab take its place
- The Downloads module, its rail icon and badge are gone. A bookmark, the remembered page and an old
  history entry that name it land on Files (urlState.resolvePage); `g d` goes to Files too.
- Users page: a Storage tab — the disk in numbers, every user's use against their own limit with
  their file count, every user's downloads; aligned tables, one menu per row (edit / reset quota,
  open source, copy, the owner's quota). The admin storage rows carry quota + file count, the admin
  job list its owner.
- Settings: a Downloads tab — the download formats in one list (duplicate a built-in, edit or delete
  your own) and the site sign-ins, moved from Account.
- "View" on an added download, a clip or a converting upload opens the tray on that row (unfolded,
  back if closed; a finished one opens as a file; under a player it opens without taking focus).
- The download dialog opens on the format used last.
- DataTable: `pager={false}` for a short list, `cardRow` to give a lone "…" its own card line.
- Welcome and README speak of Files; a new Files screenshot.
2026-10-11 19:37:13 +02:00

104 lines
3.1 KiB
TypeScript

// @vitest-environment jsdom
//
// The `page` scope as App binds it through `useGlobalKeymap`: the `g` chords jump to a module this
// account can reach, stay out of an overlay's way, and leave the global keys (Shift+W, h) intact.
// Its own file for the same reason as keymap.sequence.test.ts: a pending `g` is module state.
import { useEffect } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { act, cleanup, render } from "@testing-library/react";
import { closeKeyboardHelp, useGlobalKeymap, useKeyboardHelpOpen } from "./keymap";
import { pushLayer, removeLayer } from "./layerStack";
import type { Page } from "./urlState";
let clock = Date.UTC(2031, 0, 1);
beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
clock += 3_600_000;
vi.setSystemTime(clock);
});
afterEach(() => {
cleanup();
closeKeyboardHelp();
vi.useRealTimers();
});
const ALL: Page[] = ["home", "feed", "channels", "playlists", "plex", "files", "settings"];
function Host({
onNavigate,
pages = ALL,
onHelp = () => {},
}: {
onNavigate: (p: Page) => void;
pages?: Page[];
onHelp?: (open: boolean) => void;
}) {
useGlobalKeymap(true, onNavigate, pages);
const help = useKeyboardHelpOpen();
useEffect(() => {
onHelp(help);
}, [help, onHelp]);
return null;
}
const key = (init: KeyboardEventInit) =>
act(() => {
window.dispatchEvent(
new KeyboardEvent("keydown", { bubbles: true, cancelable: true, ...init }),
);
});
describe("the g chords", () => {
it("jump to the module named by the second key", () => {
const nav = vi.fn();
render(<Host onNavigate={nav} />);
key({ key: "f" });
expect(nav).not.toHaveBeenCalled();
key({ key: "g" });
key({ key: "f" });
key({ key: "g" });
key({ key: "," });
expect(nav.mock.calls).toEqual([["feed"], ["settings"]]);
});
// The retired Downloads page's chord still answers — where its content went.
it("g d, the old Downloads chord, goes to Files", () => {
const nav = vi.fn();
render(<Host onNavigate={nav} />);
key({ key: "g" });
key({ key: "d" });
expect(nav.mock.calls).toEqual([["files"]]);
});
// `h` alone opens the sheet; the `h` of `g h` must not ALSO do that.
it("take the h of g h away from the help sheet", () => {
const nav = vi.fn();
let help = false;
render(<Host onNavigate={nav} onHelp={(v) => (help = v)} />);
key({ key: "g" });
key({ key: "h" });
expect(nav).toHaveBeenCalledWith("home");
expect(help).toBe(false);
});
it("refuse a module this account cannot reach", () => {
const nav = vi.fn();
render(<Host onNavigate={nav} pages={["home", "feed"]} />);
key({ key: "g" });
key({ key: "x" });
expect(nav).not.toHaveBeenCalled();
});
it("stand aside for an overlay, while Shift+W still answers there", () => {
const nav = vi.fn();
render(<Host onNavigate={nav} />);
const id = pushLayer();
key({ key: "g" });
key({ key: "f" });
expect(nav).not.toHaveBeenCalled();
key({ key: "w", shiftKey: true });
expect(nav).toHaveBeenCalledWith("home");
removeLayer(id);
});
});