Skip to content

Commit 9ab5ebb

Browse files
committed
feat: add some tests
1 parent 95ddec6 commit 9ab5ebb

File tree

9 files changed

+237
-23
lines changed

9 files changed

+237
-23
lines changed

package-lock.json

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-assistant/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@
5656
"ai": "^5.0.5",
5757
"compass-preferences-model": "^2.49.0",
5858
"react": "^17.0.2",
59-
"use-sync-external-store": "^1.5.0"
59+
"use-sync-external-store": "^1.5.0",
60+
"throttleit": "^2.1.0"
6061
},
6162
"devDependencies": {
62-
"throttleit": "^2.1.0",
6363
"@mongodb-js/eslint-config-compass": "^1.4.5",
6464
"@mongodb-js/mocha-config-compass": "^1.7.0",
6565
"@mongodb-js/prettier-config-compass": "^1.2.8",

packages/compass-assistant/src/@ai-sdk/react/use-chat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import type { AbstractChat, ChatInit, CreateUIMessage, UIMessage } from 'ai';
1515
import { useCallback, useEffect, useRef } from 'react';
1616
import { useSyncExternalStore } from 'use-sync-external-store/shim';
17-
import { Chat } from './chat.react';
17+
import { Chat } from './chat-react';
1818

1919
export type { CreateUIMessage, UIMessage };
2020

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import React from 'react';
2+
import { render, screen, userEvent } from '@mongodb-js/testing-library-compass';
3+
import { AssistantChat } from './assistant-chat';
4+
import { expect } from 'chai';
5+
import type { UIMessage } from './@ai-sdk/react/use-chat';
6+
7+
describe('AssistantChat', function () {
8+
const mockMessages: UIMessage[] = [
9+
{
10+
id: '1',
11+
role: 'user',
12+
parts: [{ type: 'text', text: 'Hello, MongoDB Assistant!' }],
13+
},
14+
{
15+
id: '2',
16+
role: 'assistant',
17+
parts: [
18+
{
19+
type: 'text',
20+
text: 'Hello! How can I help you with MongoDB today?',
21+
},
22+
],
23+
},
24+
];
25+
26+
it('renders input field and send button', function () {
27+
render(<AssistantChat messages={[]} />);
28+
29+
const inputField = screen.getByTestId('assistant-chat-input');
30+
const sendButton = screen.getByTestId('assistant-chat-send-button');
31+
32+
expect(inputField).to.exist;
33+
expect(sendButton).to.exist;
34+
});
35+
36+
it('input field accepts text input', function () {
37+
render(<AssistantChat messages={[]} />);
38+
39+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
40+
const inputField = screen.getByTestId(
41+
'assistant-chat-input'
42+
) as HTMLInputElement;
43+
44+
userEvent.type(inputField, 'What is MongoDB?');
45+
46+
expect(inputField.value).to.equal('What is MongoDB?');
47+
});
48+
49+
it('send button is disabled when input is empty', function () {
50+
render(<AssistantChat messages={[]} />);
51+
52+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
53+
const sendButton = screen.getByTestId(
54+
'assistant-chat-send-button'
55+
) as HTMLButtonElement;
56+
57+
expect(sendButton.disabled).to.be.true;
58+
});
59+
60+
it('send button is enabled when input has text', function () {
61+
render(<AssistantChat messages={[]} />);
62+
63+
const inputField = screen.getByTestId('assistant-chat-input');
64+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
65+
const sendButton = screen.getByTestId(
66+
'assistant-chat-send-button'
67+
) as HTMLButtonElement;
68+
69+
userEvent.type(inputField, 'What is MongoDB?');
70+
71+
expect(sendButton.disabled).to.be.false;
72+
});
73+
74+
it('send button is disabled for whitespace-only input', function () {
75+
render(<AssistantChat messages={[]} />);
76+
77+
const inputField = screen.getByTestId('assistant-chat-input');
78+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
79+
const sendButton = screen.getByTestId(
80+
'assistant-chat-send-button'
81+
) as HTMLButtonElement;
82+
83+
userEvent.type(inputField, ' ');
84+
85+
expect(sendButton.disabled).to.be.true;
86+
});
87+
88+
it('displays messages in the chat feed', function () {
89+
render(<AssistantChat messages={mockMessages} />);
90+
91+
expect(screen.getByTestId('assistant-message-user')).to.exist;
92+
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;
96+
});
97+
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+
106+
const inputField = screen.getByTestId('assistant-chat-input');
107+
const sendButton = screen.getByTestId('assistant-chat-send-button');
108+
109+
userEvent.type(inputField, 'What is aggregation?');
110+
userEvent.click(sendButton);
111+
112+
expect(sentMessage).to.equal('What is aggregation?');
113+
});
114+
115+
it('clears input field after successful submission', function () {
116+
const handleSendMessage = () => {};
117+
118+
render(<AssistantChat messages={[]} onSendMessage={handleSendMessage} />);
119+
120+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
121+
const inputField = screen.getByTestId(
122+
'assistant-chat-input'
123+
) as HTMLInputElement;
124+
125+
userEvent.type(inputField, 'Test message');
126+
expect(inputField.value).to.equal('Test message');
127+
128+
userEvent.click(screen.getByTestId('assistant-chat-send-button'));
129+
expect(inputField.value).to.equal('');
130+
});
131+
132+
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} />);
139+
140+
const inputField = screen.getByTestId('assistant-chat-input');
141+
142+
userEvent.type(inputField, ' What is sharding? ');
143+
userEvent.click(screen.getByTestId('assistant-chat-send-button'));
144+
145+
expect(sentMessage).to.equal('What is sharding?');
146+
});
147+
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} />);
155+
156+
const inputField = screen.getByTestId('assistant-chat-input');
157+
const chatForm = screen.getByTestId('assistant-chat-form');
158+
159+
// Test empty input
160+
userEvent.click(chatForm);
161+
expect(messageSent).to.be.false;
162+
163+
// Test whitespace-only input
164+
userEvent.type(inputField, ' ');
165+
userEvent.click(chatForm);
166+
expect(messageSent).to.be.false;
167+
});
168+
169+
it('displays user and assistant messages with different styling', function () {
170+
render(<AssistantChat messages={mockMessages} />);
171+
172+
const userMessage = screen.getByTestId('assistant-message-user');
173+
const assistantMessage = screen.getByTestId('assistant-message-assistant');
174+
175+
// User messages should have different background color than assistant messages
176+
expect(userMessage).to.exist;
177+
expect(assistantMessage).to.exist;
178+
179+
const userStyle = window.getComputedStyle(userMessage);
180+
const assistantStyle = window.getComputedStyle(assistantMessage);
181+
182+
expect(userStyle.backgroundColor).to.not.equal(
183+
assistantStyle.backgroundColor
184+
);
185+
});
186+
187+
it('handles messages with multiple text parts', function () {
188+
const messagesWithMultipleParts: UIMessage[] = [
189+
{
190+
id: '1',
191+
role: 'assistant',
192+
parts: [
193+
{ type: 'text', text: 'Here is part 1. ' },
194+
{ type: 'text', text: 'And here is part 2.' },
195+
],
196+
},
197+
];
198+
199+
render(<AssistantChat messages={messagesWithMultipleParts} />);
200+
201+
expect(screen.getByText('Here is part 1. And here is part 2.')).to.exist;
202+
});
203+
204+
it('handles messages with mixed part types (filters to text only)', function () {
205+
const messagesWithMixedParts: UIMessage[] = [
206+
{
207+
id: '1',
208+
role: 'assistant',
209+
parts: [
210+
{ type: 'text', text: 'This is text content.' },
211+
{ type: 'tool-call' as any, text: 'This should be filtered out.' },
212+
{ type: 'text', text: ' More text content.' },
213+
],
214+
},
215+
];
216+
217+
render(<AssistantChat messages={messagesWithMixedParts} />);
218+
219+
expect(screen.getByText('This is text content. More text content.')).to
220+
.exist;
221+
expect(screen.queryByText('This should be filtered out.')).to.not.exist;
222+
});
223+
});

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
3535
height: '100%',
3636
width: '100%',
3737
}}
38+
data-testid="assistant-chat"
3839
>
3940
{/* Message Feed */}
4041
<div
42+
data-testid="assistant-chat-messages"
4143
style={{
4244
width: '100%',
4345
flex: 1,
@@ -51,6 +53,7 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
5153
{messages.map((message) => (
5254
<div
5355
key={message.id}
56+
data-testid={`assistant-message-${message.role}`}
5457
style={{
5558
marginBottom: '12px',
5659
padding: '8px 12px',
@@ -73,6 +76,7 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
7376

7477
{/* Input Bar */}
7578
<form
79+
data-testid="assistant-chat-form"
7680
onSubmit={handleInputSubmit}
7781
style={{
7882
display: 'flex',
@@ -85,6 +89,7 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
8589
}}
8690
>
8791
<input
92+
data-testid="assistant-chat-input"
8893
type="text"
8994
value={inputValue}
9095
onChange={(e) => setInputValue(e.target.value)}
@@ -98,6 +103,7 @@ export const AssistantChat: React.FunctionComponent<AssistantChatProps> = ({
98103
}}
99104
/>
100105
<button
106+
data-testid="assistant-chat-send-button"
101107
type="submit"
102108
disabled={!inputValue.trim()}
103109
style={{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { DrawerSection } from '@mongodb-js/compass-components';
22
import React, { type PropsWithChildren, useCallback, useRef } from 'react';
33
import { type UIMessage, useChat } from './@ai-sdk/react/use-chat';
4-
import type { Chat } from './@ai-sdk/react/chat.react';
4+
import type { Chat } from './@ai-sdk/react/chat-react';
55
import { AssistantChat } from './assistant-chat';
66
import { usePreference } from 'compass-preferences-model/provider';
77

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

Lines changed: 0 additions & 12 deletions
This file was deleted.

packages/compass-assistant/src/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { registerCompassPlugin } from '@mongodb-js/compass-app-registry';
2-
import { createLoggerLocator } from '@mongodb-js/compass-logging/provider';
32
import { AssistantProvider } from './assistant-provider';
4-
import { Chat } from './@ai-sdk/react/chat.react';
3+
import { Chat } from './@ai-sdk/react/chat-react';
54
import { docsProviderTransport } from './docs-provider-transport';
65

76
const CompassAssistantProvider = registerCompassPlugin(
@@ -17,7 +16,7 @@ const CompassAssistantProvider = registerCompassPlugin(
1716
},
1817
},
1918
{
20-
logger: createLoggerLocator('COMPASS-ASSISTANT'),
19+
transport: () => docsProviderTransport,
2120
}
2221
);
2322

0 commit comments

Comments
 (0)