story-kit: merge 163_story_remove_bubble_styling_from_streaming_chat_messages
This commit is contained in:
@@ -892,3 +892,164 @@ describe("Chat message queue (Story 155)", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Remove bubble styling from streaming messages (Story 163)", () => {
|
||||
beforeEach(() => {
|
||||
capturedWsHandlers = null;
|
||||
setupMocks();
|
||||
});
|
||||
|
||||
it("AC1: streaming assistant message uses transparent background, no extra padding, no border-radius", async () => {
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
|
||||
// Send a message to put chat into loading state
|
||||
const input = screen.getByPlaceholderText("Send a message...");
|
||||
await act(async () => {
|
||||
fireEvent.change(input, { target: { value: "Hello" } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
||||
});
|
||||
|
||||
// Simulate streaming tokens arriving
|
||||
act(() => {
|
||||
capturedWsHandlers?.onToken("Streaming response text");
|
||||
});
|
||||
|
||||
// Find the streaming message container (the inner div wrapping the Markdown)
|
||||
const streamingText = await screen.findByText("Streaming response text");
|
||||
// The markdown-body wrapper is the parent, and the styled div is its parent
|
||||
const styledDiv = streamingText.closest(".markdown-body")
|
||||
?.parentElement as HTMLElement;
|
||||
|
||||
expect(styledDiv).toBeTruthy();
|
||||
const styleAttr = styledDiv.getAttribute("style") ?? "";
|
||||
expect(styleAttr).toContain("background: transparent");
|
||||
expect(styleAttr).toContain("padding: 0px");
|
||||
expect(styleAttr).toContain("border-radius: 0px");
|
||||
expect(styleAttr).toContain("max-width: 100%");
|
||||
});
|
||||
|
||||
it("AC1: streaming message wraps Markdown in markdown-body class", async () => {
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
|
||||
const input = screen.getByPlaceholderText("Send a message...");
|
||||
await act(async () => {
|
||||
fireEvent.change(input, { target: { value: "Hello" } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
||||
});
|
||||
|
||||
act(() => {
|
||||
capturedWsHandlers?.onToken("Some markdown content");
|
||||
});
|
||||
|
||||
const streamingText = await screen.findByText("Some markdown content");
|
||||
const markdownBody = streamingText.closest(".markdown-body");
|
||||
expect(markdownBody).toBeTruthy();
|
||||
});
|
||||
|
||||
it("AC2: no visual change when streaming ends and message transitions to completed", async () => {
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
|
||||
// Send a message to start streaming
|
||||
const input = screen.getByPlaceholderText("Send a message...");
|
||||
await act(async () => {
|
||||
fireEvent.change(input, { target: { value: "Hello" } });
|
||||
});
|
||||
await act(async () => {
|
||||
fireEvent.keyDown(input, { key: "Enter", shiftKey: false });
|
||||
});
|
||||
|
||||
// Simulate streaming tokens
|
||||
act(() => {
|
||||
capturedWsHandlers?.onToken("Final response");
|
||||
});
|
||||
|
||||
// Capture streaming message style attribute
|
||||
const streamingText = await screen.findByText("Final response");
|
||||
const streamingStyledDiv = streamingText.closest(".markdown-body")
|
||||
?.parentElement as HTMLElement;
|
||||
const streamingStyleAttr = streamingStyledDiv.getAttribute("style") ?? "";
|
||||
|
||||
// Transition: onUpdate completes the message
|
||||
act(() => {
|
||||
capturedWsHandlers?.onUpdate([
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Final response" },
|
||||
]);
|
||||
});
|
||||
|
||||
// Find the completed message — it should have the same styling
|
||||
const completedText = await screen.findByText("Final response");
|
||||
const completedMarkdownBody = completedText.closest(".markdown-body");
|
||||
const completedStyledDiv =
|
||||
completedMarkdownBody?.parentElement as HTMLElement;
|
||||
|
||||
expect(completedStyledDiv).toBeTruthy();
|
||||
const completedStyleAttr = completedStyledDiv.getAttribute("style") ?? "";
|
||||
|
||||
// Both streaming and completed use transparent bg, 0 padding, 0 border-radius
|
||||
expect(completedStyleAttr).toContain("background: transparent");
|
||||
expect(completedStyleAttr).toContain("padding: 0px");
|
||||
expect(completedStyleAttr).toContain("border-radius: 0px");
|
||||
expect(streamingStyleAttr).toContain("background: transparent");
|
||||
expect(streamingStyleAttr).toContain("padding: 0px");
|
||||
expect(streamingStyleAttr).toContain("border-radius: 0px");
|
||||
|
||||
// Both have the markdown-body class wrapper
|
||||
expect(streamingStyledDiv.querySelector(".markdown-body")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("AC3: completed assistant messages retain transparent background and no border-radius", async () => {
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
|
||||
act(() => {
|
||||
capturedWsHandlers?.onUpdate([
|
||||
{ role: "user", content: "Hi" },
|
||||
{ role: "assistant", content: "Hello there!" },
|
||||
]);
|
||||
});
|
||||
|
||||
const assistantText = await screen.findByText("Hello there!");
|
||||
const markdownBody = assistantText.closest(".markdown-body");
|
||||
const styledDiv = markdownBody?.parentElement as HTMLElement;
|
||||
|
||||
expect(styledDiv).toBeTruthy();
|
||||
const styleAttr = styledDiv.getAttribute("style") ?? "";
|
||||
expect(styleAttr).toContain("background: transparent");
|
||||
expect(styleAttr).toContain("padding: 0px");
|
||||
expect(styleAttr).toContain("border-radius: 0px");
|
||||
expect(styleAttr).toContain("max-width: 100%");
|
||||
});
|
||||
|
||||
it("AC3: completed user messages still have their bubble styling", async () => {
|
||||
render(<Chat projectPath="/tmp/project" onCloseProject={vi.fn()} />);
|
||||
|
||||
await waitFor(() => expect(capturedWsHandlers).not.toBeNull());
|
||||
|
||||
act(() => {
|
||||
capturedWsHandlers?.onUpdate([
|
||||
{ role: "user", content: "I am a user message" },
|
||||
{ role: "assistant", content: "I am a response" },
|
||||
]);
|
||||
});
|
||||
|
||||
// findByText returns the styled div itself for user messages (text is direct child)
|
||||
const userStyledDiv = await screen.findByText("I am a user message");
|
||||
const styleAttr = userStyledDiv.getAttribute("style") ?? "";
|
||||
// User messages retain bubble: distinct background, padding, rounded corners
|
||||
expect(styleAttr).toContain("padding: 10px 16px");
|
||||
expect(styleAttr).toContain("border-radius: 20px");
|
||||
expect(styleAttr).not.toContain("background: transparent");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user