import { test, expect, vi, beforeEach } from "vitest"; import React, { useState } from "react"; import { render, act as domAct } from "@testing-library/react"; import "@testing-library/jest-dom/vitest"; import { useFunction } from "./useFunction.js"; import { ReportContext, ReportContextValue } from "../context/index.js"; import { v4 as uuid } from "uuid"; import { SketchProperties } from "../types/index.js"; import { GeoprocessingTaskStatus, GeoprocessingTask } from "../aws/tasks.js"; import { renderHook, act } from "@testing-library/react"; // switch to manual fetch mocking or vitest-fetch-mock // import fetchMock from "fetch-mock-jest"; // import createFetchMock from "vitest-fetch-mock"; // const fetchMock = createFetchMock(vi); const fetchMock: any = {}; const makeSketchProperties = (id?: string): SketchProperties => { id = id || uuid(); return { id, name: "sketch name", updatedAt: new Date().toISOString(), sketchClassId: "abc123", } as SketchProperties; }; const ContextWrapper: React.FunctionComponent<{ children?: any; value?: ReportContextValue; }> = (props) => { const sketchProperties = makeSketchProperties(); return ( {props.children} ); }; // fetchMock.get("https://example.com/project", { // geoprocessingServices: [ // { // title: "calcFoo", // endpoint: "https://example.com/calcFoo", // }, // ], // }); beforeEach(() => { fetchMock.resetHistory(); }); test.skip("useFunction won't accept unrecognizable responses", async () => { vi.useFakeTimers(); fetchMock.getOnce( "*", {}, { overwriteRoutes: true, }, ); const { result } = renderHook(() => useFunction("calcFoo"), { wrapper: ContextWrapper, }); expect(result.current.loading).toBe(true); await act(async () => { vi.runAllTimers(); }); expect(fetchMock.calls(/calcFoo/).length).toBe(1); expect(result.current.loading).toBe(false); expect(result.current.error).toContain("response"); }); test.skip("useFunction unsets loading prop and sets task upon completion of job (executionMode=sync)", async () => { vi.useFakeTimers(); const id = uuid(); fetchMock.getOnce( "*", JSON.stringify({ startedAt: new Date().toISOString(), duration: 10, geometryUri: `https://example.com/calcFoo/${id}/geometry`, location: `https://example.com/calcFoo/${id}`, id, logUriTemplate: `https://example.com/calcFoo/${id}/logs`, service: "calcFoo", wss: "", status: "completed", data: { foo: "plenty", }, } as GeoprocessingTask), { overwriteRoutes: true }, ); const { result } = renderHook(() => useFunction("calcFoo"), { wrapper: ContextWrapper, }); expect(result.current.loading).toBe(true); await act(async () => { vi.runAllTimers(); }); expect(fetchMock.calls(/calcFoo/).length).toBe(1); expect(result.current.loading).toBe(false); const task: GeoprocessingTask = result.current.task!; expect(task.data.foo).toBe("plenty"); expect(task.status).toBe(GeoprocessingTaskStatus.Completed); expect(task.error).toBeUndefined(); }); test.skip("useFunction handles errors thrown within geoprocessing function", async () => { vi.useFakeTimers(); const id = uuid(); fetchMock.getOnce( "*", { startedAt: new Date().toISOString(), duration: 10, geometryUri: `https://example.com/calcFoo/${id}/geometry`, location: `https://example.com/calcFoo/${id}`, id, logUriTemplate: `https://example.com/calcFoo/${id}/logs`, service: "calcFoo", wss: "", status: "failed", error: "Task error", } as GeoprocessingTask, { overwriteRoutes: true }, ); const { result } = renderHook(() => useFunction("calcFoo"), { wrapper: ContextWrapper, }); expect(result.current.loading).toBe(true); await act(async () => { vi.runAllTimers(); }); expect(fetchMock.calls(/calcFoo/).length).toBe(1); expect(result.current.loading).toBe(false); const task: GeoprocessingTask = result.current.task!; expect(task.error).toBe("Task error"); expect(result.current.error).toBe("Task error"); }); test.skip("throws error if ReportContext is not set", async () => { const { result } = renderHook(() => useFunction("calcFoo")); expect(result && result.current.error).toBeTruthy(); if (!result || !result.current.error) return; expect(result.current.error).toContain("ReportContext"); }); const TestReport = () => { const { loading, error, task } = useFunction("calcFoo"); return (
{loading && ( loading... )} {error && Errors: {error}} {task?.status === GeoprocessingTaskStatus.Completed && ( Task Complete! )}
); }; interface TestContainerProps { children: React.ReactNode; } const TestContainer: React.FC = (props) => { const [sketchId, setSketchId] = useState(1); return ( {props.children} ); }; test.skip("changing ReportContext.geometryUri fetches new results", async () => { vi.useFakeTimers(); const id = uuid(); fetchMock.getOnce( "*", JSON.stringify({ startedAt: new Date().toISOString(), duration: 10, geometryUri: `https://example.com/calcFoo/${id}/geometry`, location: `https://example.com/calcFoo/${id}`, id, logUriTemplate: `https://example.com/calcFoo/${id}/logs`, service: "calcFoo", wss: "", status: "completed", data: { foo: "plenty", }, } as GeoprocessingTask), { overwriteRoutes: true }, ); const { getByRole, getByText, getAllByText } = render( , ); expect(getByRole("alert")).toHaveTextContent("loading..."); await domAct(async () => { vi.runAllTimers(); }); // expect(fetchMock.calls("https://example.com/project").length).toBe(1); expect(fetchMock.calls(/calcFoo/).length).toBe(1); expect(getAllByText(/task complete/i).length).toBe(1); expect(getByText(/Task Complete/)).toHaveAttribute("data-results", "plenty"); expect(fetchMock.calls(/calcFoo/).length).toBe(1); // now setup another mock because clicking the button will change the geometryUri const id2 = uuid(); fetchMock.getOnce( "*", JSON.stringify({ startedAt: new Date().toISOString(), duration: 10, geometryUri: `https://example.com/calcFoo/${id2}/geometry`, location: `https://example.com/calcFoo/${id2}`, id, logUriTemplate: `https://example.com/calcFoo/${id2}/logs`, service: "calcFoo", wss: "", status: "completed", data: { foo: "lots!", }, } as GeoprocessingTask), { overwriteRoutes: true }, ); await domAct(async () => { getByRole("button").click(); }); expect(getAllByText(/task complete/i).length).toBe(1); expect(getByText(/Task Complete/)).toHaveAttribute("data-results", "lots!"); expect(fetchMock.calls(/calcFoo/).length).toBe(2); }); const MultiCardTestReport = () => { const { loading, error, task } = useFunction("calcFoo"); const task2 = useFunction("calcFoo"); return ( <>
{loading && ( loading... )} {error && Errors: {error}} {task?.status === GeoprocessingTaskStatus.Completed && ( Task Complete! )}
{task2.loading && ( loading... )} {task2.error && Errors: {task2.error}} {task2.task?.status === GeoprocessingTaskStatus.Completed && ( Task Complete! )}
); }; test.skip("useFunction called multiple times with the same arguments will only fetch once", async () => { vi.useFakeTimers(); const id = uuid(); fetchMock.getOnce( "*", JSON.stringify({ startedAt: new Date().toISOString(), duration: 10, geometryUri: `https://example.com/calcFoo/${id}/geometry`, location: `https://example.com/calcFoo/${id}`, id, logUriTemplate: `https://example.com/calcFoo/${id}/logs`, service: "calcFoo", wss: "", status: "completed", data: { foo: "plenty", }, } as GeoprocessingTask), { overwriteRoutes: true }, ); const { getAllByRole, getAllByText } = render( , ); for (const el of getAllByRole("alert")) { expect(el).toHaveTextContent("loading..."); } await domAct(async () => { vi.runAllTimers(); }); expect(fetchMock.calls(/calcFoo/).length).toBe(1); expect(getAllByText(/task complete/i).length).toBe(2); const completeEls = getAllByText(/task complete/i); for (const el of completeEls) { expect(el).toHaveAttribute("data-results", "plenty"); } expect(fetchMock.calls(/calcFoo/).length).toBe(1); }); test.skip("useFunction uses a local cache for repeat requests", async () => { vi.useFakeTimers(); const id = uuid(); const sketchProperties = makeSketchProperties(id); fetchMock.getOnce( "*", JSON.stringify({ startedAt: new Date().toISOString(), duration: 10, geometryUri: `https://example.com/calcFoo/${id}/geometry`, location: `https://example.com/calcFoo/${id}`, id, logUriTemplate: `https://example.com/calcFoo/${id}/logs`, service: "calcFoo", wss: "", status: "completed", data: { foo: "plenty", }, } as GeoprocessingTask), { overwriteRoutes: true }, ); const { getByRole, getByText, getAllByText } = render( // @ts-expect-error type mismatch , ); expect(getByRole("alert")).toHaveTextContent("loading..."); await domAct(async () => { vi.runAllTimers(); }); expect(getAllByText(/task complete/i).length).toBe(1); expect(getByText(/Task Complete/)).toHaveAttribute("data-results", "plenty"); expect(fetchMock.calls(/calcFoo/).length).toBe(1); // eslint-disable-next-line @typescript-eslint/no-unused-vars const queries = render( // @ts-expect-error type mismatch , ); await domAct(async () => { vi.runAllTimers(); }); expect(fetchMock.calls(/calcFoo/).length).toBe(1); }); test.skip("Returns error if ReportContext does not include required values", () => { const { result } = renderHook(() => useFunction("calcFoo"), { wrapper: ({ children }) => ( ), }); expect(result.current.error).toContain("Client Error"); }); test.skip("Exposes error to client if project metadata can't be fetched", async () => { vi.useFakeTimers(); fetchMock.get("https://example.com/project", 500, { overwriteRoutes: true }); useFunction.reset(); const { result } = renderHook(() => useFunction("calcFoo"), { wrapper: ({ children }) => ( ), }); await act(async () => { vi.runAllTimers(); }); expect(result.current.error).toContain("metadata"); });