The onConfirm handler in Sidebar resolves (not rejects) when the daemon refuses an unforced kill — it mutates state to switch to force mode and returns. That left `pending` stuck at true in the KillDialog, so the "Force kill" button rendered disabled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
// @vitest-environment happy-dom
|
|
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { KillDialog } from "./KillDialog.js";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
describe("KillDialog", () => {
|
|
it("focuses the kill button, so Enter confirms", () => {
|
|
render(<KillDialog name="alpha" blockers={null} onConfirm={() => Promise.resolve()} onCancel={vi.fn()} />);
|
|
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Kill" }));
|
|
});
|
|
|
|
it("focuses the force button too", () => {
|
|
render(
|
|
<KillDialog
|
|
name="alpha"
|
|
blockers={null}
|
|
force
|
|
reason="worktree is dirty."
|
|
onConfirm={() => Promise.resolve()}
|
|
onCancel={vi.fn()}
|
|
/>,
|
|
);
|
|
expect(document.activeElement).toBe(screen.getByRole("button", { name: "Force kill" }));
|
|
});
|
|
|
|
it("names the blockers in the confirm body", () => {
|
|
render(
|
|
<KillDialog
|
|
name="alpha"
|
|
blockers={{ uncommitted_files: 2, untracked_files: 0, unpushed_commits: 0 }}
|
|
onConfirm={() => Promise.resolve()}
|
|
onCancel={vi.fn()}
|
|
/>,
|
|
);
|
|
expect(screen.getByText(/will be refused unless it is forced/)).not.toBeNull();
|
|
});
|
|
|
|
it("cancels on Cancel, on Escape and on a backdrop click", () => {
|
|
const onCancel = vi.fn();
|
|
const first = render(<KillDialog name="alpha" blockers={null} onConfirm={() => Promise.resolve()} onCancel={onCancel} />);
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
first.unmount();
|
|
|
|
const second = render(<KillDialog name="alpha" blockers={null} onConfirm={() => Promise.resolve()} onCancel={onCancel} />);
|
|
fireEvent.keyDown(screen.getByRole("dialog"), { key: "Escape" });
|
|
expect(onCancel).toHaveBeenCalledTimes(2);
|
|
second.unmount();
|
|
|
|
render(<KillDialog name="alpha" blockers={null} onConfirm={() => Promise.resolve()} onCancel={onCancel} />);
|
|
fireEvent.click(screen.getByRole("dialog"));
|
|
expect(onCancel).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
it("disables the button while pending and shows a rejection inline, so the press can be retried", async () => {
|
|
let reject: (e: Error) => void = () => {};
|
|
const onConfirm = vi.fn(() => new Promise<void>((_, r) => (reject = r)));
|
|
render(<KillDialog name="alpha" blockers={null} onConfirm={onConfirm} onCancel={vi.fn()} />);
|
|
|
|
const kill = screen.getByRole("button", { name: "Kill" }) as HTMLButtonElement;
|
|
fireEvent.click(kill);
|
|
expect(onConfirm).toHaveBeenCalledOnce();
|
|
expect(kill.disabled).toBe(true);
|
|
|
|
reject(new Error("daemon unreachable"));
|
|
await waitFor(() => expect(kill.disabled).toBe(false));
|
|
expect(screen.getByText("Failed to kill: daemon unreachable")).not.toBeNull();
|
|
});
|
|
|
|
it("resets pending when force changes, so the force button is clickable", async () => {
|
|
// Simulate the Sidebar flow: onConfirm resolves (parent switches to
|
|
// force mode via rerender), but pending was set to true before the call.
|
|
const onConfirm = vi.fn(() => Promise.resolve());
|
|
const { rerender } = render(
|
|
<KillDialog name="alpha" blockers={null} onConfirm={onConfirm} onCancel={vi.fn()} />,
|
|
);
|
|
|
|
const kill = screen.getByRole("button", { name: "Kill" }) as HTMLButtonElement;
|
|
fireEvent.click(kill);
|
|
expect(kill.disabled).toBe(true);
|
|
|
|
// Parent re-renders with force=true (the daemon refused the unforced kill).
|
|
await waitFor(() => expect(onConfirm).toHaveBeenCalledOnce());
|
|
rerender(
|
|
<KillDialog
|
|
name="alpha"
|
|
blockers={{ uncommitted_files: 1, untracked_files: 0, unpushed_commits: 0 }}
|
|
force
|
|
reason="worktree is dirty"
|
|
onConfirm={onConfirm}
|
|
onCancel={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
const forceBtn = screen.getByRole("button", { name: "Force kill" }) as HTMLButtonElement;
|
|
expect(forceBtn.disabled).toBe(false);
|
|
});
|
|
});
|