Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions frontend/src/components/Chat/MessageList.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ export const useMessageListStyles = makeStyles({
flexDirection: 'row-reverse',
},
messageContent: {
backgroundColor: tokens.colorNeutralBackground3,
backgroundColor: tokens.colorNeutralBackground1,
border: `1px solid ${tokens.colorNeutralStroke2}`,
padding: tokens.spacingVerticalM,
borderRadius: tokens.borderRadiusMedium,
flex: 1,
},
userMessageContent: {
backgroundColor: tokens.colorBrandBackground,
backgroundColor: tokens.colorBrandBackground2,
border: `1px solid ${tokens.colorBrandStroke2}`,
},
messageText: {
whiteSpace: 'pre-wrap',
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/components/Chat/MessageList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,50 @@ describe("MessageList", () => {
expect(screen.getByText("Assistant message test")).toBeInTheDocument();
});

describe("bubble class composition", () => {
// Regression guard for an earlier bug where the user-bubble background
// override silently lost to the assistant-bubble base style. The cause was
// string-concatenated class names — Griffel's atomic CSS doesn't dedupe
// conflicting properties unless mergeClasses is used. We assert here that
// the two bubble containers receive distinguishable className strings, so
// the override always has a chance to win.
it("renders user and assistant bubbles with distinct class signatures", () => {
render(
<TestWrapper>
<MessageList
messages={[
{
role: "user",
content: "u",
timestamp: new Date().toISOString(),
},
{
role: "assistant",
content: "a",
timestamp: new Date().toISOString(),
},
]}
/>
</TestWrapper>
);
const userBubble = screen.getByTestId("message-bubble-0");
const assistantBubble = screen.getByTestId("message-bubble-1");
// The two bubble class strings must differ — otherwise the user
// override silently lost (which was the original bug).
expect(userBubble.className).not.toBe(assistantBubble.className);
// Both bubbles must carry at least one style hook (catches a future
// refactor that drops the className entirely).
expect(userBubble.className.trim()).not.toBe('');
expect(assistantBubble.className.trim()).not.toBe('');
// The user bubble must carry at least one style hook the assistant
// bubble doesn't have — that's the override actually being applied.
const userClasses = userBubble.className.split(/\s+/).filter(Boolean);
const assistantClasses = new Set(assistantBubble.className.split(/\s+/).filter(Boolean));
const userOnly = userClasses.filter(c => !assistantClasses.has(c));
expect(userOnly.length).toBeGreaterThan(0);
});
});

it("should handle messages with image attachments", () => {
const messagesWithAttachments: Message[] = [
{
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/components/Chat/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Button,
Tooltip,
Spinner,
mergeClasses,
} from '@fluentui/react-components'
import { ArrowDownloadRegular, ArrowReplyRegular, ArrowForwardRegular, ChatAddRegular, BranchForkRegular } from '@fluentui/react-icons'
import { Message, MessageAttachment } from '../../types'
Expand Down Expand Up @@ -138,13 +139,16 @@ export default function MessageList({ messages, onCopyToInput, onCopyToNewConver
return (
<div
key={index}
className={`${styles.message} ${isUser ? styles.userMessage : ''}`}
className={mergeClasses(styles.message, isUser && styles.userMessage)}
>
<Avatar
name={avatarName}
color={isUser ? 'colorful' : isSimulated ? 'steel' : 'brand'}
/>
<div className={`${styles.messageContent} ${isUser ? styles.userMessageContent : ''}`}>
<div
className={mergeClasses(styles.messageContent, isUser && styles.userMessageContent)}
data-testid={`message-bubble-${index}`}
>
{/* Error rendering */}
{message.error && (
<div className={styles.errorContainer}>
Expand Down
Loading