Skip to content

Commit d6fe3d3

Browse files
authored
Merge pull request #2108 from GetStream/develop
v10.12.0
2 parents 367289c + 38796ac commit d6fe3d3

File tree

14 files changed

+1152
-74
lines changed

14 files changed

+1152
-74
lines changed

docusaurus/docs/React/components/utility-components/channel-preview-ui.mdx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,40 @@ Latest message preview to display. Will be either a string or a JSX.Element rend
109109
| --------------------- |
110110
| string \| JSX.Element |
111111

112+
### messageDeliveryStatus
113+
114+
Status describing whether own message has been delivered or read by another. If the last message is not an own message, then the status is undefined. The value is calculated from `channel.read` data on mount and updated on every `message.new` resp. `message.read` WS event.
115+
116+
| Type |
117+
|-------------------------|
118+
| `MessageDeliveryStatus` |
119+
120+
Use `MessageDeliveryStatus` enum to determine the current delivery status, for example:
121+
122+
```typescript jsx
123+
import { MessageDeliveryStatus } from 'stream-chat-react';
124+
import {
125+
DoubleCheckMarkIcon,
126+
SingleCheckMarkIcon,
127+
} from '../icons';
128+
129+
type MessageDeliveryStatusIndicator = {
130+
messageDeliveryStatus: MessageDeliveryStatus;
131+
}
132+
133+
export const MessageDeliveryStatusIndicator = ({ messageDeliveryStatus }: MessageDeliveryStatusIndicator) => {
134+
// the last message is not an own message in the channel
135+
if (!messageDeliveryStatus) return null;
136+
137+
return (
138+
<div>
139+
{messageDeliveryStatus === MessageDeliveryStatus.DELIVERED && <SingleCheckMarkIcon /> }
140+
{messageDeliveryStatus === MessageDeliveryStatus.READ && <DoubleCheckMarkIcon /> }
141+
</div>
142+
);
143+
};
144+
```
145+
112146
### onSelect
113147

114148
Custom handler invoked when the `ChannelPreview` is clicked. The SDK uses `ChannelPreview` to display items of channel search results. There, behind the scenes, the new active channel is set.

docusaurus/docs/React/components/utility-components/channel-search.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,14 @@ Custom search function to override default. The first argument should expect an
338338
| --------------------------------------------------------------------------------------------------- |
339339
| (`params: ChannelSearchFunctionParams, event: React.BaseSyntheticEvent` ) => Promise<void\> \| void |
340340

341+
### searchDebounceIntervalMs
342+
343+
The number of milliseconds to debounce the search query.
344+
345+
| Type | Default |
346+
|----------|---------|
347+
| `number` | 300 |
348+
341349
### SearchInput
342350

343351
Custom UI component to display the search text input.

docusaurus/docs/React/guides/customization/channel-list-preview.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ import '@stream-io/stream-chat-css/dist/css/index.css';
141141
>
142142
```
143143

144-
Next, let's add a little bit more useful information to the component using more of the default props and a value pulled from the `ChatContext`, as well as some styling using custom CSS.
144+
Next, let's add a bit more useful information to the component using more of the default props and a value pulled from the `ChatContext`, as well as some styling using custom CSS.
145145
This context also exposes the client which makes it possible to use API methods.
146146

147147
:::note

docusaurus/docs/React/guides/customization/channel-search.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ const additionalProps = {
311311
312312
### The searchFunction Prop:
313313
314-
By default the `ChannelSearch` component searches just for users. Use the `searchForChannels` prop to also search for channels.
314+
By default, the `ChannelSearch` component searches just for users. Use the `searchForChannels` prop to also search for channels.
315315
316316
To override the search method, completely use the `searchFunction` prop. This prop is useful, say, when you want to search just for channels
317317
and for only channels that the current logged in user is a member of. See the example below for this.

src/components/ChannelList/__tests__/ChannelList.test.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const channelsQueryStateMock = {
4848
};
4949

5050
/**
51-
* We are gonna use following custom UI components for preview and list.
51+
* We use the following custom UI components for preview and list.
5252
* If we use ChannelPreviewMessenger or ChannelPreviewLastMessage here, then changes
5353
* to those components might end up breaking tests for ChannelList, which will be quite painful
5454
* to debug then.
@@ -438,6 +438,7 @@ describe('ChannelList', () => {
438438
});
439439

440440
describe('channel search', () => {
441+
const defaultSearchDebounceInterval = 300;
441442
const inputText = 'xxxxxxxxxx';
442443
const user1 = generateUser();
443444
const user2 = generateUser();
@@ -551,20 +552,24 @@ describe('ChannelList', () => {
551552
])(
552553
'theme v%s %s unmount search results on result click, if configured',
553554
async (themeVersion, _, clearSearchOnClickOutside) => {
555+
jest.useFakeTimers('modern');
556+
jest.spyOn(client, 'queryUsers').mockResolvedValue({ users: [generateUser()] });
554557
const { container } = await renderComponents(
555558
{ channel, client, themeVersion },
556559
{ additionalChannelSearchProps: { clearSearchOnClickOutside } },
557560
);
558-
const input = screen.queryByTestId('search-input');
561+
const input = screen.getByTestId('search-input');
559562
await act(() => {
560563
fireEvent.change(input, {
561564
target: {
562565
value: inputText,
563566
},
564567
});
565568
});
566-
567-
const searchResults = screen.queryAllByRole('option');
569+
await act(() => {
570+
jest.advanceTimersByTime(defaultSearchDebounceInterval + 1);
571+
});
572+
const searchResults = screen.queryAllByTestId('channel-search-result-user');
568573
useMockedApis(client, [getOrCreateChannelApi(generateChannel())]);
569574
await act(() => {
570575
fireEvent.click(searchResults[0]);
@@ -577,6 +582,7 @@ describe('ChannelList', () => {
577582
expect(container.querySelector(SEARCH_RESULT_LIST_SELECTOR)).toBeInTheDocument();
578583
}
579584
});
585+
jest.useRealTimers();
580586
},
581587
);
582588

@@ -645,6 +651,8 @@ describe('ChannelList', () => {
645651
it.each([['1'], ['2']])(
646652
'theme v%s should add the selected result to the top of the channel list',
647653
async (themeVersion) => {
654+
jest.useFakeTimers('modern');
655+
jest.spyOn(client, 'queryUsers').mockResolvedValue({ users: [generateUser()] });
648656
const getComputedStyleMock = jest.spyOn(window, 'getComputedStyle');
649657
getComputedStyleMock.mockReturnValue({
650658
getPropertyValue: jest.fn().mockReturnValue(themeVersion),
@@ -679,8 +687,11 @@ describe('ChannelList', () => {
679687
},
680688
});
681689
});
690+
await act(() => {
691+
jest.advanceTimersByTime(defaultSearchDebounceInterval + 1);
692+
});
682693

683-
const targetChannelPreview = screen.queryByText(channelNotInTheList.channel.name);
694+
const targetChannelPreview = screen.getByText(channelNotInTheList.channel.name);
684695
expect(targetChannelPreview).toBeInTheDocument();
685696
await act(() => {
686697
fireEvent.click(targetChannelPreview);
@@ -693,6 +704,7 @@ describe('ChannelList', () => {
693704
}
694705
});
695706
getComputedStyleMock.mockClear();
707+
jest.useRealTimers();
696708
},
697709
);
698710
});

src/components/ChannelPreview/ChannelPreview.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getLatestMessagePreview } from './utils';
77

88
import { ChatContextValue, useChatContext } from '../../context/ChatContext';
99
import { useTranslationContext } from '../../context/TranslationContext';
10+
import { MessageDeliveryStatus, useMessageDeliveryStatus } from './hooks/useMessageDeliveryStatus';
1011

1112
import type { Channel, Event } from 'stream-chat';
1213

@@ -29,6 +30,8 @@ export type ChannelPreviewUIComponentProps<
2930
lastMessage?: StreamMessage<StreamChatGenerics>;
3031
/** Latest message preview to display, will be a string or JSX element supporting markdown. */
3132
latestMessage?: string | JSX.Element;
33+
/** Status describing whether own message has been delivered or read by another. If the last message is not an own message, then the status is undefined. */
34+
messageDeliveryStatus?: MessageDeliveryStatus;
3235
/** Number of unread Messages */
3336
unread?: number;
3437
};
@@ -73,6 +76,10 @@ export const ChannelPreview = <
7376
channel.state.messages[channel.state.messages.length - 1],
7477
);
7578
const [unread, setUnread] = useState(0);
79+
const { messageDeliveryStatus } = useMessageDeliveryStatus<StreamChatGenerics>({
80+
channel,
81+
lastMessage,
82+
});
7683

7784
const isActive = activeChannel?.cid === channel.cid;
7885
const { muted } = useIsChannelMuted(channel);
@@ -126,6 +133,7 @@ export const ChannelPreview = <
126133
displayTitle={displayTitle}
127134
lastMessage={lastMessage}
128135
latestMessage={latestMessage}
136+
messageDeliveryStatus={messageDeliveryStatus}
129137
setActiveChannel={setActiveChannel}
130138
unread={unread}
131139
/>

0 commit comments

Comments
 (0)