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");
});
});
});