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(); });});
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();
});
});