(false);
// The list of message DOM and their rendered promises.
@@ -84,7 +86,6 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
}
fetchHistory();
- setCurrentWriters([]);
}, [model]);
/**
@@ -95,16 +96,10 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
setMessages([...model.messages]);
}
- function handleWritersChange(_: IChatModel, writers: IChatModel.IWriter[]) {
- setCurrentWriters(writers.map(writer => writer.user));
- }
-
model.messagesUpdated.connect(handleChatEvents);
- model.writersChanged?.connect(handleWritersChange);
return function cleanup() {
model.messagesUpdated.disconnect(handleChatEvents);
- model.writersChanged?.disconnect(handleChatEvents);
};
}, [model]);
@@ -170,6 +165,7 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
};
}, [messages, allRendered]);
+ const horizontalPadding = props.area === 'main' ? 8 : 4;
return (
<>
@@ -179,39 +175,67 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
content={props.welcomeMessage}
/>
)}
-
- {messages.map((message, i) => {
- renderedPromise.current[i] = new PromiseDelegate();
- return (
- // extra div needed to ensure each bubble is on a new line
-
-
- (listRef.current[i] = el)}
- />
- {props.messageFooterRegistry && (
-
+ {messages
+ .filter(message => !message.deleted)
+ .map((message, i) => {
+ renderedPromise.current[i] = new PromiseDelegate();
+ const isCurrentUser =
+ model.user !== undefined &&
+ model.user.username === message.sender.username;
+ return (
+ // extra div needed to ensure each bubble is on a new line
+
+
+ (listRef.current[i] = el)}
/>
- )}
-
- );
- })}
+ {props.messageFooterRegistry && (
+
+ )}
+
+ );
+ })}
-
>
);
diff --git a/packages/jupyter-chat/src/components/messages/toolbar.tsx b/packages/jupyter-chat/src/components/messages/toolbar.tsx
index 45ef2a70..b7fe0b9d 100644
--- a/packages/jupyter-chat/src/components/messages/toolbar.tsx
+++ b/packages/jupyter-chat/src/components/messages/toolbar.tsx
@@ -3,11 +3,9 @@
* Distributed under the terms of the Modified BSD License.
*/
-import {
- ToolbarButtonComponent,
- deleteIcon,
- editIcon
-} from '@jupyterlab/ui-components';
+// import EditIcon from '@mui/icons-material/Edit';
+import DeleteIcon from '@mui/icons-material/Delete';
+import { Box, IconButton, Tooltip } from '@mui/material';
import React from 'react';
const TOOLBAR_CLASS = 'jp-chat-toolbar';
@@ -18,27 +16,59 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
export function MessageToolbar(props: MessageToolbar.IProps): JSX.Element {
const buttons: JSX.Element[] = [];
- if (props.edit !== undefined) {
- const editButton = ToolbarButtonComponent({
- icon: editIcon,
- onClick: props.edit,
- tooltip: 'Edit'
- });
- buttons.push(editButton);
- }
+ // if (props.edit !== undefined) {
+ // const editButton = (
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // );
+ // buttons.push(editButton);
+ // }
if (props.delete !== undefined) {
- const deleteButton = ToolbarButtonComponent({
- icon: deleteIcon,
- onClick: props.delete,
- tooltip: 'Delete'
- });
+ const deleteButton = (
+
+
+
+
+
+
+
+ );
buttons.push(deleteButton);
}
return (
-
- {buttons.map(toolbarButton => toolbarButton)}
-
+
+ {buttons}
+
);
}
diff --git a/packages/jupyter-chat/src/components/messages/writers.tsx b/packages/jupyter-chat/src/components/messages/writers.tsx
deleted file mode 100644
index b5d5cd92..00000000
--- a/packages/jupyter-chat/src/components/messages/writers.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) Jupyter Development Team.
- * Distributed under the terms of the Modified BSD License.
- */
-
-import { Box, Typography } from '@mui/material';
-import React, { useMemo } from 'react';
-
-import { Avatar } from '../avatar';
-import { IUser } from '../../types';
-
-const WRITERS_CLASS = 'jp-chat-writers';
-
-/**
- * The writers component props.
- */
-type writersProps = {
- /**
- * The list of users currently writing.
- */
- writers: IUser[];
-};
-
-/**
- * Animated typing indicator component
- */
-const TypingIndicator = (): JSX.Element => (
-
-
-
-
-
-);
-
-/**
- * The writers component, displaying the current writers.
- */
-export function WritingUsersList(props: writersProps): JSX.Element | null {
- const { writers } = props;
-
- // Don't render if no writers
- if (writers.length === 0) {
- return null;
- }
-
- const writersText = writers.length > 1 ? ' are writing' : ' is writing';
-
- const writingUsers: JSX.Element[] = useMemo(
- () =>
- writers.map((writer, index) => (
-
-
-
- {writer.display_name ??
- writer.name ??
- (writer.username || 'User undefined')}
-
- {index < writers.length - 1 && (
-
- {index < writers.length - 2 ? ', ' : ' and '}
-
- )}
-
- )),
- [writers]
- );
-
- return (
-
-
- {writingUsers}
-
-
- {writersText}
-
-
-
-
-
- );
-}
diff --git a/packages/jupyter-chat/src/types.ts b/packages/jupyter-chat/src/types.ts
index cdd395f9..808b4a51 100644
--- a/packages/jupyter-chat/src/types.ts
+++ b/packages/jupyter-chat/src/types.ts
@@ -172,3 +172,8 @@ export interface IAttachmentSelection {
* An empty interface to describe optional settings that could be fetched from server.
*/
export interface ISettings {} /* eslint-disable-line @typescript-eslint/no-empty-object-type */
+
+/**
+ * The area where the chat is displayed.
+ */
+export type ChatArea = 'sidebar' | 'main';
diff --git a/packages/jupyter-chat/src/widgets/multichat-panel.tsx b/packages/jupyter-chat/src/widgets/multichat-panel.tsx
index 582121a8..515e4306 100644
--- a/packages/jupyter-chat/src/widgets/multichat-panel.tsx
+++ b/packages/jupyter-chat/src/widgets/multichat-panel.tsx
@@ -155,7 +155,8 @@ export class MultiChatPanel extends SidePanel {
attachmentOpenerRegistry: this._attachmentOpenerRegistry,
inputToolbarRegistry,
messageFooterRegistry: this._messageFooterRegistry,
- welcomeMessage: this._welcomeMessage
+ welcomeMessage: this._welcomeMessage,
+ area: 'sidebar'
});
const section = new ChatSection({
diff --git a/packages/jupyter-chat/style/chat.css b/packages/jupyter-chat/style/chat.css
index bf07bded..0d11567a 100644
--- a/packages/jupyter-chat/style/chat.css
+++ b/packages/jupyter-chat/style/chat.css
@@ -2,20 +2,18 @@
* Copyright (c) Jupyter Development Team.
* Distributed under the terms of the Modified BSD License.
*/
-.jp-chat-message:not(.jp-chat-message-stacked) {
- padding: 1em 1em 0;
-}
-.jp-chat-message:not(:first-child, .jp-chat-message-stacked) {
- border-top: 1px solid var(--jp-border-color2);
+.jp-chat-rendered-markdown {
+ position: relative;
}
-.jp-chat-message.jp-chat-message-stacked {
- padding: 0 1em;
+.jp-chat-rendered-markdown hr {
+ color: #00000026;
+ background-color: transparent;
}
-.jp-chat-rendered-markdown {
- position: relative;
+.jp-chat-rendered-markdown .jp-RenderedHTMLCommon > :last-child {
+ margin-bottom: 0;
}
/*
@@ -38,7 +36,7 @@
overflow-x: auto;
white-space: pre;
margin: 0;
- padding: 4px 2px 0 6px;
+ padding: 4px 6px;
border: var(--jp-border-width) solid var(--jp-cell-editor-border-color);
}
@@ -53,7 +51,7 @@
}
.jp-chat-toolbar {
- display: none;
+ visibility: hidden;
position: absolute;
right: 2px;
top: 2px;
@@ -67,107 +65,13 @@
}
.jp-chat-rendered-markdown:hover .jp-chat-toolbar {
- display: inherit;
+ visibility: visible;
}
.jp-chat-toolbar > .jp-ToolbarButtonComponent {
margin-top: 0;
}
-.jp-chat-writers {
- display: flex;
- flex-wrap: wrap;
- position: sticky;
- bottom: 0;
- padding: 8px;
- background-color: var(--jp-layout-color0);
- border-top: 1px solid var(--jp-border-color2);
- z-index: 1;
-}
-
-.jp-chat-writers-content {
- display: flex;
- align-items: center;
- gap: 4px;
- flex-wrap: wrap;
-}
-
-.jp-chat-writer-item {
- display: flex;
- align-items: center;
- gap: 6px;
-}
-
-.jp-chat-writer-name {
- color: var(--jp-ui-font-color1);
- font-weight: 500;
-}
-
-.jp-chat-writer-separator {
- color: var(--jp-ui-font-color2);
-}
-
-.jp-chat-writing-status {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.jp-chat-writing-text {
- color: var(--jp-ui-font-color2);
-}
-
-/* Animated typing indicator */
-.jp-chat-typing-indicator {
- display: flex;
- align-items: center;
- gap: 2px;
- padding: 2px 4px;
-}
-
-.jp-chat-typing-dot {
- width: 4px;
- height: 4px;
- border-radius: 50%;
- background-color: var(--jp-brand-color1);
- animation: jp-chat-typing-bounce 1.4s infinite ease-in-out;
-}
-
-.jp-chat-typing-dot:nth-child(1) {
- animation-delay: -0.32s;
-}
-
-.jp-chat-typing-dot:nth-child(2) {
- animation-delay: -0.16s;
-}
-
-.jp-chat-typing-dot:nth-child(3) {
- animation-delay: 0s;
-}
-
-/* Keyframe animations */
-@keyframes jp-chat-typing-bounce {
- 0%,
- 80%,
- 100% {
- transform: scale(0.8);
- opacity: 0.5;
- }
-
- 40% {
- transform: scale(1.2);
- opacity: 1;
- }
-}
-
-.jp-chat-writers > div {
- display: flex;
- align-items: center;
- gap: 0.2em;
- white-space: pre;
- padding-left: 0.5em;
-}
-
.jp-chat-navigation {
position: absolute;
right: 10px;
@@ -197,40 +101,8 @@
bottom: 120px;
}
-.jp-chat-attachments {
- display: flex;
- gap: 4px;
- flex-wrap: wrap;
- min-height: 1.5em;
- padding: 4px 0;
-}
-
-.jp-chat-attachment {
- border: solid 1px;
- border-radius: 10px;
- margin: 0 0.2em;
- padding: 0 0.3em;
- align-content: center;
- background-color: var(--jp-border-color3);
- flex-shrink: 0;
-}
-
-.jp-chat-attachment .jp-chat-attachment-clickable:hover {
- cursor: pointer;
-}
-
-.jp-chat-command-name {
- font-weight: normal;
- margin: 5px;
-}
-
-.jp-chat-command-description {
- color: gray;
- margin: 5px;
-}
-
.jp-chat-mention {
- border-radius: 10px;
- padding: 0 0.2em;
- background-color: var(--jp-brand-color4);
+ border-radius: 4px;
+ padding: 2px 0;
+ font-weight: bold;
}
diff --git a/packages/jupyter-chat/style/input.css b/packages/jupyter-chat/style/input.css
index f00673de..307b3b58 100644
--- a/packages/jupyter-chat/style/input.css
+++ b/packages/jupyter-chat/style/input.css
@@ -42,64 +42,6 @@
white-space: nowrap;
}
-/*
- * INPUT TEXT FIELD
- */
-.jp-chat-input-component {
- border: 1px solid var(--jp-ui-font-color3);
- border-radius: 4px;
-}
-
-.jp-chat-input-textfield .jp-chat-input-component::before,
-.jp-chat-input-textfield .jp-chat-input-component::after {
- border-bottom: unset !important;
-}
-
-/* Use the textfield label below the input. */
-.jp-chat-input-textfield .jp-chat-input-component {
- padding-top: 8px !important;
- padding-bottom: 15px !important;
- background-color: unset !important;
-}
-
-.jp-chat-input-textfield label {
- top: unset;
- left: unset;
- bottom: 0;
- right: 0;
- color: var(--jp-ui-font-color2) !important;
-}
-
-/*
- * INPUT TOOLBAR
- */
-.jp-chat-input-toolbar {
- gap: 1px;
- align-self: flex-end;
- min-height: unset !important;
- margin-bottom: 4px;
-}
-
-.jp-chat-input-toolbar .jp-chat-tooltipped-wrap button {
- border-radius: 0;
- min-width: unset;
-}
-
-.jp-chat-input-toolbar .jp-chat-tooltipped-wrap:first-child button {
- border-top-left-radius: 2px;
- border-bottom-left-radius: 2px;
-}
-
-.jp-chat-input-toolbar .jp-chat-tooltipped-wrap:last-child button {
- border-top-right-radius: 2px;
- border-bottom-right-radius: 2px;
-}
-
-.jp-chat-input-toolbar .jp-chat-attach-button,
-.jp-chat-input-toolbar .jp-chat-cancel-button {
- padding: 4px;
-}
-
.jp-chat-input-toolbar .jp-chat-send-include-opener {
padding: 4px 0;
}
diff --git a/packages/jupyterlab-chat/src/factory.ts b/packages/jupyterlab-chat/src/factory.ts
index 2badb370..22d250f9 100644
--- a/packages/jupyterlab-chat/src/factory.ts
+++ b/packages/jupyterlab-chat/src/factory.ts
@@ -4,6 +4,7 @@
*/
import {
+ ChatArea,
ChatWidget,
IActiveCellManager,
IAttachmentOpenerRegistry,
@@ -99,6 +100,7 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
context.attachmentOpenerRegistry = this._attachmentOpenerRegistry;
context.messageFooterRegistry = this._messageFooterRegistry;
context.welcomeMessage = this._welcomeMessage;
+ context.area = 'main';
if (this._inputToolbarFactory) {
context.inputToolbarRegistry = this._inputToolbarFactory.create();
}
@@ -137,6 +139,7 @@ export namespace ChatWidgetFactory {
inputToolbarRegistry?: IInputToolbarRegistry;
messageFooterRegistry?: IMessageFooterRegistry;
welcomeMessage?: string;
+ area?: ChatArea;
}
export interface IOptions
diff --git a/ui-tests/tests/code-toolbar.spec.ts b/ui-tests/tests/code-toolbar.spec.ts
index 4817cd69..e947060b 100644
--- a/ui-tests/tests/code-toolbar.spec.ts
+++ b/ui-tests/tests/code-toolbar.spec.ts
@@ -58,7 +58,7 @@ test.describe('#codeToolbar', () => {
test('buttons should be disabled without notebook', async ({ page }) => {
const chatPanel = await openChat(page, FILENAME);
const message = chatPanel.locator('.jp-chat-message');
- const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button');
+ const toolbarButtons = message.locator('.jp-chat-code-toolbar-item');
await sendMessage(page, FILENAME, MESSAGE);
await expect(toolbarButtons).toHaveCount(4);
@@ -73,7 +73,7 @@ test.describe('#codeToolbar', () => {
}) => {
const chatPanel = await openChat(page, FILENAME);
const message = chatPanel.locator('.jp-chat-message');
- const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button');
+ const toolbarButtons = message.locator('.jp-chat-code-toolbar-item');
await page.notebook.createNew();
@@ -88,7 +88,7 @@ test.describe('#codeToolbar', () => {
}) => {
const chatPanel = await openChat(page, FILENAME);
const message = chatPanel.locator('.jp-chat-message');
- const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button');
+ const toolbarButtons = message.locator('.jp-chat-code-toolbar-item');
const notebook = await page.notebook.createNew();
@@ -102,7 +102,7 @@ test.describe('#codeToolbar', () => {
test('insert code above', async ({ page }) => {
const chatPanel = await openChat(page, FILENAME);
const message = chatPanel.locator('.jp-chat-message');
- const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button');
+ const toolbarButtons = message.locator('.jp-chat-code-toolbar-item');
const notebook = await page.notebook.createNew();
@@ -123,7 +123,7 @@ test.describe('#codeToolbar', () => {
test('insert code below', async ({ page }) => {
const chatPanel = await openChat(page, FILENAME);
const message = chatPanel.locator('.jp-chat-message');
- const toolbarButtons = message.locator('.jp-chat-code-toolbar-item button');
+ const toolbarButtons = message.locator('.jp-chat-code-toolbar-item');
const notebook = await page.notebook.createNew();
diff --git a/ui-tests/tests/input-toolbar.spec.ts b/ui-tests/tests/input-toolbar.spec.ts
index 870157c1..ae5cba8c 100644
--- a/ui-tests/tests/input-toolbar.spec.ts
+++ b/ui-tests/tests/input-toolbar.spec.ts
@@ -53,7 +53,7 @@ test.describe('#inputToolbar', () => {
'.jp-chat-input-container .jp-chat-input-toolbar'
);
await expect(inputToolbar).toBeVisible();
- await expect(inputToolbar.locator('button')).toHaveCount(2);
+ await expect(inputToolbar.locator('button')).toHaveCount(1);
expect(inputToolbar.locator('.jp-chat-attach-button')).not.toBeAttached();
// The side panel chat input should contain the 'attach' button.
@@ -62,7 +62,7 @@ test.describe('#inputToolbar', () => {
'.jp-chat-input-container .jp-chat-input-toolbar'
);
await expect(inputToolbarSide).toBeVisible();
- await expect(inputToolbarSide.locator('button')).toHaveCount(3);
+ await expect(inputToolbarSide.locator('button')).toHaveCount(2);
expect(inputToolbarSide.locator('.jp-chat-attach-button')).toBeAttached();
});
});
diff --git a/ui-tests/tests/message-toolbar.spec.ts b/ui-tests/tests/message-toolbar.spec.ts
index bcc1e7c7..08712577 100644
--- a/ui-tests/tests/message-toolbar.spec.ts
+++ b/ui-tests/tests/message-toolbar.spec.ts
@@ -18,8 +18,6 @@ test.use({
});
test.describe('#messageToolbar', () => {
- const additionnalContent = ' Messages can be edited';
-
const msg = {
type: 'msg',
id: UUID.uuid4(),
@@ -61,74 +59,6 @@ test.describe('#messageToolbar', () => {
await expect(message.locator('.jp-chat-toolbar')).toBeVisible();
});
- test('should update the message', async ({ page }) => {
- const chatPanel = await openChat(page, FILENAME);
- const message = chatPanel
- .locator('.jp-chat-messages-container .jp-chat-message')
- .first();
- const messageContent = message.locator('.jp-chat-rendered-markdown');
-
- // Should display the message toolbar
- await messageContent.hover({ position: { x: 5, y: 5 } });
- await messageContent.locator('.jp-chat-toolbar jp-button').first().click();
-
- await expect(messageContent).not.toBeVisible();
-
- const editInput = chatPanel
- .locator('.jp-chat-messages-container .jp-chat-input-container')
- .getByRole('combobox');
-
- await expect(editInput).toBeVisible();
- await editInput.focus();
- await editInput.press('End');
- await editInput.pressSequentially(additionnalContent);
- await editInput.press('Enter');
-
- // It seems that the markdown renderer adds a new line.
- await expect(messageContent).toHaveText(
- MSG_CONTENT + additionnalContent + '\n'
- );
- expect(
- await message.locator('.jp-chat-message-header').textContent()
- ).toContain('(edited)');
- });
-
- test('should cancel message edition', async ({ page }) => {
- const chatPanel = await openChat(page, FILENAME);
- const message = chatPanel
- .locator('.jp-chat-messages-container .jp-chat-message')
- .first();
- const messageContent = message.locator('.jp-chat-rendered-markdown');
-
- // Should display the message toolbar
- await messageContent.hover({ position: { x: 5, y: 5 } });
- await messageContent.locator('.jp-chat-toolbar jp-button').first().click();
-
- await expect(messageContent).not.toBeVisible();
-
- const editInput = chatPanel
- .locator('.jp-chat-messages-container .jp-chat-input-container')
- .getByRole('combobox');
-
- await expect(editInput).toBeVisible();
- await editInput.focus();
- await editInput.press('End');
- await editInput.pressSequentially(additionnalContent);
-
- const cancelButton = chatPanel
- .locator('.jp-chat-messages-container .jp-chat-input-container')
- .getByTitle('Cancel edition');
- await expect(cancelButton).toBeVisible();
- await cancelButton.click();
- await expect(editInput).not.toBeVisible();
-
- // It seems that the markdown renderer adds a new line.
- await expect(messageContent).toHaveText(MSG_CONTENT + '\n');
- expect(
- await message.locator('.jp-chat-message-header').textContent()
- ).not.toContain('(edited)');
- });
-
test('should set the message as deleted', async ({ page }) => {
const chatPanel = await openChat(page, FILENAME);
const message = chatPanel
@@ -138,11 +68,8 @@ test.describe('#messageToolbar', () => {
// Should display the message toolbar
await messageContent.hover({ position: { x: 5, y: 5 } });
- await messageContent.locator('.jp-chat-toolbar jp-button').last().click();
+ await messageContent.locator('.jp-chat-toolbar button').last().click();
await expect(messageContent).not.toBeVisible();
- expect(
- await message.locator('.jp-chat-message-header').textContent()
- ).toContain('(message deleted)');
});
});
diff --git a/ui-tests/tests/raw-time.spec.ts b/ui-tests/tests/raw-time.spec.ts
deleted file mode 100644
index 99e90f86..00000000
--- a/ui-tests/tests/raw-time.spec.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) Jupyter Development Team.
- * Distributed under the terms of the Modified BSD License.
- */
-
-import { expect, galata, test } from '@jupyterlab/galata';
-import { UUID } from '@lumino/coreutils';
-
-import { openChat, sendMessage, USER } from './test-utils';
-
-const FILENAME = 'my-chat.chat';
-const MSG_CONTENT = 'Hello World!';
-const USERNAME = USER.identity.username;
-
-test.use({
- mockUser: USER,
- mockSettings: { ...galata.DEFAULT_SETTINGS }
-});
-
-test.describe('#raw_time', () => {
- const msg_raw_time = {
- type: 'msg',
- id: UUID.uuid4(),
- sender: USERNAME,
- body: MSG_CONTENT,
- time: 1714116341,
- raw_time: true
- };
- const msg_verif = {
- type: 'msg',
- id: UUID.uuid4(),
- sender: USERNAME,
- body: MSG_CONTENT,
- time: 1714116341,
- raw_time: false
- };
- const chatContent = {
- messages: [msg_raw_time, msg_verif],
- users: {}
- };
- chatContent.users[USERNAME] = USER.identity;
-
- test.beforeEach(async ({ page }) => {
- // Create a chat file with content
- await page.filebrowser.contents.uploadContent(
- JSON.stringify(chatContent),
- 'text',
- FILENAME
- );
- });
-
- test.afterEach(async ({ page }) => {
- if (await page.filebrowser.contents.fileExists(FILENAME)) {
- await page.filebrowser.contents.deleteFile(FILENAME);
- }
- });
-
- test('message timestamp should be raw according to file content', async ({
- page
- }) => {
- const chatPanel = await openChat(page, FILENAME);
- const messages = chatPanel.locator(
- '.jp-chat-messages-container .jp-chat-message'
- );
-
- const raw_time = messages.locator('.jp-chat-message-time').first();
- expect(await raw_time.getAttribute('title')).toBe('Unverified time');
- expect(await raw_time.textContent()).toMatch(/\*$/);
-
- const verified_time = messages.locator('.jp-chat-message-time').last();
- expect(await verified_time.getAttribute('title')).toBe('');
- expect(await verified_time.textContent()).toMatch(/[^\*]$/);
- });
-
- test('time for new message should not be raw', async ({ page }) => {
- const chatPanel = await openChat(page, FILENAME);
- const messages = chatPanel.locator(
- '.jp-chat-messages-container .jp-chat-message'
- );
-
- // Send a new message
- await sendMessage(page, FILENAME, MSG_CONTENT);
-
- expect(messages).toHaveCount(3);
- await expect(
- messages.locator('.jp-chat-message-time').last()
- ).toHaveAttribute('title', '');
- });
-});
diff --git a/ui-tests/tests/send-message.spec.ts b/ui-tests/tests/send-message.spec.ts
index 83e5a265..2e5a79d3 100644
--- a/ui-tests/tests/send-message.spec.ts
+++ b/ui-tests/tests/send-message.spec.ts
@@ -142,155 +142,4 @@ test.describe('#sendMessages', () => {
messages.locator('.jp-chat-message .jp-chat-rendered-markdown')
).toHaveText(MSG_CONTENT + '\n');
});
-
- test('should disable send with selection when there is no notebook', async ({
- page
- }) => {
- const chatPanel = await openChat(page, FILENAME);
- const input = chatPanel
- .locator('.jp-chat-input-container')
- .getByRole('combobox');
- const openerButton = chatPanel.locator(
- '.jp-chat-input-container .jp-chat-send-include-opener'
- );
- const sendWithSelection = page.locator('.jp-chat-send-include');
-
- await input.pressSequentially(MSG_CONTENT);
- await openerButton.click();
- await expect(sendWithSelection).toBeVisible();
- await expect(sendWithSelection).toBeDisabled();
- await expect(sendWithSelection).toContainText(
- 'No selection or active cell'
- );
- });
-
- test('should send with code cell content', async ({ page }) => {
- const cellContent = 'a = 1\nprint(f"a={a}")';
- const chatPanel = await openChat(page, FILENAME);
- const messages = chatPanel.locator('.jp-chat-messages-container');
- const input = chatPanel
- .locator('.jp-chat-input-container')
- .getByRole('combobox');
- const openerButton = chatPanel.locator(
- '.jp-chat-input-container .jp-chat-send-include-opener'
- );
- const sendWithSelection = page.locator('.jp-chat-send-include');
-
- const notebook = await page.notebook.createNew();
- // write content in the first cell.
- const cell = (await page.notebook.getCellLocator(0))!;
- await cell.getByRole('textbox').pressSequentially(cellContent);
-
- await splitMainArea(page, notebook!);
-
- await input.pressSequentially(MSG_CONTENT);
- await openerButton.click();
- await expect(sendWithSelection).toBeVisible();
- await expect(sendWithSelection).toBeEnabled();
- await expect(sendWithSelection).toContainText('Code from 1 active cell');
- await sendWithSelection.click();
- await expect(messages!.locator('.jp-chat-message')).toHaveCount(1);
-
- // It seems that the markdown renderer adds a new line, but the '\n' inserter when
- // pressing Enter above is trimmed.
- const rendered = messages.locator(
- '.jp-chat-message .jp-chat-rendered-markdown'
- );
- await expect(rendered).toHaveText(`${MSG_CONTENT}\n${cellContent}\n`);
-
- // Code should have python language class.
- await expect(rendered.locator('code')).toHaveClass('language-python');
- });
-
- test('should send with markdown cell content', async ({ page }) => {
- const cellContent = 'markdown content';
- const chatPanel = await openChat(page, FILENAME);
- const messages = chatPanel.locator('.jp-chat-messages-container');
- const input = chatPanel
- .locator('.jp-chat-input-container')
- .getByRole('combobox');
- const openerButton = chatPanel.locator(
- '.jp-chat-input-container .jp-chat-send-include-opener'
- );
- const sendWithSelection = page.locator('.jp-chat-send-include');
-
- const notebook = await page.notebook.createNew();
- // write content in the first cell after changing it to markdown.
- const cell = (await page.notebook.getCellLocator(0))!;
- await page.notebook.setCellType(0, 'markdown');
- await cell.getByRole('textbox').pressSequentially(cellContent);
-
- await splitMainArea(page, notebook!);
-
- await input.pressSequentially(MSG_CONTENT);
- await openerButton.click();
- await expect(sendWithSelection).toBeVisible();
- await expect(sendWithSelection).toBeEnabled();
- await expect(sendWithSelection).toContainText('Code from 1 active cell');
- await sendWithSelection.click();
- await expect(messages!.locator('.jp-chat-message')).toHaveCount(1);
-
- // It seems that the markdown renderer adds a new line, but the '\n' inserter when
- // pressing Enter above is trimmed.
- const rendered = messages.locator(
- '.jp-chat-message .jp-chat-rendered-markdown'
- );
- await expect(rendered).toHaveText(`${MSG_CONTENT}\n${cellContent}\n`);
-
- // Code should not have python language class since it come from a markdown cell.
- await expect(rendered.locator('code')).toHaveClass('');
- });
-
- test('should send with text selection', async ({ page }) => {
- const cellContent = 'a = 1\nprint(f"a={a}")';
- const chatPanel = await openChat(page, FILENAME);
- const messages = chatPanel.locator('.jp-chat-messages-container');
- const input = chatPanel
- .locator('.jp-chat-input-container')
- .getByRole('combobox');
- const openerButton = chatPanel.locator(
- '.jp-chat-input-container .jp-chat-send-include-opener'
- );
- const sendWithSelection = page.locator('.jp-chat-send-include');
-
- const notebook = await page.notebook.createNew();
- await splitMainArea(page, notebook!);
-
- // write content in the first cell.
- const cell = (await page.notebook.getCellLocator(0))!;
- await cell.getByRole('textbox').pressSequentially(cellContent);
-
- // wait for code mirror to be ready.
- await expect(cell.locator('.cm-line')).toHaveCount(2);
- await expect(
- cell.locator('.cm-line').nth(1).locator('.cm-builtin')
- ).toBeAttached();
-
- // select the 'print' statement in the second line.
- const selection = cell
- ?.locator('.cm-line')
- .nth(1)
- .locator('.cm-builtin')
- .first();
- await selection.dblclick({ position: { x: 10, y: 10 } });
-
- await input.pressSequentially(MSG_CONTENT);
- await openerButton.click();
- await expect(sendWithSelection).toBeVisible();
- await expect(sendWithSelection).toBeEnabled();
- await expect(sendWithSelection).toContainText('1 line(s) selected');
- await sendWithSelection.click();
-
- await expect(messages!.locator('.jp-chat-message')).toHaveCount(1);
-
- // It seems that the markdown renderer adds a new line, but the '\n' inserter when
- // pressing Enter above is trimmed.
- const rendered = messages.locator(
- '.jp-chat-message .jp-chat-rendered-markdown'
- );
- await expect(rendered).toHaveText(`${MSG_CONTENT}\nprint\n`);
-
- // Code should have python or ipython language class.
- await expect(rendered.locator('code')).toHaveClass(/language-[i]?python/);
- });
});
diff --git a/ui-tests/tests/ui-config.spec.ts b/ui-tests/tests/ui-config.spec.ts
index 979c1ea6..d26c2c06 100644
--- a/ui-tests/tests/ui-config.spec.ts
+++ b/ui-tests/tests/ui-config.spec.ts
@@ -210,41 +210,7 @@ test.describe('#typingNotification', () => {
await guestInput.press('a');
await expect(writers).toBeAttached();
const start = Date.now();
- await expect(writers).toHaveText(/jovyan_2 is writing/);
- await expect(writers).not.toBeAttached();
-
- // Message should disappear after 1s, but this delay include the awareness update.
- expect(Date.now() - start).toBeLessThanOrEqual(2000);
- });
-
- test('should display typing user editing a message', async ({ page }) => {
- const chatPanel = await openChat(page, FILENAME);
- const writers = chatPanel.locator('.jp-chat-writers');
-
- const guestChatPanel = await openChat(guestPage, FILENAME);
-
- await sendMessage(guestPage, FILENAME, 'test');
- await expect(writers).not.toBeAttached();
- const message = guestChatPanel
- .locator('.jp-chat-messages-container .jp-chat-message')
- .first();
- const messageContent = message.locator('.jp-chat-rendered-markdown');
-
- // Should display the message toolbar
- await messageContent.hover({ position: { x: 5, y: 5 } });
- await messageContent.locator('.jp-chat-toolbar jp-button').first().click();
-
- const editInput = guestChatPanel
- .locator('.jp-chat-messages-container .jp-chat-input-container')
- .getByRole('combobox');
-
- await editInput.focus();
-
- await editInput.press('a');
- await expect(writers).toBeAttached();
- const start = Date.now();
- await expect(writers).toHaveText(/jovyan_2 is writing/);
- await expect(writers).not.toBeAttached();
+ await expect(writers).toHaveText(/jovyan_2 is typing/);
// Message should disappear after 1s, but this delay include the awareness update.
expect(Date.now() - start).toBeLessThanOrEqual(2000);
@@ -276,14 +242,17 @@ test.describe('#typingNotification', () => {
await guestInput.press('a');
- let visible = true;
+ let hasContent = true;
try {
- await page.waitForCondition(() => writers.isVisible(), 3000);
+ await page.waitForCondition(
+ async () => !!(await writers.textContent())?.trim(),
+ 3000
+ );
} catch {
- visible = false;
+ hasContent = false;
}
- if (visible) {
+ if (hasContent) {
throw Error('The typing notification should not be attached.');
}
});
@@ -339,12 +308,11 @@ test.describe('#typingNotification', () => {
await guest2Input.press('a');
await expect(writers).toBeAttached();
- const regexp = /JP(jovyan_[2|3]) and JP(jovyan_[2|3]) are writing/;
+ const regexp = /jovyan_[2|3] and jovyan_[2|3] are typing/;
await expect(writers).toHaveText(regexp);
const result = regexp.exec((await writers.textContent()) ?? '');
expect(result?.[1] !== undefined);
expect(result?.[1] !== result?.[2]);
- await expect(writers).not.toBeAttached();
});
});
diff --git a/ui-tests/tests/ui-config.spec.ts-snapshots/not-stacked-messages-linux.png b/ui-tests/tests/ui-config.spec.ts-snapshots/not-stacked-messages-linux.png
index 377dea00..dfa47efc 100644
Binary files a/ui-tests/tests/ui-config.spec.ts-snapshots/not-stacked-messages-linux.png and b/ui-tests/tests/ui-config.spec.ts-snapshots/not-stacked-messages-linux.png differ
diff --git a/ui-tests/tests/ui-config.spec.ts-snapshots/stacked-messages-linux.png b/ui-tests/tests/ui-config.spec.ts-snapshots/stacked-messages-linux.png
index 8f791839..dfa47efc 100644
Binary files a/ui-tests/tests/ui-config.spec.ts-snapshots/stacked-messages-linux.png and b/ui-tests/tests/ui-config.spec.ts-snapshots/stacked-messages-linux.png differ
diff --git a/ui-tests/tests/unread.spec.ts-snapshots/navigation-top-linux.png b/ui-tests/tests/unread.spec.ts-snapshots/navigation-top-linux.png
index d386a81f..85a55557 100644
Binary files a/ui-tests/tests/unread.spec.ts-snapshots/navigation-top-linux.png and b/ui-tests/tests/unread.spec.ts-snapshots/navigation-top-linux.png differ