import { describe, expect, it } from "vitest"; import { render, screen } from "@testing-library/react"; import { Attribution, parseAttribution } from "./attribution"; import { DEFAULT_TILE_ATTRIBUTION } from "@/lib/basemap"; // `parseAttribution` moved here from the hand-rolled renderer's tile module when // that module went. Nothing about parsing a credit line was basemap arithmetic, // and half of what reaches this component is the geocoder's credit rather than a // map's - so it lives beside the component that renders it, and so do its tests. describe("parseAttribution", () => { it("splits links out of the surrounding text", () => { expect( parseAttribution("Map [© CARTO](https://carto.com/attributions) here"), ).toEqual([ { text: "Map " }, { text: "© CARTO", href: "https://carto.com/attributions" }, { text: " here" }, ]); }); it("reads the raster default's credit as a link to the licence", () => { const parts = parseAttribution(DEFAULT_TILE_ATTRIBUTION); expect(parts.filter((part) => part.href)).toEqual([ { text: "© OpenStreetMap contributors", href: "https://www.openstreetmap.org/copyright", }, ]); }); it("leaves a plain string alone, for a provider with nothing to link to", () => { expect(parseAttribution("© Someone")).toEqual([{ text: "© Someone" }]); }); // This string comes from an environment variable or from whatever // `GEOCODER_URL` points at, so the one thing it must not do is put an // arbitrary scheme into an href. it("keeps the label but drops a link that is not http(s)", () => { expect(parseAttribution("[Credit](javascript:alert)")).toEqual([ { text: "Credit" }, ]); expect(parseAttribution("[Credit](data:text/html,x)")).toEqual([ { text: "Credit" }, ]); expect(parseAttribution("[Credit](not a url)")).toEqual([ { text: "[Credit](not a url)" }, ]); }); }); describe("Attribution", () => { it("renders the linked part as a link that leaves the form alone", () => { render( , ); const link = screen.getByRole("link", { name: "OpenStreetMap" }); expect(link).toHaveAttribute( "href", "https://www.openstreetmap.org/copyright", ); // These appear inside dialogs holding a half-filled form, and navigating // away in the same tab would throw it away. expect(link).toHaveAttribute("target", "_blank"); expect(link).toHaveAttribute("rel", expect.stringContaining("noreferrer")); }); // The whole reason this takes a string and parses it: `react/no-danger` is an // error in this repo, and a credit line is exactly the sort of "it's only // markup" HTML that gets waved through. it("renders markup in the credit as text rather than as elements", () => { const { container } = render( , ); expect(container.querySelector("b")).toBeNull(); expect(container).toHaveTextContent("© Someone x"); }); // Links in copy are underlined here - they share the text's colour, so // nothing else marks them. A map's credit chip is the owner's exception. it("underlines its links unless it is a map's credit chip", () => { const value = "Data [OpenStreetMap](https://www.openstreetmap.org/copyright)"; const { unmount } = render(); expect(screen.getByRole("link")).toHaveClass("underline"); unmount(); render(); expect(screen.getByRole("link")).not.toHaveClass("underline"); expect(screen.getByRole("link")).toHaveClass("hover:underline"); }); });