import { describe, expect, it } from "@effect/vitest"; import { integrationFaviconFallbackUrl, integrationFaviconSrc, integrationFaviconUrl, integrationInferredUrl, integrationLocalIconUrl, integrationPresetIconUrl, } from "./integration-favicon"; describe("IntegrationFavicon", () => { it("uses the integrations.sh logo proxy as the primary favicon source", () => { expect(integrationFaviconUrl("https://api.github.com/graphql", 20)).toBe( "https://integrations.sh/logo/github.com?sz=40", ); }); it("falls back to the Google favicon service", () => { expect(integrationFaviconFallbackUrl("https://api.github.com/graphql", 20)).toBe( "https://www.google.com/s2/favicons?domain=github.com&sz=40", ); }); it("does not request favicons for local URLs", () => { expect(integrationFaviconUrl("http://localhost:3000/private", 20)).toBeNull(); expect(integrationFaviconUrl("http://127.0.0.1:3000/private", 20)).toBeNull(); expect(integrationFaviconFallbackUrl("http://localhost:3000/private", 20)).toBeNull(); }); it("sends only the registrable domain to the logo proxy", () => { expect(integrationFaviconUrl("https://api.github.com/private", 20)).toBe( "https://integrations.sh/logo/github.com?sz=40", ); }); it("walks the cascade from the proxy to the favicon service on failure", () => { const url = "https://api.github.com/graphql"; const primary = integrationFaviconSrc({ url, size: 20 }); expect(primary).toBe("https://integrations.sh/logo/github.com?sz=40"); expect(integrationFaviconSrc({ url, size: 20, failedSrcs: [primary ?? ""] })).toBe( "https://www.google.com/s2/favicons?domain=github.com&sz=40", ); }); it("uses the Executor favicon for the built-in executor source", () => { expect(integrationLocalIconUrl("executor")).toBe("/favicon-32.png"); expect(integrationLocalIconUrl("openapi")).toBeNull(); }); it("resolves the Executor sidebar icon only when the source id is threaded (cloud/self-host repro)", () => { // Reconstruct the props the multiplayer shell derives for the built-in // executor source (EXECUTOR_SOURCE: kind "built-in", name "Executor", no // displayUrl). The sidebar's IntegrationList builds icon/url exactly this way. const slug = "executor"; const name = "Executor"; const icon = integrationPresetIconUrl({ id: slug, kind: "built-in", name, url: undefined }, []); const url = integrationInferredUrl({ id: slug, name }) ?? undefined; // The built-in source matches no preset and has no inferable host, so the // sourceId branch is the only thing that can resolve its icon. expect(icon).toBeNull(); expect(url).toBeUndefined(); // Bug: cloud/self-host dropped sourceId, so the cascade fell through to null // and rendered the neutral BoxIcon placeholder. expect(integrationFaviconSrc({ icon, url, size: 16 })).toBeNull(); // Fix: threading sourceId resolves the bundled Executor favicon. expect(integrationFaviconSrc({ icon, sourceId: slug, url, size: 16 })).toBe("/favicon-32.png"); }); it("finds preset icons from a source URL", () => { expect( integrationPresetIconUrl( { id: "google_sheets", kind: "googleDiscovery", name: "Google Sheets API", url: "https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", }, [ { key: "google", label: "Google", add: () => null, edit: () => null, presets: [ { id: "google-sheets", name: "Google Sheets", summary: "Spreadsheets.", url: "https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest", icon: "https://example.com/sheets.svg", }, ], }, ], ), ).toBe("https://example.com/sheets.svg"); }); it("finds preset icons from display names with suffixes", () => { expect( integrationPresetIconUrl( { id: "google_search_console_api", kind: "googleDiscovery", name: "Google Search Console API", }, [ { key: "google", label: "Google", add: () => null, edit: () => null, presets: [ { id: "google-search-console", name: "Google Search Console", summary: "Search performance.", icon: "https://example.com/google.svg", }, ], }, ], ), ).toBe("https://example.com/google.svg"); }); it("finds preset icons from a source id when the URL is missing", () => { expect( integrationPresetIconUrl( { id: "sentry", kind: "mcp", name: "Sentry MCP", }, [ { key: "mcp", label: "MCP", add: () => null, edit: () => null, presets: [ { id: "sentry", name: "Sentry", summary: "Errors.", icon: "https://example.com/sentry.png", }, ], }, ], ), ).toBe("https://example.com/sentry.png"); }); it("matches migrated MCP slugs with host/suffix noise", () => { const presets = [ { key: "mcp", label: "MCP", add: () => null, edit: () => null, presets: [ { id: "posthog", name: "PostHog", summary: "Analytics.", icon: "https://example.com/posthog.png", }, { id: "linear", name: "Linear", summary: "Issues.", icon: "https://example.com/linear.png", }, { id: "planetscale", name: "PlanetScale", summary: "Databases.", icon: "https://example.com/pscale.png", }, ], }, ]; expect( integrationPresetIconUrl( { id: "mcp_posthog_com", kind: "mcp", name: "mcp.posthog.com" }, presets, ), ).toBe("https://example.com/posthog.png"); expect( integrationPresetIconUrl( { id: "mcp_linear_app", kind: "mcp", name: "mcp.linear.app" }, presets, ), ).toBe("https://example.com/linear.png"); expect( integrationPresetIconUrl({ id: "pscale_mcp", kind: "mcp", name: "Pscale MCP" }, presets), ).toBe("https://example.com/pscale.png"); }); it("matches migrated OpenAPI slugs with API/REST suffixes", () => { expect( integrationPresetIconUrl({ id: "stripe_api", kind: "openapi", name: "Stripe API" }, [ { key: "openapi", label: "OpenAPI", add: () => null, edit: () => null, presets: [ { id: "stripe", name: "Stripe", summary: "Payments.", icon: "https://example.com/stripe.png", }, ], }, ]), ).toBe("https://example.com/stripe.png"); }); it("does not split generic words into brand matches", () => { expect( integrationPresetIconUrl( { id: "spotify_web_api", kind: "openapi", name: "Spotify Web API", url: "https://api.spotify.com/v1", }, [ { key: "openapi", label: "OpenAPI", add: () => null, edit: () => null, presets: [ { id: "exa-websets", name: "Exa Websets", summary: "Web data.", icon: "https://example.com/exa.png", }, { id: "spotify", name: "Spotify", summary: "Music.", icon: "https://example.com/spotify.png", }, ], }, ], ), ).toBe("https://example.com/spotify.png"); }); it("infers favicon URLs from migrated host-shaped MCP names and slugs", () => { expect(integrationInferredUrl({ id: "mcp_posthog_com", name: "mcp.posthog.com" })).toBe( "https://mcp.posthog.com", ); expect(integrationInferredUrl({ id: "ai_todoist_net", name: "ai.todoist.net" })).toBe( "https://ai.todoist.net", ); expect(integrationInferredUrl({ id: "mcp_pscale_dev", name: "mcp.pscale.dev" })).toBe( "https://mcp.pscale.dev", ); expect(integrationInferredUrl({ id: "stripe_api", name: "Stripe API" })).toBeNull(); }); });