This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/spawnFormNames.test.ts
T
Miguel Palhas 6173c86694 feat(maestro): web spawn form stops naming sessions
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
2026-09-02 18:00:03 +01:00

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");
});
});