Add a keyboard-driven project setup flow (#2097)
* feat(projects): add keyboard-driven project setup * test(app): update add project loading assertion * fix(projects): harden GitHub project setup
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createGitHubService,
|
||||
type GitHubCommandRunner,
|
||||
type GitHubCommandRunnerOptions,
|
||||
} from "./github-service.js";
|
||||
|
||||
interface RunnerCall {
|
||||
args: string[];
|
||||
options: GitHubCommandRunnerOptions;
|
||||
}
|
||||
|
||||
function createRunner(outputs: string[]): { calls: RunnerCall[]; runner: GitHubCommandRunner } {
|
||||
const calls: RunnerCall[] = [];
|
||||
return {
|
||||
calls,
|
||||
runner: async (args, options) => {
|
||||
calls.push({ args, options });
|
||||
return { stdout: outputs.shift() ?? "", stderr: "" };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("GitHub repository search", () => {
|
||||
it("lists recent owned repositories for an empty query and normalizes clone identity", async () => {
|
||||
const runner = createRunner([
|
||||
JSON.stringify([
|
||||
{
|
||||
id: " R_recent ",
|
||||
name: " paseo ",
|
||||
nameWithOwner: " getpaseo/paseo ",
|
||||
description: null,
|
||||
visibility: "PUBLIC",
|
||||
updatedAt: "2026-07-15T12:00:00Z",
|
||||
sshUrl: " git@github.com:getpaseo/paseo.git ",
|
||||
url: "https://github.com/getpaseo/paseo",
|
||||
},
|
||||
]),
|
||||
"ssh\n",
|
||||
]);
|
||||
const service = createGitHubService({
|
||||
runner: runner.runner,
|
||||
resolveGhPath: async () => "/usr/bin/gh",
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.searchRepositories({ cwd: "/tmp", query: " ", limit: 8 }),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
id: "R_recent",
|
||||
name: "paseo",
|
||||
nameWithOwner: "getpaseo/paseo",
|
||||
description: null,
|
||||
visibility: "public",
|
||||
updatedAt: "2026-07-15T12:00:00Z",
|
||||
cloneUrl: "git@github.com:getpaseo/paseo.git",
|
||||
},
|
||||
]);
|
||||
expect(runner.calls).toEqual([
|
||||
{
|
||||
args: [
|
||||
"repo",
|
||||
"list",
|
||||
"--json",
|
||||
"id,name,nameWithOwner,description,visibility,updatedAt,sshUrl,url",
|
||||
"--limit",
|
||||
"8",
|
||||
],
|
||||
options: { cwd: "/tmp" },
|
||||
},
|
||||
{
|
||||
args: ["config", "get", "git_protocol", "--host", "github.com"],
|
||||
options: { cwd: "/tmp" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("searches accessible repositories for a typed query", async () => {
|
||||
const runner = createRunner([
|
||||
JSON.stringify([
|
||||
{
|
||||
id: 42,
|
||||
name: "private-repo",
|
||||
fullName: "octo/private-repo",
|
||||
description: "Private project",
|
||||
visibility: "PRIVATE",
|
||||
updatedAt: "2026-07-14T08:00:00Z",
|
||||
url: "https://github.com/octo/private-repo",
|
||||
},
|
||||
]),
|
||||
"https",
|
||||
]);
|
||||
const service = createGitHubService({
|
||||
runner: runner.runner,
|
||||
resolveGhPath: async () => "/usr/bin/gh",
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.searchRepositories({ cwd: "/tmp", query: " private project ", limit: 5 }),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
id: "42",
|
||||
name: "private-repo",
|
||||
nameWithOwner: "octo/private-repo",
|
||||
description: "Private project",
|
||||
visibility: "private",
|
||||
updatedAt: "2026-07-14T08:00:00Z",
|
||||
cloneUrl: "https://github.com/octo/private-repo",
|
||||
},
|
||||
]);
|
||||
expect(runner.calls).toEqual([
|
||||
{
|
||||
args: [
|
||||
"search",
|
||||
"repos",
|
||||
"private project",
|
||||
"--json",
|
||||
"id,name,fullName,description,visibility,updatedAt,url",
|
||||
"--sort",
|
||||
"updated",
|
||||
"--order",
|
||||
"desc",
|
||||
"--limit",
|
||||
"5",
|
||||
],
|
||||
options: { cwd: "/tmp" },
|
||||
},
|
||||
{
|
||||
args: ["config", "get", "git_protocol", "--host", "github.com"],
|
||||
options: { cwd: "/tmp" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user