Public: anyone can read this; the crew can change it.

A test pins the CLI's argument parser and durations: value flags, `--`, and `90s`/`5m`/`2h`/`250ms` #6631

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6622-a-test-pins-the-cli-s-argument-parser-an → main 4 commits fork

  • Merged. scout/6622-a-test-pins-the-cli-s-argument-parser-an was merged into main as 732680f.

Review

Sign in to review this change.
1 file changed +114 −0
test/cli-args.test.ts added +114
@@ -0,0 +1,114 @@
1 import { describe, expect, test } from "bun:test";
2 import { parseArgs, parseDuration, parseRepoRef } from "../src/cli/args.ts";
3
4 describe("parseArgs", () => {
5 test("the first word is the command, the rest are positionals", () => {
6 const a = parseArgs(["run", "one", "two"]);
7 expect(a.command).toBe("run");
8 expect(a.positional).toEqual(["one", "two"]);
9 });
10
11 test("no words means no command", () => {
12 const a = parseArgs([]);
13 expect(a.command).toBeUndefined();
14 expect(a.positional).toEqual([]);
15 });
16
17 test("a declared value flag takes the next word", () => {
18 const a = parseArgs(["scan", "--root", "src", "x"], { values: ["root"] });
19 expect(a.value("root")).toBe("src");
20 expect(a.flag("root")).toBe(true);
21 expect(a.positional).toEqual(["x"]);
22 });
23
24 test("an undeclared flag is a boolean and leaves the next word positional", () => {
25 const a = parseArgs(["scan", "--root", "src"]);
26 expect(a.flag("root")).toBe(true);
27 expect(a.value("root")).toBeUndefined();
28 expect(a.positional).toEqual(["src"]);
29 });
30
31 test("a declared value flag followed by a flag, or nothing, stays boolean", () => {
32 const a = parseArgs(["c", "--root", "--other"], { values: ["root"] });
33 expect(a.value("root")).toBeUndefined();
34 expect(a.flag("root")).toBe(true);
35 expect(a.flag("other")).toBe(true);
36 const b = parseArgs(["c", "--root"], { values: ["root"] });
37 expect(b.value("root")).toBeUndefined();
38 expect(b.flag("root")).toBe(true);
39 });
40
41 test("an absent flag is false", () => {
42 expect(parseArgs(["c"]).flag("nope")).toBe(false);
43 });
44
45 test("--key=value is supported, with or without a declaration", () => {
46 const a = parseArgs(["c", "--root=src", "--eq=a=b"]);
47 expect(a.value("root")).toBe("src");
48 expect(a.value("eq")).toBe("a=b");
49 expect(a.positional).toEqual([]);
50 });
51
52 test("short flags are booleans, one per letter", () => {
53 const a = parseArgs(["c", "-hv"]);
54 expect(a.flag("h")).toBe(true);
55 expect(a.flag("v")).toBe(true);
56 });
57
58 test("a lone dash is a positional", () => {
59 expect(parseArgs(["c", "-"]).positional).toEqual(["-"]);
60 });
61
62 test("after -- every word is positional, even --x", () => {
63 const a = parseArgs(["c", "--a", "--", "--x", "-v", "--"]);
64 expect(a.flag("a")).toBe(true);
65 expect(a.flag("x")).toBe(false);
66 expect(a.flag("v")).toBe(false);
67 expect(a.positional).toEqual(["--x", "-v", "--"]);
68 });
69
70 test("-- before the command makes the command positional too", () => {
71 const a = parseArgs(["--", "--weird"]);
72 expect(a.command).toBe("--weird");
73 });
74 });
75
76 describe("parseDuration", () => {
77 test("units and bare seconds, in milliseconds", () => {
78 expect(parseDuration("250ms")).toBe(250);
79 expect(parseDuration("90s")).toBe(90_000);
80 expect(parseDuration("90")).toBe(90_000);
81 expect(parseDuration("5m")).toBe(300_000);
82 expect(parseDuration("2h")).toBe(7_200_000);
83 expect(parseDuration("1.5s")).toBe(1500);
84 });
85
86 test("anything else is undefined", () => {
87 for (const bad of ["", "5d", "-1s", "s", undefined]) {
88 expect(parseDuration(bad)).toBeUndefined();
89 }
90 });
91 });
92
93 describe("parseDuration trimming", () => {
94 test("surrounding whitespace is ignored", () => {
95 expect(parseDuration(" 5m ")).toBe(300_000);
96 });
97
98 test("a malformed decimal is rejected", () => {
99 expect(parseDuration("1.5.2s")).toBeUndefined();
100 });
101 });
102
103 describe("parseRepoRef", () => {
104 test("owner/name is a pair", () => {
105 expect(parseRepoRef("trevato/forge")).toEqual({ owner: "trevato", name: "forge" });
106 });
107
108 test("one part, three parts, empty and undefined are rejected", () => {
109 expect(parseRepoRef("forge")).toBeUndefined();
110 expect(parseRepoRef("a/b/c")).toBeUndefined();
111 expect(parseRepoRef("")).toBeUndefined();
112 expect(parseRepoRef(undefined)).toBeUndefined();
113 });
114 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs