import { expect, test, describe } from "bun:test"; import { Command } from "../src/index"; describe("command parser", () => { test("sets name and description", () => { const cmd = new Command("test") .setName("test") .setDescription("Test command"); expect(cmd.name).toBe("test"); expect(cmd.description).toBe("Test command"); }); test("parses boolean flags", async () => { let context: any; const cmd = new Command("test") .addFlag("verbose", { short: "v", default: false }) .addFlag("force", { short: "f", default: true }) .setAction((ctx) => { context = ctx; }); await cmd.parse([]); expect(context.verbose).toBe(false); expect(context.force).toBe(true); await cmd.parse(["--verbose", "-f"]); expect(context.verbose).toBe(true); expect(context.force).toBe(true); }); test("groups short flags", async () => { let context: any; const cmd = new Command("test") .addFlag("verbose", { short: "v" }) .addFlag("force", { short: "f" }) .addStringArgument("config", { short: "c" }) .setAction((ctx) => { context = ctx; }); await cmd.parse(["-vfc", "config.json"]); expect(context.verbose).toBe(true); expect(context.force).toBe(true); expect(context.config).toBe("config.json"); }); test("parses string arguments and defaults", async () => { let context: any; const cmd = new Command("test") .addStringArgument("config", { short: "c", default: "default.json" }) .setAction((ctx) => { context = ctx; }); await cmd.parse([]); expect(context.config).toBe("default.json"); await cmd.parse(["--config", "config.json"]); expect(context.config).toBe("config.json"); await cmd.parse(["-c", "other.json"]); expect(context.config).toBe("other.json"); await cmd.parse(["--config=inline.json"]); expect(context.config).toBe("inline.json"); }); test("throws on missing required arguments", async () => { const cmd = new Command("test").addStringArgument("env", { required: true, }); expect(cmd.parse([])).rejects.toThrow("Missing required argument: --env"); }); test("parses number arguments and defaults", async () => { let context: any; const cmd = new Command("test") .addNumberArgument("retries", { default: 5 }) .setAction((ctx) => { context = ctx; }); await cmd.parse([]); expect(context.retries).toBe(5); await cmd.parse(["--retries", "3"]); expect(context.retries).toBe(3); expect(cmd.parse(["--retries", "abc"])).rejects.toThrow( "Option --retries must be a valid number", ); }); test("parses choice arguments and defaults", async () => { let context: any; const cmd = new Command("test") .addChoiceArgument("format", { choices: ["json", "xml"], default: "xml" }) .setAction((ctx) => { context = ctx; }); await cmd.parse([]); expect(context.format).toBe("xml"); await cmd.parse(["--format", "json"]); expect(context.format).toBe("json"); expect(cmd.parse(["--format", "csv"])).rejects.toThrow( "Option --format must be one of: json, xml", ); }); test("parses positionals", async () => { let context: any; const cmd = new Command("test") .addPositional("source", { required: true }) .addPositional("dest", { required: false }) .setAction((ctx) => { context = ctx; }); await cmd.parse(["src-dir"]); expect(context.source).toBe("src-dir"); expect(context.dest).toBeUndefined(); await cmd.parse(["src-dir", "dest-dir"]); expect(context.source).toBe("src-dir"); expect(context.dest).toBe("dest-dir"); expect(cmd.parse([])).rejects.toThrow( "Missing required positional argument: ", ); expect(cmd.parse(["src-dir", "dest-dir", "extra-dir"])).rejects.toThrow( "Unknown positional argument: extra-dir", ); }); test("handles subcommands", async () => { let rootCalled = false; let subCalled = false; let subContext: any; const sub = new Command("push").addFlag("force").setAction((ctx) => { subCalled = true; subContext = ctx; }); const root = new Command("git").addSubcommand(sub).setAction(() => { rootCalled = true; }); await root.parse(["push", "--force"]); expect(rootCalled).toBe(false); expect(subCalled).toBe(true); expect(subContext.force).toBe(true); }); });