import { describe, it, beforeEach, mock } from "node:test"; import assert from "node:assert/strict"; import { MockFetch } from "../../testHelpers.js"; import "/js/components/plugin-icon.js"; describe("plugin-icon", () => { const SAMPLE_SVG = ''; function flush() { return new Promise((resolve) => setTimeout(resolve, 0)); } function okResponse(body) { return { ok: true, status: 200, statusText: "OK", text: async () => body, }; } function notFoundResponse() { return { ok: false, status: 404, statusText: "Not Found", text: async () => "", }; } beforeEach(() => { document.body.innerHTML = ""; customElements.get("plugin-icon").cache = new Map(); }); describe("PluginIcon - iconset resolution", () => { it("resolves an icon from the majesticons set", async () => { const fetch = new MockFetch(); fetch.__intercept("/img/icons/", async () => okResponse(SAMPLE_SVG)); globalThis.fetch = fetch; const element = document.createElement("plugin-icon"); element.setAttribute("icon", "bell"); document.body.appendChild(element); await flush(); assert.deepEqual(fetch.calls[0].url, "/img/icons/majesticons/bell.svg"); }); it("resolves an icon from the custom set", async () => { const fetch = new MockFetch(); fetch.__intercept("/img/icons/", async () => okResponse(SAMPLE_SVG)); globalThis.fetch = fetch; const element = document.createElement("plugin-icon"); element.setAttribute("icon", "verified-check"); document.body.appendChild(element); await flush(); assert.deepEqual( fetch.calls[0].url, "/img/icons/custom/verified-check.svg", ); }); it("warns and renders nothing for an unknown icon", async () => { const fetch = new MockFetch(); globalThis.fetch = fetch; const originalWarn = console.warn; const warnMock = mock.fn(); console.warn = warnMock; try { const element = document.createElement("plugin-icon"); element.setAttribute("icon", "not-a-real-icon"); document.body.appendChild(element); await flush(); assert.deepEqual(fetch.calls.length, 0); assert.deepEqual(element.innerHTML, ""); assert.deepEqual(warnMock.mock.callCount(), 1); } finally { console.warn = originalWarn; } }); }); describe("PluginIcon - rendering", () => { it("injects the fetched SVG markup", async () => { const fetch = new MockFetch(); fetch.__intercept("/img/icons/", async () => okResponse(SAMPLE_SVG)); globalThis.fetch = fetch; const element = document.createElement("plugin-icon"); element.setAttribute("icon", "cake"); document.body.appendChild(element); await flush(); const svg = element.querySelector("svg"); assert(svg !== null); assert.deepEqual(svg.getAttribute("viewBox"), "0 0 24 24"); }); it("renders nothing when icon is empty", async () => { const fetch = new MockFetch(); globalThis.fetch = fetch; const element = document.createElement("plugin-icon"); document.body.appendChild(element); await flush(); assert.deepEqual(fetch.calls.length, 0); assert.deepEqual(element.innerHTML, ""); }); it("swaps the icon when icon changes", async () => { const fetch = new MockFetch(); fetch.__intercept("/img/icons/majesticons/bus.svg", async () => okResponse(''), ); fetch.__intercept("/img/icons/majesticons/car.svg", async () => okResponse(''), ); globalThis.fetch = fetch; const element = document.createElement("plugin-icon"); element.setAttribute("icon", "bus"); document.body.appendChild(element); await flush(); assert.deepEqual(element.querySelector("svg").id, "first"); element.setAttribute("icon", "car"); await flush(); assert.deepEqual(element.querySelector("svg").id, "second"); }); }); describe("PluginIcon - caching", () => { it("only fetches once when the same icon is rendered twice", async () => { const fetch = new MockFetch(); fetch.__intercept("/img/icons/", async () => okResponse(SAMPLE_SVG)); globalThis.fetch = fetch; const first = document.createElement("plugin-icon"); first.setAttribute("icon", "chat"); document.body.appendChild(first); const second = document.createElement("plugin-icon"); second.setAttribute("icon", "chat"); document.body.appendChild(second); await flush(); assert.deepEqual(fetch.calls.length, 1); assert(first.querySelector("svg") !== null); assert(second.querySelector("svg") !== null); }); }); describe("PluginIcon - error handling", () => { it("warns and renders nothing when the fetch 404s; does not retry", async () => { const fetch = new MockFetch(); fetch.__intercept("/img/icons/", async () => notFoundResponse()); globalThis.fetch = fetch; const originalWarn = console.warn; const warnMock = mock.fn(); console.warn = warnMock; try { const element = document.createElement("plugin-icon"); element.setAttribute("icon", "moon"); document.body.appendChild(element); await flush(); assert.deepEqual(element.innerHTML, ""); assert.deepEqual(warnMock.mock.callCount(), 1); const retry = document.createElement("plugin-icon"); retry.setAttribute("icon", "moon"); document.body.appendChild(retry); await flush(); assert.deepEqual(fetch.calls.length, 1); } finally { console.warn = originalWarn; } }); }); });