Skip to content

Commit b8e1084

Browse files
fix: update stream-chat-js to version 6.5.0 containing the markRead fix, add E2E tests (#1514)
* fix: add lastRead argument to the countUnread method * chore: upgrade package @ladle/react * test: update fixtures.mjs and .env.example to accept additional channels * refactor: rename .js files with JSX to .jsx * test: added mark read test, moved ConnectedUser component * ci: added additional channels variable * refactor: remove unnecessary delay from test * fix: revert Channel lastRead fix * chore: update stream-chat-js package * chore: update `stream-chat` peer dependency * test: remove channel name from the URL regular expression * ci: update checkout and cache to v3 * ci: add --ignore-scripts to yarn install * test: fix race conditions
1 parent cd39a04 commit b8e1084

File tree

13 files changed

+487
-124
lines changed

13 files changed

+487
-124
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ E2E_TEST_USER_2='test-user-2'
66
E2E_TEST_USER_2_TOKEN='test-user-2-jwt'
77
E2E_JUMP_TO_MESSAGE_CHANNEL='jump-to-message'
88
E2E_ADD_MESSAGE_CHANNEL='add-message'
9+
E2E_ADDITIONAL_CHANNELS="mr-channel-1,mr-channel-2"

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ jobs:
2525
runs-on: ubuntu-latest
2626
name: End-to-end tests
2727
steps:
28-
- uses: actions/checkout@v2
28+
- uses: actions/checkout@v3
2929

3030
- name: 💾 Cache Dependencies
31-
uses: actions/cache@v2
31+
uses: actions/cache@v3
3232
with:
3333
path: ./node_modules
3434
key: ${{ runner.os }}-${{ matrix.node }}-modules-${{ hashFiles('**/yarn.lock') }}
3535

3636
- name: 🔨 Install Dependencies & Build
37-
run: yarn install --frozen-lockfile --ignore-engines
37+
run: yarn install --frozen-lockfile --ignore-engines --ignore-scripts
3838

3939
- name: ⚗️ End-to-end tests
4040
run: |
@@ -54,6 +54,7 @@ jobs:
5454
E2E_APP_SECRET: ${{ secrets.E2E_APP_SECRET }}
5555
E2E_TEST_USER_1_TOKEN: ${{ secrets.E2E_TEST_USER_1_TOKEN }}
5656
E2E_TEST_USER_2_TOKEN: ${{ secrets.E2E_TEST_USER_2_TOKEN }}
57+
E2E_ADDITIONAL_CHANNELS: mr-channel-1, mr-channel-2
5758

5859
test:
5960
runs-on: ubuntu-latest

e2e/fixtures.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dotenv.config({ path: `.env.local` });
77
(async () => {
88
const {
99
E2E_ADD_MESSAGE_CHANNEL,
10+
E2E_ADDITIONAL_CHANNELS = '',
1011
E2E_APP_KEY,
1112
E2E_APP_SECRET,
1213
E2E_JUMP_TO_MESSAGE_CHANNEL,
@@ -27,6 +28,7 @@ dotenv.config({ path: `.env.local` });
2728
const channel = chat.channel('messaging', E2E_JUMP_TO_MESSAGE_CHANNEL, {
2829
created_by_id: E2E_TEST_USER_1,
2930
members: [E2E_TEST_USER_1, E2E_TEST_USER_2],
31+
name: E2E_JUMP_TO_MESSAGE_CHANNEL,
3032
});
3133
await channel.create();
3234
await channel.truncate();
@@ -60,6 +62,23 @@ dotenv.config({ path: `.env.local` });
6062
await channel.create();
6163
await channel.truncate();
6264
}
65+
66+
// Create additional channels from .env
67+
{
68+
const additionalChannels = E2E_ADDITIONAL_CHANNELS.replace(/\s+/g, '').split(',');
69+
70+
for (const additionalChannel of additionalChannels) {
71+
if (!additionalChannel || !additionalChannel.length) continue;
72+
console.log(`Creating additional channel '${additionalChannel}'...`);
73+
const channel = chat.channel('messaging', additionalChannel, {
74+
created_by_id: E2E_TEST_USER_1,
75+
members: [E2E_TEST_USER_1, E2E_TEST_USER_2],
76+
name: additionalChannel,
77+
});
78+
await channel.create();
79+
await channel.truncate();
80+
}
81+
}
6382
})();
6483

6584
const printProgress = (progress) => {

e2e/mark-read.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/* eslint-disable jest/no-done-callback */
2+
/* eslint-disable jest/require-top-level-describe */
3+
import { expect, test } from '@playwright/test';
4+
5+
test.describe('mark read', () => {
6+
test.beforeEach(async ({ baseURL, page }) => {
7+
await page.goto(`${baseURL}/?story=mark-read--user1`, { waitUntil: 'networkidle' });
8+
9+
// wait for page load
10+
await page.waitForSelector('data-testid=unread-count');
11+
12+
const channelListItems = page.locator('.str-chat__channel-list-messenger__main > div');
13+
14+
const listCount = await channelListItems.count();
15+
16+
for (let i = 1; i <= listCount; ++i) {
17+
// select channel
18+
await Promise.all([
19+
page.waitForSelector(`.str-chat__main-panel >> text=mr-channel-${i}`),
20+
page.click(`data-testid=channel-mr-channel-${i}`),
21+
]);
22+
23+
// truncate messages
24+
await Promise.all([
25+
page.waitForResponse((r) => r.url().includes('/truncate') && r.ok()),
26+
page.click('data-testid=truncate'),
27+
]);
28+
29+
// add message to channel
30+
await Promise.all([
31+
page.waitForResponse((r) => r.url().includes('/message') && r.ok()),
32+
page.click('data-testid=add-message'),
33+
]);
34+
}
35+
36+
await page.goto(`${baseURL}/?story=mark-read--user2`, { waitUntil: 'networkidle' });
37+
// wait for page load
38+
await page.waitForSelector('data-testid=unread-count');
39+
});
40+
41+
test('unread count in "mr-channel-1" channel is 1', async ({ page }) => {
42+
const unreadCountSpan = page.locator(
43+
'data-testid=channel-mr-channel-1 >> data-testid=unread-count',
44+
);
45+
46+
await expect(unreadCountSpan).toHaveText('1');
47+
});
48+
49+
test('unread count changes to 0 after setting "mr-channel-1" channel as active', async ({
50+
page,
51+
}) => {
52+
await Promise.all([
53+
page.waitForSelector('.str-chat__main-panel >> text=mr-channel-1'),
54+
page.click('data-testid=channel-mr-channel-1'),
55+
]);
56+
57+
const unreadCountSpan = page.locator(
58+
'data-testid=channel-mr-channel-1 >> data-testid=unread-count',
59+
);
60+
61+
await expect(unreadCountSpan).toHaveText('0');
62+
});
63+
64+
test('unread count stays 0 after switching channels', async ({ page }) => {
65+
await Promise.all([
66+
page.waitForSelector('.str-chat__main-panel >> text=mr-channel-1'),
67+
page.click('data-testid=channel-mr-channel-1'),
68+
]);
69+
70+
await Promise.all([
71+
page.waitForSelector('.str-chat__main-panel >> text=mr-channel-2'),
72+
page.click('data-testid=channel-mr-channel-2'),
73+
]);
74+
75+
const unreadCountSpan1 = page.locator(
76+
'data-testid=channel-mr-channel-1 >> data-testid=unread-count',
77+
);
78+
79+
const unreadCountSpan2 = page.locator(
80+
'data-testid=channel-mr-channel-2 >> data-testid=unread-count',
81+
);
82+
83+
await expect(unreadCountSpan1).toHaveText('0');
84+
await expect(unreadCountSpan2).toHaveText('0');
85+
});
86+
87+
test('unread count stays 0 after switching channels and reloading page', async ({ page }) => {
88+
await Promise.all([
89+
page.waitForSelector('.str-chat__main-panel >> text=mr-channel-1'),
90+
page.click('data-testid=channel-mr-channel-1'),
91+
]);
92+
93+
// click on channel-2 and await response
94+
await Promise.all([
95+
page.waitForResponse((r) => r.url().includes('/mr-channel-2/read') && r.ok()),
96+
page.click('data-testid=channel-mr-channel-2'),
97+
]);
98+
99+
// reload the page
100+
await Promise.all([
101+
page.waitForSelector('data-testid=unread-count'),
102+
page.reload({ waitUntil: 'networkidle' }),
103+
]);
104+
105+
const unreadCountSpan = page.locator(
106+
'data-testid=channel-mr-channel-2 >> data-testid=unread-count',
107+
);
108+
109+
await expect(unreadCountSpan).toHaveText('0');
110+
});
111+
});

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"sideEffects": [
1717
"*.css"
1818
],
19+
"source": "src/index.tsx",
1920
"jsdelivr": "./dist/browser.full-bundle.min.js",
2021
"keywords": [
2122
"chat",
@@ -60,7 +61,7 @@
6061
"peerDependencies": {
6162
"react": "^17.0.0 || ^16.8.0",
6263
"react-dom": "^17.0.0 || ^16.8.0",
63-
"stream-chat": "^6.4.0"
64+
"stream-chat": "^6.5.0"
6465
},
6566
"files": [
6667
"dist",
@@ -79,7 +80,7 @@
7980
"@babel/preset-typescript": "^7.12.7",
8081
"@commitlint/cli": "^16.2.3",
8182
"@commitlint/config-conventional": "^16.2.1",
82-
"@ladle/react": "^0.8.5",
83+
"@ladle/react": "^0.11.0",
8384
"@playwright/test": "^1.20.0",
8485
"@rollup/plugin-babel": "^5.2.1",
8586
"@rollup/plugin-image": "^2.1.1",
@@ -163,7 +164,7 @@
163164
"rollup-plugin-visualizer": "^4.2.0",
164165
"semantic-release": "^19.0.2",
165166
"semantic-release-cli": "^5.4.4",
166-
"stream-chat": "6.4.0",
167+
"stream-chat": "^6.5.0",
167168
"style-loader": "^2.0.0",
168169
"ts-jest": "^26.5.1",
169170
"tslib": "2.3.0",

src/components/Channel/Channel.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -418,14 +418,11 @@ const ChannelInner = <
418418
}, [channel.cid, doMarkReadRequest]);
419419

420420
useEffect(() => {
421-
if (state.thread && state.messages?.length) {
422-
for (let i = state.messages.length - 1; i >= 0; i -= 1) {
423-
if (state.messages[i].id === state.thread.id) {
424-
dispatch({ message: state.messages[i], type: 'setThread' });
425-
break;
426-
}
427-
}
428-
}
421+
if (!state.thread) return;
422+
423+
const message = state.messages?.find((m) => m.id === state.thread?.id);
424+
425+
if (message) dispatch({ message, type: 'setThread' });
429426
}, [state.messages, state.thread]);
430427

431428
/** MESSAGE */
Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import '@stream-io/stream-chat-css/dist/css/index.css';
3-
import React, { useEffect, useState } from 'react';
4-
import { ChannelSort, Event, StreamChat } from 'stream-chat';
3+
import React from 'react';
4+
import { ChannelSort, StreamChat } from 'stream-chat';
55
import {
66
Channel,
77
ChannelHeader,
88
ChannelList,
9-
Chat,
109
MessageList,
1110
useChannelStateContext,
1211
Window,
1312
} from '../index';
14-
import { apiKey, StreamChatGenerics } from './utils';
13+
import { apiKey, ConnectedUser, ConnectedUserProps, StreamChatGenerics } from './utils';
1514

1615
const channelId = import.meta.env.E2E_ADD_MESSAGE_CHANNEL;
1716
if (!channelId || typeof channelId !== 'string') {
@@ -45,48 +44,18 @@ const sort: ChannelSort = { last_updated: 1 };
4544

4645
const chatClient = StreamChat.getInstance<StreamChatGenerics>(apiKey);
4746

48-
// wait for disconnect to happen since there's only one shared
49-
// client and two separate Chat components using it to prevent crashes
50-
let sharedPromise = Promise.resolve();
51-
52-
const ConnectedUser = ({ token, userId }: { token: string; userId: string }) => {
53-
const [connected, setConnected] = useState(false);
54-
55-
useEffect(() => {
56-
sharedPromise.then(() => chatClient.connectUser({ id: userId }, token));
57-
58-
const handleConnectionChange = ({ online = false }: Event) => {
59-
setConnected(online);
60-
};
61-
62-
chatClient.on('connection.changed', handleConnectionChange);
63-
64-
return () => {
65-
chatClient.off('connection.changed', handleConnectionChange);
66-
sharedPromise = chatClient.disconnectUser();
67-
};
68-
}, []);
69-
70-
if (!connected) {
71-
return <p>Connecting {userId}...</p>;
72-
}
73-
74-
return (
75-
<>
76-
<h3>User: {userId}</h3>
77-
<Chat client={chatClient}>
78-
<ChannelList filters={{ members: { $in: [userId] } }} sort={sort} />
79-
<Channel>
80-
<Window>
81-
<ChannelHeader />
82-
<MessageList />
83-
<Controls />
84-
</Window>
85-
</Channel>
86-
</Chat>
87-
</>
88-
);
89-
};
47+
const WrappedConnectedUser = ({ token, userId }: Omit<ConnectedUserProps, 'children'>) => (
48+
<ConnectedUser client={chatClient} token={token} userId={userId}>
49+
<ChannelList filters={{ members: { $in: [userId] } }} sort={sort} />
50+
<Channel>
51+
<Window>
52+
<ChannelHeader />
53+
<MessageList />
54+
<Controls />
55+
</Window>
56+
</Channel>
57+
</ConnectedUser>
58+
);
9059

9160
export const User1 = () => {
9261
const userId = import.meta.env.E2E_TEST_USER_1;
@@ -97,7 +66,7 @@ export const User1 = () => {
9766
if (!token || typeof token !== 'string') {
9867
throw new Error('expected TEST_USER_1_TOKEN');
9968
}
100-
return <ConnectedUser token={token} userId={userId} />;
69+
return <WrappedConnectedUser token={token} userId={userId} />;
10170
};
10271

10372
export const User2 = () => {
@@ -109,5 +78,5 @@ export const User2 = () => {
10978
if (!token || typeof token !== 'string') {
11079
throw new Error('expected TEST_USER_2_TOKEN');
11180
}
112-
return <ConnectedUser token={token} userId={userId} />;
81+
return <WrappedConnectedUser token={token} userId={userId} />;
11382
};

0 commit comments

Comments
 (0)