evOo-local/packages/core/src/composer-mentions.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

247 lines
7.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
appendConnectorIntent,
buildComposerMentionOptions,
connectorIntentLine,
mentionChipKey,
partitionComposerMentions,
resolveComposerSendPlan,
routineScheduleSubtitle,
stripMentionKinds,
stripMentionToken,
toThreadMentionPayload,
} from "./composer-mentions.js";
describe("buildComposerMentionOptions", () => {
it("lists bots groups routines connectors and everyone", () => {
const options = buildComposerMentionOptions({
query: "",
includeEveryone: true,
bots: [{ id: "b1", name: "Chief", color: "#111" }],
groups: [{ id: "g1", name: "Planning" }],
routines: [
{ id: "r1", name: "Daily digest", crons: ["0 9 * * 1-5"], botId: "b1", botName: "Chief" },
],
connectors: [
{ id: "c1", name: "Gmail", authStatus: "connected", connectionId: "c1" },
{ id: "catalog:stripe", name: "Stripe", authStatus: "needs_auth" },
],
});
expect(options.map((option) => option.kind)).toEqual([
"everyone",
"bot",
"group",
"routine",
"connector",
"connector",
]);
expect(options.find((option) => option.kind === "bot")?.subtitle).toBe("Bot");
expect(options.find((option) => option.name === "Gmail")?.subtitle).toBe("Connected");
expect(options.find((option) => option.name === "Stripe")?.subtitle).toBe("Needs auth");
expect(options.find((option) => option.kind === "routine")?.subtitle).toMatch(/Weekdays/i);
});
it("disambiguates same-name routines with bot name", () => {
const options = buildComposerMentionOptions({
query: "daily",
bots: [],
groups: [],
routines: [
{ id: "r1", name: "Daily", crons: ["0 9 * * *"], botId: "b1", botName: "Chief" },
{ id: "r2", name: "Daily", crons: ["0 10 * * *"], botId: "b2", botName: "Writer" },
],
connectors: [],
});
expect(options).toHaveLength(2);
expect(options[0]?.subtitle).toContain("Chief");
expect(options[1]?.subtitle).toContain("Writer");
expect(mentionChipKey(options[0]!)).toBe("routine:r1");
expect(mentionChipKey(options[1]!)).toBe("routine:r2");
});
it("skips the current group", () => {
const options = buildComposerMentionOptions({
query: "",
currentGroupId: "g1",
bots: [],
groups: [
{ id: "g1", name: "Here" },
{ id: "g2", name: "Elsewhere" },
],
routines: [],
connectors: [],
});
expect(options.map((option) => option.id)).toEqual(["g2"]);
});
it("filters by query before applying limit so later kinds stay reachable", () => {
const bots = Array.from({ length: 40 }, (_, index) => ({
id: `b${index}`,
name: `Bot${index}`,
}));
const options = buildComposerMentionOptions({
query: "gmail",
bots,
groups: [],
routines: [],
connectors: [
{ id: "c1", name: "Gmail", authStatus: "connected", connectionId: "c1" },
{ id: "c2", name: "Granola", authStatus: "needs_auth" },
],
limit: 10,
});
expect(options.map((option) => option.name)).toEqual(["Gmail"]);
});
it("returns the full catalog when limit is omitted", () => {
const bots = Array.from({ length: 80 }, (_, index) => ({
id: `b${index}`,
name: `Bot${index}`,
}));
const options = buildComposerMentionOptions({
query: "",
bots,
groups: [],
routines: [],
connectors: [{ id: "c1", name: "Gmail", authStatus: "connected", connectionId: "c1" }],
});
expect(options).toHaveLength(81);
expect(options.at(-1)?.name).toBe("Gmail");
});
});
describe("resolveComposerSendPlan", () => {
it("opens a chip-only @Group from a 1:1 without sending", () => {
const plan = resolveComposerSendPlan({
text: "@Planning",
mentions: [{ kind: "group", id: "g1", name: "Planning" }],
hasAttachments: false,
});
expect(plan.isNoOp).toBe(false);
expect(plan.shouldSend).toBe(false);
expect(plan.shouldOpenGroup).toBe(true);
expect(plan.rerouteGroupId).toBe("g1");
expect(plan.trimmed).toBe("");
});
it("sends leftover text into the mentioned group", () => {
const plan = resolveComposerSendPlan({
text: "@Planning follow up",
mentions: [{ kind: "group", id: "g1", name: "Planning" }],
hasAttachments: false,
});
expect(plan.shouldSend).toBe(true);
expect(plan.shouldOpenGroup).toBe(true);
expect(plan.trimmed).toBe("follow up");
});
it("runs routines then opens the group when both chips have no text", () => {
const plan = resolveComposerSendPlan({
text: "@Planning @Digest",
mentions: [
{ kind: "group", id: "g1", name: "Planning" },
{ kind: "routine", id: "r1", name: "Digest" },
],
hasAttachments: false,
});
expect(plan.shouldRunRoutines).toBe(true);
expect(plan.routineIds).toEqual(["r1"]);
expect(plan.shouldOpenGroup).toBe(true);
expect(plan.shouldSend).toBe(false);
expect(plan.isNoOp).toBe(false);
});
it("opens another group from inside a group thread", () => {
const plan = resolveComposerSendPlan({
text: "@Other",
mentions: [{ kind: "group", id: "g2", name: "Other" }],
hasAttachments: false,
});
expect(plan.shouldOpenGroup).toBe(true);
expect(plan.rerouteGroupId).toBe("g2");
expect(plan.shouldSend).toBe(false);
expect(plan.isNoOp).toBe(false);
});
});
describe("partition and payload", () => {
it("partitions typed mentions", () => {
const parts = partitionComposerMentions([
{ kind: "bot", id: "b1", name: "Chief" },
{ kind: "group", id: "g1", name: "Planning" },
{ kind: "routine", id: "r1", name: "Digest" },
{ kind: "connector", id: "c1", name: "Gmail", authStatus: "connected", connectionId: "c1" },
{ kind: "everyone", id: "everyone", name: "everyone" },
]);
expect(parts.bots).toHaveLength(1);
expect(parts.groups).toHaveLength(1);
expect(parts.routines).toHaveLength(1);
expect(parts.connectors).toHaveLength(1);
expect(parts.everyone).toBe(true);
});
it("omits needs-auth catalog connectors from API payload", () => {
expect(
toThreadMentionPayload([
{ kind: "bot", id: "b1", name: "Chief" },
{
kind: "connector",
id: "catalog:stripe",
name: "Stripe",
authStatus: "needs_auth",
},
{
kind: "connector",
id: "c1",
name: "Gmail",
authStatus: "connected",
connectionId: "c1",
},
]),
).toEqual([
{ kind: "bot", id: "b1" },
{ kind: "connector", id: "c1" },
]);
});
});
describe("strip and connector intent", () => {
it("strips selected mention kinds from prompt text", () => {
expect(
stripMentionKinds(
"@Planning @Chief check this",
[
{ kind: "group", name: "Planning" },
{ kind: "bot", name: "Chief" },
],
["group"],
),
).toBe("@Chief check this");
});
it("strips multi-word mention tokens", () => {
expect(stripMentionToken("Ask @Draft team please", "Draft team")).toBe("Ask please");
});
it("preserves newlines when stripping mention tokens", () => {
expect(stripMentionToken("@Planning\n\nline two", "Planning")).toBe("line two");
expect(stripMentionToken("one\n@Planning\ntwo", "Planning")).toBe("one\n\ntwo");
});
it("builds connector intent lines", () => {
expect(connectorIntentLine(["Gmail", "Stripe"])).toBe(
"Use these connectors if relevant: Gmail, Stripe.",
);
expect(appendConnectorIntent("check inbox", ["Gmail"])).toBe(
"check inbox\n\nUse these connectors if relevant: Gmail.",
);
expect(appendConnectorIntent("", ["Gmail"])).toBe("Use these connectors if relevant: Gmail.");
});
it("formats routine subtitles", () => {
expect(routineScheduleSubtitle(["0 9 * * 1-5"])).toMatch(/Weekdays/i);
expect(routineScheduleSubtitle(["0 9 * * 1-5"], "Chief")).toContain("Chief");
});
});