/** * 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, []); });