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.
247 lines
7.9 KiB
TypeScript
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");
|
|
});
|
|
});
|