// @vitest-environment jsdom // // E32 S2: the admin's download-quota editor rendered NOTHING while its quota loaded and nothing when // the load failed, so the click looked dead. It now opens at once and says which it is. The save // turns the GB field back into bytes; the reset drops the override. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; import { api } from "../lib/api"; import { QuotaModal } from "./downloadAdmin"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key, i18n: { language: "en" } }), }; }); const GiB = 1024 ** 3; const QUOTA = { max_bytes: 5 * GiB, max_jobs: 10, max_concurrent: 2, unlimited: false }; let qc: QueryClient; const wrap = (ui: ReactNode) => {ui}; const open = (onClose = () => {}) => render(wrap()); beforeEach(() => { qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); }); afterEach(() => { cleanup(); vi.restoreAllMocks(); }); describe("the download-quota editor", () => { it("opens at once and says it is loading", () => { vi.spyOn(api, "adminGetDownloadQuota").mockReturnValue(new Promise(() => {}) as never); open(); expect(screen.getByText("downloads.admin.quotaTitle")).toBeTruthy(); expect(screen.getAllByRole("status").length).toBeGreaterThan(0); }); it("says a failed load failed, and retries it", async () => { const get = vi .spyOn(api, "adminGetDownloadQuota") .mockRejectedValueOnce(new Error("down")) .mockResolvedValueOnce(QUOTA as never); open(); expect((await screen.findByRole("alert")).textContent).toContain("common.loadError"); fireEvent.click(screen.getByText("common.retry")); await screen.findByDisplayValue("5"); expect(get).toHaveBeenCalledTimes(2); }); it("gives the first field the focus when the form arrives after a retry", async () => { // jsdom lays nothing out, so every element reads as hidden to the dialog's focus rules. vi.spyOn(HTMLElement.prototype, "offsetParent", "get").mockReturnValue(document.body); vi.spyOn(api, "adminGetDownloadQuota") .mockRejectedValueOnce(new Error("down")) .mockResolvedValueOnce(QUOTA as never); open(); await screen.findByRole("alert"); const retry = screen.getByText("common.retry").closest("button")!; await waitFor(() => expect(document.activeElement).toBe(retry)); fireEvent.click(retry); // the Retry gives way to the spinner: its focus must not fall to const first = await screen.findByDisplayValue("5"); await waitFor(() => expect(document.activeElement).toBe(first)); }); it("saves the GB field as bytes, and the reset drops the override", async () => { vi.spyOn(api, "adminGetDownloadQuota").mockResolvedValue(QUOTA as never); const set = vi.spyOn(api, "adminSetDownloadQuota").mockResolvedValue(QUOTA as never); const reset = vi.spyOn(api, "adminResetDownloadQuota").mockResolvedValue(QUOTA as never); const onClose = vi.fn(); open(onClose); fireEvent.change(await screen.findByDisplayValue("5"), { target: { value: "1.5" } }); fireEvent.click(screen.getByText("downloads.admin.save")); await waitFor(() => expect(onClose).toHaveBeenCalledTimes(1)); expect(set).toHaveBeenCalledWith(4, { max_bytes: Math.round(1.5 * GiB), max_jobs: 10, max_concurrent: 2, unlimited: false, }); fireEvent.click(screen.getByText("downloads.admin.reset")); await waitFor(() => expect(reset).toHaveBeenCalledWith(4)); }); });