| 1 | // Run: tsx src/__tests__/tool-approval-mode.test.ts |
| 2 | |
| 3 | import { restorableToolApprovalMode, toggleYoloToolApprovalMode } from "../lib/toolApprovalMode"; |
| 4 | import { en } from "../locales/en"; |
| 5 | import { zh } from "../locales/zh"; |
| 6 | import { zhTW } from "../locales/zh-TW"; |
| 7 | |
| 8 | let passed = 0; |
| 9 | let failed = 0; |
| 10 | |
| 11 | function eq(a: unknown, b: unknown, label: string) { |
| 12 | if (a === b) { |
| 13 | process.stdout.write(` PASS ${label}\n`); |
| 14 | passed += 1; |
| 15 | } else { |
| 16 | process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`); |
| 17 | failed += 1; |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | console.log("\ntool approval mode"); |
| 22 | |
| 23 | eq(restorableToolApprovalMode("ask"), "ask", "ask restores to ask"); |
| 24 | eq(restorableToolApprovalMode("auto"), "auto", "auto restores to auto"); |
| 25 | eq(restorableToolApprovalMode("yolo"), "ask", "yolo cannot be a restore base"); |
| 26 | |
| 27 | let next = toggleYoloToolApprovalMode("ask"); |
| 28 | eq(next.mode, "yolo", "Ctrl+Y turns ask into yolo"); |
| 29 | eq(next.restore, "ask", "Ctrl+Y remembers ask"); |
| 30 | |
| 31 | next = toggleYoloToolApprovalMode("auto"); |
| 32 | eq(next.mode, "yolo", "Ctrl+Y turns auto into yolo"); |
| 33 | eq(next.restore, "auto", "Ctrl+Y remembers auto"); |
| 34 | |
| 35 | next = toggleYoloToolApprovalMode("yolo", "auto"); |
| 36 | eq(next.mode, "auto", "Ctrl+Y restores auto from yolo"); |
| 37 | |
| 38 | next = toggleYoloToolApprovalMode("yolo"); |
| 39 | eq(next.mode, "ask", "Ctrl+Y falls back to ask when no restore base exists"); |
| 40 | |
| 41 | eq(en["composer.accessAskTitle"].includes("not read-only"), true, "English Ask copy is not presented as read-only"); |
| 42 | eq(zh["composer.accessAskTitle"].includes("不是只读"), true, "Simplified Chinese Ask copy is not presented as read-only"); |
| 43 | eq(zhTW["composer.accessAskTitle"].includes("不是唯讀"), true, "Traditional Chinese Ask copy is not presented as read-only"); |
| 44 | eq(en["heartbeat.approvalModeAskHint"].includes("not read-only"), true, "heartbeat Ask hint preserves the same boundary"); |
| 45 | eq(en["composer.taskModePlanDesc"].includes("permissions and sandbox"), true, "Plan copy names permissions and sandbox"); |
| 46 | eq(en["composer.accessFullDesc"].includes("ordinary") && en["composer.accessFullDesc"].includes("fresh reviews"), true, "English Full access preserves fresh-review boundary"); |
| 47 | eq(zh["composer.accessFullDesc"].includes("普通") && zh["composer.accessFullDesc"].includes("强制新鲜审查"), true, "Simplified Chinese Full access preserves fresh-review boundary"); |
| 48 | eq(zhTW["composer.accessFullDesc"].includes("普通") && zhTW["composer.accessFullDesc"].includes("強制新鮮審查"), true, "Traditional Chinese Full access preserves fresh-review boundary"); |
| 49 | eq(en["composer.accessFullDesc"].includes("all tool permission approvals"), false, "Full access does not promise to bypass every approval"); |
| 50 | eq(en["status.yoloTitle"].includes("ordinary") && en["status.yoloTitle"].includes("fresh reviews"), true, "English YOLO status preserves fresh-review boundary"); |
| 51 | eq(zh["status.yoloTitle"].includes("普通") && zh["status.yoloTitle"].includes("强制新鲜审批"), true, "Simplified Chinese YOLO status preserves fresh-review boundary"); |
| 52 | eq(zhTW["status.yoloTitle"].includes("普通") && zhTW["status.yoloTitle"].includes("強制新鮮審批"), true, "Traditional Chinese YOLO status preserves fresh-review boundary"); |
| 53 | eq(en["settings.yolo"].includes("ordinary"), true, "English YOLO setting names ordinary prompts only"); |
| 54 | eq(zh["settings.yolo"].includes("普通"), true, "Simplified Chinese YOLO setting names ordinary prompts only"); |
| 55 | eq(zhTW["settings.yolo"].includes("普通"), true, "Traditional Chinese YOLO setting names ordinary prompts only"); |
| 56 | |
| 57 | console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`); |
| 58 | if (failed > 0) process.exit(1); |
| 59 |