Risk labels, menu callback parsing, settings callbacks, table cell width and markdown link targets now use tables and small helpers (CC <= 10).
370 lines
12 KiB
TypeScript
370 lines
12 KiB
TypeScript
/**
|
|
* 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(), "<b>⚙️ Settings:</b>");
|
|
|
|
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), /<code>on<\/code>/);
|
|
assert.match(buildDraftPreviewsSettingsText(false), /<code>off<\/code>/);
|
|
assert.equal(
|
|
buildDraftPreviewsSettingsReplyMarkup(true).inline_keyboard[1]?.[0]?.text,
|
|
"🟢 On",
|
|
);
|
|
assert.match(buildAssistantRenderingSettingsText("html"), /<code>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:<b>👄 Voice reply mode:</b> <code>manual</code>",
|
|
"answer:Voice reply mode: hidden",
|
|
"time:hidden",
|
|
"update:<b>🕒 Time injection mode:</b> <code>hidden</code>",
|
|
"answer:Time injection: hidden",
|
|
"draft-previews:true",
|
|
"update:<b>📝 Draft previews:</b> <code>off</code>",
|
|
"answer:Draft previews enabled",
|
|
"rendering:html",
|
|
"update:<b>🧾 Assistant rendering:</b> <code>rich</code>",
|
|
"answer:Rendering: html",
|
|
"proactive:true",
|
|
"update:<b>📌 Proactive push:</b> <code>off</code>",
|
|
"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:<b>⚙️ Settings:</b>"],
|
|
["settings:open:proactive", "update:<b>📌 Proactive push:</b> <code>off</code>"],
|
|
["settings:open:draft-previews", "update:<b>📝 Draft previews:</b> <code>off</code>"],
|
|
["settings:open:rich-drafts", "update:<b>📝 Draft previews:</b> <code>off</code>"],
|
|
["settings:open:assistant-rendering", "update:<b>🧾 Assistant rendering:</b> <code>rich</code>"],
|
|
["settings:open:voice-reply", "update:<b>👄 Voice reply mode:</b> <code>manual</code>"],
|
|
["settings:open:time-injection", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>"],
|
|
["settings:open:time", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>"],
|
|
];
|
|
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:<b>👄 Voice reply mode:</b> <code>manual</code>", "answer:Voice reply mode: mirror"]],
|
|
["settings:set:voice-reply:always", ["voice:always", "update:<b>👄 Voice reply mode:</b> <code>manual</code>", "answer:Voice reply mode: always"]],
|
|
["settings:set:time-injection:interval", ["time:interval", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>", "answer:Time injection: interval"]],
|
|
["settings:set:time-injection:off", ["time:hidden", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>", "answer:Time injection: hidden"]],
|
|
["settings:set:time:always", ["time:always", "update:<b>🕒 Time injection mode:</b> <code>hidden</code>", "answer:Time injection: always"]],
|
|
["settings:set:draft-previews:off", ["draft-previews:false", "update:<b>📝 Draft previews:</b> <code>off</code>", "answer:Draft previews disabled"]],
|
|
["settings:set:rich-drafts:on", ["draft-previews:true", "update:<b>📝 Draft previews:</b> <code>off</code>", "answer:Draft previews enabled"]],
|
|
["settings:set:assistant-rendering:rich", ["rendering:rich", "update:<b>🧾 Assistant rendering:</b> <code>rich</code>", "answer:Rendering: rich"]],
|
|
["settings:set:proactive:off", ["proactive:false", "update:<b>📌 Proactive push:</b> <code>off</code>", "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, []);
|
|
});
|