cruise 0.1.78

YAML-driven coding agent workflow orchestrator
Documentation
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ImageAttachments } from "./ImageAttachments";

vi.mock("@tauri-apps/api/core", () => ({
  convertFileSrc: (path: string) => `tauri-asset://${path}`,
}));

vi.mock("@tauri-apps/api/webview", () => ({
  getCurrentWebview: () => {
    throw new Error("not in tauri runtime");
  },
}));

const openDialogMock = vi.fn();
vi.mock("@tauri-apps/plugin-dialog", () => ({
  open: (...args: unknown[]) => openDialogMock(...args),
}));

afterEach(() => {
  cleanup();
  openDialogMock.mockReset();
});

describe("ImageAttachments", () => {
  it("renders the empty drop-zone hint when no attachments", () => {
    render(<ImageAttachments value={[]} onChange={() => {}} />);
    expect(screen.getByText(/Drop image files here/i)).toBeInTheDocument();
  });

  it("renders one preview tile per attachment", () => {
    render(
      <ImageAttachments
        value={["/tmp/a.png", "/tmp/sub/b.jpg"]}
        onChange={() => {}}
      />,
    );
    expect(screen.getByAltText("a.png")).toBeInTheDocument();
    expect(screen.getByAltText("b.jpg")).toBeInTheDocument();
  });

  it("calls onChange with the remaining items when a preview's × is clicked", async () => {
    const onChange = vi.fn();
    render(
      <ImageAttachments
        value={["/tmp/a.png", "/tmp/b.png"]}
        onChange={onChange}
      />,
    );
    const removeBtn = screen.getByRole("button", { name: /Remove a.png/i });
    await userEvent.click(removeBtn);
    expect(onChange).toHaveBeenCalledWith(["/tmp/b.png"]);
  });

  it("appends picker selections via Browse and dedupes against existing paths", async () => {
    const onChange = vi.fn();
    openDialogMock.mockResolvedValueOnce(["/tmp/a.png", "/tmp/c.png"]);
    render(
      <ImageAttachments value={["/tmp/a.png"]} onChange={onChange} />,
    );
    await userEvent.click(screen.getByRole("button", { name: /Browse/i }));
    await vi.waitFor(() => {
      expect(onChange).toHaveBeenCalledWith(["/tmp/a.png", "/tmp/c.png"]);
    });
  });

  it("ignores non-image selections from Browse", async () => {
    const onChange = vi.fn();
    openDialogMock.mockResolvedValueOnce(["/tmp/notes.txt"]);
    render(<ImageAttachments value={[]} onChange={onChange} />);
    await userEvent.click(screen.getByRole("button", { name: /Browse/i }));
    await vi.waitFor(() => {
      expect(onChange).toHaveBeenCalledWith([]);
    });
  });

  it("does nothing when the dialog is cancelled (null result)", async () => {
    const onChange = vi.fn();
    openDialogMock.mockResolvedValueOnce(null);
    render(<ImageAttachments value={[]} onChange={onChange} />);
    await userEvent.click(screen.getByRole("button", { name: /Browse/i }));
    await new Promise((r) => setTimeout(r, 10));
    expect(onChange).not.toHaveBeenCalled();
  });
});