import type { Report, ReportParameter, ReportParameterValues, SavedFilter } from "@olenbetong/appframe-core"; import { render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; const download = vi.fn(async () => undefined); const openRendered = vi.fn(async () => undefined); const print = vi.fn(async () => undefined); const save = vi.fn(async () => undefined); const convertFilter = vi.fn(async () => ({ filterObject: null, filterString: "", whereClause: "" })); let report: Report | null = null; let parameters: ReportParameter[] = []; let defaultValues: ReportParameterValues = {}; let savedFilters: SavedFilter[] = []; let initialFilter: string | null = null; vi.mock("@olenbetong/appframe-core", async (importOriginal) => ({ ...(await importOriginal>()), convertFilter: (...args: unknown[]) => convertFilter(...(args as [])), })); vi.mock("@olenbetong/appframe-react", async (importOriginal) => ({ ...(await importOriginal>()), setNamedFilter: vi.fn(), useFilterFields: () => ({ fields: [], loading: false, error: null, refresh: vi.fn() }), useFilterConversion: () => ({ filterString: "[ProjectID] = 1", isConverting: false, error: null, parseFilterString: vi.fn(async () => null), }), useReport: () => ({ report, parameters, initialFilter, defaultValues, loading: false, error: null, refresh: vi.fn(), }), useReportRender: () => ({ open: openRendered, print, download, render: vi.fn(), cancel: vi.fn(), isRendering: false, progress: null, error: null, }), useSavedFilters: () => ({ filters: savedFilters, mine: savedFilters, shared: [], loading: false, error: null, refresh: vi.fn(), save, rename: vi.fn(), remove: vi.fn(), setCrossDomain: vi.fn(), }), })); const { ReportLauncher } = await import("./ReportLauncher.js"); function makeReport(overrides: Partial = {}): Report { return { reportId: "arpt_Test_Report", shortId: 4711, title: "Test report", description: null, viewName: "aviw_Test_View", initialFilter: null, hasTitle: true, hasHeader: true, formats: ["PDF", "XLSX"], timeout: null, ...overrides, }; } function savedFilter(overrides: Partial = {}): SavedFilter { return { filterId: 1, primKey: "pk-1", name: "Mitt filter", criteria: "[ProjectID] = 1", dbObjectId: "aviw_Test_View", accessLevel: "Manager", createdByUser: true, createdBy: "bvh", domain: "OB", crossDomain: false, isPersonallyShared: false, hideCriteria: true, headerText: "Lagret overskrift", comment: null, parameters: '202601', reportTitle: null, created: null, updated: null, ...overrides, }; } function numberParameter(overrides: Partial = {}): ReportParameter { return { name: "Period", label: "Periode", description: null, type: "Integer", editor: "number", defaultValue: null, items: [], recordSource: null, valueMember: null, displayMember: null, sortOrder: 0, lookupWidth: null, lookupHeight: null, ...overrides, }; } function renderLauncher(props: Record = {}) { return render(); } describe("ReportLauncher", () => { beforeEach(() => { vi.clearAllMocks(); report = makeReport(); parameters = []; defaultValues = {}; savedFilters = []; initialFilter = null; }); it("tells the user when the report is not available to them", () => { report = null; renderLauncher(); expect(screen.getByRole("alert").textContent).toContain("do not have access"); }); it("shows the report option fields the report has bands for", () => { renderLauncher(); expect(screen.getByLabelText("Report name")).toHaveValue("Test report"); expect(screen.getByLabelText("Optional text (at the top of every page)")).toBeInTheDocument(); }); it("puts the parameters and the criteria in a tab each, with the options below both", async () => { parameters = [numberParameter()]; defaultValues = { Period: null }; renderLauncher(); expect(screen.getByLabelText("Periode")).toBeInTheDocument(); expect(screen.queryByLabelText("Filter string")).toBeNull(); await userEvent.click(screen.getByText("Filter")); expect(screen.queryByLabelText("Periode")).toBeNull(); expect(screen.getByLabelText("Filter string")).toBeInTheDocument(); // The options apply to the report whichever tab is open. expect(screen.getByLabelText("Report name")).toBeInTheDocument(); }); it("can start on the filter tab", () => { parameters = [numberParameter()]; renderLauncher({ defaultTab: "filter" }); expect(screen.getByLabelText("Filter string")).toBeInTheDocument(); expect(screen.queryByLabelText("Periode")).toBeNull(); }); it("names the report and its number in the dialog heading", () => { render(); expect(screen.getByRole("heading").textContent).toBe("Report: Test report (ID: 4711)"); }); it("hides the title and header fields when the report has no such bands", () => { report = makeReport({ hasTitle: false, hasHeader: false }); renderLauncher(); expect(screen.queryByLabelText("Report name")).toBeNull(); expect(screen.queryByLabelText("Optional text (at the top of every page)")).toBeNull(); }); it("offers the report's first format on the save button and the rest in a menu", async () => { renderLauncher(); await userEvent.click(screen.getByRole("button", { name: "Save as PDF" })); expect(download).toHaveBeenCalledWith( expect.objectContaining({ format: "PDF", fileName: "Test report", filter: "[ProjectID] = 1" }), ); expect(screen.getByRole("button", { name: "More formats" })).toBeInTheDocument(); }); it("does not offer a format menu for a report with a single format", () => { report = makeReport({ formats: ["PDF"] }); renderLauncher(); expect(screen.queryByRole("button", { name: "More formats" })).toBeNull(); }); it("previews and prints as PDF", async () => { renderLauncher(); await userEvent.click(screen.getByRole("button", { name: "Preview" })); await userEvent.click(screen.getByRole("button", { name: "Print" })); expect(openRendered).toHaveBeenCalledWith(expect.objectContaining({ format: "PDF" })); expect(print).toHaveBeenCalledWith(expect.objectContaining({ format: "PDF" })); }); it("cannot preview or print a report that has no PDF format", () => { report = makeReport({ formats: ["CSV"] }); renderLauncher(); expect(screen.getByRole("button", { name: "Preview" }).hasAttribute("disabled")).toBe(true); expect(screen.getByRole("button", { name: "Print" }).hasAttribute("disabled")).toBe(true); }); it("sends the header text and the hide-criteria flag with the render", async () => { renderLauncher(); await userEvent.type(screen.getByLabelText("Optional text (at the top of every page)"), "Hei"); await userEvent.click(screen.getByLabelText("Do not show the filter criteria at the top of the pages")); await userEvent.click(screen.getByRole("button", { name: "Save as PDF" })); expect(download).toHaveBeenCalledWith(expect.objectContaining({ headerText: "Hei", hideFilterString: true })); }); it("restores the report options stored with a saved filter", async () => { parameters = [numberParameter()]; defaultValues = { Period: null }; savedFilters = [savedFilter()]; renderLauncher(); await userEvent.click(screen.getByRole("button", { name: "Mitt filter" })); await waitFor(() => expect(screen.getByLabelText("Periode")).toHaveValue(202601)); expect(screen.getByLabelText("Optional text (at the top of every page)")).toHaveValue("Lagret overskrift"); expect( (screen.getByLabelText("Do not show the filter criteria at the top of the pages") as HTMLInputElement).checked, ).toBe(true); }); it("stores the report options on the filter when it is saved", async () => { parameters = []; // "Save as" is only offered for a filter with conditions, so the launcher // starts from the report's own filter. convertFilter.mockResolvedValue({ filterObject: { type: "group", mode: "and", items: [{ type: "expression", column: "ProjectID", operator: "equals", value: 1, valueType: "number" }], }, filterString: "[ProjectID] = 1", whereClause: "", } as never); initialFilter = "[ProjectID] = 1"; renderLauncher(); await userEvent.type(screen.getByLabelText("Optional text (at the top of every page)"), "Hei"); await userEvent.click(screen.getByRole("button", { name: "Save as" })); let dialog = screen.getByRole("dialog"); await userEvent.type(within(dialog).getByRole("textbox", { name: "Filter name" }), "Nytt filter"); await userEvent.click(within(dialog).getByRole("button", { name: "Save" })); await waitFor(() => expect(save).toHaveBeenCalledWith(expect.objectContaining({ headerText: "Hei", hideCriteria: false })), ); }); });