stream-chat-react-native-core 9.9.0 → 9.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/Channel/Channel.js +3 -0
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js +2 -0
- package/lib/commonjs/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageWrapper.js +6 -3
- package/lib/commonjs/components/Message/MessageItemView/MessageWrapper.js.map +1 -1
- package/lib/commonjs/contexts/channelContext/ChannelContext.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +3 -0
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/Channel/hooks/useCreateChannelContext.js +2 -0
- package/lib/module/components/Channel/hooks/useCreateChannelContext.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageWrapper.js +6 -3
- package/lib/module/components/Message/MessageItemView/MessageWrapper.js.map +1 -1
- package/lib/module/contexts/channelContext/ChannelContext.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts +1 -1
- package/lib/typescript/components/Channel/hooks/useCreateChannelContext.d.ts.map +1 -1
- package/lib/typescript/components/Channel/hooks/useMessageListPagination.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageWrapper.d.ts.map +1 -1
- package/lib/typescript/contexts/channelContext/ChannelContext.d.ts +6 -0
- package/lib/typescript/contexts/channelContext/ChannelContext.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Channel/Channel.tsx +3 -0
- package/src/components/Channel/hooks/useCreateChannelContext.ts +2 -0
- package/src/components/Message/MessageItemView/MessageWrapper.tsx +5 -1
- package/src/components/MessageList/__tests__/dateSeparatorSystemMessages.test.tsx +373 -0
- package/src/contexts/channelContext/ChannelContext.tsx +6 -0
- package/src/version.json +1 -1
|
@@ -9,6 +9,7 @@ export const useCreateChannelContext = ({
|
|
|
9
9
|
enableMessageGroupingByUser,
|
|
10
10
|
enforceUniqueReaction,
|
|
11
11
|
error,
|
|
12
|
+
allowDateSeparatorForSystemMessages,
|
|
12
13
|
hideDateSeparators,
|
|
13
14
|
hideStickyDateHeader,
|
|
14
15
|
highlightedMessageId,
|
|
@@ -49,6 +50,7 @@ export const useCreateChannelContext = ({
|
|
|
49
50
|
enableMessageGroupingByUser,
|
|
50
51
|
enforceUniqueReaction,
|
|
51
52
|
error,
|
|
53
|
+
allowDateSeparatorForSystemMessages,
|
|
52
54
|
hideDateSeparators,
|
|
53
55
|
hideStickyDateHeader,
|
|
54
56
|
highlightedMessageId,
|
|
@@ -34,6 +34,7 @@ export const MessageWrapper = React.memo(function MessageWrapper(props: MessageW
|
|
|
34
34
|
const { message, previousMessage, nextMessage } = props;
|
|
35
35
|
const { client } = useChatContext();
|
|
36
36
|
const {
|
|
37
|
+
allowDateSeparatorForSystemMessages,
|
|
37
38
|
channelUnreadStateStore,
|
|
38
39
|
channel,
|
|
39
40
|
hideDateSeparators,
|
|
@@ -111,7 +112,10 @@ export const MessageWrapper = React.memo(function MessageWrapper(props: MessageW
|
|
|
111
112
|
return (
|
|
112
113
|
<View testID={`message-list-item-${message.id}`}>
|
|
113
114
|
{message.type === 'system' ? (
|
|
114
|
-
|
|
115
|
+
<>
|
|
116
|
+
{allowDateSeparatorForSystemMessages ? renderDateSeperator : null}
|
|
117
|
+
<MessageSystem message={message} style={messageContainer} />
|
|
118
|
+
</>
|
|
115
119
|
) : wrapMessageInTheme ? (
|
|
116
120
|
<ThemeProvider mergedStyle={modifiedTheme}>
|
|
117
121
|
{renderDateSeperator}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { cleanup, render, waitFor } from '@testing-library/react-native';
|
|
5
|
+
|
|
6
|
+
import { Channel as ChannelType, LocalMessage } from 'stream-chat';
|
|
7
|
+
|
|
8
|
+
import { WithComponents } from '../../../contexts/componentsContext/ComponentsContext';
|
|
9
|
+
import { OverlayProvider } from '../../../contexts/overlayContext/OverlayProvider';
|
|
10
|
+
import { usePaginatedMessageListContext } from '../../../contexts/paginatedMessageListContext/PaginatedMessageListContext';
|
|
11
|
+
import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
|
|
12
|
+
import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
|
|
13
|
+
import { generateChannelResponse } from '../../../mock-builders/generator/channel';
|
|
14
|
+
import { generateMember } from '../../../mock-builders/generator/member';
|
|
15
|
+
import { generateMessage } from '../../../mock-builders/generator/message';
|
|
16
|
+
import { generateUser } from '../../../mock-builders/generator/user';
|
|
17
|
+
import { getTestClientWithUser } from '../../../mock-builders/mock';
|
|
18
|
+
import { Channel } from '../../Channel/Channel';
|
|
19
|
+
import { Chat } from '../../Chat/Chat';
|
|
20
|
+
import { InlineDateSeparator, InlineDateSeparatorProps } from '../InlineDateSeparator';
|
|
21
|
+
import { MessageList } from '../MessageList';
|
|
22
|
+
|
|
23
|
+
const user = generateUser();
|
|
24
|
+
const at = (day: number, hour: number) => new Date(Date.UTC(2026, 0, day, hour, 0, 0));
|
|
25
|
+
|
|
26
|
+
const message = (day: number, hour: number) =>
|
|
27
|
+
generateMessage({ text: `message d${day} ${hour}h`, timestamp: at(day, hour), user });
|
|
28
|
+
|
|
29
|
+
const systemMessage = (day: number, hour: number) =>
|
|
30
|
+
generateMessage({
|
|
31
|
+
text: `system d${day} ${hour}h`,
|
|
32
|
+
timestamp: at(day, hour),
|
|
33
|
+
type: 'system',
|
|
34
|
+
user,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
type TestMessage = ReturnType<typeof message>;
|
|
38
|
+
|
|
39
|
+
const renderMessageList = async (
|
|
40
|
+
messages: TestMessage[],
|
|
41
|
+
channelProps: Partial<React.ComponentProps<typeof Channel>> = {},
|
|
42
|
+
components:
|
|
43
|
+
| React.ComponentProps<typeof WithComponents>['overrides']
|
|
44
|
+
| ((channel: ChannelType) => React.ComponentProps<typeof WithComponents>['overrides']) = {},
|
|
45
|
+
{ hasPrev }: { hasPrev?: boolean } = {},
|
|
46
|
+
) => {
|
|
47
|
+
const mockedChannel = generateChannelResponse({
|
|
48
|
+
members: [generateMember({ user })],
|
|
49
|
+
messages,
|
|
50
|
+
});
|
|
51
|
+
const chatClient = await getTestClientWithUser({ id: user.id });
|
|
52
|
+
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
|
|
53
|
+
const channel = chatClient.channel('messaging', mockedChannel.channel.id);
|
|
54
|
+
await channel.watch();
|
|
55
|
+
|
|
56
|
+
if (hasPrev !== undefined) {
|
|
57
|
+
const currentSet = channel.state.messageSets.find((set) => set.isCurrent);
|
|
58
|
+
if (currentSet) {
|
|
59
|
+
currentSet.pagination = { hasNext: false, hasPrev };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const overrides = typeof components === 'function' ? components(channel) : components;
|
|
64
|
+
|
|
65
|
+
const result = render(
|
|
66
|
+
<OverlayProvider>
|
|
67
|
+
<Chat client={chatClient}>
|
|
68
|
+
<WithComponents overrides={overrides}>
|
|
69
|
+
<Channel channel={channel} {...channelProps}>
|
|
70
|
+
<MessageList />
|
|
71
|
+
</Channel>
|
|
72
|
+
</WithComponents>
|
|
73
|
+
</Chat>
|
|
74
|
+
</OverlayProvider>,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(result.queryAllByTestId(/^message-list-item-/).length).toBe(messages.length);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** The rendered list, oldest first, with separators interleaved where they appear. */
|
|
85
|
+
const readRows = (
|
|
86
|
+
queryAllByTestId: Awaited<ReturnType<typeof renderMessageList>>['queryAllByTestId'],
|
|
87
|
+
messages: TestMessage[],
|
|
88
|
+
) =>
|
|
89
|
+
queryAllByTestId(/^(date-separator|message-list-item-)/)
|
|
90
|
+
.map((node) => {
|
|
91
|
+
const testID = node.props.testID as string;
|
|
92
|
+
if (testID === 'date-separator') {
|
|
93
|
+
return `--- ${node.props.children?.props?.accessibilityLabel} ---`;
|
|
94
|
+
}
|
|
95
|
+
const id = testID.replace('message-list-item-', '');
|
|
96
|
+
return messages.find((item) => item.id === id)?.text as string;
|
|
97
|
+
})
|
|
98
|
+
// the list is inverted, so tree order is newest first
|
|
99
|
+
.reverse();
|
|
100
|
+
|
|
101
|
+
describe('allowDateSeparatorForSystemMessages', () => {
|
|
102
|
+
afterEach(cleanup);
|
|
103
|
+
|
|
104
|
+
it('leaves a day opened by a system message undated when the flag is off', async () => {
|
|
105
|
+
// Default behaviour: the system message consumes the day boundary and renders nothing, and
|
|
106
|
+
// the regular message below shares its day, so neither gets a separator.
|
|
107
|
+
const messages = [message(1, 9), systemMessage(2, 8), message(2, 9)];
|
|
108
|
+
|
|
109
|
+
const { queryAllByTestId } = await renderMessageList(messages);
|
|
110
|
+
|
|
111
|
+
expect(readRows(queryAllByTestId, messages)).toEqual([
|
|
112
|
+
'--- January 1, 2026 ---',
|
|
113
|
+
'message d1 9h',
|
|
114
|
+
'system d2 8h',
|
|
115
|
+
'message d2 9h',
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('dates that day when the flag is on, above the system message', async () => {
|
|
120
|
+
const messages = [message(1, 9), systemMessage(2, 8), message(2, 9)];
|
|
121
|
+
|
|
122
|
+
const { queryAllByTestId } = await renderMessageList(messages, {
|
|
123
|
+
allowDateSeparatorForSystemMessages: true,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(readRows(queryAllByTestId, messages)).toEqual([
|
|
127
|
+
'--- January 1, 2026 ---',
|
|
128
|
+
'message d1 9h',
|
|
129
|
+
'--- January 2, 2026 ---',
|
|
130
|
+
'system d2 8h',
|
|
131
|
+
'message d2 9h',
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('dates a channel whose every day opens with a system message', async () => {
|
|
136
|
+
// With the flag off this renders zero separators across the whole channel, because the
|
|
137
|
+
// oldest row is a system message too, so even the start-of-history separator is consumed.
|
|
138
|
+
const messages = [
|
|
139
|
+
systemMessage(1, 8),
|
|
140
|
+
message(1, 10),
|
|
141
|
+
systemMessage(2, 8),
|
|
142
|
+
message(2, 10),
|
|
143
|
+
systemMessage(3, 8),
|
|
144
|
+
message(3, 10),
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const off = await renderMessageList(messages);
|
|
148
|
+
expect(off.queryAllByTestId('date-separator')).toHaveLength(0);
|
|
149
|
+
cleanup();
|
|
150
|
+
|
|
151
|
+
const on = await renderMessageList(messages, { allowDateSeparatorForSystemMessages: true });
|
|
152
|
+
expect(on.queryAllByTestId('date-separator')).toHaveLength(3);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('does not date a day twice when a system message sits mid-day', async () => {
|
|
156
|
+
const messages = [message(1, 9), systemMessage(1, 12), message(1, 15)];
|
|
157
|
+
|
|
158
|
+
const { queryAllByTestId } = await renderMessageList(messages, {
|
|
159
|
+
allowDateSeparatorForSystemMessages: true,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(readRows(queryAllByTestId, messages)).toEqual([
|
|
163
|
+
'--- January 1, 2026 ---',
|
|
164
|
+
'message d1 9h',
|
|
165
|
+
'system d1 12h',
|
|
166
|
+
'message d1 15h',
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('stays off when separators are hidden entirely', async () => {
|
|
171
|
+
const { queryAllByTestId } = await renderMessageList(
|
|
172
|
+
[message(1, 9), systemMessage(2, 8), message(2, 9)],
|
|
173
|
+
{ allowDateSeparatorForSystemMessages: true, hideDateSeparators: true },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
expect(queryAllByTestId('date-separator')).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('a channel that writes a system message at every session boundary', () => {
|
|
181
|
+
afterEach(cleanup);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Some integrations write a system message whenever a session starts, and use those markers as
|
|
185
|
+
* their own day anchors. They want a separator only on days that hold a real message: a session
|
|
186
|
+
* opened and abandoned leaves a marker behind, and dating that day gives a header with nothing
|
|
187
|
+
* meaningful under it.
|
|
188
|
+
*
|
|
189
|
+
* The flag makes those days reachable - the separator is rendered on the marker, so the
|
|
190
|
+
* component override is invoked and can decide. The list is read from
|
|
191
|
+
* `PaginatedMessageListContext` rather than `channel.state` because that context value is
|
|
192
|
+
* memoized on a digest of the whole list, so the separator re-evaluates whenever the list
|
|
193
|
+
* changes, including for rows whose own neighbours did not.
|
|
194
|
+
*/
|
|
195
|
+
const SessionAwareDateSeparator = ({ date }: InlineDateSeparatorProps) => {
|
|
196
|
+
const { hasMore, messages } = usePaginatedMessageListContext();
|
|
197
|
+
|
|
198
|
+
if (!date) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const day = date.toDateString();
|
|
203
|
+
const hasRealMessage = messages.some(
|
|
204
|
+
(item: LocalMessage) => item.type !== 'system' && item.created_at.toDateString() === day,
|
|
205
|
+
);
|
|
206
|
+
// A day we have not finished loading may still gain a real message - keep it for now, or the
|
|
207
|
+
// separator pops in mid-scroll once `loadMore` brings that day's messages in.
|
|
208
|
+
const dayIsPartiallyLoaded = day === messages[0]?.created_at.toDateString() && hasMore;
|
|
209
|
+
|
|
210
|
+
if (!hasRealMessage && !dayIsPartiallyLoaded) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// The SDK's wrapper padding is zeroed through the theme in this setup, so it is re-added here
|
|
215
|
+
// and only applies to separators that actually render.
|
|
216
|
+
return (
|
|
217
|
+
<View style={{ paddingVertical: 8 }}>
|
|
218
|
+
<InlineDateSeparator date={date} />
|
|
219
|
+
</View>
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const renderSessionChannel = (messages: TestMessage[]) =>
|
|
224
|
+
renderMessageList(
|
|
225
|
+
messages,
|
|
226
|
+
{ allowDateSeparatorForSystemMessages: true },
|
|
227
|
+
{ InlineDateSeparator: SessionAwareDateSeparator },
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
it('dates every day with a real message and skips the abandoned session', async () => {
|
|
231
|
+
const messages = [
|
|
232
|
+
systemMessage(1, 9),
|
|
233
|
+
message(1, 10),
|
|
234
|
+
message(1, 11),
|
|
235
|
+
systemMessage(2, 14), // session opened and abandoned - no messages that day
|
|
236
|
+
systemMessage(3, 8),
|
|
237
|
+
message(3, 9),
|
|
238
|
+
systemMessage(4, 11),
|
|
239
|
+
message(4, 12),
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
const { queryAllByTestId } = await renderSessionChannel(messages);
|
|
243
|
+
|
|
244
|
+
expect(readRows(queryAllByTestId, messages)).toEqual([
|
|
245
|
+
'--- January 1, 2026 ---',
|
|
246
|
+
'system d1 9h',
|
|
247
|
+
'message d1 10h',
|
|
248
|
+
'message d1 11h',
|
|
249
|
+
'system d2 14h',
|
|
250
|
+
'--- January 3, 2026 ---',
|
|
251
|
+
'system d3 8h',
|
|
252
|
+
'message d3 9h',
|
|
253
|
+
'--- January 4, 2026 ---',
|
|
254
|
+
'system d4 11h',
|
|
255
|
+
'message d4 12h',
|
|
256
|
+
]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('does not date a day made up of nothing but abandoned sessions', async () => {
|
|
260
|
+
const messages = [
|
|
261
|
+
message(1, 9),
|
|
262
|
+
systemMessage(2, 8),
|
|
263
|
+
systemMessage(2, 15),
|
|
264
|
+
systemMessage(2, 20),
|
|
265
|
+
message(3, 9),
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const { queryAllByTestId } = await renderSessionChannel(messages);
|
|
269
|
+
|
|
270
|
+
expect(
|
|
271
|
+
queryAllByTestId('date-separator')
|
|
272
|
+
.map((node) => node.props.children?.props?.accessibilityLabel)
|
|
273
|
+
.reverse(),
|
|
274
|
+
).toEqual(['January 1, 2026', 'January 3, 2026']);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('dates a day once when sessions restart during it', async () => {
|
|
278
|
+
const messages = [
|
|
279
|
+
systemMessage(1, 9),
|
|
280
|
+
message(1, 10),
|
|
281
|
+
systemMessage(1, 14), // second session, same day
|
|
282
|
+
message(1, 15),
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const { queryAllByTestId } = await renderSessionChannel(messages);
|
|
286
|
+
|
|
287
|
+
expect(queryAllByTestId('date-separator')).toHaveLength(1);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('reading the list from the channel instead of context', () => {
|
|
292
|
+
afterEach(cleanup);
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The same rule, written against the `channel` the integration already holds rather than the
|
|
296
|
+
* message list context. `hasMore` on `PaginatedMessageListContext` is set from
|
|
297
|
+
* `channel.state.messagePagination.hasPrev` at every call site, so the two are equivalent.
|
|
298
|
+
*/
|
|
299
|
+
const channelBackedSeparator =
|
|
300
|
+
(channel: ChannelType) =>
|
|
301
|
+
({ date }: InlineDateSeparatorProps) => {
|
|
302
|
+
if (!date) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const { messages } = channel.state;
|
|
307
|
+
const day = date.toDateString();
|
|
308
|
+
const hasRealMessage = messages.some(
|
|
309
|
+
(item) => item.type !== 'system' && item.created_at.toDateString() === day,
|
|
310
|
+
);
|
|
311
|
+
const dayIsPartiallyLoaded =
|
|
312
|
+
day === messages[0]?.created_at.toDateString() && channel.state.messagePagination.hasPrev;
|
|
313
|
+
|
|
314
|
+
if (!hasRealMessage && !dayIsPartiallyLoaded) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return <InlineDateSeparator date={date} />;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const labels = (
|
|
322
|
+
queryAllByTestId: Awaited<ReturnType<typeof renderMessageList>>['queryAllByTestId'],
|
|
323
|
+
) =>
|
|
324
|
+
queryAllByTestId('date-separator')
|
|
325
|
+
.map((node) => node.props.children?.props?.accessibilityLabel)
|
|
326
|
+
.reverse();
|
|
327
|
+
|
|
328
|
+
it('suppresses the abandoned session day, same as the context-backed version', async () => {
|
|
329
|
+
const messages = [
|
|
330
|
+
systemMessage(1, 9),
|
|
331
|
+
message(1, 10),
|
|
332
|
+
systemMessage(2, 14), // opened and abandoned
|
|
333
|
+
systemMessage(3, 8),
|
|
334
|
+
message(3, 9),
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
const { queryAllByTestId } = await renderMessageList(
|
|
338
|
+
messages,
|
|
339
|
+
{ allowDateSeparatorForSystemMessages: true },
|
|
340
|
+
(channel) => ({ InlineDateSeparator: channelBackedSeparator(channel) }),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect(labels(queryAllByTestId)).toEqual(['January 1, 2026', 'January 3, 2026']);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('keeps the oldest loaded day while older pages remain', async () => {
|
|
347
|
+
// day 1 holds only a marker so far - suppressing it now would mean inserting the separator
|
|
348
|
+
// mid-scroll once loadMore brings that day's messages in
|
|
349
|
+
const messages = [systemMessage(1, 9), systemMessage(2, 8), message(2, 9)];
|
|
350
|
+
|
|
351
|
+
const { queryAllByTestId } = await renderMessageList(
|
|
352
|
+
messages,
|
|
353
|
+
{ allowDateSeparatorForSystemMessages: true },
|
|
354
|
+
(channel) => ({ InlineDateSeparator: channelBackedSeparator(channel) }),
|
|
355
|
+
{ hasPrev: true },
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
expect(labels(queryAllByTestId)).toEqual(['January 1, 2026', 'January 2, 2026']);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('suppresses that same day once the history is fully loaded', async () => {
|
|
362
|
+
const messages = [systemMessage(1, 9), systemMessage(2, 8), message(2, 9)];
|
|
363
|
+
|
|
364
|
+
const { queryAllByTestId } = await renderMessageList(
|
|
365
|
+
messages,
|
|
366
|
+
{ allowDateSeparatorForSystemMessages: true },
|
|
367
|
+
(channel) => ({ InlineDateSeparator: channelBackedSeparator(channel) }),
|
|
368
|
+
{ hasPrev: false },
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
expect(labels(queryAllByTestId)).toEqual(['January 2, 2026']);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
@@ -43,6 +43,12 @@ export type ChannelContextValue = {
|
|
|
43
43
|
*/
|
|
44
44
|
enforceUniqueReaction: boolean;
|
|
45
45
|
error: boolean | Error;
|
|
46
|
+
/**
|
|
47
|
+
* Render the inline date separator above a system message when that message is the first of
|
|
48
|
+
* its day. Off by default: without it a system message on a day boundary consumes the boundary
|
|
49
|
+
* and renders nothing, so that day gets no separator at all.
|
|
50
|
+
*/
|
|
51
|
+
allowDateSeparatorForSystemMessages: boolean;
|
|
46
52
|
/**
|
|
47
53
|
* Hide inline date separators on channel
|
|
48
54
|
*/
|
package/src/version.json
CHANGED