Skip to content

Commit 6a1e9f6

Browse files
authored
fix: show correct read status on the message status component for a message (#3023)
* fix: show correct read status on the message status component for a message * fix: lint issues
1 parent 81a13dc commit 6a1e9f6

File tree

3 files changed

+180
-114
lines changed

3 files changed

+180
-114
lines changed

package/src/components/Message/MessageSimple/MessageStatus.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { StyleSheet, Text, View } from 'react-native';
33

4+
import { useChannelContext } from '../../../contexts/channelContext/ChannelContext';
45
import {
56
MessageContextValue,
67
useMessageContext,
@@ -23,6 +24,7 @@ const MessageStatusWithContext = <
2324
>(
2425
props: MessageStatusPropsWithContext<StreamChatGenerics>,
2526
) => {
27+
const { channel } = useChannelContext<StreamChatGenerics>();
2628
const { message, threadList } = props;
2729

2830
const {
@@ -47,18 +49,29 @@ const MessageStatusWithContext = <
4749
}
4850

4951
if (isMessageWithStylesReadByAndDateSeparator(message)) {
52+
const members = channel?.state.members;
53+
const otherMembers = Object.values(members).filter(
54+
(member) => member.user_id !== message.user?.id,
55+
);
56+
const hasOtherMembersGreaterThanOne = otherMembers.length > 1;
57+
const hasReadByGreaterThanOne = typeof message.readBy === 'number' && message.readBy > 1;
58+
const shouldDisplayReadByCount = hasOtherMembersGreaterThanOne && hasReadByGreaterThanOne;
59+
const countOfReadBy =
60+
typeof message.readBy === 'number' && hasOtherMembersGreaterThanOne ? message.readBy - 1 : 0;
61+
const showDoubleCheck = hasReadByGreaterThanOne || message.readBy === true;
62+
5063
return (
5164
<View style={[styles.statusContainer, statusContainer]}>
52-
{typeof message.readBy === 'number' ? (
65+
{shouldDisplayReadByCount ? (
5366
<Text
5467
style={[styles.readByCount, { color: accent_blue }, readByCount]}
5568
testID='read-by-container'
5669
>
57-
{message.readBy}
70+
{countOfReadBy}
5871
</Text>
5972
) : null}
6073
{message.type !== 'error' ? (
61-
typeof message.readBy === 'number' || message.readBy === true ? (
74+
showDoubleCheck ? (
6275
<CheckAll pathFill={accent_blue} {...checkAllIcon} />
6376
) : (
6477
<Check pathFill={grey_dark} {...checkIcon} />

package/src/components/Message/MessageSimple/__tests__/MessageStatus.test.js

+59-24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import React from 'react';
22

33
import { cleanup, render, waitFor } from '@testing-library/react-native';
44

5+
import { Channel } from '../../..';
6+
import { ChannelsStateProvider } from '../../../../contexts/channelsStateContext/ChannelsStateContext';
7+
import { getOrCreateChannelApi } from '../../../../mock-builders/api/getOrCreateChannel';
8+
import { useMockedApis } from '../../../../mock-builders/api/useMockedApis';
9+
import { generateChannelResponse } from '../../../../mock-builders/generator/channel';
10+
import { generateMember } from '../../../../mock-builders/generator/member';
511
import { generateMessage } from '../../../../mock-builders/generator/message';
612
import { generateStaticUser, generateUser } from '../../../../mock-builders/generator/user';
713
import { getTestClientWithUser } from '../../../../mock-builders/mock';
@@ -10,26 +16,54 @@ import { Chat } from '../../../Chat/Chat';
1016
import { MessageStatus } from '../MessageStatus';
1117

1218
let chatClient;
13-
let id;
1419
let i18nInstance;
15-
20+
let channel;
1621
describe('MessageStatus', () => {
17-
beforeAll(async () => {
22+
const user1 = generateUser({ id: 'id1', name: 'name1' });
23+
const user2 = generateUser({ id: 'id2', name: 'name2' });
24+
const user3 = generateUser({ id: 'id3', name: 'name3' });
25+
const messages = [generateMessage({ user: user1 })];
26+
const members = [
27+
generateMember({ user: user1 }),
28+
generateMember({ user: user2 }),
29+
generateMember({ user: user3 }),
30+
];
31+
beforeAll(() => {
1832
id = 'testID';
19-
chatClient = await getTestClientWithUser({ id });
2033
i18nInstance = new Streami18n();
2134
});
35+
beforeEach(async () => {
36+
jest.clearAllMocks();
37+
const mockedChannel = generateChannelResponse({
38+
members,
39+
messages,
40+
});
41+
42+
chatClient = await getTestClientWithUser(user1);
43+
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
44+
channel = chatClient.channel('messaging', mockedChannel.id);
45+
});
2246
afterEach(cleanup);
2347

48+
renderMessageStatus = (options, channelProps) =>
49+
render(
50+
<ChannelsStateProvider>
51+
<Chat client={chatClient}>
52+
<Channel channel={channel} {...channelProps}>
53+
<MessageStatus {...options} />
54+
</Channel>
55+
</Chat>
56+
</ChannelsStateProvider>,
57+
);
58+
2459
it('should render message status with delivered container', async () => {
2560
const user = generateUser();
2661
const message = generateMessage({ user });
2762

28-
const { getByTestId } = render(
29-
<Chat client={chatClient} i18nInstance={i18nInstance}>
30-
<MessageStatus lastReceivedId={message.id} message={{ ...message, status: 'received' }} />
31-
</Chat>,
32-
);
63+
const { getByTestId } = renderMessageStatus({
64+
lastReceivedId: message.id,
65+
message: { ...message, status: 'received' },
66+
});
3367

3468
await waitFor(() => {
3569
expect(getByTestId('delivered-container')).toBeTruthy();
@@ -40,42 +74,43 @@ describe('MessageStatus', () => {
4074
const user = generateUser();
4175
const message = generateMessage({ readBy: 2, user });
4276

43-
const { getByTestId, getByText, rerender, toJSON } = render(
44-
<Chat client={chatClient} i18nInstance={i18nInstance}>
45-
<MessageStatus lastReceivedId={message.id} message={message} />
46-
</Chat>,
47-
);
77+
const { getByTestId, getByText, rerender, toJSON } = renderMessageStatus({
78+
lastReceivedId: message.id,
79+
message,
80+
});
4881

4982
await waitFor(() => {
5083
expect(getByTestId('read-by-container')).toBeTruthy();
51-
expect(getByText(message.readBy.toString())).toBeTruthy();
84+
expect(getByText((message.readBy - 1).toString())).toBeTruthy();
5285
});
5386

5487
const staticUser = generateStaticUser(0);
5588
const staticMessage = generateMessage({ readBy: 2, staticUser });
5689

5790
rerender(
58-
<Chat client={chatClient} i18nInstance={i18nInstance}>
59-
<MessageStatus lastReceivedId={staticMessage.id} message={staticMessage} />
60-
</Chat>,
91+
<ChannelsStateProvider>
92+
<Chat client={chatClient} i18nInstance={i18nInstance}>
93+
<Channel channel={channel}>
94+
<MessageStatus lastReceivedId={staticMessage.id} message={staticMessage} />
95+
</Channel>
96+
</Chat>
97+
</ChannelsStateProvider>,
6198
);
6299

63100
await waitFor(() => {
64101
expect(toJSON()).toMatchSnapshot();
65102
expect(getByTestId('read-by-container')).toBeTruthy();
66-
expect(getByText(staticMessage.readBy.toString())).toBeTruthy();
103+
expect(getByText((staticMessage.readBy - 1).toString())).toBeTruthy();
67104
});
68105
});
69106

70107
it('should render message status with sending container', async () => {
71108
const user = generateUser();
72109
const message = generateMessage({ user });
73110

74-
const { getByTestId } = render(
75-
<Chat client={chatClient} i18nInstance={i18nInstance}>
76-
<MessageStatus message={{ ...message, status: 'sending' }} />
77-
</Chat>,
78-
);
111+
const { getByTestId } = renderMessageStatus({
112+
message: { ...message, status: 'sending' },
113+
});
79114

80115
await waitFor(() => {
81116
expect(getByTestId('sending-container')).toBeTruthy();

package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageStatus.test.js.snap

+105-87
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,124 @@
22

33
exports[`MessageStatus should render message status with read by container 1`] = `
44
<View
5+
keyboardVerticalOffset={86.5}
6+
onLayout={[Function]}
57
style={
6-
[
7-
{
8-
"alignItems": "flex-end",
9-
"flexDirection": "row",
10-
"justifyContent": "center",
11-
"paddingRight": 3,
12-
},
13-
{},
14-
]
8+
{
9+
"paddingBottom": 0,
10+
}
1511
}
1612
>
17-
<Text
18-
style={
19-
[
20-
{
21-
"fontSize": 11,
22-
"fontWeight": "700",
23-
"paddingRight": 3,
24-
},
25-
{
26-
"color": "#005FFF",
27-
},
28-
{},
29-
]
30-
}
31-
testID="read-by-container"
32-
>
33-
2
34-
</Text>
35-
<RNSVGSvgView
36-
align="xMidYMid"
37-
bbHeight={16}
38-
bbWidth={16}
39-
focusable={false}
40-
height={16}
41-
meetOrSlice={0}
42-
minX={0}
43-
minY={0}
44-
pathFill="#005FFF"
13+
<View
4514
style={
46-
[
47-
{
48-
"backgroundColor": "transparent",
49-
"borderWidth": 0,
50-
},
51-
{
52-
"flex": 0,
53-
"height": 16,
54-
"width": 16,
55-
},
56-
]
15+
{
16+
"height": "100%",
17+
}
5718
}
58-
vbHeight={24}
59-
vbWidth={24}
60-
width={16}
6119
>
62-
<RNSVGGroup
63-
fill={
64-
{
65-
"payload": 4278190080,
66-
"type": 0,
67-
}
20+
<View
21+
style={
22+
[
23+
{
24+
"alignItems": "flex-end",
25+
"flexDirection": "row",
26+
"justifyContent": "center",
27+
"paddingRight": 3,
28+
},
29+
{},
30+
]
6831
}
6932
>
70-
<RNSVGPath
71-
clipRule={0}
72-
d="M2.293 11.293a1 1 0 011.414 0l4 4a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414zM9.293 12.293a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414z"
73-
fill={
74-
{
75-
"payload": 4278214655,
76-
"type": 0,
77-
}
78-
}
79-
fillRule={0}
80-
propList={
33+
<Text
34+
style={
8135
[
82-
"fill",
83-
"fillRule",
36+
{
37+
"fontSize": 11,
38+
"fontWeight": "700",
39+
"paddingRight": 3,
40+
},
41+
{
42+
"color": "#005FFF",
43+
},
44+
{},
8445
]
8546
}
86-
/>
87-
<RNSVGPath
88-
clipRule={0}
89-
d="M15.707 7.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414-1.414l8-8a1 1 0 011.414 0zM21.707 7.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414-1.414l8-8a1 1 0 011.414 0z"
90-
fill={
91-
{
92-
"payload": 4278214655,
93-
"type": 0,
94-
}
95-
}
96-
fillRule={0}
97-
propList={
47+
testID="read-by-container"
48+
>
49+
1
50+
</Text>
51+
<RNSVGSvgView
52+
align="xMidYMid"
53+
bbHeight={16}
54+
bbWidth={16}
55+
focusable={false}
56+
height={16}
57+
meetOrSlice={0}
58+
minX={0}
59+
minY={0}
60+
pathFill="#005FFF"
61+
style={
9862
[
99-
"fill",
100-
"fillRule",
63+
{
64+
"backgroundColor": "transparent",
65+
"borderWidth": 0,
66+
},
67+
{
68+
"flex": 0,
69+
"height": 16,
70+
"width": 16,
71+
},
10172
]
10273
}
103-
/>
104-
</RNSVGGroup>
105-
</RNSVGSvgView>
74+
vbHeight={24}
75+
vbWidth={24}
76+
width={16}
77+
>
78+
<RNSVGGroup
79+
fill={
80+
{
81+
"payload": 4278190080,
82+
"type": 0,
83+
}
84+
}
85+
>
86+
<RNSVGPath
87+
clipRule={0}
88+
d="M2.293 11.293a1 1 0 011.414 0l4 4a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414zM9.293 12.293a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414z"
89+
fill={
90+
{
91+
"payload": 4278214655,
92+
"type": 0,
93+
}
94+
}
95+
fillRule={0}
96+
propList={
97+
[
98+
"fill",
99+
"fillRule",
100+
]
101+
}
102+
/>
103+
<RNSVGPath
104+
clipRule={0}
105+
d="M15.707 7.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414-1.414l8-8a1 1 0 011.414 0zM21.707 7.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414-1.414l8-8a1 1 0 011.414 0z"
106+
fill={
107+
{
108+
"payload": 4278214655,
109+
"type": 0,
110+
}
111+
}
112+
fillRule={0}
113+
propList={
114+
[
115+
"fill",
116+
"fillRule",
117+
]
118+
}
119+
/>
120+
</RNSVGGroup>
121+
</RNSVGSvgView>
122+
</View>
123+
</View>
106124
</View>
107125
`;

0 commit comments

Comments
 (0)