UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

321 lines • 13.2 kB
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 } from "../context/index.js"; import { v4 as uuid } from "uuid"; import { GeoprocessingTaskStatus } 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 = {}; const makeSketchProperties = (id) => { id = id || uuid(); return { id, name: "sketch name", updatedAt: new Date().toISOString(), sketchClassId: "abc123", }; }; const ContextWrapper = (props) => { const sketchProperties = makeSketchProperties(); return (React.createElement(ReportContext.Provider, { value: { geometryUri: `https://localhost/${sketchProperties.id}`, sketchProperties, projectUrl: "https://example.com/project", ...props.value, visibleLayers: [], language: "en", } }, 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", }, }), { 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 = 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", }, { 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 = 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 (React.createElement("div", null, loading && (React.createElement("span", { role: "alert", "aria-busy": "true" }, "loading...")), error && React.createElement("span", { role: "alert" }, "Errors: ", error), task?.status === GeoprocessingTaskStatus.Completed && (React.createElement("span", { role: "alert", "data-results": task.data.foo }, "Task Complete!")))); }; const TestContainer = (props) => { const [sketchId, setSketchId] = useState(1); return (React.createElement(ReportContext.Provider, { value: { sketchProperties: // @ts-expect-error type mismatch props.sketchProperties || makeSketchProperties(sketchId.toString()), geometryUri: `https://example.com/geometry/${sketchId}`, projectUrl: "https://example.com/project", visibleLayers: [], language: "en", } }, React.createElement("button", { onClick: () => setSketchId(sketchId + 1) }, "change sketch id"), 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", }, }), { overwriteRoutes: true }); const { getByRole, getByText, getAllByText } = render(React.createElement(TestContainer, null, React.createElement(TestReport, null))); 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!", }, }), { 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 (React.createElement(React.Fragment, null, React.createElement("div", null, loading && (React.createElement("span", { role: "alert", "aria-busy": "true" }, "loading...")), error && React.createElement("span", { role: "alert" }, "Errors: ", error), task?.status === GeoprocessingTaskStatus.Completed && (React.createElement("span", { role: "alert", "data-results": (task?.data).foo }, "Task Complete!"))), React.createElement("div", { id: "task2" }, task2.loading && (React.createElement("span", { role: "alert", "aria-busy": "true" }, "loading...")), task2.error && React.createElement("span", { role: "alert" }, "Errors: ", task2.error), task2.task?.status === GeoprocessingTaskStatus.Completed && (React.createElement("span", { role: "alert", "data-results": (task2.task?.data).foo }, "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", }, }), { overwriteRoutes: true }); const { getAllByRole, getAllByText } = render(React.createElement(TestContainer, null, React.createElement(MultiCardTestReport, null))); 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", }, }), { overwriteRoutes: true }); const { getByRole, getByText, getAllByText } = render( // @ts-expect-error type mismatch React.createElement(TestContainer, { sketchProperties: sketchProperties }, React.createElement(TestReport, null))); 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 React.createElement(TestContainer, { sketchProperties: sketchProperties }, React.createElement(TestReport, null))); 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 }) => (React.createElement(ContextWrapper // eslint-disable-next-line react/no-children-prop , { // eslint-disable-next-line react/no-children-prop children: children, value: { projectUrl: null } })), }); 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 }) => (React.createElement(ContextWrapper // eslint-disable-next-line react/no-children-prop , { // eslint-disable-next-line react/no-children-prop children: children, value: { projectUrl: "https://example.com/project", geometryUri: "https://example.com/geometry/123", sketchProperties: {}, } })), }); await act(async () => { vi.runAllTimers(); }); expect(result.current.error).toContain("metadata"); }); //# sourceMappingURL=useFunction.test.js.map