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.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// @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 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user