Skip to content

Commit f75b710

Browse files
Refactor UI tests (#86)
* Move side panel tests * Move navigation tests * Move send message test * Move the notifications tests * Update Playwright Snapshots * Move message toolbar tests * Group tests on unread messages * lint * Move config related tests * Update snapshots --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 4b0a1eb commit f75b710

24 files changed

+1635
-1528
lines changed

python/jupyterlab-collaborative-chat/ui-tests/tests/jupyterlab_collaborative_chat.spec.ts

Lines changed: 4 additions & 1528 deletions
Large diffs are not rendered by default.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) Jupyter Development Team.
3+
* Distributed under the terms of the Modified BSD License.
4+
*/
5+
6+
import { expect, galata, test } from '@jupyterlab/galata';
7+
import { UUID } from '@lumino/coreutils';
8+
9+
import { openChat, USER } from './test-utils';
10+
11+
const FILENAME = 'my-chat.chat';
12+
const MSG_CONTENT = 'Hello World!';
13+
const USERNAME = USER.identity.username;
14+
15+
test.use({
16+
mockUser: USER,
17+
mockSettings: { ...galata.DEFAULT_SETTINGS }
18+
});
19+
20+
test.describe('#messageToolbar', () => {
21+
const additionnalContent = ' Messages can be edited';
22+
23+
const msg = {
24+
type: 'msg',
25+
id: UUID.uuid4(),
26+
sender: USERNAME,
27+
body: MSG_CONTENT,
28+
time: 1714116341
29+
};
30+
const chatContent = {
31+
messages: [msg],
32+
users: {}
33+
};
34+
chatContent.users[USERNAME] = USER.identity;
35+
36+
test.beforeEach(async ({ page }) => {
37+
// Create a chat file with content
38+
await page.filebrowser.contents.uploadContent(
39+
JSON.stringify(chatContent),
40+
'text',
41+
FILENAME
42+
);
43+
});
44+
45+
test.afterEach(async ({ page }) => {
46+
if (await page.filebrowser.contents.fileExists(FILENAME)) {
47+
await page.filebrowser.contents.deleteFile(FILENAME);
48+
}
49+
});
50+
51+
test('message should have a toolbar', async ({ page }) => {
52+
const chatPanel = await openChat(page, FILENAME);
53+
const message = chatPanel
54+
.locator('.jp-chat-messages-container .jp-chat-rendermime-markdown')
55+
.first();
56+
57+
await expect(message.locator('.jp-chat-toolbar')).not.toBeVisible();
58+
59+
//Should display the message toolbar
60+
await message.hover({ position: { x: 5, y: 5 } });
61+
await expect(message.locator('.jp-chat-toolbar')).toBeVisible();
62+
});
63+
64+
test('should update the message', async ({ page }) => {
65+
const chatPanel = await openChat(page, FILENAME);
66+
const message = chatPanel
67+
.locator('.jp-chat-messages-container .jp-chat-message')
68+
.first();
69+
const messageContent = message.locator('.jp-chat-rendermime-markdown');
70+
71+
// Should display the message toolbar
72+
await messageContent.hover({ position: { x: 5, y: 5 } });
73+
await messageContent.locator('.jp-chat-toolbar jp-button').first().click();
74+
75+
await expect(messageContent).not.toBeVisible();
76+
77+
const editInput = chatPanel
78+
.locator('.jp-chat-messages-container .jp-chat-input-container')
79+
.getByRole('combobox');
80+
81+
await expect(editInput).toBeVisible();
82+
await editInput.focus();
83+
await editInput.press('End');
84+
await editInput.pressSequentially(additionnalContent);
85+
await editInput.press('Enter');
86+
87+
// It seems that the markdown renderer adds a new line.
88+
await expect(messageContent).toHaveText(
89+
MSG_CONTENT + additionnalContent + '\n'
90+
);
91+
expect(
92+
await message.locator('.jp-chat-message-header').textContent()
93+
).toContain('(edited)');
94+
});
95+
96+
test('should cancel message edition', async ({ page }) => {
97+
const chatPanel = await openChat(page, FILENAME);
98+
const message = chatPanel
99+
.locator('.jp-chat-messages-container .jp-chat-message')
100+
.first();
101+
const messageContent = message.locator('.jp-chat-rendermime-markdown');
102+
103+
// Should display the message toolbar
104+
await messageContent.hover({ position: { x: 5, y: 5 } });
105+
await messageContent.locator('.jp-chat-toolbar jp-button').first().click();
106+
107+
await expect(messageContent).not.toBeVisible();
108+
109+
const editInput = chatPanel
110+
.locator('.jp-chat-messages-container .jp-chat-input-container')
111+
.getByRole('combobox');
112+
113+
await expect(editInput).toBeVisible();
114+
await editInput.focus();
115+
await editInput.press('End');
116+
await editInput.pressSequentially(additionnalContent);
117+
118+
const cancelButton = chatPanel
119+
.locator('.jp-chat-messages-container .jp-chat-input-container')
120+
.getByTitle('Cancel edition');
121+
await expect(cancelButton).toBeVisible();
122+
await cancelButton.click();
123+
await expect(editInput).not.toBeVisible();
124+
125+
// It seems that the markdown renderer adds a new line.
126+
await expect(messageContent).toHaveText(MSG_CONTENT + '\n');
127+
expect(
128+
await message.locator('.jp-chat-message-header').textContent()
129+
).not.toContain('(edited)');
130+
});
131+
132+
test('should set the message as deleted', async ({ page }) => {
133+
const chatPanel = await openChat(page, FILENAME);
134+
const message = chatPanel
135+
.locator('.jp-chat-messages-container .jp-chat-message')
136+
.first();
137+
const messageContent = message.locator('.jp-chat-rendermime-markdown');
138+
139+
// Should display the message toolbar
140+
await messageContent.hover({ position: { x: 5, y: 5 } });
141+
await messageContent.locator('.jp-chat-toolbar jp-button').last().click();
142+
143+
await expect(messageContent).not.toBeVisible();
144+
expect(
145+
await message.locator('.jp-chat-message-header').textContent()
146+
).toContain('(message deleted)');
147+
});
148+
});
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright (c) Jupyter Development Team.
3+
* Distributed under the terms of the Modified BSD License.
4+
*/
5+
6+
import {
7+
IJupyterLabPageFixture,
8+
expect,
9+
galata,
10+
test
11+
} from '@jupyterlab/galata';
12+
13+
import { User } from '@jupyterlab/services';
14+
import { UUID } from '@lumino/coreutils';
15+
16+
import { openChat, openSettings, sendMessage, USER } from './test-utils';
17+
18+
const FILENAME = 'my-chat.chat';
19+
const MSG_CONTENT = 'Hello World!';
20+
const USERNAME = USER.identity.username;
21+
22+
test.describe('#notifications', () => {
23+
const baseTime = 1714116341;
24+
const messagesCount = 15;
25+
const messagesList: any[] = [];
26+
for (let i = 0; i < messagesCount; i++) {
27+
messagesList.push({
28+
type: 'msg',
29+
id: UUID.uuid4(),
30+
sender: USERNAME,
31+
body: `Message ${i}`,
32+
time: baseTime + i * 60
33+
});
34+
}
35+
36+
const chatContent = {
37+
messages: messagesList,
38+
users: {}
39+
};
40+
chatContent.users[USERNAME] = USER.identity;
41+
42+
let guestPage: IJupyterLabPageFixture;
43+
44+
test.beforeEach(
45+
async ({ baseURL, browser, page, tmpPath, waitForApplication }) => {
46+
// Create a chat file with content
47+
await page.filebrowser.contents.uploadContent(
48+
JSON.stringify(chatContent),
49+
'text',
50+
FILENAME
51+
);
52+
53+
// Create a new user.
54+
const user2: Partial<User.IUser> = {
55+
identity: {
56+
username: 'jovyan_2',
57+
name: 'jovyan_2',
58+
display_name: 'jovyan_2',
59+
initials: 'JP',
60+
color: 'var(--jp-collaborator-color2)'
61+
}
62+
};
63+
64+
// Create a new page for guest.
65+
const { page: newPage } = await galata.newPage({
66+
baseURL: baseURL!,
67+
browser,
68+
mockUser: user2,
69+
tmpPath,
70+
waitForApplication
71+
});
72+
await newPage.evaluate(() => {
73+
// Acknowledge any dialog
74+
window.galataip.on('dialog', d => {
75+
d?.resolve();
76+
});
77+
});
78+
guestPage = newPage;
79+
}
80+
);
81+
82+
test.afterEach(async ({ page }) => {
83+
guestPage.close();
84+
if (await page.filebrowser.contents.fileExists(FILENAME)) {
85+
await page.filebrowser.contents.deleteFile(FILENAME);
86+
}
87+
});
88+
89+
test('should receive notification on unread message', async ({ page }) => {
90+
const chatPanel = await openChat(page, FILENAME);
91+
const messages = chatPanel.locator('.jp-chat-message');
92+
await messages.first().scrollIntoViewIfNeeded();
93+
94+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
95+
await page.waitForCondition(
96+
async () => (await page.notifications).length > 0
97+
);
98+
const notifications = await page.notifications;
99+
expect(notifications).toHaveLength(1);
100+
101+
// TODO: fix it, the notification should be info but is 'default'
102+
// expect(notifications[0].type).toBe('info');
103+
expect(notifications[0].message).toBe(
104+
'1 incoming message(s) in my-chat.chat'
105+
);
106+
});
107+
108+
test('should remove notification when the message is read', async ({
109+
page
110+
}) => {
111+
const chatPanel = await openChat(page, FILENAME);
112+
const messages = chatPanel.locator('.jp-chat-message');
113+
await messages.first().scrollIntoViewIfNeeded();
114+
115+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
116+
await page.waitForCondition(
117+
async () => (await page.notifications).length > 0
118+
);
119+
let notifications = await page.notifications;
120+
expect(notifications).toHaveLength(1);
121+
122+
await messages.last().scrollIntoViewIfNeeded();
123+
await page.waitForCondition(
124+
async () => (await page.notifications).length === 0
125+
);
126+
});
127+
128+
test('should update existing notification on new message', async ({
129+
page
130+
}) => {
131+
const chatPanel = await openChat(page, FILENAME);
132+
const messages = chatPanel.locator('.jp-chat-message');
133+
await messages.first().scrollIntoViewIfNeeded();
134+
135+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
136+
await page.waitForCondition(
137+
async () => (await page.notifications).length > 0
138+
);
139+
let notifications = await page.notifications;
140+
expect(notifications).toHaveLength(1);
141+
142+
expect(notifications[0].message).toBe(
143+
'1 incoming message(s) in my-chat.chat'
144+
);
145+
146+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
147+
notifications = await page.notifications;
148+
expect(notifications[0].message).toBe(
149+
'2 incoming message(s) in my-chat.chat'
150+
);
151+
});
152+
153+
test('should remove notifications from settings', async ({ page }) => {
154+
const chatPanel = await openChat(page, FILENAME);
155+
const messages = chatPanel.locator('.jp-chat-message');
156+
await messages.first().scrollIntoViewIfNeeded();
157+
158+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
159+
await page.waitForCondition(
160+
async () => (await page.notifications).length > 0
161+
);
162+
let notifications = await page.notifications;
163+
expect(notifications).toHaveLength(1);
164+
165+
const settings = await openSettings(page);
166+
const unreadNotifications = settings?.getByRole('checkbox', {
167+
name: 'unreadNotifications'
168+
});
169+
await unreadNotifications?.uncheck();
170+
171+
// wait for the settings to be saved
172+
await expect(page.activity.getTabLocator('Settings')).toHaveAttribute(
173+
'class',
174+
/jp-mod-dirty/
175+
);
176+
await expect(page.activity.getTabLocator('Settings')).not.toHaveAttribute(
177+
'class',
178+
/jp-mod-dirty/
179+
);
180+
181+
// Activate the chat panel
182+
await page.activity.activateTab(FILENAME);
183+
184+
await page.waitForCondition(
185+
async () => (await page.notifications).length === 0
186+
);
187+
188+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
189+
await expect(messages).toHaveCount(messagesCount + 2);
190+
191+
notifications = await page.notifications;
192+
expect(notifications).toHaveLength(0);
193+
});
194+
195+
test('should add unread symbol in tab label', async ({ page }) => {
196+
const chatPanel = await openChat(page, FILENAME);
197+
const messages = chatPanel.locator('.jp-chat-message');
198+
await messages.first().scrollIntoViewIfNeeded();
199+
200+
const tab = page.activity.getTabLocator(FILENAME);
201+
const tabLabel = tab.locator('.lm-TabBar-tabLabel');
202+
await expect(tabLabel).toHaveText(FILENAME);
203+
204+
await sendMessage(guestPage, FILENAME, MSG_CONTENT);
205+
const beforePseudo = tabLabel.evaluate(elem => {
206+
return window.getComputedStyle(elem, ':before');
207+
});
208+
expect(await beforePseudo).toHaveProperty('content', '"* "');
209+
expect(await tab.screenshot()).toMatchSnapshot('tab-with-unread.png');
210+
211+
await messages.last().scrollIntoViewIfNeeded();
212+
expect(await tab.screenshot()).toMatchSnapshot('tab-without-unread.png');
213+
});
214+
});
1.23 KB
Loading

0 commit comments

Comments
 (0)