import { describe, expect, it } from "@effect/vitest";import { type Integration, IntegrationSlug } from "@executor-js/sdk/shared"; import { groupIntegrations, isMultiServiceFamily, type IntegrationFamilyGroup, type IntegrationSingle,} from "./integration-grouping"; const integration = (slug: string, kind: string, name = slug): Integration => ({ slug: IntegrationSlug.make(slug), name, description: name, kind, canRemove: true, canRefresh: true, authMethods: [],}); describe("groupIntegrations", () => { it("collapses a multi-member family under one umbrella", () => { const items = groupIntegrations([ integration("google_calendar", "google", "Google Calendar"), integration("google_gmail", "google", "Gmail"), integration("google_drive", "google", "Google Drive"), ]); expect(items).toHaveLength(1); const group = items[0] as IntegrationFamilyGroup; expect(group.type).toBe("group"); expect(group.kind).toBe("google"); expect(group.label).toBe("Google"); expect(group.members.map((m) => String(m.slug))).toEqual([ "google_calendar", "google_gmail", "google_drive", ]); }); it("leaves non-family integrations ungrouped", () => { const items = groupIntegrations([ integration("stripe", "openapi", "Stripe"), integration("sentry", "mcp", "Sentry"), ]); expect(items).toHaveLength(2); expect(items.every((i) => i.type === "single")).toBe(true); }); it("renders a lone family member flat, not as a one-item umbrella", () => { const items = groupIntegrations([integration("google_calendar", "google", "Google Calendar")]); expect(items).toHaveLength(1); expect((items[0] as IntegrationSingle).type).toBe("single"); }); it("places each group where its first member first appeared, keeping order stable", () => { const items = groupIntegrations([ integration("stripe", "openapi", "Stripe"), integration("google_calendar", "google", "Google Calendar"), integration("microsoft_mail", "microsoft", "Outlook Mail"), integration("google_gmail", "google", "Gmail"), integration("microsoft_calendar", "microsoft", "Outlook Calendar"), integration("sentry", "mcp", "Sentry"), ]); // stripe (single) · Google group (first at index 1) · Microsoft group // (first at index 2) · sentry (single). Later family members fold back into // their group rather than re-appearing. expect(items.map((i) => (i.type === "group" ? `group:${i.kind}` : "single"))).toEqual([ "single", "group:google", "group:microsoft", "single", ]); const google = items[1] as IntegrationFamilyGroup; const microsoft = items[2] as IntegrationFamilyGroup; expect(google.members.map((m) => String(m.slug))).toEqual(["google_calendar", "google_gmail"]); expect(microsoft.members.map((m) => String(m.slug))).toEqual([ "microsoft_mail", "microsoft_calendar", ]); }); it("keeps custom per-service integrations inside their family group", () => { const items = groupIntegrations([ integration("google_calendar", "google", "Google Calendar"), integration("google_custom", "google", "Custom Google API"), ]); expect(items).toHaveLength(1); const group = items[0] as IntegrationFamilyGroup; expect(group.members.map((m) => String(m.slug))).toEqual(["google_calendar", "google_custom"]); }); it("recognizes the multi-service family allowlist", () => { expect(isMultiServiceFamily("google")).toBe(true); expect(isMultiServiceFamily("microsoft")).toBe(true); expect(isMultiServiceFamily("openapi")).toBe(false); });});
import { describe, expect, it } from "@effect/vitest";
import { type Integration, IntegrationSlug } from "@executor-js/sdk/shared";
import {
groupIntegrations,
isMultiServiceFamily,
type IntegrationFamilyGroup,
type IntegrationSingle,
} from "./integration-grouping";
const integration = (slug: string, kind: string, name = slug): Integration => ({
slug: IntegrationSlug.make(slug),
name,
description: name,
kind,
canRemove: true,
canRefresh: true,
authMethods: [],
});
describe("groupIntegrations", () => {
it("collapses a multi-member family under one umbrella", () => {
const items = groupIntegrations([
integration("google_calendar", "google", "Google Calendar"),
integration("google_gmail", "google", "Gmail"),
integration("google_drive", "google", "Google Drive"),
]);
expect(items).toHaveLength(1);
const group = items[0] as IntegrationFamilyGroup;
expect(group.type).toBe("group");
expect(group.kind).toBe("google");
expect(group.label).toBe("Google");
expect(group.members.map((m) => String(m.slug))).toEqual([
"google_calendar",
"google_gmail",
"google_drive",
]);
});
it("leaves non-family integrations ungrouped", () => {
const items = groupIntegrations([
integration("stripe", "openapi", "Stripe"),
integration("sentry", "mcp", "Sentry"),
]);
expect(items).toHaveLength(2);
expect(items.every((i) => i.type === "single")).toBe(true);
});
it("renders a lone family member flat, not as a one-item umbrella", () => {
const items = groupIntegrations([integration("google_calendar", "google", "Google Calendar")]);
expect(items).toHaveLength(1);
expect((items[0] as IntegrationSingle).type).toBe("single");
});
it("places each group where its first member first appeared, keeping order stable", () => {
const items = groupIntegrations([
integration("stripe", "openapi", "Stripe"),
integration("google_calendar", "google", "Google Calendar"),
integration("microsoft_mail", "microsoft", "Outlook Mail"),
integration("google_gmail", "google", "Gmail"),
integration("microsoft_calendar", "microsoft", "Outlook Calendar"),
integration("sentry", "mcp", "Sentry"),
]);
// stripe (single) · Google group (first at index 1) · Microsoft group
// (first at index 2) · sentry (single). Later family members fold back into
// their group rather than re-appearing.
expect(items.map((i) => (i.type === "group" ? `group:${i.kind}` : "single"))).toEqual([
"single",
"group:google",
"group:microsoft",
"single",
]);
const google = items[1] as IntegrationFamilyGroup;
const microsoft = items[2] as IntegrationFamilyGroup;
expect(google.members.map((m) => String(m.slug))).toEqual(["google_calendar", "google_gmail"]);
expect(microsoft.members.map((m) => String(m.slug))).toEqual([
"microsoft_mail",
"microsoft_calendar",
]);
});
it("keeps custom per-service integrations inside their family group", () => {
const items = groupIntegrations([
integration("google_calendar", "google", "Google Calendar"),
integration("google_custom", "google", "Custom Google API"),
]);
expect(items).toHaveLength(1);
const group = items[0] as IntegrationFamilyGroup;
expect(group.members.map((m) => String(m.slug))).toEqual(["google_calendar", "google_custom"]);
});
it("recognizes the multi-service family allowlist", () => {
expect(isMultiServiceFamily("google")).toBe(true);
expect(isMultiServiceFamily("microsoft")).toBe(true);
expect(isMultiServiceFamily("openapi")).toBe(false);
});
});