| 1 | // Tests for Eleven's deterministic command floor. node test/commands.test.mjs |
| 2 | import assert from "node:assert/strict"; |
| 3 | import { parseCommand, runCommand, commandNames, commandSummary } from "../agent/commands.mjs"; |
| 4 | import { sanitizeActions } from "../web/static/ui.js"; |
| 5 | |
| 6 | let failures = 0; |
| 7 | async function test(name, fn) { |
| 8 | try { |
| 9 | await fn(); |
| 10 | console.log(`PASS ${name}`); |
| 11 | } catch (e) { |
| 12 | failures++; |
| 13 | console.log(`FAIL ${name} — ${e && e.message}`); |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | await test("parseCommand recognises registered commands", () => { |
| 18 | assert.deepEqual(parseCommand("/theme lagoon"), { name: "/theme", args: "lagoon" }); |
| 19 | assert.deepEqual(parseCommand("/help"), { name: "/help", args: "" }); |
| 20 | assert.deepEqual(parseCommand(" /Dark "), { name: "/dark", args: "" }); // trims + lowercases the verb |
| 21 | }); |
| 22 | |
| 23 | await test("parseCommand ignores non-commands", () => { |
| 24 | assert.equal(parseCommand("hello there"), null); |
| 25 | assert.equal(parseCommand("/unknown thing"), null); // unowned verb → falls through to chat |
| 26 | assert.equal(parseCommand(""), null); |
| 27 | assert.equal(parseCommand("not /theme"), null); // a slash mid-sentence isn't a command |
| 28 | }); |
| 29 | |
| 30 | await test("theme command emits a valid set_theme chip", () => { |
| 31 | const res = runCommand("/theme plum"); |
| 32 | assert.equal(res.actions[0].type, "set_theme"); |
| 33 | assert.equal(res.actions[0].theme, "plum"); |
| 34 | }); |
| 35 | |
| 36 | await test("unknown theme offers the picker instead of a bad chip", () => { |
| 37 | const res = runCommand("/theme nope"); |
| 38 | assert.equal(res.actions[0].type, "open_theme_picker"); |
| 39 | assert.match(res.text, /lagoon/); // lists the real options |
| 40 | }); |
| 41 | |
| 42 | await test("appearance + profile commands emit their chips", () => { |
| 43 | assert.equal(runCommand("/dark").actions[0].appearance, "dark"); |
| 44 | assert.equal(runCommand("/light").actions[0].appearance, "light"); |
| 45 | assert.equal(runCommand("/profile").actions[0].type, "open_profile"); |
| 46 | }); |
| 47 | |
| 48 | await test("every command's chips survive the client whitelist", () => { |
| 49 | // The whole point: a deterministic chip is indistinguishable from a model one, |
| 50 | // so sanitizeActions (the client's only render gate) must accept them all. |
| 51 | for (const name of commandNames()) { |
| 52 | const res = runCommand(name === "/theme" ? "/theme lagoon" : name); |
| 53 | const actions = res.actions || []; |
| 54 | assert.equal(sanitizeActions(actions).length, actions.length, `${name} produced an unrenderable chip`); |
| 55 | } |
| 56 | }); |
| 57 | |
| 58 | await test("help wording reflects whether AI is available", () => { |
| 59 | assert.match(runCommand("/help", { aiEnabled: true }).text, /ask me anything/i); |
| 60 | assert.match(runCommand("/help", { aiEnabled: false }).text, /aren't enabled/i); |
| 61 | }); |
| 62 | |
| 63 | await test("runCommand returns null for non-commands", () => { |
| 64 | assert.equal(runCommand("just chatting"), null); |
| 65 | }); |
| 66 | |
| 67 | await test("commandSummary lists every command (fed to Eleven's instructions)", () => { |
| 68 | const s = commandSummary(); |
| 69 | for (const name of commandNames()) assert.match(s, new RegExp(name.replace("/", "\\/"))); |
| 70 | }); |
| 71 | |
| 72 | console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`); |
| 73 | process.exit(failures === 0 ? 0 : 1); |
| 74 | |