fix(files): review round 1, the page — honest states, the keyboard, the tray's words
- A library past the list's cap is searched and sorted on the server; the count says "the newest". - Each folder and each search opens on its first page (DataTable pageScope, useCardPager scope). - A file dropped where the page takes none never reaches the browser (which would unload the app). - The viewer's arrows wait while a layer opened from it is on top, and scroll a wide text line; a text shows its first 5000 lines and says where the rest is. - Confirms name the file (no more "your downloads"); the asking menu rows end in "…". - The tray names a running step beside its bar, shows a pause mark for paused work, polls slowly while only paused work waits, and forgets a finished file that was deleted. - An unchanged rename sends nothing; a paste opens the download dialog only with nothing on top; the link box never replaces what was typed. - Honest states: folders that failed to load, a file no longer there, the storage figure; the rail says "nothing downloaded" only when there is nothing. - Focus after deleting the open folder and when the format form opens; the format name's label. - ui/Meter (one danger line for both quota bars) and ui/RenameField in the platform layer. - Copy fixes, stale comments and the speculative hub key; a contract test for the list's parameters; e2e: the Files page and the paste intake.
This commit is contained in:
@@ -17,6 +17,7 @@ const h = vi.hoisted(() => ({
|
||||
share: vi.fn(async () => "https://x/watch/t"),
|
||||
notify: vi.fn(),
|
||||
prefs: new Map<string, unknown>(),
|
||||
intervalMs: ((_d: unknown) => 0) as (d: unknown) => number,
|
||||
}));
|
||||
|
||||
vi.mock("react-i18next", async (importOriginal) => ({
|
||||
@@ -28,13 +29,16 @@ vi.mock("react-i18next", async (importOriginal) => ({
|
||||
}),
|
||||
}));
|
||||
vi.mock("../lib/useLiveQuery", () => ({
|
||||
useLiveQuery: () => ({
|
||||
data: h.jobs,
|
||||
dataUpdatedAt: h.updatedAt,
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
refetch: vi.fn(),
|
||||
}),
|
||||
useLiveQuery: (_key: unknown, _fn: unknown, opts: { intervalMs: (d: unknown) => number }) => (
|
||||
(h.intervalMs = opts.intervalMs),
|
||||
{
|
||||
data: h.jobs,
|
||||
dataUpdatedAt: h.updatedAt,
|
||||
isError: false,
|
||||
isLoading: false,
|
||||
refetch: vi.fn(),
|
||||
}
|
||||
),
|
||||
}));
|
||||
// The prefs as state over a map that outlives a mount — a reload, as far as the tray can tell. How
|
||||
// usePref stores them is its own business (usePref.dom.test).
|
||||
@@ -137,6 +141,31 @@ describe("DownloadTray", () => {
|
||||
expect(within(rowOf("Clip 2")).getByText("downloads.status.queued")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("a running job names its step: the second stream's bar does not read as a restart", () => {
|
||||
poll([job(1, "running", { phase: "audio" }), job(2, "running", { phase: "merging" })]);
|
||||
mount();
|
||||
const [audio, merging] = screen.getAllByRole("progressbar");
|
||||
expect(within(rowOf("Clip 1")).getByText("downloads.phase.audio")).toBeTruthy();
|
||||
expect(audio!.getAttribute("aria-valuenow")).toBe("40");
|
||||
// A step that reports no bytes has no % to claim.
|
||||
expect(within(rowOf("Clip 2")).getByText("downloads.phase.merging")).toBeTruthy();
|
||||
expect(merging!.getAttribute("aria-valuenow")).toBeNull();
|
||||
expect(within(rowOf("Clip 2")).queryByText("40%")).toBeNull();
|
||||
});
|
||||
|
||||
it("a paused download is waited on slowly, and the tray neither spins nor says done", () => {
|
||||
poll([job(1, "paused")]);
|
||||
mount();
|
||||
// Nothing moves until its user resumes it: no fast polling for that.
|
||||
expect(h.intervalMs([job(1, "paused")])).toBe(60000);
|
||||
expect(h.intervalMs([job(1, "paused"), job(2, "queued")])).toBe(2000);
|
||||
const marks = [...tray()!.querySelectorAll("svg")].map((i) => i.getAttribute("class") ?? "");
|
||||
expect(marks.some((c) => /lucide-(circle-check|check-circle)/.test(c))).toBe(false);
|
||||
expect(
|
||||
marks.some((c) => /lucide-(circle-pause|pause-circle)/.test(c) && /text-warn/.test(c)),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("a failure says why, and its Retry resumes it", () => {
|
||||
const resume = vi.spyOn(api, "resumeDownload").mockResolvedValue({} as never);
|
||||
poll([job(3, "error", { error_code: "login_required" })]);
|
||||
|
||||
Reference in New Issue
Block a user