diff --git a/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js b/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js index 64eb311ed42..c8e938dc10b 100644 --- a/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js +++ b/configs/mocha-config-compass/register/jsdom-extra-mocks-register.js @@ -51,3 +51,6 @@ globalThis.CustomEvent = window.CustomEvent; globalThis.Event = window.Event; globalThis.Blob = window.Blob; globalThis.File = window.File; + +// jsdom doesn't support scrollTo on the Element, so make it a no-op +globalThis.Element.prototype.scrollTo = () => {}; diff --git a/configs/webpack-config-compass/src/index.ts b/configs/webpack-config-compass/src/index.ts index 3a7ae22794d..59d6065e9ea 100644 --- a/configs/webpack-config-compass/src/index.ts +++ b/configs/webpack-config-compass/src/index.ts @@ -279,7 +279,23 @@ export function createElectronRendererConfig( overlay: process.env.DISABLE_DEVSERVER_OVERLAY === 'true' ? false - : { warnings: false, errors: true, runtimeErrors: true }, + : { + runtimeErrors: (error) => { + // ResizeObserver errors are harmless and expected in some cases. + // We currently get them when opening the Assistant drawer. + if ( + error?.message === + 'ResizeObserver loop completed with undelivered notifications.' + ) { + // eslint-disable-next-line no-console + console.warn(error); + return false; + } + return true; + }, + errors: true, + warnings: false, + }, }, https: false, hot: opts.hot, diff --git a/packages/compass-assistant/src/assistant-chat.spec.tsx b/packages/compass-assistant/src/assistant-chat.spec.tsx index 93c17215831..8de0b4a5885 100644 --- a/packages/compass-assistant/src/assistant-chat.spec.tsx +++ b/packages/compass-assistant/src/assistant-chat.spec.tsx @@ -1,12 +1,26 @@ import React from 'react'; -import { render, screen, userEvent } from '@mongodb-js/testing-library-compass'; +import { + render, + screen, + userEvent, + waitFor, +} from '@mongodb-js/testing-library-compass'; import { AssistantChat } from './assistant-chat'; import { expect } from 'chai'; import { createMockChat } from '../test/utils'; import type { AssistantMessage } from './compass-assistant-provider'; -// TODO: some internal logic in lg-chat breaks all these tests, re-enable the tests -describe.skip('AssistantChat', function () { +describe('AssistantChat', function () { + let originalScrollTo: typeof Element.prototype.scrollTo; + // Mock scrollTo method for DOM elements to prevent test failures + before(function () { + originalScrollTo = Element.prototype.scrollTo.bind(Element.prototype); + Element.prototype.scrollTo = () => {}; + }); + after(function () { + Element.prototype.scrollTo = originalScrollTo; + }); + const mockMessages: AssistantMessage[] = [ { id: 'user', @@ -36,8 +50,10 @@ describe.skip('AssistantChat', function () { it('renders input field and send button', function () { renderWithChat([]); - const inputField = screen.getByTestId('assistant-chat-input'); - const sendButton = screen.getByTestId('assistant-chat-send-button'); + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); + const sendButton = screen.getByLabelText('Send message'); expect(inputField).to.exist; expect(sendButton).to.exist; @@ -47,9 +63,9 @@ describe.skip('AssistantChat', function () { renderWithChat([]); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const inputField = screen.getByTestId( - 'assistant-chat-input' - ) as HTMLInputElement; + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ) as HTMLTextAreaElement; userEvent.type(inputField, 'What is MongoDB?'); @@ -60,20 +76,22 @@ describe.skip('AssistantChat', function () { renderWithChat([]); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const sendButton = screen.getByTestId( - 'assistant-chat-send-button' + const sendButton = screen.getByLabelText( + 'Send message' ) as HTMLButtonElement; - expect(sendButton.disabled).to.be.true; + expect(sendButton.getAttribute('aria-disabled')).to.equal('true'); }); it('send button is enabled when input has text', function () { renderWithChat([]); - const inputField = screen.getByTestId('assistant-chat-input'); + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const sendButton = screen.getByTestId( - 'assistant-chat-send-button' + const sendButton = screen.getByLabelText( + 'Send message' ) as HTMLButtonElement; userEvent.type(inputField, 'What is MongoDB?'); @@ -81,18 +99,23 @@ describe.skip('AssistantChat', function () { expect(sendButton.disabled).to.be.false; }); - it('send button is disabled for whitespace-only input', function () { + // Not currently supported by the LeafyGreen Input Bar + it.skip('send button is disabled for whitespace-only input', async function () { renderWithChat([]); - const inputField = screen.getByTestId('assistant-chat-input'); + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const sendButton = screen.getByTestId( - 'assistant-chat-send-button' + const sendButton = screen.getByLabelText( + 'Send message' ) as HTMLButtonElement; userEvent.type(inputField, ' '); - expect(sendButton.disabled).to.be.true; + await waitFor(() => { + expect(sendButton.getAttribute('aria-disabled')).to.equal('true'); + }); }); it('displays messages in the chat feed', function () { @@ -100,18 +123,20 @@ describe.skip('AssistantChat', function () { expect(screen.getByTestId('assistant-message-user')).to.exist; expect(screen.getByTestId('assistant-message-assistant')).to.exist; - expect(screen.getByTestId('assistant-message-user')).to.have.text( + expect(screen.getByTestId('assistant-message-user')).to.contain.text( 'Hello, MongoDB Assistant!' ); - expect(screen.getByTestId('assistant-message-assistant')).to.have.text( + expect(screen.getByTestId('assistant-message-assistant')).to.contain.text( 'Hello! How can I help you with MongoDB today?' ); }); it('calls sendMessage when form is submitted', function () { const { chat } = renderWithChat([]); - const inputField = screen.getByTestId('assistant-chat-input'); - const sendButton = screen.getByTestId('assistant-chat-send-button'); + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); + const sendButton = screen.getByLabelText('Send message'); userEvent.type(inputField, 'What is aggregation?'); userEvent.click(sendButton); @@ -124,24 +149,26 @@ describe.skip('AssistantChat', function () { renderWithChat([]); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const inputField = screen.getByTestId( - 'assistant-chat-input' - ) as HTMLInputElement; + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ) as HTMLTextAreaElement; userEvent.type(inputField, 'Test message'); expect(inputField.value).to.equal('Test message'); - userEvent.click(screen.getByTestId('assistant-chat-send-button')); + userEvent.click(screen.getByLabelText('Send message')); expect(inputField.value).to.equal(''); }); it('trims whitespace from input before sending', function () { const { chat } = renderWithChat([]); - const inputField = screen.getByTestId('assistant-chat-input'); + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); userEvent.type(inputField, ' What is sharding? '); - userEvent.click(screen.getByTestId('assistant-chat-send-button')); + userEvent.click(screen.getByLabelText('Send message')); expect(chat.sendMessage.calledWith({ text: 'What is sharding?' })).to.be .true; @@ -150,8 +177,10 @@ describe.skip('AssistantChat', function () { it('does not call sendMessage when input is empty or whitespace-only', function () { const { chat } = renderWithChat([]); - const inputField = screen.getByTestId('assistant-chat-input'); - const chatForm = screen.getByTestId('assistant-chat-form'); + const inputField = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); + const chatForm = screen.getByTestId('assistant-chat-input'); // Test empty input userEvent.click(chatForm); @@ -169,16 +198,12 @@ describe.skip('AssistantChat', function () { const userMessage = screen.getByTestId('assistant-message-user'); const assistantMessage = screen.getByTestId('assistant-message-assistant'); - // User messages should have different background color than assistant messages + // User messages should have different class names than assistant messages expect(userMessage).to.exist; expect(assistantMessage).to.exist; - const userStyle = window.getComputedStyle(userMessage); - const assistantStyle = window.getComputedStyle(assistantMessage); - - expect(userStyle.backgroundColor).to.not.equal( - assistantStyle.backgroundColor - ); + // Check that they have different class names (indicating different styling) + expect(userMessage.className).to.not.equal(assistantMessage.className); }); it('handles messages with multiple text parts', function () { diff --git a/packages/compass-assistant/src/assistant-chat.tsx b/packages/compass-assistant/src/assistant-chat.tsx index f0fb335f1aa..5f1a04ac201 100644 --- a/packages/compass-assistant/src/assistant-chat.tsx +++ b/packages/compass-assistant/src/assistant-chat.tsx @@ -8,6 +8,8 @@ import { LgChatMessage, LgChatMessageFeed, LgChatInputBar, + spacing, + css, } from '@mongodb-js/compass-components'; const { ChatWindow } = LgChatChatWindow; @@ -20,6 +22,20 @@ interface AssistantChatProps { chat: Chat; } +// TODO(COMPASS-9751): These are temporary patches to make the Assistant chat take the entire +// width and height of the drawer since Leafygreen doesn't support this yet. +const assistantChatFixesStyles = css({ + // Negative margin to patch the padding of the drawer. + margin: -spacing[400], + '> div, > div > div, > div > div > div, > div > div > div > div': { + height: '100%', + }, +}); +const messageFeedFixesStyles = css({ height: '100%' }); +const chatWindowFixesStyles = css({ + height: '100%', +}); + export const AssistantChat: React.FunctionComponent = ({ chat, }) => { @@ -42,19 +58,30 @@ export const AssistantChat: React.FunctionComponent = ({ const handleMessageSend = useCallback( (messageBody: string) => { - void sendMessage({ text: messageBody }); + const trimmedMessageBody = messageBody.trim(); + if (trimmedMessageBody) { + void sendMessage({ text: trimmedMessageBody }); + } }, [sendMessage] ); return ( -
+
- - + + {lgMessages.map((messageFields) => ( diff --git a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx index e5442c5e691..56b896f09b0 100644 --- a/packages/compass-assistant/src/compass-assistant-provider.spec.tsx +++ b/packages/compass-assistant/src/compass-assistant-provider.spec.tsx @@ -84,8 +84,7 @@ describe('AssistantProvider', function () { ); }); - // TODO: some internal logic in lg-chat breaks all these tests, re-enable the tests - describe.skip('with existing chat instance', function () { + describe('with existing chat instance', function () { before(function () { // TODO(COMPASS-9618): skip in electron runtime for now, drawer has issues rendering if ((process as any).type === 'renderer') { @@ -118,7 +117,7 @@ describe('AssistantProvider', function () { ); expect(screen.getByTestId('assistant-message-2')).to.exist; - expect(screen.getByTestId('assistant-message-2')).to.have.text( + expect(screen.getByTestId('assistant-message-2')).to.contain.text( 'Test assistant message' ); }); @@ -138,8 +137,10 @@ describe('AssistantProvider', function () { await renderOpenAssistantDrawer(mockChat); - const input = screen.getByTestId('assistant-chat-input'); - const sendButton = screen.getByTestId('assistant-chat-send-button'); + const input = screen.getByPlaceholderText( + 'Ask MongoDB Assistant a question' + ); + const sendButton = screen.getByLabelText('Send message'); userEvent.type(input, 'Hello assistant'); userEvent.click(sendButton); @@ -176,10 +177,10 @@ describe('AssistantProvider', function () { await renderOpenAssistantDrawer(mockChat); userEvent.type( - screen.getByTestId('assistant-chat-input'), + screen.getByPlaceholderText('Ask MongoDB Assistant a question'), 'Hello assistant!' ); - userEvent.click(screen.getByTestId('assistant-chat-send-button')); + userEvent.click(screen.getByLabelText('Send message')); expect(sendMessageSpy.calledOnce).to.be.true; expect(sendMessageSpy.firstCall.args[0]).to.deep.include({