import { describe, it, expect } from "vitest"; import { START_TEXT, HELP_TEXT, TRIAL_TEXT, TRIAL_RE, COMMANDS, findTrialEntry, findFreeTwin, extractSku, TRIAL_INTENT_RE, startSkuText, parseTrialConsent, ASK_USERNAME_TEXT, confirmReuseText, parseAccountDecision, extractUsername, ASK_NEW_ACCOUNT_USERNAME_TEXT, trialSuccessText, trialErrorText, MEMORY_KEY_TRIAL_USERNAME, resolveLanguageKey, TRIAL_CB, startSkuKeyboard, confirmReuseKeyboard, TRIAL_ACCT_PREFIX, trialAcctCb, chooseAccountText, chooseAccountTooManyText, chooseAccountKeyboard, } from "../src/channels/telegram/commands/index.js"; describe("Telegram commands", () => { it("registers start, help and trial", () => { expect(COMMANDS.map((c) => c.command).sort()).toEqual(["help", "start", "trial"]); }); it("has tri-lingual start, help and trial text", () => { expect(START_TEXT).toContain("欢迎"); expect(START_TEXT).toContain("Welcome"); expect(START_TEXT).toContain("Selamat"); expect(HELP_TEXT).toContain("帮助"); expect(HELP_TEXT).toContain("Help"); }); describe("resolveLanguageKey (第4批: 单语模板的语言归一)", () => { it("maps zh/en/ms prefixes, ignores case and treats id as ms", () => { expect(resolveLanguageKey("zh")).toBe("zh"); expect(resolveLanguageKey("zh-cn")).toBe("zh"); expect(resolveLanguageKey("zh-Hant")).toBe("zh"); expect(resolveLanguageKey("en")).toBe("en"); expect(resolveLanguageKey("en-US")).toBe("en"); expect(resolveLanguageKey("ms")).toBe("ms"); expect(resolveLanguageKey("ms-MY")).toBe("ms"); expect(resolveLanguageKey("id")).toBe("ms"); expect(resolveLanguageKey("id-ID")).toBe("ms"); }); it("defaults to zh for unknown/undefined/empty codes", () => { expect(resolveLanguageKey("fr")).toBe("zh"); expect(resolveLanguageKey("")).toBe("zh"); expect(resolveLanguageKey(undefined)).toBe("zh"); }); }); it("trial instructions are single-language per lang (第4批)", () => { const zh = TRIAL_TEXT("zh"); expect(zh).toContain("FREECXM04"); expect(zh).toContain("免费试用"); expect(zh).not.toContain("Percubaan"); expect(zh).not.toContain("trial FREECXM04"); const en = TRIAL_TEXT("en"); expect(en).toContain("Free Trial"); expect(en).toContain("trial FREECXM04 username:abc123"); expect(en).not.toContain("免费"); const ms = TRIAL_TEXT("ms"); expect(ms).toContain("Percubaan Percuma"); expect(ms).toContain("percubaan FREECXM04 username:abc123"); expect(ms).not.toContain("Free Trial"); }); it("TRIAL_TEXT defaults to zh when lang omitted", () => { expect(TRIAL_TEXT()).toContain("免费试用"); expect(TRIAL_TEXT()).not.toContain("Free Trial"); }); it("TRIAL_RE parses tri-lingual forms incl. ASCII username/password labels", () => { // Chinese form const zh = "免费试用 FREECXM04 用户名:abc123".match(TRIAL_RE); expect(zh?.[1]).toBe("FREECXM04"); expect(zh?.[2]).toBe("abc123"); // English + Malay forms (broken before the fix: username: label was ignored) const en = "trial FREECXM04 username:abc123".match(TRIAL_RE); expect(en?.[1]).toBe("FREECXM04"); expect(en?.[2]).toBe("abc123"); const ms = "percubaan FREECXM04 username:abc123".match(TRIAL_RE); expect(ms?.[1]).toBe("FREECXM04"); expect(ms?.[2]).toBe("abc123"); // Optional password under both labels expect("trial FREECXM04 username:abc123 password:pw789".match(TRIAL_RE)?.[3]).toBe("pw789"); expect("免费试用 FREECXM04 用户名:abc123 密码:pw789".match(TRIAL_RE)?.[3]).toBe("pw789"); }); it("TRIAL_RE requires a username for provision", () => { expect("trial FREECXM04".match(TRIAL_RE)?.[2]).toBeUndefined(); expect("trial FREECXM04 username:abc123".match(TRIAL_RE)?.[2]).toBe("abc123"); }); it("never advertises contact details proactively", () => { expect(HELP_TEXT).not.toContain("012-"); expect(HELP_TEXT).not.toContain("WhatsApp"); expect(HELP_TEXT).not.toContain("@DigiKedai"); expect(HELP_TEXT).not.toContain("@MrFullStackDev"); expect(HELP_TEXT).not.toContain("t.me"); }); it("deep-link: resolves a FREE-prefixed trial SKU from the catalog", () => { const e = findTrialEntry("FREECXM04"); expect(e).toBeDefined(); expect(e!.isTrial).toBe(true); expect(e!.name).toContain("免费试看"); }); it("deep-link: lookup is case-insensitive, skips paid/unknown/empty payloads", () => { expect(findTrialEntry("freecxm04 ")).toBeDefined(); expect(findTrialEntry("CXM04")).toBeUndefined(); // paid SKU, not a trial expect(findTrialEntry("NOPE99")).toBeUndefined(); expect(findTrialEntry(" ")).toBeUndefined(); }); describe("findFreeTwin (改动1: 付费 SKU → 免费 twin 反查)", () => { it("resolves a paid SKU to its FREE twin from the catalog", () => { const twin = findFreeTwin("CZH01"); expect(twin).toBeDefined(); expect(twin!.isTrial).toBe(true); expect(twin!.name).toContain("免费试看"); expect(twin!.sku).toBe("FREECZH01"); }); it("is idempotent: an already-FREE SKU resolves to itself", () => { const twin = findFreeTwin("FREECZH01"); expect(twin?.sku).toBe("FREECZH01"); // case-insensitive expect(findFreeTwin("freeczh01")?.sku).toBe("FREECZH01"); }); it("returns undefined for unknown SKUs and empty input", () => { expect(findFreeTwin("NOPE99")).toBeUndefined(); expect(findFreeTwin(" ")).toBeUndefined(); expect(findFreeTwin("")).toBeUndefined(); }); it("returns undefined when the paid SKU has no free twin (not in catalog)", () => { // CZH99 doesn't exist in the catalog at all → no twin expect(findFreeTwin("CZH99")).toBeUndefined(); }); it("does not fabricate twins for paid SKUs that exist without a FREE variant", () => { // CZP01 exists as a paid SKU (B 马来西亚名师课) with NO FREE twin — // verified against catalog: only CZP01/CZP02 lack twins. // findFreeTwin must NOT invent FREECZP01. expect(findFreeTwin("CZP01")).toBeUndefined(); expect(findFreeTwin("CZP02")).toBeUndefined(); }); }); describe("extractSku (改动1: 从消息文本提取 SKU)", () => { it("extracts the SKU from a digikedai.com product URL", () => { expect(extractSku("digikedai.com/products/czh01")).toBe("CZH01"); expect(extractSku("https://www.digikedai.com/products/czh01/")).toBe("CZH01"); }); it("extracts a SKU embedded in a natural-language trial request", () => { expect(extractSku("我要这个试用:\ndigikedai.com/products/czh01")).toBe("CZH01"); expect(extractSku("想试试 CZH01 的免费版")).toBe("CZH01"); }); it("extracts bare SKU tokens from casual text (case-insensitive)", () => { expect(extractSku("czh01 有免费版吗")).toBe("CZH01"); expect(extractSku("tell me about freecxm04")).toBe("FREECXM04"); }); it("returns undefined when no known SKU appears", () => { expect(extractSku("你好")).toBeUndefined(); expect(extractSku("这个产品多少钱")).toBeUndefined(); expect(extractSku("")).toBeUndefined(); expect(extractSku(" ")).toBeUndefined(); }); }); describe("TRIAL_INTENT_RE (试用意图检测)", () => { it("matches zh/en/ms/id trial-intent phrases", () => { // zh expect(TRIAL_INTENT_RE.test("我要这个试用:")).toBe(true); expect(TRIAL_INTENT_RE.test("免费版有吗")).toBe(true); expect(TRIAL_INTENT_RE.test("想试试看")).toBe(true); expect(TRIAL_INTENT_RE.test("免费试用")).toBe(true); expect(TRIAL_INTENT_RE.test("看看这个")).toBe(true); expect(TRIAL_INTENT_RE.test("预览一下")).toBe(true); expect(TRIAL_INTENT_RE.test("试听一下")).toBe(true); expect(TRIAL_INTENT_RE.test("体验一下")).toBe(true); expect(TRIAL_INTENT_RE.test("试读看看")).toBe(true); // en expect(TRIAL_INTENT_RE.test("trial pls")).toBe(true); expect(TRIAL_INTENT_RE.test("I want to try")).toBe(true); expect(TRIAL_INTENT_RE.test("try this")).toBe(true); expect(TRIAL_INTENT_RE.test("free trial")).toBe(true); expect(TRIAL_INTENT_RE.test("preview")).toBe(true); expect(TRIAL_INTENT_RE.test("sample")).toBe(true); expect(TRIAL_INTENT_RE.test("demo")).toBe(true); expect(TRIAL_INTENT_RE.test("check out")).toBe(true); expect(TRIAL_INTENT_RE.test("take a look")).toBe(true); expect(TRIAL_INTENT_RE.test("give it a try")).toBe(true); // ms / id expect(TRIAL_INTENT_RE.test("percubaan")).toBe(true); expect(TRIAL_INTENT_RE.test("cuba")).toBe(true); expect(TRIAL_INTENT_RE.test("nak cuba")).toBe(true); expect(TRIAL_INTENT_RE.test("tengok dulu")).toBe(true); expect(TRIAL_INTENT_RE.test("nak tengok")).toBe(true); expect(TRIAL_INTENT_RE.test("lihat dulu")).toBe(true); expect(TRIAL_INTENT_RE.test("coba")).toBe(true); expect(TRIAL_INTENT_RE.test("gratis")).toBe(true); }); it("does not match plain product/price questions", () => { expect(TRIAL_INTENT_RE.test("这个产品多少钱")).toBe(false); expect(TRIAL_INTENT_RE.test("CZH01 价格是多少")).toBe(false); expect(TRIAL_INTENT_RE.test("how much")).toBe(false); }); it("word boundaries avoid English false positives", () => { expect(TRIAL_INTENT_RE.test("do you have chemistry courses?")).toBe(false); expect(TRIAL_INTENT_RE.test("please retry")).toBe(false); expect(TRIAL_INTENT_RE.test("latest update")).toBe(false); expect(TRIAL_INTENT_RE.test("country list")).toBe(false); }); }); it("deep-link guidance points to the claim button (第3批 一步一动作)", () => { const e = findTrialEntry("FREECXM04")!; const zh = startSkuText(e, "zh"); expect(zh).toContain("免费试看"); expect(zh).toContain("FREECXM04"); expect(zh).toContain("领取"); expect(zh).not.toContain("ok username:abc123"); expect(zh).not.toContain("同意 用户名:abc123"); expect(zh).not.toContain("setuju username:abc123"); const en = startSkuText(e, "en"); expect(en).toContain("claim"); expect(en).not.toContain("ok username:abc123"); expect(en).not.toContain("同意 用户名:abc123"); const ms = startSkuText(e, "ms"); expect(ms).toContain("menuntut"); expect(ms).not.toContain("setuju username:abc123"); }); it("parseTrialConsent: zh/en/ms consent words + label line all accepted", () => { expect(parseTrialConsent("同意 abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("好的 abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("好 abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("ok abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("okay abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("yes abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("setuju abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("saya setuju abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("username:abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("用户名:abc123")).toEqual({ consented: true, username: "abc123" }); expect(parseTrialConsent("nama pengguna abc123")).toEqual({ consented: true, username: "abc123" }); }); it("parseTrialConsent: consent without username is consented but leaves it blank", () => { expect(parseTrialConsent("同意")).toEqual({ consented: true, username: undefined }); expect(parseTrialConsent("ok")).toEqual({ consented: true, username: undefined }); expect(parseTrialConsent("setuju")).toEqual({ consented: true, username: undefined }); }); it("parseTrialConsent: ordinary chat is NOT a consent reply", () => { expect(parseTrialConsent("这个产品多少钱")).toEqual({ consented: false }); expect(parseTrialConsent("如何获得账号")).toEqual({ consented: false }); expect(parseTrialConsent("谢谢")).toEqual({ consented: false }); expect(parseTrialConsent("")).toEqual({ consented: false }); }); it("ASK_USERNAME_TEXT asks only for the username (第3批 一步一动作)", () => { const zh = ASK_USERNAME_TEXT("zh"); expect(zh).toContain("请回复你的用户名"); expect(zh).toContain("abc123"); expect(zh).not.toContain("ok username:abc123"); expect(zh).not.toContain("同意 用户名:abc123"); const en = ASK_USERNAME_TEXT("en"); expect(en).toContain("Please reply with your username"); expect(en).toContain("abc123"); expect(en).not.toContain("ok username:abc123"); const ms = ASK_USERNAME_TEXT("ms"); expect(ms).toContain("nama pengguna"); expect(ms).toContain("abc123"); expect(ms).not.toContain("setuju username:abc123"); }); it("trialSuccessText (第5批): drive login URL + bookmark + products up-sell, in order, single-language", () => { const fresh = trialSuccessText( { ok: true, username: "alice", password: "pw12345678", expiresAt: "2026-09-14", productUrl: "https://www.digikedai.com/products/cxm04/", message: "Free account ready: alice", }, "zh", ); // 全新账号:标题 + 凭据 expect(fresh).toContain("免费账号已开通"); expect(fresh).toContain("👤 用户名: alice"); expect(fresh).toContain("🔑 密码: pw12345678"); // 第2批:product URL 是原付费品页(不是 trial 页) expect(fresh).toContain("https://www.digikedai.com/products/cxm04/"); expect(fresh).not.toContain("freecxm04"); // 第5批:登录网址独立成行(URL 单独一行,前后都是换行) const lines = fresh.split("\n"); expect(lines).toContain("https://drive.digikedai.com"); const driveIdx = lines.indexOf("https://drive.digikedai.com"); expect(driveIdx).toBeGreaterThan(0); // bookmark 提醒在 drive URL 之后 expect(lines[driveIdx + 1]).toContain("browser bookmark"); // products up-sell 在 drive/bookmark 之后 const productsIdx = lines.indexOf("https://www.digikedai.com/products"); expect(productsIdx).toBeGreaterThan(driveIdx); expect(lines[productsIdx + 1]).toContain("免费试看"); // 单语:无英文残留 expect(fresh).not.toContain("Free account ready"); }); it("trialSuccessText: reused / added-product outcomes drop password, keep drive info", () => { const reused = trialSuccessText( { ok: true, existed: true, username: "alice", expiresAt: "2026-09-14", productUrl: "https://www.digikedai.com/products/cxm04/", message: "Added FREECXM04 to your existing account.", }, "zh", ); expect(reused).toContain("已有账号"); expect(reused).toContain("密码与之前一致"); expect(reused).not.toContain("🔑 密码: "); expect(reused).toContain("https://drive.digikedai.com"); const added = trialSuccessText( { ok: true, existed: true, addedProduct: true, username: "alice", expiresAt: "2026-09-14", productUrl: "https://www.digikedai.com/products/cxm04/", message: "Added FREECXM04 to your existing account.", }, "zh", ); expect(added).toContain("新产品已加入你的账号"); expect(added).toContain("密码与之前一致"); expect(added).not.toContain("🔑 密码: "); }); it("trialSuccessText: en and ms variants are single-language with same URL layout", () => { const base = { ok: true, username: "alice", password: "pw12345678", expiresAt: "2026-09-14", productUrl: "https://www.digikedai.com/products/cxm04/", message: "Free account ready: alice", }; const en = trialSuccessText(base, "en"); expect(en).toContain("Free account ready"); expect(en).toContain("https://drive.digikedai.com"); expect(en).toContain("browser bookmark"); expect(en).toContain("https://www.digikedai.com/products"); expect(en).not.toContain("免费账号已开通"); const ms = trialSuccessText(base, "ms"); expect(ms).toContain("Akaun percuma sedia"); expect(ms).toContain("https://drive.digikedai.com"); expect(ms).toContain("penanda buku"); expect(ms).not.toContain("Free account ready"); }); it("trialErrorText appends the /trial form reminder in one language", () => { const zh = trialErrorText("Username taken", "zh"); expect(zh).toContain("⚠️ Username taken"); expect(zh).toContain("FREECXM04"); expect(zh).not.toContain("Percubaan Percuma"); const en = trialErrorText("Username taken", "en"); expect(en).toContain("Free Trial"); expect(en).not.toContain("免费试用"); }); it("user_memory key for trial username is stable", () => { expect(MEMORY_KEY_TRIAL_USERNAME).toBe("trial_username"); }); it("四·补·三第3批: confirmReuseText 单语确认句包含既有账号名与两条路径", () => { const zh = confirmReuseText("lover", "zh"); expect(zh).toContain("检测到你的账号"); expect(zh).toContain("lover"); expect(zh).toContain("可以"); expect(zh).toContain("新账号 用户名:abc123"); expect(zh).not.toContain("I found your existing account"); expect(zh).not.toContain("Akaun sedia ada"); const en = confirmReuseText("lover", "en"); expect(en).toContain("I found your existing account"); expect(en).toContain("lover"); expect(en).toContain("new account username:abc123"); expect(en).not.toContain("检测到你的账号"); const ms = confirmReuseText("lover", "ms"); expect(ms).toContain("Akaun sedia ada"); expect(ms).toContain("akaun baru username:abc123"); expect(ms).not.toContain("新账号 用户名"); }); it("四·补·三第3批: parseAccountDecision — 同意词 → reuse(加到既有账号)", () => { expect(parseAccountDecision("可以")).toEqual({ action: "reuse" }); expect(parseAccountDecision("同意")).toEqual({ action: "reuse" }); expect(parseAccountDecision("好的")).toEqual({ action: "reuse" }); expect(parseAccountDecision("好")).toEqual({ action: "reuse" }); expect(parseAccountDecision("ok")).toEqual({ action: "reuse" }); expect(parseAccountDecision("okay")).toEqual({ action: "reuse" }); expect(parseAccountDecision("yes")).toEqual({ action: "reuse" }); expect(parseAccountDecision("ya")).toEqual({ action: "reuse" }); expect(parseAccountDecision("setuju")).toEqual({ action: "reuse" }); expect(parseAccountDecision("saya setuju")).toEqual({ action: "reuse" }); expect(parseAccountDecision("agree")).toEqual({ action: "reuse" }); expect(parseAccountDecision("可以,继续")).toEqual({ action: "reuse" }); }); it("四·补·三第3批: parseAccountDecision — 新账号意图 → new(可带 username)", () => { expect(parseAccountDecision("新账号 abc123")).toEqual({ action: "new", username: "abc123" }); expect(parseAccountDecision("新账号 用户名:abc123")).toEqual({ action: "new", username: "abc123" }); expect(parseAccountDecision("新账号 username:abc123")).toEqual({ action: "new", username: "abc123" }); expect(parseAccountDecision("new account abc123")).toEqual({ action: "new", username: "abc123" }); expect(parseAccountDecision("new account username:abc123")).toEqual({ action: "new", username: "abc123" }); expect(parseAccountDecision("akaun baru abc123")).toEqual({ action: "new", username: "abc123" }); expect(parseAccountDecision("akaun baru username:abc123")).toEqual({ action: "new", username: "abc123" }); // 只说要新账号、没给 username → bot 再追问 expect(parseAccountDecision("新账号")).toEqual({ action: "new", username: undefined }); expect(parseAccountDecision("new account")).toEqual({ action: "new", username: undefined }); expect(parseAccountDecision("akaun baru")).toEqual({ action: "new", username: undefined }); }); it("四·补·三第3批: parseAccountDecision — 无关消息返回 null(掉落普通管线)", () => { expect(parseAccountDecision("我想问价格")).toBeNull(); expect(parseAccountDecision("谢谢")).toBeNull(); expect(parseAccountDecision("随便聊聊")).toBeNull(); expect(parseAccountDecision("")).toBeNull(); expect(parseAccountDecision(" ")).toBeNull(); }); it("四·补·五第3批: ASK_NEW_ACCOUNT_USERNAME_TEXT 只追问新用户名(一步一动作)", () => { const zh = ASK_NEW_ACCOUNT_USERNAME_TEXT("zh"); expect(zh).toContain("开新账号"); expect(zh).toContain("abc123"); expect(zh).not.toContain("新账号 用户名:abc123"); const en = ASK_NEW_ACCOUNT_USERNAME_TEXT("en"); expect(en).toContain("new account"); expect(en).toContain("abc123"); expect(en).not.toContain("new account username:abc123"); const ms = ASK_NEW_ACCOUNT_USERNAME_TEXT("ms"); expect(ms).toContain("akaun baharu"); expect(ms).toContain("abc123"); expect(ms).not.toContain("akaun baru username:abc123"); }); it("四·补·三第3批: extractUsername 提取 label 行或裸用户名", () => { expect(extractUsername("username:abc123")).toBe("abc123"); expect(extractUsername("用户名:abc123")).toBe("abc123"); expect(extractUsername("nama pengguna abc123")).toBe("abc123"); expect(extractUsername("abc123")).toBe("abc123"); expect(extractUsername("ABC123")).toBe("ABC123"); // 大小写原样,provision 层规范化 expect(extractUsername("随便聊聊 abc123")).toBe("abc123"); // label 行可带前缀语气词 expect(extractUsername("太好了")).toBeUndefined(); expect(extractUsername("")).toBeUndefined(); }); describe("第3批 — free 试看领取 inline 按钮(callback_data 契约)", () => { it("TRIAL_CB 契约稳定且不与购买 buy:* 冲突", () => { expect(TRIAL_CB.confirm).toBe("trial:confirm"); expect(TRIAL_CB.newUsername).toBe("trial:new-username"); expect(TRIAL_CB.reuse).toBe("trial:reuse"); for (const v of Object.values(TRIAL_CB)) { expect(v.startsWith("trial:")).toBe(true); expect(v.startsWith("buy:")).toBe(false); } }); it("startSkuKeyboard: 领取 + 换新用户名,三语跟随(第3批 一步一动作)", () => { const zh = startSkuKeyboard("zh"); const zhRows = zh.inline_keyboard as { text: string; callback_data: string }[][]; expect(zhRows.length).toBe(2); // 两行按钮 expect(zhRows[0][0].text).toBe("✅ 领取"); expect(zhRows[0][0].callback_data).toBe(TRIAL_CB.confirm); expect(zhRows[1][0].text).toBe("✏️ 换新用户名"); expect(zhRows[1][0].callback_data).toBe(TRIAL_CB.newUsername); const en = startSkuKeyboard("en"); const enRows = en.inline_keyboard as { text: string; callback_data: string }[][]; expect(enRows[0][0].text).toBe("✅ Claim"); expect(enRows[1][0].text).toBe("✏️ New username"); const ms = startSkuKeyboard("ms"); const msRows = ms.inline_keyboard as { text: string; callback_data: string }[][]; expect(msRows[0][0].text).toBe("✅ Tuntut"); expect(msRows[1][0].text).toBe("✏️ Username baharu"); }); it("confirmReuseKeyboard: 加到现有账号 + 开新账号,三语跟随", () => { const zh = confirmReuseKeyboard("zh"); const zhRows = zh.inline_keyboard as { text: string; callback_data: string }[][]; expect(zhRows.length).toBe(2); expect(zhRows[0][0].text).toBe("♻️ 加到现有账号"); expect(zhRows[0][0].callback_data).toBe(TRIAL_CB.reuse); expect(zhRows[1][0].text).toBe("✏️ 开新账号"); expect(zhRows[1][0].callback_data).toBe(TRIAL_CB.newUsername); const en = confirmReuseKeyboard("en"); const enRows = en.inline_keyboard as { text: string; callback_data: string }[][]; expect(enRows[0][0].text).toBe("♻️ Add to existing account"); expect(enRows[1][0].text).toBe("✏️ Open new account"); const ms = confirmReuseKeyboard("ms"); const msRows = ms.inline_keyboard as { text: string; callback_data: string }[][]; expect(msRows[0][0].text).toBe("♻️ Tambah ke akaun sedia ada"); expect(msRows[1][0].text).toBe("✏️ Buka akaun baharu"); }); it("按钮文本中性词纪律:不出现平台/支付名", () => { const noise = ["Shopee", "Lazada", "Add-On", "Touch 'n Go", "TnG", "bank-in", "银行转账"]; for (const lang of ["zh", "en", "ms"] as const) { for (const kb of [startSkuKeyboard(lang), confirmReuseKeyboard(lang)]) { const rows = kb.inline_keyboard as { text: string }[][]; for (const row of rows) { for (const btn of row) { for (const n of noise) expect(btn.text).not.toContain(n); } } } } }); }); describe("四·补·五第2批 — 多账号选择(choose-account)", () => { it("TRIAL_ACCT_PREFIX 与 trialAcctCb 构造稳定,且不进 TRIAL_CB", () => { expect(TRIAL_ACCT_PREFIX).toBe("trial:acct:"); expect(trialAcctCb(13)).toBe("trial:acct:13"); // 不进 TRIAL_CB:契约测试 Object.values 全为字符串 for (const v of Object.values(TRIAL_CB)) { expect(v).not.toBe("trial:acct:"); } }); it("chooseAccountText 三语单语,无其他语言残留", () => { const accounts = [ { id: 13, username: "lover" }, { id: 14, username: "abc" }, ]; const zh = chooseAccountText(accounts, "zh"); expect(zh).toContain("多个账号"); expect(zh).not.toContain("multiple accounts"); expect(zh).not.toContain("beberapa akaun"); const en = chooseAccountText(accounts, "en"); expect(en).toContain("multiple accounts"); expect(en).not.toContain("多个账号"); const ms = chooseAccountText(accounts, "ms"); expect(ms).toContain("beberapa akaun"); expect(ms).not.toContain("multiple accounts"); }); it("chooseAccountTooManyText 三语含完整编号清单", () => { const accounts = [ { id: 13, username: "lover" }, { id: 14, username: "abc" }, ]; const zh = chooseAccountTooManyText(accounts, "zh"); expect(zh).toContain("2 个账号"); expect(zh).toContain("1. lover"); expect(zh).toContain("2. abc"); const en = chooseAccountTooManyText(accounts, "en"); expect(en).toContain("2 accounts"); expect(en).toContain("1. lover"); expect(en).toContain("2. abc"); const ms = chooseAccountTooManyText(accounts, "ms"); expect(ms).toContain("2 akaun"); expect(ms).toContain("1. lover"); expect(ms).toContain("2. abc"); }); it("chooseAccountKeyboard: 每账号一行按钮 + 开新账号;cap 8", () => { const accounts = [ { id: 13, username: "lover" }, { id: 14, username: "abc" }, ]; const kb = chooseAccountKeyboard(accounts, "zh"); const rows = kb.inline_keyboard as { text: string; callback_data: string }[][]; expect(rows.length).toBe(3); // 2 账号 + 1 开新账号 expect(rows[0][0].text).toBe("👤 lover"); expect(rows[0][0].callback_data).toBe("trial:acct:13"); expect(rows[1][0].text).toBe("👤 abc"); expect(rows[1][0].callback_data).toBe("trial:acct:14"); expect(rows[2][0].text).toBe("✏️ 开新账号"); expect(rows[2][0].callback_data).toBe(TRIAL_CB.newUsername); // cap 8:传 10 个账号只出 8 行 + 1 行开新账号 const ten = Array.from({ length: 10 }, (_, i) => ({ id: i + 1, username: `user${i + 1}`, })); const kb2 = chooseAccountKeyboard(ten, "zh"); const rows2 = kb2.inline_keyboard as { text: string; callback_data: string }[][]; expect(rows2.length).toBe(9); // 8 账号 + 1 开新账号 expect(rows2[7][0].callback_data).toBe("trial:acct:8"); expect(rows2[8][0].text).toBe("✏️ 开新账号"); // 三语「开新账号」标签 const en = chooseAccountKeyboard(accounts, "en"); const enRows = en.inline_keyboard as { text: string }[][]; expect(enRows[2][0].text).toBe("✏️ Open new account"); const ms = chooseAccountKeyboard(accounts, "ms"); const msRows = ms.inline_keyboard as { text: string }[][]; expect(msRows[2][0].text).toBe("✏️ Buka akaun baharu"); }); }); });