Files
oh-my-pi/packages/coding-agent/test/status-line-git-utils.test.ts
T
Kevin LoftisandGitHub 5ee80f3a36 feat: Add PR number segment to status bar (#258)
* Add PR number segment to status bar

Add a new 'pr' status line segment that shows the GitHub PR number
(e.g., #1234) as a clickable OSC 8 hyperlink when the current branch
has an associated pull request.

- Async lookup via 'gh pr view', cached per branch
- Invalidates on branch change via .git/HEAD watcher
- Falls back to hidden segment when no PR exists or gh unavailable
- Supports all theme presets (unicode, nerd font, ascii)
- Added to all preset layouts after the git segment

* Skip PR lookup on default branch, resolve dynamically

* Extract git-utils, add tests for parseGitHubRepo and parseDefaultBranch

* Fix PR lookup race condition, invalidation churn, and dotted repo names

- Guard #cachedPr writes against branch change during in-flight lookup
- Stop clearing PR cache in invalidate() (only .git/HEAD watcher should)
- Allow dots in GitHub repo names in parseGitHubRepo regex

* Simplify PR lookup: use gh pr view, try upstream/HEAD for default branch

- Replace gh pr list --head with gh pr view (requires gh repo set-default)
- Remove manual remote URL resolution — gh handles it
- Default branch detection falls back to upstream/HEAD when origin/HEAD is unset
2026-03-03 03:24:15 +01:00

79 lines
2.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { parseDefaultBranch, parseGitHubRepo } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/git-utils";
describe("parseGitHubRepo", () => {
test("parses HTTPS URL", () => {
expect(parseGitHubRepo("https://github.com/can1357/oh-my-pi.git")).toBe("can1357/oh-my-pi");
});
test("parses HTTPS URL without .git suffix", () => {
expect(parseGitHubRepo("https://github.com/can1357/oh-my-pi")).toBe("can1357/oh-my-pi");
});
test("parses SSH scp-style URL", () => {
expect(parseGitHubRepo("git@github.com:loftiskg/oh-my-pi.git")).toBe("loftiskg/oh-my-pi");
});
test("parses SSH scp-style URL without .git suffix", () => {
expect(parseGitHubRepo("git@github.com:loftiskg/oh-my-pi")).toBe("loftiskg/oh-my-pi");
});
test("parses ssh:// protocol URL", () => {
expect(parseGitHubRepo("ssh://git@github.com/user/repo.git")).toBe("user/repo");
});
test("returns null for non-GitHub URL", () => {
expect(parseGitHubRepo("https://gitlab.com/user/repo.git")).toBeNull();
});
test("returns null for empty string", () => {
expect(parseGitHubRepo("")).toBeNull();
});
test("returns null for malformed URL", () => {
expect(parseGitHubRepo("not-a-url")).toBeNull();
});
test("handles GitHub Enterprise-style URLs (no match)", () => {
expect(parseGitHubRepo("https://github.corp.com/org/repo.git")).toBeNull();
});
test("parses HTTPS URL with dots in repo name", () => {
expect(parseGitHubRepo("https://github.com/org/my.repo.name.git")).toBe("org/my.repo.name");
});
test("parses SSH URL with dots in repo name", () => {
expect(parseGitHubRepo("git@github.com:org/dotted.repo.git")).toBe("org/dotted.repo");
});
test("parses URL with dots in repo name and no .git suffix", () => {
expect(parseGitHubRepo("https://github.com/org/my.repo")).toBe("org/my.repo");
});
});
describe("parseDefaultBranch", () => {
test("strips origin/ prefix from origin/main", () => {
expect(parseDefaultBranch("origin/main")).toBe("main");
});
test("strips origin/ prefix from origin/master", () => {
expect(parseDefaultBranch("origin/master")).toBe("master");
});
test("strips origin/ prefix from origin/develop", () => {
expect(parseDefaultBranch("origin/develop")).toBe("develop");
});
test("strips upstream/ prefix", () => {
expect(parseDefaultBranch("upstream/main")).toBe("main");
});
test("returns bare branch name unchanged", () => {
expect(parseDefaultBranch("main")).toBe("main");
});
test("handles empty string", () => {
expect(parseDefaultBranch("")).toBe("");
});
});