import { act, fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { ChatInput } from "./ChatInput";
describe("ChatInput component (Story 178 AC1)", () => {
it("renders a textarea with Send a message... placeholder", () => {
render(
,
);
const textarea = screen.getByPlaceholderText("Send a message...");
expect(textarea.tagName.toLowerCase()).toBe("textarea");
});
it("manages input state internally — typing updates value without calling onSubmit", async () => {
const onSubmit = vi.fn();
render(
,
);
const textarea = screen.getByPlaceholderText("Send a message...");
await act(async () => {
fireEvent.change(textarea, { target: { value: "hello world" } });
});
expect((textarea as HTMLTextAreaElement).value).toBe("hello world");
expect(onSubmit).not.toHaveBeenCalled();
});
it("calls onSubmit with the input text on Enter key press", async () => {
const onSubmit = vi.fn();
render(
,
);
const textarea = screen.getByPlaceholderText("Send a message...");
await act(async () => {
fireEvent.change(textarea, { target: { value: "test message" } });
});
await act(async () => {
fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
});
expect(onSubmit).toHaveBeenCalledWith("test message");
});
it("clears input after submitting", async () => {
render(
,
);
const textarea = screen.getByPlaceholderText("Send a message...");
await act(async () => {
fireEvent.change(textarea, { target: { value: "hello" } });
});
await act(async () => {
fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
});
expect((textarea as HTMLTextAreaElement).value).toBe("");
});
it("does not submit on Shift+Enter", async () => {
const onSubmit = vi.fn();
render(
,
);
const textarea = screen.getByPlaceholderText("Send a message...");
await act(async () => {
fireEvent.change(textarea, { target: { value: "multiline" } });
});
await act(async () => {
fireEvent.keyDown(textarea, { key: "Enter", shiftKey: true });
});
expect(onSubmit).not.toHaveBeenCalled();
expect((textarea as HTMLTextAreaElement).value).toBe("multiline");
});
it("calls onCancel when stop button is clicked while loading with empty input", async () => {
const onCancel = vi.fn();
render(
,
);
const stopButton = screen.getByRole("button", { name: "■" });
await act(async () => {
fireEvent.click(stopButton);
});
expect(onCancel).toHaveBeenCalled();
});
it("renders queued message indicators", () => {
render(
,
);
const indicators = screen.getAllByTestId("queued-message-indicator");
expect(indicators).toHaveLength(2);
expect(indicators[0]).toHaveTextContent("first message");
expect(indicators[1]).toHaveTextContent("second message");
});
it("calls onRemoveQueuedMessage when cancel button is clicked", async () => {
const onRemove = vi.fn();
render(
,
);
const cancelBtn = screen.getByTitle("Cancel queued message");
await act(async () => {
fireEvent.click(cancelBtn);
});
expect(onRemove).toHaveBeenCalledWith("q1");
});
it("edit button restores queued message text to input and removes from queue", async () => {
const onRemove = vi.fn();
render(
,
);
const editBtn = screen.getByTitle("Edit queued message");
await act(async () => {
fireEvent.click(editBtn);
});
const textarea = screen.getByPlaceholderText("Send a message...");
expect((textarea as HTMLTextAreaElement).value).toBe("edit me back");
expect(onRemove).toHaveBeenCalledWith("q1");
});
});