Skip to content

Commit ba6f480

Browse files
committed
fix(tests): replace bun:test anti-patterns with idiomatic parametrization and matchers
Across 41 test files, converts manual for-loops that defined test cases or asserted per-item into test.each()/describe.each(), replaces generic expect(x).toBe(true/false) boolean checks with specific matchers, hoists duplicated per-test setup/teardown into beforeEach/afterEach, and replaces ad-hoc `if (...) return;` skip guards with test.skipIf(). Adds one missing error-path test (signup.test.ts). Found via an audit against https://bun.com/docs/test/writing-tests; all 86 fixes independently re-verified with no coverage loss and a clean `bun run validate`. Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
1 parent a9dab40 commit ba6f480

41 files changed

Lines changed: 1381 additions & 1790 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tests/commands/adr/create.test.ts

Lines changed: 185 additions & 213 deletions
Large diffs are not rendered by default.

tests/commands/adr/list.test.ts

Lines changed: 113 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,22 @@ describe("registerAdrListCommand", () => {
7878

7979
describe("adr list action handler", () => {
8080
let tempDir: string;
81+
let adrsDir: string;
8182
let originalCwd: string;
8283
let logSpy: ReturnType<typeof spyOn>;
8384
let exitSpy: ReturnType<typeof spyOn>;
8485

8586
beforeEach(() => {
8687
tempDir = mkdtempSync(join(tmpdir(), "archgate-list-test-"));
88+
adrsDir = join(tempDir, ".archgate", "adrs");
8789
originalCwd = process.cwd();
8890
// Prevent findProjectRoot() from walking above the temp dir
8991
Bun.env.ARCHGATE_PROJECT_CEILING = tempDir;
9092
logSpy = spyOn(console, "log").mockImplementation(() => {});
9193
exitSpy = spyOn(process, "exit").mockImplementation(() => {
9294
throw new Error("process.exit");
9395
});
96+
process.chdir(tempDir);
9497
});
9598

9699
afterEach(() => {
@@ -107,141 +110,128 @@ describe("adr list action handler", () => {
107110
return parent;
108111
}
109112

110-
test("lists ADRs from .archgate/adrs/ directory in table format", async () => {
111-
const adrsDir = join(tempDir, ".archgate", "adrs");
112-
mkdirSync(adrsDir, { recursive: true });
113-
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
114-
writeFileSync(
115-
join(adrsDir, "GEN-001-use-conventional-commits.md"),
116-
ADR_CONTENT_2
117-
);
118-
119-
process.chdir(tempDir);
120-
const parent = makeProgram();
121-
await parent.parseAsync(["node", "adr", "list"]);
122-
123-
const allOutput = logSpy.mock.calls
124-
.map((c: unknown[]) => String(c[0]))
125-
.join("\n");
126-
expect(allOutput).toContain("ARCH-001");
127-
expect(allOutput).toContain("GEN-001");
128-
});
129-
130-
test("outputs JSON when --json flag is passed", async () => {
131-
const adrsDir = join(tempDir, ".archgate", "adrs");
132-
mkdirSync(adrsDir, { recursive: true });
133-
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
134-
135-
process.chdir(tempDir);
136-
const parent = makeProgram();
137-
await parent.parseAsync(["node", "adr", "list", "--json"]);
138-
139-
const allOutput = logSpy.mock.calls
140-
.map((c: unknown[]) => String(c[0]))
141-
.join("\n");
142-
const parsed = JSON.parse(allOutput);
143-
expect(Array.isArray(parsed)).toBe(true);
144-
expect(parsed[0].id).toBe("ARCH-001");
145-
expect(parsed[0].domain).toBe("architecture");
146-
});
113+
describe("with .archgate/adrs scaffolded", () => {
114+
beforeEach(() => {
115+
mkdirSync(adrsDir, { recursive: true });
116+
});
147117

148-
test("JSON output carries identity fields only, omitting files globs", async () => {
149-
const adrsDir = join(tempDir, ".archgate", "adrs");
150-
mkdirSync(adrsDir, { recursive: true });
151-
writeFileSync(
152-
join(adrsDir, "BE-001-api-response-envelope.md"),
153-
ADR_CONTENT_WITH_FILES
154-
);
118+
test("lists ADRs from .archgate/adrs/ directory in table format", async () => {
119+
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
120+
writeFileSync(
121+
join(adrsDir, "GEN-001-use-conventional-commits.md"),
122+
ADR_CONTENT_2
123+
);
124+
125+
const parent = makeProgram();
126+
await parent.parseAsync(["node", "adr", "list"]);
127+
128+
const allOutput = logSpy.mock.calls
129+
.map((c: unknown[]) => String(c[0]))
130+
.join("\n");
131+
expect(allOutput).toContain("ARCH-001");
132+
expect(allOutput).toContain("GEN-001");
133+
});
155134

156-
process.chdir(tempDir);
157-
const parent = makeProgram();
158-
await parent.parseAsync(["node", "adr", "list", "--json"]);
159-
160-
const allOutput = logSpy.mock.calls
161-
.map((c: unknown[]) => String(c[0]))
162-
.join("\n");
163-
const parsed = JSON.parse(allOutput);
164-
165-
// `files`/`respectGitignore` stay out of the listing so large ADR sets
166-
// remain small enough for agents to read inline — `adr show` has the rest.
167-
expect(Object.keys(parsed[0]).toSorted()).toEqual([
168-
"domain",
169-
"id",
170-
"rules",
171-
"title",
172-
]);
173-
expect(allOutput).not.toContain("src/api/**");
174-
});
135+
test("outputs JSON when --json flag is passed", async () => {
136+
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
175137

176-
test("filters by domain with --domain flag", async () => {
177-
const adrsDir = join(tempDir, ".archgate", "adrs");
178-
mkdirSync(adrsDir, { recursive: true });
179-
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
180-
writeFileSync(
181-
join(adrsDir, "GEN-001-use-conventional-commits.md"),
182-
ADR_CONTENT_2
183-
);
138+
const parent = makeProgram();
139+
await parent.parseAsync(["node", "adr", "list", "--json"]);
184140

185-
process.chdir(tempDir);
186-
const parent = makeProgram();
187-
await parent.parseAsync([
188-
"node",
189-
"adr",
190-
"list",
191-
"--domain",
192-
"architecture",
193-
]);
194-
195-
const allOutput = logSpy.mock.calls
196-
.map((c: unknown[]) => String(c[0]))
197-
.join("\n");
198-
expect(allOutput).toContain("ARCH-001");
199-
expect(allOutput).not.toContain("GEN-001");
200-
});
141+
const allOutput = logSpy.mock.calls
142+
.map((c: unknown[]) => String(c[0]))
143+
.join("\n");
144+
const parsed = JSON.parse(allOutput);
145+
expect(parsed).toBeInstanceOf(Array);
146+
expect(parsed[0].id).toBe("ARCH-001");
147+
expect(parsed[0].domain).toBe("architecture");
148+
});
201149

202-
test("combines --domain and --json filters", async () => {
203-
const adrsDir = join(tempDir, ".archgate", "adrs");
204-
mkdirSync(adrsDir, { recursive: true });
205-
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
206-
writeFileSync(
207-
join(adrsDir, "GEN-001-use-conventional-commits.md"),
208-
ADR_CONTENT_2
209-
);
150+
test("JSON output carries identity fields only, omitting files globs", async () => {
151+
writeFileSync(
152+
join(adrsDir, "BE-001-api-response-envelope.md"),
153+
ADR_CONTENT_WITH_FILES
154+
);
155+
156+
const parent = makeProgram();
157+
await parent.parseAsync(["node", "adr", "list", "--json"]);
158+
159+
const allOutput = logSpy.mock.calls
160+
.map((c: unknown[]) => String(c[0]))
161+
.join("\n");
162+
const parsed = JSON.parse(allOutput);
163+
164+
// `files`/`respectGitignore` stay out of the listing so large ADR sets
165+
// remain small enough for agents to read inline — `adr show` has the rest.
166+
expect(Object.keys(parsed[0]).toSorted()).toEqual([
167+
"domain",
168+
"id",
169+
"rules",
170+
"title",
171+
]);
172+
expect(allOutput).not.toContain("src/api/**");
173+
});
210174

211-
process.chdir(tempDir);
212-
const parent = makeProgram();
213-
await parent.parseAsync([
214-
"node",
215-
"adr",
216-
"list",
217-
"--domain",
218-
"general",
219-
"--json",
220-
]);
221-
222-
const allOutput = logSpy.mock.calls
223-
.map((c: unknown[]) => String(c[0]))
224-
.join("\n");
225-
const parsed = JSON.parse(allOutput);
226-
expect(parsed).toHaveLength(1);
227-
expect(parsed[0].id).toBe("GEN-001");
228-
});
175+
test("filters by domain with --domain flag", async () => {
176+
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
177+
writeFileSync(
178+
join(adrsDir, "GEN-001-use-conventional-commits.md"),
179+
ADR_CONTENT_2
180+
);
181+
182+
const parent = makeProgram();
183+
await parent.parseAsync([
184+
"node",
185+
"adr",
186+
"list",
187+
"--domain",
188+
"architecture",
189+
]);
190+
191+
const allOutput = logSpy.mock.calls
192+
.map((c: unknown[]) => String(c[0]))
193+
.join("\n");
194+
expect(allOutput).toContain("ARCH-001");
195+
expect(allOutput).not.toContain("GEN-001");
196+
});
229197

230-
test("prints 'No ADRs found.' when adrs directory is empty", async () => {
231-
mkdirSync(join(tempDir, ".archgate", "adrs"), { recursive: true });
198+
test("combines --domain and --json filters", async () => {
199+
writeFileSync(join(adrsDir, "ARCH-001-use-typescript.md"), ADR_CONTENT_1);
200+
writeFileSync(
201+
join(adrsDir, "GEN-001-use-conventional-commits.md"),
202+
ADR_CONTENT_2
203+
);
204+
205+
const parent = makeProgram();
206+
await parent.parseAsync([
207+
"node",
208+
"adr",
209+
"list",
210+
"--domain",
211+
"general",
212+
"--json",
213+
]);
214+
215+
const allOutput = logSpy.mock.calls
216+
.map((c: unknown[]) => String(c[0]))
217+
.join("\n");
218+
const parsed = JSON.parse(allOutput);
219+
expect(parsed).toHaveLength(1);
220+
expect(parsed[0].id).toBe("GEN-001");
221+
});
232222

233-
process.chdir(tempDir);
234-
const parent = makeProgram();
235-
await parent.parseAsync(["node", "adr", "list"]);
223+
test("prints 'No ADRs found.' when adrs directory is empty", async () => {
224+
const parent = makeProgram();
225+
await parent.parseAsync(["node", "adr", "list"]);
236226

237-
const allOutput = logSpy.mock.calls
238-
.map((c: unknown[]) => String(c[0]))
239-
.join("\n");
240-
expect(allOutput).toContain("No ADRs found.");
227+
const allOutput = logSpy.mock.calls
228+
.map((c: unknown[]) => String(c[0]))
229+
.join("\n");
230+
expect(allOutput).toContain("No ADRs found.");
231+
});
241232
});
242233

243234
test("exits with error when .archgate/ directory is missing", async () => {
244-
process.chdir(tempDir);
245235
const parent = makeProgram();
246236

247237
await expect(parent.parseAsync(["node", "adr", "list"])).rejects.toThrow(

tests/commands/review-context.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Test decision.
158158
.join("");
159159
const parsed = JSON.parse(output);
160160
// With no git changes, domains should still be populated but with no changed files
161-
expect(Array.isArray(parsed.domains)).toBe(true);
161+
expect(parsed.domains).toBeInstanceOf(Array);
162162
expect(parsed.allChangedFiles).toEqual([]);
163163
});
164164

tests/commands/session-context.test.ts

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe("registerSessionContextCommand", () => {
103103
expect(opts).not.toContain("--skip");
104104
});
105105

106-
test("each editor subcommand has list and show children", () => {
106+
test("session-context has exactly the four editor subcommands", () => {
107107
const program = new Command();
108108
registerSessionContextCommand(program);
109109
const parent = program.commands.find(
@@ -116,30 +116,38 @@ describe("registerSessionContextCommand", () => {
116116
"cursor",
117117
"opencode",
118118
]);
119-
for (const editor of ["claude-code", "copilot", "cursor", "opencode"]) {
119+
});
120+
121+
test.each(["claude-code", "copilot", "cursor", "opencode"])(
122+
"%s subcommand has list and show children",
123+
(editor) => {
124+
const program = new Command();
125+
registerSessionContextCommand(program);
126+
const parent = program.commands.find(
127+
(c) => c.name() === "session-context"
128+
)!;
120129
const sub = parent.commands.find((c) => c.name() === editor)!;
121130
const children = sub.commands.map((c) => c.name()).sort();
122131
expect(children).toEqual(["list", "show"]);
123132
}
124-
});
133+
);
125134

126-
test("only opencode show has --root", () => {
135+
test.each([
136+
["claude-code", false],
137+
["copilot", false],
138+
["cursor", false],
139+
["opencode", true],
140+
] as const)("%s show has --root: %p", (editor, hasRoot) => {
127141
const program = new Command();
128142
registerSessionContextCommand(program);
129143
const parent = program.commands.find(
130144
(c) => c.name() === "session-context"
131145
)!;
132-
for (const editor of ["claude-code", "copilot", "cursor", "opencode"]) {
133-
const sub = parent.commands.find((c) => c.name() === editor)!;
134-
const show = sub.commands.find((c) => c.name() === "show")!;
135-
const opts = show.options.map((o) => o.long);
136-
expect(opts).toContain("--max-entries");
137-
if (editor === "opencode") {
138-
expect(opts).toContain("--root");
139-
} else {
140-
expect(opts).not.toContain("--root");
141-
}
142-
}
146+
const sub = parent.commands.find((c) => c.name() === editor)!;
147+
const show = sub.commands.find((c) => c.name() === "show")!;
148+
const opts = show.options.map((o) => o.long);
149+
expect(opts).toContain("--max-entries");
150+
expect(opts.includes("--root")).toBe(hasRoot);
143151
});
144152

145153
test("opencode subcommand has only --max-entries (read current conversation)", () => {

0 commit comments

Comments
 (0)