Files
siftlode/frontend/src/components/FilesProvider.dom.test.tsx
T
peter d22c64f4dd feat(files): folders managed in place, a grid per folder, drop to upload, open on one file
- Own folders are made, renamed and deleted from the rail (right click, long press,
  Shift+F10) or the "…" beside an open folder's name; any folder's retention is a
  submenu (7/30/90 days, until deleted). Deleting confirms where its files go.
- List or grid per folder, sharing one sort; the grid is the one media card.
- A file dropped anywhere on the page opens Upload with it, bound for the folder
  on screen.
- A link (?item=) or the upload notice opens Files on the file: its page turned
  to, the keyboard on it, a ring round it.
- An in-place name field left by Enter or Escape hands the focus back to what
  replaced it (the row, the entry, the "…") instead of dropping it on <body>.
- The table's saved page size is per account.
2026-10-11 15:59:12 +02:00

93 lines
3.6 KiB
TypeScript

// @vitest-environment jsdom
//
// FilesProvider's arrival at one file: `openFile` (a notice's "Open") and a `?item=` link both make a
// pending focus that the Files page spends once it has looked; what it found is held until the page
// is left. Real storage with the tab's account pinned; only the navigation is mocked, its page
// switchable.
import { beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderHook } from "@testing-library/react";
import type { ReactNode } from "react";
const nav = vi.hoisted(() => ({ page: "files", setPage: vi.fn() }));
vi.mock("./navigationContext", () => ({
useNavigation: () => ({ page: nav.page }),
useNavigationActions: () => ({ setPage: nav.setPage }),
}));
import { FilesProvider } from "./FilesProvider";
import { ALL_FILES, useFiles, useFilesActions } from "./filesContext";
const wrapper = ({ children }: { children: ReactNode }) => (
<FilesProvider>{children}</FilesProvider>
);
const render = () => renderHook(() => ({ ...useFiles(), ...useFilesActions() }), { wrapper });
beforeEach(() => {
localStorage.clear();
sessionStorage.setItem("siftlode.activeAccount", "1");
window.history.replaceState(null, "", "/");
nav.page = "files";
nav.setPage.mockClear();
});
describe("FilesProvider · arriving at one file", () => {
it("openFile opens its folder on the Files page, with no search hiding it", () => {
nav.page = "feed";
const { result, rerender } = render();
act(() => result.current.setSearch("valami"));
act(() => result.current.openFile(7, 3));
expect(nav.setPage).toHaveBeenCalledWith("files");
expect(result.current.view).toEqual({ kind: "folder", id: 3 });
expect(result.current.search).toBe("");
nav.page = "files";
rerender();
expect(result.current.focus).toMatchObject({ id: 7 });
// The address carries it until it is spent, so a reload mid-arrival still lands on the file.
expect(new URLSearchParams(window.location.search).get("item")).toBe("7");
});
it("a file in no folder opens every file", () => {
const { result } = render();
act(() => result.current.openFile(7, null));
expect(result.current.view).toEqual(ALL_FILES);
});
it("a ?item= link is an arrival from the start", () => {
window.history.replaceState(null, "", "/?item=12");
const { result } = render();
expect(result.current.focus).toMatchObject({ id: 12 });
});
it("spent, it is what the page found — until the page is left", () => {
const { result, rerender } = render();
act(() => result.current.openFile(7, null));
act(() => result.current.consumeFocus("all"));
expect(result.current.focus).toBeNull();
expect(result.current.arrived).toMatchObject({ id: 7, view: "all" });
expect(new URLSearchParams(window.location.search).get("item")).toBeNull();
nav.page = "feed";
rerender();
expect(result.current.arrived).toBeNull();
nav.page = "files";
rerender();
// Back on the page later: nothing is rung or turned to again.
expect(result.current.arrived).toBeNull();
});
it("an arrival found nowhere leaves nothing found", () => {
const { result } = render();
act(() => result.current.openFile(7, null));
act(() => result.current.consumeFocus(null));
expect(result.current.focus).toBeNull();
expect(result.current.arrived).toBeNull();
});
it("the same file opened again is a new arrival", () => {
const { result } = render();
act(() => result.current.openFile(7, null));
act(() => result.current.consumeFocus("all"));
act(() => result.current.openFile(7, null));
expect(result.current.focus).toMatchObject({ id: 7 });
});
});