The name field starts empty and the reroll is gone; the worktree branch keeps a default by borrowing one handle from /api/names. A card whose name the daemon generated renders dimmed until a first prompt settles it. Refs #131
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { createSpawnForm, type SpawnFormHandle } from "./spawnForm.js";
|
|
|
|
const BATCH_A = ["cello", "fugue", "lyre"];
|
|
|
|
/** Stub the fetches the form's initial load makes. Records every /api/names
|
|
* URL so tests can assert how many handles were asked for. */
|
|
function stubFetch(batches: string[][]): string[] {
|
|
let batch = 0;
|
|
const nameUrls: string[] = [];
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async (input: RequestInfo | URL) => {
|
|
const url = String(input);
|
|
if (url.includes("/api/names")) {
|
|
nameUrls.push(url);
|
|
const names = batches[Math.min(batch, batches.length - 1)];
|
|
batch += 1;
|
|
return new Response(JSON.stringify({ names }), { status: 200 });
|
|
}
|
|
const body = url.includes("/api/info")
|
|
? { hostname: "h", version: "0", home_dir: "/home/user", presets: [] }
|
|
: [];
|
|
return new Response(JSON.stringify(body), { status: 200 });
|
|
}),
|
|
);
|
|
return nameUrls;
|
|
}
|
|
|
|
describe("spawnForm naming (#131)", () => {
|
|
let handle: SpawnFormHandle | null = null;
|
|
let nameUrls: string[];
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
handle?.destroy();
|
|
handle = null;
|
|
vi.unstubAllGlobals();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
async function mount(seed?: Parameters<typeof createSpawnForm>[0]["seed"]) {
|
|
nameUrls = stubFetch([BATCH_A]);
|
|
handle = createSpawnForm({ seed, onSuccess() {} });
|
|
document.body.appendChild(handle.el);
|
|
for (let i = 0; i < 40; i++) await Promise.resolve();
|
|
return handle.el;
|
|
}
|
|
|
|
const nameOf = (form: HTMLFormElement) =>
|
|
form.querySelector<HTMLInputElement>("input[name=name]")!.value;
|
|
const branchOf = (form: HTMLFormElement) =>
|
|
form.querySelector<HTMLInputElement>("input[name=worktree_branch]")!.value;
|
|
|
|
it("leaves the name field empty so the daemon names the session", async () => {
|
|
const form = await mount();
|
|
expect(nameOf(form)).toBe("");
|
|
expect(form.querySelector(".reroll")).toBeNull();
|
|
});
|
|
|
|
it("still seeds the worktree branch with one generated handle", async () => {
|
|
const form = await mount();
|
|
expect(BATCH_A).toContain(branchOf(form));
|
|
expect(nameUrls).toHaveLength(1);
|
|
expect(nameUrls[0]).toContain("count=1");
|
|
});
|
|
|
|
it("keeps a name a clone prefilled, and seeds the branch from it", async () => {
|
|
const form = await mount({ name: "my-session" });
|
|
expect(nameOf(form)).toBe("my-session");
|
|
expect(branchOf(form)).toBe("my-session");
|
|
});
|
|
|
|
it("seeds the branch from a typed name, overriding the fetched handle", async () => {
|
|
const form = await mount();
|
|
const input = form.querySelector<HTMLInputElement>("input[name=name]")!;
|
|
input.value = "hand-typed";
|
|
input.dispatchEvent(new Event("input"));
|
|
|
|
expect(nameOf(form)).toBe("hand-typed");
|
|
expect(branchOf(form)).toBe("hand-typed");
|
|
});
|
|
|
|
it("never overwrites a branch the operator typed", async () => {
|
|
nameUrls = stubFetch([BATCH_A]);
|
|
handle = createSpawnForm({ onSuccess() {} });
|
|
const form = handle.el;
|
|
document.body.appendChild(form);
|
|
|
|
const branch = form.querySelector<HTMLInputElement>("input[name=worktree_branch]")!;
|
|
branch.value = "feat/mine";
|
|
branch.dispatchEvent(new Event("input"));
|
|
for (let i = 0; i < 40; i++) await Promise.resolve();
|
|
|
|
expect(branchOf(form)).toBe("feat/mine");
|
|
});
|
|
});
|