Skip to content

Commit 969326b

Browse files
authored
chore(compass-assistant): split drawer and provider usage for connections context COMPASS-9603 (#7199)
1 parent 1162b73 commit 969326b

File tree

12 files changed

+409
-200
lines changed

12 files changed

+409
-200
lines changed

packages/compass-assistant/src/assistant-chat.spec.tsx

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
33
import { AssistantChat } from './assistant-chat';
44
import { expect } from 'chai';
55
import type { UIMessage } from './@ai-sdk/react/use-chat';
6+
import { createMockChat } from '../test/utils';
67

78
describe('AssistantChat', function () {
89
const mockMessages: UIMessage[] = [
910
{
10-
id: '1',
11+
id: 'user',
1112
role: 'user',
1213
parts: [{ type: 'text', text: 'Hello, MongoDB Assistant!' }],
1314
},
1415
{
15-
id: '2',
16+
id: 'assistant',
1617
role: 'assistant',
1718
parts: [
1819
{
@@ -23,8 +24,16 @@ describe('AssistantChat', function () {
2324
},
2425
];
2526

27+
function renderWithChat(messages: UIMessage[]) {
28+
const chat = createMockChat({ messages });
29+
return {
30+
result: render(<AssistantChat chat={chat} />),
31+
chat,
32+
};
33+
}
34+
2635
it('renders input field and send button', function () {
27-
render(<AssistantChat messages={[]} />);
36+
renderWithChat([]);
2837

2938
const inputField = screen.getByTestId('assistant-chat-input');
3039
const sendButton = screen.getByTestId('assistant-chat-send-button');
@@ -34,7 +43,7 @@ describe('AssistantChat', function () {
3443
});
3544

3645
it('input field accepts text input', function () {
37-
render(<AssistantChat messages={[]} />);
46+
renderWithChat([]);
3847

3948
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
4049
const inputField = screen.getByTestId(
@@ -47,7 +56,7 @@ describe('AssistantChat', function () {
4756
});
4857

4958
it('send button is disabled when input is empty', function () {
50-
render(<AssistantChat messages={[]} />);
59+
renderWithChat([]);
5160

5261
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
5362
const sendButton = screen.getByTestId(
@@ -58,7 +67,7 @@ describe('AssistantChat', function () {
5867
});
5968

6069
it('send button is enabled when input has text', function () {
61-
render(<AssistantChat messages={[]} />);
70+
renderWithChat([]);
6271

6372
const inputField = screen.getByTestId('assistant-chat-input');
6473
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
@@ -72,7 +81,7 @@ describe('AssistantChat', function () {
7281
});
7382

7483
it('send button is disabled for whitespace-only input', function () {
75-
render(<AssistantChat messages={[]} />);
84+
renderWithChat([]);
7685

7786
const inputField = screen.getByTestId('assistant-chat-input');
7887
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
@@ -86,36 +95,32 @@ describe('AssistantChat', function () {
8695
});
8796

8897
it('displays messages in the chat feed', function () {
89-
render(<AssistantChat messages={mockMessages} />);
98+
renderWithChat(mockMessages);
9099

91100
expect(screen.getByTestId('assistant-message-user')).to.exist;
92101
expect(screen.getByTestId('assistant-message-assistant')).to.exist;
93-
expect(screen.getByText('Hello, MongoDB Assistant!')).to.exist;
94-
expect(screen.getByText('Hello! How can I help you with MongoDB today?')).to
95-
.exist;
102+
expect(screen.getByTestId('assistant-message-user')).to.have.text(
103+
'Hello, MongoDB Assistant!'
104+
);
105+
expect(screen.getByTestId('assistant-message-assistant')).to.have.text(
106+
'Hello! How can I help you with MongoDB today?'
107+
);
96108
});
97109

98-
it('calls onSendMessage when form is submitted', function () {
99-
let sentMessage = '';
100-
const handleSendMessage = (message: string) => {
101-
sentMessage = message;
102-
};
103-
104-
render(<AssistantChat messages={[]} onSendMessage={handleSendMessage} />);
105-
110+
it('calls sendMessage when form is submitted', function () {
111+
const { chat } = renderWithChat([]);
106112
const inputField = screen.getByTestId('assistant-chat-input');
107113
const sendButton = screen.getByTestId('assistant-chat-send-button');
108114

109115
userEvent.type(inputField, 'What is aggregation?');
110116
userEvent.click(sendButton);
111117

112-
expect(sentMessage).to.equal('What is aggregation?');
118+
expect(chat.sendMessage.calledWith({ text: 'What is aggregation?' })).to.be
119+
.true;
113120
});
114121

115122
it('clears input field after successful submission', function () {
116-
const handleSendMessage = () => {};
117-
118-
render(<AssistantChat messages={[]} onSendMessage={handleSendMessage} />);
123+
renderWithChat([]);
119124

120125
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
121126
const inputField = screen.getByTestId(
@@ -130,44 +135,35 @@ describe('AssistantChat', function () {
130135
});
131136

132137
it('trims whitespace from input before sending', function () {
133-
let sentMessage = '';
134-
const handleSendMessage = (message: string) => {
135-
sentMessage = message;
136-
};
137-
138-
render(<AssistantChat messages={[]} onSendMessage={handleSendMessage} />);
138+
const { chat } = renderWithChat([]);
139139

140140
const inputField = screen.getByTestId('assistant-chat-input');
141141

142142
userEvent.type(inputField, ' What is sharding? ');
143143
userEvent.click(screen.getByTestId('assistant-chat-send-button'));
144144

145-
expect(sentMessage).to.equal('What is sharding?');
145+
expect(chat.sendMessage.calledWith({ text: 'What is sharding?' })).to.be
146+
.true;
146147
});
147148

148-
it('does not call onSendMessage when input is empty or whitespace-only', function () {
149-
let messageSent = false;
150-
const handleSendMessage = () => {
151-
messageSent = true;
152-
};
153-
154-
render(<AssistantChat messages={[]} onSendMessage={handleSendMessage} />);
149+
it('does not call sendMessage when input is empty or whitespace-only', function () {
150+
const { chat } = renderWithChat([]);
155151

156152
const inputField = screen.getByTestId('assistant-chat-input');
157153
const chatForm = screen.getByTestId('assistant-chat-form');
158154

159155
// Test empty input
160156
userEvent.click(chatForm);
161-
expect(messageSent).to.be.false;
157+
expect(chat.sendMessage.notCalled).to.be.true;
162158

163159
// Test whitespace-only input
164160
userEvent.type(inputField, ' ');
165161
userEvent.click(chatForm);
166-
expect(messageSent).to.be.false;
162+
expect(chat.sendMessage.notCalled).to.be.true;
167163
});
168164

169165
it('displays user and assistant messages with different styling', function () {
170-
render(<AssistantChat messages={mockMessages} />);
166+
renderWithChat(mockMessages);
171167

172168
const userMessage = screen.getByTestId('assistant-message-user');
173169
const assistantMessage = screen.getByTestId('assistant-message-assistant');
@@ -196,7 +192,7 @@ describe('AssistantChat', function () {
196192
},
197193
];
198194

199-
render(<AssistantChat messages={messagesWithMultipleParts} />);
195+
renderWithChat(messagesWithMultipleParts);
200196

201197
expect(screen.getByText('Here is part 1. And here is part 2.')).to.exist;
202198
});
@@ -215,7 +211,7 @@ describe('AssistantChat', function () {
215211
},
216212
];
217213

218-
render(<AssistantChat messages={messagesWithMixedParts} />);
214+
renderWithChat(messagesWithMixedParts);
219215

220216
expect(screen.getByText('This is text content. More text content.')).to
221217
.exist;

packages/compass-assistant/src/assistant-chat.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
11
import React, { useCallback, useState } from 'react';
22
import type { UIMessage } from './@ai-sdk/react/use-chat';
3+
import type { Chat } from './@ai-sdk/react/chat-react';
4+
import { useChat } from './@ai-sdk/react/use-chat';
35

46
interface AssistantChatProps {
5-
messages: UIMessage[];
6-
onSendMessage?: (message: string) => void;
7+
chat: Chat<UIMessage>;
78
}
89

910
/**
1011
* This component is currently using placeholders as Leafygreen UI updates are not available yet.
1112
* Before release, we will replace this with the actual Leafygreen chat components.
1213
*/
1314
export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
14-
messages,
15-
onSendMessage,
15+
chat,
1616
}) => {
1717
const [inputValue, setInputValue] = useState('');
18+
const { messages, sendMessage } = useChat({
19+
chat,
20+
});
1821

1922
const handleInputSubmit = useCallback(
2023
(e: React.FormEvent) => {
2124
e.preventDefault();
22-
if (inputValue.trim() && onSendMessage) {
23-
onSendMessage(inputValue.trim());
25+
if (inputValue.trim()) {
26+
void sendMessage({ text: inputValue.trim() });
2427
setInputValue('');
2528
}
2629
},
27-
[inputValue, onSendMessage]
30+
[inputValue, sendMessage]
2831
);
2932

3033
return (
@@ -53,7 +56,7 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
5356
{messages.map((message) => (
5457
<div
5558
key={message.id}
56-
data-testid={`assistant-message-${message.role}`}
59+
data-testid={`assistant-message-${message.id}`}
5760
style={{
5861
marginBottom: '12px',
5962
padding: '8px 12px',

packages/compass-assistant/src/assistant-provider.tsx

Lines changed: 0 additions & 60 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React, { useContext } from 'react';
2+
import { DrawerSection } from '@mongodb-js/compass-components';
3+
import { AssistantChat } from './assistant-chat';
4+
import {
5+
ASSISTANT_DRAWER_ID,
6+
AssistantContext,
7+
} from './compass-assistant-provider';
8+
import { usePreference } from 'compass-preferences-model/provider';
9+
10+
/**
11+
* CompassAssistantDrawer component that wraps AssistantChat in a DrawerSection.
12+
* This component can be placed at any level in the component tree as long as
13+
* it's within an AssistantProvider.
14+
*/
15+
export const CompassAssistantDrawer: React.FunctionComponent<{
16+
autoOpen?: boolean;
17+
}> = ({ autoOpen }) => {
18+
const chat = useContext(AssistantContext);
19+
20+
const enableAIAssistant = usePreference('enableAIAssistant');
21+
22+
if (!enableAIAssistant) {
23+
return null;
24+
}
25+
26+
if (!chat) {
27+
throw new Error(
28+
'CompassAssistantDrawer must be used within an CompassAssistantProvider'
29+
);
30+
}
31+
32+
return (
33+
<DrawerSection
34+
id={ASSISTANT_DRAWER_ID}
35+
title="MongoDB Assistant"
36+
label="MongoDB Assistant"
37+
glyph="Sparkle"
38+
autoOpen={autoOpen}
39+
>
40+
<AssistantChat chat={chat} />
41+
</DrawerSection>
42+
);
43+
};

0 commit comments

Comments
 (0)