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
47 changes: 32 additions & 15 deletions packages/module/src/Message/Message.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,34 +64,35 @@ const checkListItemsRendered = () => {

describe('Message', () => {
it('should render user messages correctly', () => {
render(<Message role="user" name="User" content="Hi" />);
render(<Message avatar="./img" role="user" name="User" content="Hi" />);
expect(screen.getByText('User')).toBeTruthy();
expect(screen.getByText('Hi')).toBeTruthy();
const date = new Date();
expect(screen.getByText(`${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`)).toBeTruthy();
expect(screen.queryByText('Loading message')).toBeFalsy();
expect(screen.getByRole('img')).toHaveAttribute('src', './img');
});
it('should render bot messages correctly', () => {
render(<Message role="bot" name="Bot" content="Hi" />);
render(<Message avatar="./img" role="bot" name="Bot" content="Hi" />);
expect(screen.getByText('Bot')).toBeTruthy();
expect(screen.getByText('AI')).toBeTruthy();
expect(screen.getByText('Hi')).toBeTruthy();
const date = new Date();
expect(screen.getByText(`${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`)).toBeTruthy();
});
it('should render avatar correctly', () => {
render(<Message role="bot" name="Bot" content="Hi" avatar="./testImg" />);
render(<Message avatar="./testImg" role="bot" name="Bot" content="Hi" />);
expect(screen.getByRole('img')).toHaveAttribute('src', './testImg');
});
it('should render botWord correctly', () => {
render(<Message role="bot" name="Bot" content="Hi" botWord="人工知能" />);
render(<Message avatar="./img" role="bot" name="Bot" content="Hi" botWord="人工知能" />);
expect(screen.getByText('Bot')).toBeTruthy();
expect(screen.getByText('人工知能')).toBeTruthy();
expect(screen.queryByText('AI')).toBeFalsy();
expect(screen.getByText('Hi')).toBeTruthy();
});
it('should render timestamps', () => {
render(<Message role="bot" name="Bot" content="Hi" timestamp="2 hours ago" />);
render(<Message avatar="./img" role="bot" name="Bot" content="Hi" timestamp="2 hours ago" />);
expect(screen.getByText('Bot')).toBeTruthy();
expect(screen.getByText('AI')).toBeTruthy();
expect(screen.getByText('Hi')).toBeTruthy();
Expand All @@ -100,29 +101,33 @@ describe('Message', () => {
expect(screen.queryByText(`${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`)).toBeFalsy();
});
it('should render attachments', () => {
render(<Message role="user" content="Hi" attachments={[{ name: 'testAttachment' }]} />);
render(<Message avatar="./img" role="user" content="Hi" attachments={[{ name: 'testAttachment' }]} />);
expect(screen.getByText('Hi')).toBeTruthy();
expect(screen.getByText('testAttachment')).toBeTruthy();
});
it('should be able to click attachments', async () => {
const spy = jest.fn();
render(<Message role="user" content="Hi" attachments={[{ name: 'testAttachment', onClick: spy }]} />);
render(
<Message avatar="./img" role="user" content="Hi" attachments={[{ name: 'testAttachment', onClick: spy }]} />
);
expect(screen.getByText('Hi')).toBeTruthy();
expect(screen.getByText('testAttachment')).toBeTruthy();
await userEvent.click(screen.getByRole('button', { name: /testAttachment/i }));
expect(spy).toHaveBeenCalledTimes(1);
});
it('should be able to close attachments', async () => {
const spy = jest.fn();
render(<Message role="user" content="Hi" attachments={[{ name: 'testAttachment', onClose: spy }]} />);
render(
<Message avatar="./img" role="user" content="Hi" attachments={[{ name: 'testAttachment', onClose: spy }]} />
);
expect(screen.getByText('Hi')).toBeTruthy();
expect(screen.getByText('testAttachment')).toBeTruthy();
expect(screen.getByRole('button', { name: /close testAttachment/i })).toBeTruthy();
await userEvent.click(screen.getByRole('button', { name: /close testAttachment/i }));
expect(spy).toHaveBeenCalledTimes(1);
});
it('should render loading state', () => {
render(<Message role="bot" name="Bot" content="Hi" isLoading />);
render(<Message avatar="./img" role="bot" name="Bot" content="Hi" isLoading />);
expect(screen.getByText('Bot')).toBeTruthy();
expect(screen.getByText('AI')).toBeTruthy();
expect(screen.queryByText('Hi')).toBeFalsy();
Expand All @@ -133,6 +138,7 @@ describe('Message', () => {
it('should be able to show sources', async () => {
render(
<Message
avatar="./img"
role="bot"
name="Bot"
content="Hi"
Expand All @@ -152,6 +158,7 @@ describe('Message', () => {
it('should not show sources if loading', () => {
render(
<Message
avatar="./img"
role="bot"
name="Bot"
content="Hi"
Expand All @@ -173,6 +180,7 @@ describe('Message', () => {
it('should be able to show actions', async () => {
render(
<Message
avatar="./img"
role="bot"
name="Bot"
content="Hi"
Expand All @@ -197,6 +205,7 @@ describe('Message', () => {
it('should not show actions if loading', async () => {
render(
<Message
avatar="./img"
role="bot"
name="Bot"
content="Hi"
Expand All @@ -221,22 +230,22 @@ describe('Message', () => {
});
});
it('should render unordered lists correctly', () => {
render(<Message role="user" name="User" content={UNORDERED_LIST} />);
render(<Message avatar="./img" role="user" name="User" content={UNORDERED_LIST} />);
expect(screen.getByText('Here is an unordered list:')).toBeTruthy();
checkListItemsRendered();
});
it('should render ordered lists correctly', () => {
render(<Message role="user" name="User" content={ORDERED_LIST} />);
render(<Message avatar="./img" role="user" name="User" content={ORDERED_LIST} />);
expect(screen.getByText('Here is an ordered list:')).toBeTruthy();
checkListItemsRendered();
});
it('should render inline code', () => {
render(<Message role="user" name="User" content={INLINE_CODE} />);
render(<Message avatar="./img" role="user" name="User" content={INLINE_CODE} />);
expect(screen.getByText(/() => void/i)).toBeTruthy();
expect(screen.queryByRole('button', { name: 'Copy code button' })).toBeFalsy();
});
it('should render code correctly', () => {
render(<Message role="user" name="User" content={CODE_MESSAGE} />);
render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} />);
expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
expect(screen.getByRole('button', { name: 'Copy code button' })).toBeTruthy();
expect(screen.getByText(/apiVersion: helm.openshift.io\/v1beta1/i)).toBeTruthy();
Expand All @@ -251,14 +260,22 @@ describe('Message', () => {
it('can click copy code button', async () => {
// need explicit setup since RTL stubs clipboard if you do this
const user = userEvent.setup();
render(<Message role="user" name="User" content={CODE_MESSAGE} />);
render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} />);
expect(screen.getByRole('button', { name: 'Copy code button' })).toBeTruthy();
await user.click(screen.getByRole('button', { name: 'Copy code button' }));
const clipboardText = await navigator.clipboard.readText();
expect(clipboardText.trim()).toEqual(CODE.trim());
});
it('should handle codeBlockProps correctly by spreading it onto the CodeMessage', () => {
render(<Message role="user" name="User" content={CODE_MESSAGE} codeBlockProps={{ 'aria-label': 'test' }} />);
render(
<Message
avatar="./img"
role="user"
name="User"
content={CODE_MESSAGE}
codeBlockProps={{ 'aria-label': 'test' }}
/>
);
expect(screen.getByRole('button', { name: 'test' })).toBeTruthy();
});
});
19 changes: 3 additions & 16 deletions packages/module/src/Message/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ export interface MessageProps extends Omit<React.HTMLProps<HTMLDivElement>, 'rol
/** Role of the user sending the message */
role: 'user' | 'bot';
/** Message content */
content: string;
content?: string;
/** Name of the user */
name?: string;
/** Avatar src for the user */
avatar?: string;
avatar: string;
/** Timestamp for the message */
timestamp?: string;
/** Set this to true if message is being loaded */
Expand Down Expand Up @@ -95,19 +95,6 @@ export const Message: React.FunctionComponent<MessageProps> = ({
attachments,
...props
}: MessageProps) => {
// Configure default values
const DEFAULTS = {
user: {
name: 'User',
avatar: 'https://img.freepik.com/premium-photo/graphic-designer-digital-avatar-generative-ai_934475-9292.jpg'
},
bot: {
name: 'Bot',
avatar:
'https://yt3.googleusercontent.com/ej8uvIe1AIFiJQXBwY9cfJmt0kO1cAeWxpBqG_cJndGHx95mFq1F8WakSoXIjtcprTbMQJoqH5M=s900-c-k-c0x00ffffff-no-rj'
}
};

// Keep timestamps consistent between Timestamp component and aria-label
const date = new Date();
const dateString = timestamp ?? `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
Expand All @@ -119,7 +106,7 @@ export const Message: React.FunctionComponent<MessageProps> = ({
{...props}
>
{/* We are using an empty alt tag intentionally in order to reduce noise on screen readers */}
<Avatar src={avatar ?? DEFAULTS[role].avatar} alt="" />
<Avatar src={avatar} alt="" />
<div className="pf-chatbot__message-contents">
<div className="pf-chatbot__message-meta">
{name && (
Expand Down
Loading