2026-02-25 11:41:44 +00:00
|
|
|
import { render, screen } from "@testing-library/react";
|
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
|
import { MessageItem } from "./MessageItem";
|
|
|
|
|
|
|
|
|
|
describe("MessageItem component (Story 178 AC3)", () => {
|
|
|
|
|
it("renders user message as a bubble", () => {
|
|
|
|
|
render(<MessageItem msg={{ role: "user", content: "Hello there!" }} />);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText("Hello there!")).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders assistant message with markdown-body class", () => {
|
|
|
|
|
render(
|
|
|
|
|
<MessageItem
|
|
|
|
|
msg={{ role: "assistant", content: "Here is my response." }}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText("Here is my response.")).toBeInTheDocument();
|
|
|
|
|
const text = screen.getByText("Here is my response.");
|
|
|
|
|
expect(text.closest(".markdown-body")).toBeTruthy();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders tool message as collapsible details", () => {
|
|
|
|
|
render(
|
|
|
|
|
<MessageItem
|
|
|
|
|
msg={{
|
|
|
|
|
role: "tool",
|
|
|
|
|
content: "tool output content",
|
|
|
|
|
tool_call_id: "toolu_1",
|
|
|
|
|
}}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(/Tool Output/)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders tool call badges for assistant messages with tool_calls", () => {
|
|
|
|
|
render(
|
|
|
|
|
<MessageItem
|
|
|
|
|
msg={{
|
|
|
|
|
role: "assistant",
|
|
|
|
|
content: "I will read the file.",
|
|
|
|
|
tool_calls: [
|
|
|
|
|
{
|
|
|
|
|
id: "toolu_1",
|
|
|
|
|
type: "function",
|
|
|
|
|
function: {
|
|
|
|
|
name: "Read",
|
|
|
|
|
arguments: '{"file_path":"src/main.rs"}',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText("I will read the file.")).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByText("Read(src/main.rs)")).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("is wrapped in React.memo (has displayName or $$typeof memo)", () => {
|
|
|
|
|
// React.memo wraps the component — verify the export is memoized
|
|
|
|
|
// by checking that the component has a memo wrapper
|
|
|
|
|
const { type } = { type: MessageItem };
|
|
|
|
|
// React.memo returns an object with $$typeof === Symbol(react.memo)
|
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: checking React internals for test
|
|
|
|
|
expect((type as any).$$typeof).toBeDefined();
|
|
|
|
|
// biome-ignore lint/suspicious/noExplicitAny: checking React internals for test
|
|
|
|
|
const typeofStr = String((type as any).$$typeof);
|
|
|
|
|
expect(typeofStr).toContain("memo");
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-02-25 18:08:08 +00:00
|
|
|
|
|
|
|
|
describe("MessageItem user message code fence rendering (Story 196)", () => {
|
|
|
|
|
it("renders code fences in user messages as code blocks", () => {
|
|
|
|
|
const { container } = render(
|
|
|
|
|
<MessageItem
|
|
|
|
|
msg={{
|
|
|
|
|
role: "user",
|
|
|
|
|
content: "Here is some code:\n```js\nconsole.log('hi');\n```",
|
|
|
|
|
}}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Syntax highlighter renders a pre > div > code structure
|
|
|
|
|
const codeEl = container.querySelector("pre code");
|
|
|
|
|
expect(codeEl).toBeInTheDocument();
|
|
|
|
|
expect(codeEl?.textContent).toContain("console.log");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders inline code with single backticks in user messages", () => {
|
|
|
|
|
render(
|
|
|
|
|
<MessageItem
|
|
|
|
|
msg={{ role: "user", content: "Use `npm install` to install." }}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const codeEl = screen.getByText("npm install");
|
|
|
|
|
expect(codeEl.tagName.toLowerCase()).toBe("code");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders user messages with code blocks inside user-markdown-body class", () => {
|
|
|
|
|
const { container } = render(
|
|
|
|
|
<MessageItem
|
|
|
|
|
msg={{
|
|
|
|
|
role: "user",
|
|
|
|
|
content: "```js\nconsole.log('hi');\n```",
|
|
|
|
|
}}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(container.querySelector(".user-markdown-body")).toBeTruthy();
|
|
|
|
|
});
|
|
|
|
|
});
|