diff --git a/packages/terriajs/lib/ReactViews/Preview/DataPreview.jsx b/packages/terriajs/lib/ReactViews/Preview/DataPreview.jsx index a4cfb1aacb..4d0dedb465 100644 --- a/packages/terriajs/lib/ReactViews/Preview/DataPreview.jsx +++ b/packages/terriajs/lib/ReactViews/Preview/DataPreview.jsx @@ -15,6 +15,7 @@ import MappablePreview from "./MappablePreview"; import WarningBox from "./WarningBox"; import Styles from "./data-preview.scss"; import Icon from "../../Styled/Icon"; +import { getCustomPreviewRenderer } from "./PreviewRenderers"; /** * Data preview section, for the preview map see DataPreviewMap @@ -61,9 +62,11 @@ class DataPreview extends Component { ); } else if (previewed && previewed.isMappable) { + const MappablePreviewRenderer = + getCustomPreviewRenderer(previewed.type) ?? MappablePreview; return (
- ); } else if (previewed && CatalogFunctionMixin.isMixedInto(previewed)) { + const FunctionRenderer = + getCustomPreviewRenderer(previewed.type) ?? InvokeFunction; return ( - ); } else if (previewed && previewed.isGroup) { + const GroupPreviewRenderer = + getCustomPreviewRenderer(previewed.type) ?? GroupPreview; return (
- ; + +/** + * Custom preview renderer registry. + */ +const PreviewRenderers = observable(new Map()); + +/** + * Add a custom preview renderer for the given catalog item type. + * + * Preview renderers, render the `About data` view. + * + * @param type Catalog item type + * @param renderer Preview renderer component + */ +export function addCustomPreviewRenderer( + type: string, + renderer: PreviewRendererType +) { + runInAction(() => PreviewRenderers.set(type, renderer)); +} + +/** + * Remove custom preview renderer for the given catalog item type. + * + * The type falls back to its built-in preview renderer. + * + * @param type Catalog item type + * @returns `true` if a custom renderer was registered for the type, `false` otherwise. + */ +export function removeCustomPreviewRenderer(type: string): boolean { + return runInAction(() => PreviewRenderers.delete(type)); +} + +/** + * Get registered custom previewer renderer for the given catalog item type + * + * @param type Catalog item type + * @returns Registered custom renderer or `undefined` if no custom renderer is registered for the type. + */ +export function getCustomPreviewRenderer( + type: string +): PreviewRendererType | undefined { + return PreviewRenderers.get(type); +} diff --git a/packages/terriajs/test/ReactViews/Preview/DataPreviewSpec.tsx b/packages/terriajs/test/ReactViews/Preview/DataPreviewSpec.tsx new file mode 100644 index 0000000000..08ba51ada2 --- /dev/null +++ b/packages/terriajs/test/ReactViews/Preview/DataPreviewSpec.tsx @@ -0,0 +1,135 @@ +import { screen } from "@testing-library/react"; +import { runInAction } from "mobx"; +import CatalogMemberMixin from "../../../lib/ModelMixins/CatalogMemberMixin"; +import CatalogGroup from "../../../lib/Models/Catalog/CatalogGroup"; +import GeoJsonCatalogItem from "../../../lib/Models/Catalog/CatalogItems/GeoJsonCatalogItem"; +import WebProcessingServiceCatalogFunction from "../../../lib/Models/Catalog/Ows/WebProcessingServiceCatalogFunction"; +import CommonStrata from "../../../lib/Models/Definition/CommonStrata"; +import Terria from "../../../lib/Models/Terria"; +import ViewState from "../../../lib/ReactViewModels/ViewState"; +import DataPreview from "../../../lib/ReactViews/Preview/DataPreview"; +import { + PreviewRendererType, + addCustomPreviewRenderer, + removeCustomPreviewRenderer +} from "../../../lib/ReactViews/Preview/PreviewRenderers"; +import { renderWithContexts } from "../withContext"; + +const CustomRenderer: PreviewRendererType = ({ + previewed +}: { + previewed: CatalogMemberMixin.Instance; +}) =>
Custom renderer for {previewed.name}
; + +describe("DataPreview", function () { + describe("preview rendering", function () { + let terria: Terria; + let viewState: ViewState; + let mappableItem: GeoJsonCatalogItem; + let functionItem: WebProcessingServiceCatalogFunction; + let groupItem: CatalogGroup; + + beforeEach(function () { + terria = new Terria({ baseUrl: "./" }); + viewState = new ViewState({ terria }); + + mappableItem = new GeoJsonCatalogItem("test-geojson", terria); + functionItem = new WebProcessingServiceCatalogFunction( + "test-wps", + terria + ); + groupItem = new CatalogGroup("test-group", terria); + + runInAction(() => { + mappableItem.setTrait(CommonStrata.definition, "name", "Test GeoJSON"); + // Avoid spinning up the preview map, which isn't what we're testing here. + mappableItem.setTrait(CommonStrata.definition, "disablePreview", true); + functionItem.setTrait(CommonStrata.definition, "name", "Test WPS"); + groupItem.setTrait(CommonStrata.definition, "name", "Test Group"); + }); + }); + + // The renderer registry is module global, so leave it as we found it. + afterEach(function () { + removeCustomPreviewRenderer(mappableItem.type); + removeCustomPreviewRenderer(functionItem.type); + removeCustomPreviewRenderer(groupItem.type); + }); + + function renderPreview(previewed: CatalogMemberMixin.Instance) { + return renderWithContexts( + , + viewState + ); + } + + it("renders the built-in renderer when no custom renderer is registered", function () { + // MappablePreview, InvokeFunction and GroupPreview all title the preview + // with the catalog member name. + const mappablePreview = renderPreview(mappableItem); + expect( + screen.getByRole("heading", { name: "Test GeoJSON" }) + ).toBeVisible(); + expect(screen.queryByText(/Custom renderer for/)).toBeNull(); + mappablePreview.unmount(); + + const functionPreview = renderPreview(functionItem); + expect(screen.getByRole("heading", { name: "Test WPS" })).toBeVisible(); + expect(screen.queryByText(/Custom renderer for/)).toBeNull(); + functionPreview.unmount(); + + const groupPreview = renderPreview(groupItem); + expect(screen.getByRole("heading", { name: "Test Group" })).toBeVisible(); + expect(screen.queryByText(/Custom renderer for/)).toBeNull(); + groupPreview.unmount(); + }); + + it("renders the custom renderer registered for the catalog member type", function () { + addCustomPreviewRenderer(mappableItem.type, CustomRenderer); + addCustomPreviewRenderer(functionItem.type, CustomRenderer); + addCustomPreviewRenderer(groupItem.type, CustomRenderer); + + const mappablePreview = renderPreview(mappableItem); + expect( + screen.getByText("Custom renderer for Test GeoJSON") + ).toBeVisible(); + expect( + screen.queryByRole("heading", { name: "Test GeoJSON" }) + ).toBeNull(); + mappablePreview.unmount(); + + const functionPreview = renderPreview(functionItem); + expect(screen.getByText("Custom renderer for Test WPS")).toBeVisible(); + expect(screen.queryByRole("heading", { name: "Test WPS" })).toBeNull(); + functionPreview.unmount(); + + const groupPreview = renderPreview(groupItem); + expect(screen.getByText("Custom renderer for Test Group")).toBeVisible(); + expect(screen.queryByRole("heading", { name: "Test Group" })).toBeNull(); + groupPreview.unmount(); + }); + + it("passes the preview props to the custom renderer", function () { + let receivedProps: Record | undefined; + const PropCapturingRenderer: PreviewRendererType = (props) => { + receivedProps = props; + return null; + }; + addCustomPreviewRenderer(mappableItem.type, PropCapturingRenderer); + + renderPreview(mappableItem); + + expect(receivedProps).toEqual( + jasmine.objectContaining({ + previewed: mappableItem, + terria, + viewState + }) + ); + }); + }); +});