Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/utils/display.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { formatDuration, padRight } from "./display.js";

describe("padRight", () => {
it("pads plain text to target length", () => {
assert.equal(padRight("hello", 10), "hello ");
});

it("does not pad if already at target length", () => {
assert.equal(padRight("hello", 5), "hello");
});

it("does not truncate if longer than target", () => {
assert.equal(padRight("hello world", 5), "hello world");
});

it("handles ANSI codes in length calculation", () => {
const colored = "\x1b[32mok\x1b[0m"; // "ok" in green
const padded = padRight(colored, 6);
// Visual length is 2 ("ok"), so should add 4 spaces
assert.ok(padded.endsWith(" "));
});

it("handles empty string", () => {
assert.equal(padRight("", 5), " ");
});
});

describe("formatDuration", () => {
it("formats seconds", () => {
assert.equal(formatDuration(5000), "5s");
assert.equal(formatDuration(45000), "45s");
});

it("formats minutes and seconds", () => {
assert.equal(formatDuration(65000), "1m5s");
assert.equal(formatDuration(125000), "2m5s");
});

it("handles zero", () => {
assert.equal(formatDuration(0), "0s");
});

it("rounds to nearest second", () => {
assert.equal(formatDuration(1499), "1s");
assert.equal(formatDuration(1500), "2s");
});
});
4 changes: 2 additions & 2 deletions src/utils/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ export function displayApplyInstructions(result: EnsembleResult): void {
console.log();
}

function padRight(str: string, len: number): string {
export function padRight(str: string, len: number): string {
// Strip ANSI codes for length calculation
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI escape sequence matching
const stripped = str.replace(/\x1b\[[0-9;]*m/g, "");
const padding = Math.max(0, len - stripped.length);
return str + " ".repeat(padding);
}

function formatDuration(ms: number): string {
export function formatDuration(ms: number): string {
const seconds = Math.round(ms / 1000);
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
Expand Down
57 changes: 57 additions & 0 deletions src/utils/git.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import assert from "node:assert/strict";
import { after, before, describe, it } from "node:test";
import {
cleanupBranches,
createWorktree,
getDiff,
getDiffStats,
getRepoRoot,
removeWorktree,
} from "./git.js";

describe("getRepoRoot", () => {
it("returns the repository root", async () => {
const root = await getRepoRoot();
assert.ok(root.length > 0);
assert.ok(root.includes("thinktank"));
});
});

describe("worktree lifecycle", () => {
let worktreePath: string;

it("creates a worktree", async () => {
worktreePath = await createWorktree(99);
assert.ok(worktreePath.length > 0);
assert.ok(worktreePath.includes("thinktank-agent-99"));
});

it("gets empty diff for unchanged worktree", async () => {
const diff = await getDiff(worktreePath);
assert.equal(diff, "");
});

it("gets empty diff stats for unchanged worktree", async () => {
const stats = await getDiffStats(worktreePath);
assert.deepEqual(stats.filesChanged, []);
assert.equal(stats.linesAdded, 0);
assert.equal(stats.linesRemoved, 0);
});

it("removes the worktree", async () => {
await removeWorktree(worktreePath);
// Should not throw
});

after(async () => {
// Clean up any leftover branches
await cleanupBranches().catch(() => {});
});
});

describe("cleanupBranches", () => {
it("runs without error even when no thinktank branches exist", async () => {
await cleanupBranches();
// Should not throw
});
});
Loading