cruise 0.1.45

YAML-driven coding agent workflow orchestrator
Documentation
import { describe, it, expect, vi, beforeEach } from "vitest";
import { act, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { AskUserDialog } from "../components/AskUserDialog";
import { ASK_USER_EVENT, type AskUserDetail } from "../lib/askUser";

// AskUserDialog calls respondToAsk (a Tauri IPC wrapper) on submit; mock it.
// Variadic so calling with (sessionId, answer) type-checks, and it returns a
// Promise so the component's `.catch(...)` has something to chain.
const { respondToAsk } = vi.hoisted(() => ({
  respondToAsk: vi.fn((..._args: unknown[]) => Promise.resolve()),
}));
vi.mock("../lib/commands", () => ({ respondToAsk }));

/** Dispatch the window event that surfaces an ask_user question. */
function fireAsk(detail: AskUserDetail) {
  act(() => {
    window.dispatchEvent(new CustomEvent(ASK_USER_EVENT, { detail }));
  });
}

describe("AskUserDialog", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("renders nothing until an ask_user event arrives", () => {
    render(<AskUserDialog />);
    expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
  });

  it("shows the question when an ask_user event is dispatched", () => {
    render(<AskUserDialog />);
    fireAsk({ sessionId: "sess-1", question: "JWT or sessions?" });

    expect(screen.getByRole("dialog")).toBeInTheDocument();
    expect(screen.getByText("JWT or sessions?")).toBeInTheDocument();
    // The answer field is accessible by name (not just a placeholder).
    expect(screen.getByRole("textbox", { name: "Your answer" })).toBeInTheDocument();
  });

  it("delivers the typed answer to the correct session and dismisses", async () => {
    render(<AskUserDialog />);
    fireAsk({ sessionId: "sess-42", question: "Which DB?" });

    await userEvent.type(screen.getByRole("textbox", { name: "Your answer" }), "Postgres");
    await userEvent.click(screen.getByRole("button", { name: "Send answer" }));

    expect(respondToAsk).toHaveBeenCalledTimes(1);
    expect(respondToAsk).toHaveBeenCalledWith("sess-42", "Postgres");
    // Dialog closes after submit.
    expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
  });

  it("submits with Cmd/Ctrl+Enter", async () => {
    render(<AskUserDialog />);
    fireAsk({ sessionId: "sess-7", question: "Edge case?" });

    const field = screen.getByRole("textbox", { name: "Your answer" });
    await userEvent.type(field, "yes");
    await userEvent.type(field, "{Meta>}{Enter}{/Meta}");

    expect(respondToAsk).toHaveBeenCalledWith("sess-7", "yes");
    expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
  });

  it("replaces the question and clears the prior answer when a new event arrives", async () => {
    render(<AskUserDialog />);
    fireAsk({ sessionId: "s1", question: "First?" });
    await userEvent.type(screen.getByRole("textbox", { name: "Your answer" }), "draft");

    fireAsk({ sessionId: "s2", question: "Second?" });

    expect(screen.getByText("Second?")).toBeInTheDocument();
    expect(screen.queryByText("First?")).not.toBeInTheDocument();
    // Answer box reset for the new question.
    expect(screen.getByRole("textbox", { name: "Your answer" })).toHaveValue("");
  });
});