/**
* Regression tests for Telegram settings menu helpers
* Exercises settings text/markup, callback mutations, stale-message fallback, and runtime wiring
*/
import assert from "node:assert/strict";
import test from "node:test";
import {
buildProactivePushSettingsReplyMarkup,
buildProactivePushSettingsText,
buildAssistantRenderingSettingsReplyMarkup,
buildAssistantRenderingSettingsText,
buildDraftPreviewsSettingsReplyMarkup,
buildDraftPreviewsSettingsText,
buildTelegramSettingsMenuReplyMarkup,
buildTelegramSettingsMenuText,
buildTimeInjectionModeSettingsReplyMarkup,
buildVoiceReplyModeSettingsReplyMarkup,
createTelegramSettingsMenuRuntime,
handleTelegramSettingsMenuCallbackAction,
} from "../lib/menu-settings.ts";
test("Settings menu text and reply markup expose built-in controls", () => {
assert.equal(buildTelegramSettingsMenuText(), "โ๏ธ Settings:");
const markup = buildTelegramSettingsMenuReplyMarkup(
true,
false,
"manual",
"hidden",
undefined,
false,
);
assert.deepEqual(
markup.inline_keyboard.map((row) => row[0]?.callback_data),
[
"menu:back",
"settings:open:voice-reply",
"settings:open:time-injection",
"settings:open:draft-previews",
"settings:open:assistant-rendering",
"settings:open:proactive",
],
);
assert.equal(markup.inline_keyboard[1]?.[0]?.text, "๐ Voice reply: hidden");
assert.equal(
markup.inline_keyboard[2]?.[0]?.text,
"๐ Time injection: hidden",
);
assert.equal(markup.inline_keyboard[3]?.[0]?.text, "๐ Draft previews: off");
assert.equal(markup.inline_keyboard[4]?.[0]?.text, "๐งพ Rendering: rich");
assert.equal(markup.inline_keyboard[5]?.[0]?.text, "๐ Proactive push: on");
});
test("Settings detail markups show active values", () => {
assert.match(buildProactivePushSettingsText(true), /on<\/code>/);
assert.match(buildDraftPreviewsSettingsText(false), /off<\/code>/);
assert.equal(
buildDraftPreviewsSettingsReplyMarkup(true).inline_keyboard[1]?.[0]?.text,
"๐ข On",
);
assert.match(buildAssistantRenderingSettingsText("html"), /html<\/code>/);
assert.equal(
buildAssistantRenderingSettingsReplyMarkup("rich").inline_keyboard[1]?.[0]
?.text,
"๐ข rich",
);
assert.equal(
buildProactivePushSettingsReplyMarkup(false).inline_keyboard[1]?.[1]?.text,
"๐ก Off",
);
assert.equal(
buildTimeInjectionModeSettingsReplyMarkup("interval")
.inline_keyboard[3]?.[0]?.text,
"๐ข interval",
);
assert.equal(
buildVoiceReplyModeSettingsReplyMarkup("mirror", true)
.inline_keyboard[3]?.[0]?.text,
"๐ข mirror",
);
assert.equal(
buildVoiceReplyModeSettingsReplyMarkup("manual", false)
.inline_keyboard[1]?.[0]?.text,
"๐ข hidden",
);
});
test("Settings callback action mutates voice, time, and proactive settings", async () => {
const calls: string[] = [];
const deps = {
isProactivePushEnabled: () => false,
getVoiceReplyMode: () => "manual" as const,
isVoiceReplyModeConfigured: () => true,
getTimeInjectionMode: () => "hidden" as const,
areDraftPreviewsEnabled: () => false,
getAssistantRenderingMode: () => "rich" as const,
setProactivePushEnabled: async (enabled: boolean) => {
calls.push(`proactive:${enabled}`);
},
setDraftPreviewsEnabled: async (enabled: boolean) => {
calls.push(`draft-previews:${enabled}`);
},
setAssistantRenderingMode: async (mode: "rich" | "html") => {
calls.push(`rendering:${mode}`);
},
setVoiceReplyMode: async (
mode: "manual" | "mirror" | "always" | undefined,
) => {
calls.push(`voice:${mode ?? "hidden"}`);
},
setTimeInjectionMode: async (mode: "hidden" | "always" | "interval") => {
calls.push(`time:${mode}`);
},
updateSettingsMessage: async (text: string) => {
calls.push(`update:${text.split("\n")[0]}`);
},
answerCallbackQuery: async (_id: string, text?: string) => {
calls.push(`answer:${text ?? ""}`);
},
};
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q1",
"settings:set:voice-reply:hidden",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q2",
"settings:set:time:off",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q3",
"settings:set:draft-previews:on",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q4",
"settings:set:assistant-rendering:html",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction(
"q5",
"settings:set:proactive:on",
deps,
),
true,
);
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q6", "other", deps),
false,
);
assert.deepEqual(calls, [
"voice:hidden",
"update:๐ Voice reply mode: manual",
"answer:Voice reply mode: hidden",
"time:hidden",
"update:๐ Time injection mode: hidden",
"answer:Time injection: hidden",
"draft-previews:true",
"update:๐ Draft previews: off",
"answer:Draft previews enabled",
"rendering:html",
"update:๐งพ Assistant rendering: rich",
"answer:Rendering: html",
"proactive:true",
"update:๐ Proactive push: off",
"answer:Proactive push enabled",
]);
});
test("Settings runtime opens menus and applies stale-message fallback toggles", async () => {
const state: any = {
chatId: 1,
messageId: 2,
mode: "status",
page: 0,
scope: "all",
scopedModels: [],
allModels: [],
};
const calls: string[] = [];
const runtime = createTelegramSettingsMenuRuntime({
isProactivePushEnabled: () => true,
getVoiceReplyMode: () => "manual",
isVoiceReplyModeConfigured: () => true,
getTimeInjectionMode: () => "hidden",
areDraftPreviewsEnabled: () => false,
getAssistantRenderingMode: () => "rich",
setProactivePushEnabled: async (enabled) => {
calls.push(`proactive:${enabled}`);
},
setDraftPreviewsEnabled: async (enabled) => {
calls.push(`draft-previews:${enabled}`);
},
setAssistantRenderingMode: async (mode) => {
calls.push(`rendering:${mode}`);
},
setVoiceReplyMode: async (mode) => {
calls.push(`voice:${mode ?? "hidden"}`);
},
setTimeInjectionMode: async (mode) => {
calls.push(`time:${mode}`);
},
getModelMenuState: async () => state,
getStoredModelMenuState: () => undefined,
storeModelMenuState: (nextState) => calls.push(`store:${nextState.mode}`),
editInteractiveMessage: async () => {
calls.push("edit");
},
sendInteractiveMessage: async (_chatId, _text, mode) => {
calls.push(`send:${mode}`);
return 99;
},
answerCallbackQuery: async (_id, text) => {
calls.push(`answer:${text ?? ""}`);
},
});
await runtime.openSettingsMenu(1, 2, "ctx");
assert.equal(state.messageId, 99);
assert.equal(state.mode, "settings");
assert.equal(
await runtime.handleCallbackQuery(
{ id: "q1", data: "settings:set:voice-reply:always" },
"ctx",
),
true,
);
assert.equal(
await runtime.handleCallbackQuery(
{ id: "q2", data: "settings:set:time:off" },
"ctx",
),
true,
);
assert.deepEqual(calls, [
"send:html",
"store:settings",
"voice:always",
"answer:Voice reply mode: always",
"time:hidden",
"answer:Time injection: hidden",
]);
});
function createSettingsCallbackHarness() {
const calls: string[] = [];
const deps = {
isProactivePushEnabled: () => false,
getVoiceReplyMode: () => "manual" as const,
isVoiceReplyModeConfigured: () => true,
getTimeInjectionMode: () => "hidden" as const,
areDraftPreviewsEnabled: () => false,
getAssistantRenderingMode: () => "rich" as const,
setProactivePushEnabled: async (enabled: boolean) => {
calls.push(`proactive:${enabled}`);
},
setDraftPreviewsEnabled: async (enabled: boolean) => {
calls.push(`draft-previews:${enabled}`);
},
setAssistantRenderingMode: async (mode: "rich" | "html") => {
calls.push(`rendering:${mode}`);
},
setVoiceReplyMode: async (
mode: "manual" | "mirror" | "always" | undefined,
) => {
calls.push(`voice:${mode ?? "hidden"}`);
},
setTimeInjectionMode: async (mode: "hidden" | "always" | "interval") => {
calls.push(`time:${mode}`);
},
updateSettingsMessage: async (text: string) => {
calls.push(`update:${text.split("\n")[0]}`);
},
answerCallbackQuery: async (_id: string, text?: string) => {
calls.push(`answer:${text ?? ""}`);
},
};
return { calls, deps };
}
test("Settings callback action opens each page, including legacy aliases", async () => {
const pages: Array<[string, string]> = [
["settings:list", "update:โ๏ธ Settings:"],
["settings:open:proactive", "update:๐ Proactive push: off"],
["settings:open:draft-previews", "update:๐ Draft previews: off"],
["settings:open:rich-drafts", "update:๐ Draft previews: off"],
["settings:open:assistant-rendering", "update:๐งพ Assistant rendering: rich"],
["settings:open:voice-reply", "update:๐ Voice reply mode: manual"],
["settings:open:time-injection", "update:๐ Time injection mode: hidden"],
["settings:open:time", "update:๐ Time injection mode: hidden"],
];
for (const [data, update] of pages) {
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", data, deps),
true,
data,
);
assert.deepEqual(calls, [update, "answer:"], data);
}
});
test("Settings callback action applies each mode and the inverse branch of each toggle", async () => {
const cases: Array<[string, string[]]> = [
["settings:set:voice-reply:mirror", ["voice:mirror", "update:๐ Voice reply mode: manual", "answer:Voice reply mode: mirror"]],
["settings:set:voice-reply:always", ["voice:always", "update:๐ Voice reply mode: manual", "answer:Voice reply mode: always"]],
["settings:set:time-injection:interval", ["time:interval", "update:๐ Time injection mode: hidden", "answer:Time injection: interval"]],
["settings:set:time-injection:off", ["time:hidden", "update:๐ Time injection mode: hidden", "answer:Time injection: hidden"]],
["settings:set:time:always", ["time:always", "update:๐ Time injection mode: hidden", "answer:Time injection: always"]],
["settings:set:draft-previews:off", ["draft-previews:false", "update:๐ Draft previews: off", "answer:Draft previews disabled"]],
["settings:set:rich-drafts:on", ["draft-previews:true", "update:๐ Draft previews: off", "answer:Draft previews enabled"]],
["settings:set:assistant-rendering:rich", ["rendering:rich", "update:๐งพ Assistant rendering: rich", "answer:Rendering: rich"]],
["settings:set:proactive:off", ["proactive:false", "update:๐ Proactive push: off", "answer:Proactive push disabled"]],
];
for (const [data, expected] of cases) {
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", data, deps),
true,
data,
);
assert.deepEqual(calls, expected, data);
}
});
test("Settings callback action only acknowledges invalid or unknown settings values", async () => {
for (const data of [
"settings:set:voice-reply:bogus",
"settings:set:time:bogus",
"settings:set:assistant-rendering:bogus",
"settings:set:draft-previews:maybe",
"settings:set:proactive:maybe",
"settings:unknown",
]) {
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", data, deps),
true,
data,
);
assert.deepEqual(calls, ["answer:"], data);
}
const { calls, deps } = createSettingsCallbackHarness();
assert.equal(
await handleTelegramSettingsMenuCallbackAction("q", undefined, deps),
false,
);
assert.deepEqual(calls, []);
});