import { render, screen, waitFor } from "@solidjs/testing-library"; import { QueryClient, QueryClientProvider, useQuery, } from "@tanstack/solid-query"; import { JSXElement, Show } from "solid-js"; import { beforeEach, describe, expect, it, vi } from "vitest"; import AsyncContent, { Props, } from "../../../src/ts/components/common/AsyncContent"; import * as Notifications from "../../../src/ts/states/notifications"; describe("AsyncContent", () => { const notifyErrorMock = vi.spyOn(Notifications, "showErrorNotification"); beforeEach(() => { notifyErrorMock.mockClear(); }); describe("with single query", () => { const queryClient = new QueryClient(); it("renders loading state while pending", () => { const { container } = renderWithQuery({ result: "data" }); const preloader = container.querySelector(".preloader"); expect(preloader).toBeInTheDocument(); expect(preloader?.querySelector("i")).toHaveClass( "fas", "fa-fw", "fa-spin", "fa-circle-notch", ); }); it("renders custom loader while pending", () => { const { container } = renderWithQuery( { result: "data" }, { loader: Loading... }, ); const preloader = container.querySelector(".preloader"); expect(preloader).toBeInTheDocument(); expect(preloader).toHaveTextContent("Loading..."); }); it("renders on resolve", async () => { const { container } = renderWithQuery({ result: "Test Data" }); await waitFor(() => { expect(screen.getByTestId("content")).toHaveTextContent("Test Data"); }); const preloader = container.querySelector(".preloader"); expect(preloader).not.toBeInTheDocument(); }); it("renders on resolve with object containing null", async () => { const { container } = renderWithQuery({ result: { text: "Test Data", extra: null } as any, }); await waitFor(() => { expect(screen.getByTestId("content")).toBeVisible(); }); expect(container.innerHTML).toContain("static content"); }); it("renders default error message on fail", async () => { const error = new Error("Test error"); renderWithQuery({ result: error }); await waitFor(() => { expect(screen.getByText(/An error occurred/)).toBeInTheDocument(); }); expect(notifyErrorMock).toHaveBeenCalledWith("An error occurred", { error, }); }); it("renders custom error message on fail", async () => { const error = new Error("Test error"); renderWithQuery( { result: error }, { errorMessage: "Custom error message" }, ); await waitFor(() => { expect(screen.getByText(/Custom error message/)).toBeInTheDocument(); }); expect(notifyErrorMock).toHaveBeenCalledWith("Custom error message", { error, }); }); it("ignores error on fail if ignoreError is set", async () => { renderWithQuery( { result: new Error("Test error") }, { ignoreError: true, alwaysShowContent: true }, ); await waitFor(() => { expect(screen.getByText(/no data/)).toBeInTheDocument(); }); expect(notifyErrorMock).not.toHaveBeenCalled(); }); it("renders on pending if alwaysShowContent", async () => { const { container } = renderWithQuery({ result: "Test Data" }); await waitFor(() => { expect(screen.getByTestId("content")).toHaveTextContent("Test Data"); }); const preloader = container.querySelector(".preloader"); expect(preloader).not.toBeInTheDocument(); }); it("renders on resolve if alwaysShowContent", async () => { renderWithQuery({ result: "Test Data" }, { alwaysShowContent: true }); await waitFor(() => { expect(screen.getByTestId("content")).toHaveTextContent("Test Data"); }); }); it("renders on fail if alwaysShowContent", async () => { const error = new Error("Test error"); renderWithQuery( { result: error }, { errorMessage: "Custom error message" }, ); await waitFor(() => { expect(screen.getByText(/Custom error message/)).toBeInTheDocument(); }); expect(notifyErrorMock).toHaveBeenCalledWith("Custom error message", { error, }); }); function renderWithQuery( query: { result: string | Error; }, options?: Omit, "query" | "queries" | "children">, ): { container: HTMLElement; } { const wrapper = (): JSXElement => { const myQuery = useQuery(() => ({ queryKey: ["test", Math.random() * 1000], queryFn: async () => { await new Promise((resolve) => setTimeout(resolve, 100)); if (query.result instanceof Error) { throw query.result; } return query.result; }, retry: 0, })); return ( )}> {(data: string | undefined) => ( <> static content no data}>
{data}
)}
); }; const { container } = render(() => ( {wrapper()} )); return { container, }; } }); describe("with multiple queries", () => { const queryClient = new QueryClient(); it("renders loading state while pending", () => { const { container } = renderWithQuery({ first: "data", second: "data" }); const preloader = container.querySelector(".preloader"); expect(preloader).toBeInTheDocument(); expect(preloader?.querySelector("i")).toHaveClass( "fas", "fa-fw", "fa-spin", "fa-circle-notch", ); }); it("renders custom loader while pending", () => { const { container } = renderWithQuery( { first: "data", second: "data" }, { loader: Loading... }, ); const preloader = container.querySelector(".preloader"); expect(preloader).toBeInTheDocument(); expect(preloader).toHaveTextContent("Loading..."); }); it("renders on resolve", async () => { const { container } = renderWithQuery({ first: "First Data", second: "Second Data", }); await waitFor(() => { expect(screen.getByTestId("first")).toHaveTextContent("First Data"); }); await waitFor(() => { expect(screen.getByTestId("second")).toHaveTextContent("Second Data"); }); const preloader = container.querySelector(".preloader"); expect(preloader).not.toBeInTheDocument(); }); it("renders default error message on fail", async () => { const error = new Error("Test error"); renderWithQuery({ first: "data", second: error }); await waitFor(() => { expect(screen.getByText(/An error occurred/)).toBeInTheDocument(); }); expect(notifyErrorMock).toHaveBeenCalledWith("An error occurred", { error, }); }); it("renders custom error message on fail", async () => { const firstError = new Error("First error"); renderWithQuery( { first: firstError, second: new Error("Second error") }, { errorMessage: "Custom error message" }, ); await waitFor(() => { expect(screen.getByText(/Custom error message/)).toBeInTheDocument(); }); expect(notifyErrorMock).toHaveBeenCalledWith("Custom error message", { error: firstError, }); }); it("ignores error on fail if ignoreError is set", async () => { renderWithQuery( { first: new Error("First error"), second: new Error("Second error") }, { ignoreError: true, alwaysShowContent: true }, ); await waitFor(() => { expect(screen.getByText(/no data/)).toBeInTheDocument(); }); expect(notifyErrorMock).not.toHaveBeenCalled(); }); it("renders on pending if alwaysShowContent", async () => { const { container } = renderWithQuery( { first: undefined, second: undefined, }, { alwaysShowContent: true }, ); const preloader = container.querySelector(".preloader"); expect(preloader).not.toBeInTheDocument(); await waitFor(() => { expect(screen.getByText(/no data/)).toBeInTheDocument(); }); }); it("renders on resolve if alwaysShowContent", async () => { renderWithQuery({ first: "First Data", second: "Second Data", }); await waitFor(() => { expect(screen.getByTestId("first")).toHaveTextContent("First Data"); }); await waitFor(() => { expect(screen.getByTestId("second")).toHaveTextContent("Second Data"); }); }); it("renders on fail if alwaysShowContent", async () => { const error = new Error("Test error"); renderWithQuery( { first: "data", second: error }, { errorMessage: "Custom error message" }, ); await waitFor(() => { expect(screen.getByText(/Custom error message/)).toBeInTheDocument(); }); expect(notifyErrorMock).toHaveBeenCalledWith("Custom error message", { error, }); }); function renderWithQuery( queries: { first: string | Error | undefined; second: string | Error | undefined; }, options?: Omit, "query" | "queries" | "children">, ): { container: HTMLElement; } { const wrapper = (): JSXElement => { const firstQuery = useQuery(() => ({ queryKey: ["first", Math.random() * 1000], queryFn: async () => { await new Promise((resolve) => setTimeout(resolve, 100)); if (queries.first instanceof Error) { throw queries.first; } return queries.first; }, retry: 0, })); const secondQuery = useQuery(() => ({ queryKey: ["second", Math.random() * 1000], queryFn: async () => { await new Promise((resolve) => setTimeout(resolve, 100)); if (queries.second instanceof Error) { throw queries.second; } return queries.second; }, retry: 0, })); type Q = { first: string | undefined; second: string | undefined }; return ( )} > {(results: { first: string | undefined; second: string | undefined; }) => ( <> no data} >
{results.first}
{results.second}
)}
); }; const { container } = render(() => ( {wrapper()} )); return { container, }; } }); });