stream-chat-react-native-core 9.11.0-beta.3 → 9.11.1-beta.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/ChannelPreview/ChannelLastMessagePreview.js +1 -0
- package/lib/commonjs/components/ChannelPreview/ChannelLastMessagePreview.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +2 -0
- package/lib/commonjs/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewTitle.js +1 -0
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewTitle.js.map +1 -1
- package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js +4 -0
- package/lib/commonjs/components/Message/MessageItemView/MessageStatus.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageFlashList.js +38 -6
- package/lib/commonjs/components/MessageList/MessageFlashList.js.map +1 -1
- package/lib/commonjs/components/MessageList/MessageList.js +16 -4
- package/lib/commonjs/components/MessageList/MessageList.js.map +1 -1
- package/lib/commonjs/store/apis/getChannelMessages.js +5 -3
- package/lib/commonjs/store/apis/getChannelMessages.js.map +1 -1
- package/lib/commonjs/store/apis/getChannels.js +6 -3
- package/lib/commonjs/store/apis/getChannels.js.map +1 -1
- package/lib/commonjs/store/apis/getChannelsForFilterSort.js +2 -1
- package/lib/commonjs/store/apis/getChannelsForFilterSort.js.map +1 -1
- package/lib/commonjs/store/apis/queries/selectMessagesForChannels.js +7 -5
- package/lib/commonjs/store/apis/queries/selectMessagesForChannels.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/ChannelPreview/ChannelLastMessagePreview.js +1 -0
- package/lib/module/components/ChannelPreview/ChannelLastMessagePreview.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js +2 -0
- package/lib/module/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelPreviewTitle.js +1 -0
- package/lib/module/components/ChannelPreview/ChannelPreviewTitle.js.map +1 -1
- package/lib/module/components/Message/MessageItemView/MessageStatus.js +4 -0
- package/lib/module/components/Message/MessageItemView/MessageStatus.js.map +1 -1
- package/lib/module/components/MessageList/MessageFlashList.js +38 -6
- package/lib/module/components/MessageList/MessageFlashList.js.map +1 -1
- package/lib/module/components/MessageList/MessageList.js +16 -4
- package/lib/module/components/MessageList/MessageList.js.map +1 -1
- package/lib/module/store/apis/getChannelMessages.js +5 -3
- package/lib/module/store/apis/getChannelMessages.js.map +1 -1
- package/lib/module/store/apis/getChannels.js +6 -3
- package/lib/module/store/apis/getChannels.js.map +1 -1
- package/lib/module/store/apis/getChannelsForFilterSort.js +2 -1
- package/lib/module/store/apis/getChannelsForFilterSort.js.map +1 -1
- package/lib/module/store/apis/queries/selectMessagesForChannels.js +7 -5
- package/lib/module/store/apis/queries/selectMessagesForChannels.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.d.ts.map +1 -1
- package/lib/typescript/components/Message/MessageItemView/MessageStatus.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageFlashList.d.ts +1 -1
- package/lib/typescript/components/MessageList/MessageFlashList.d.ts.map +1 -1
- package/lib/typescript/components/MessageList/MessageList.d.ts.map +1 -1
- package/lib/typescript/store/apis/getChannelMessages.d.ts +2 -1
- package/lib/typescript/store/apis/getChannelMessages.d.ts.map +1 -1
- package/lib/typescript/store/apis/getChannels.d.ts +3 -1
- package/lib/typescript/store/apis/getChannels.d.ts.map +1 -1
- package/lib/typescript/store/apis/getChannelsForFilterSort.d.ts.map +1 -1
- package/lib/typescript/store/apis/queries/selectMessagesForChannels.d.ts +3 -1
- package/lib/typescript/store/apis/queries/selectMessagesForChannels.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ChannelPreview/ChannelLastMessagePreview.tsx +1 -1
- package/src/components/ChannelPreview/ChannelMessagePreviewDeliveryStatus.tsx +8 -2
- package/src/components/ChannelPreview/ChannelPreviewTitle.tsx +1 -1
- package/src/components/Message/MessageItemView/MessageStatus.tsx +16 -2
- package/src/components/Message/MessageItemView/__tests__/MessageStatus.test.tsx +21 -0
- package/src/components/MessageList/MessageFlashList.tsx +58 -8
- package/src/components/MessageList/MessageList.tsx +22 -5
- package/src/components/MessageList/__tests__/MessageList.test.tsx +51 -0
- package/src/store/__tests__/selectMessagesForChannels.test.ts +63 -0
- package/src/store/apis/getChannelMessages.ts +4 -1
- package/src/store/apis/getChannels.ts +5 -1
- package/src/store/apis/getChannelsForFilterSort.ts +1 -0
- package/src/store/apis/queries/selectMessagesForChannels.ts +7 -2
- package/src/version.json +1 -1
|
@@ -73,6 +73,7 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
|
|
|
73
73
|
<icons.CheckAll
|
|
74
74
|
height={16}
|
|
75
75
|
stroke={styles.readCheck.color}
|
|
76
|
+
testID='message-status-read'
|
|
76
77
|
width={16}
|
|
77
78
|
{...checkAllIcon}
|
|
78
79
|
/>
|
|
@@ -80,13 +81,26 @@ const MessageStatusWithContext = (props: MessageStatusPropsWithContext) => {
|
|
|
80
81
|
<icons.CheckAll
|
|
81
82
|
stroke={styles.deliveredCheck.color}
|
|
82
83
|
height={16}
|
|
84
|
+
testID='message-status-delivered'
|
|
83
85
|
width={16}
|
|
84
86
|
{...checkAllIcon}
|
|
85
87
|
/>
|
|
86
88
|
) : sending ? (
|
|
87
|
-
<icons.Time
|
|
89
|
+
<icons.Time
|
|
90
|
+
stroke={styles.sendingCheck.color}
|
|
91
|
+
height={16}
|
|
92
|
+
testID='message-status-sending'
|
|
93
|
+
width={16}
|
|
94
|
+
{...timeIcon}
|
|
95
|
+
/>
|
|
88
96
|
) : sent ? (
|
|
89
|
-
<icons.Check
|
|
97
|
+
<icons.Check
|
|
98
|
+
stroke={styles.sentCheck.color}
|
|
99
|
+
height={16}
|
|
100
|
+
testID='message-status-sent'
|
|
101
|
+
width={16}
|
|
102
|
+
{...checkIcon}
|
|
103
|
+
/>
|
|
90
104
|
) : null}
|
|
91
105
|
</View>
|
|
92
106
|
</>
|
|
@@ -125,4 +125,25 @@ describe('MessageStatus', () => {
|
|
|
125
125
|
});
|
|
126
126
|
},
|
|
127
127
|
);
|
|
128
|
+
|
|
129
|
+
it.each([
|
|
130
|
+
[1, 1, 'sending', 'message-status-sending'],
|
|
131
|
+
[2, 2, 'received', 'message-status-read'],
|
|
132
|
+
[1, 1, 'received', 'message-status-sent'],
|
|
133
|
+
[2, 1, 'received', 'message-status-delivered'],
|
|
134
|
+
] as [number, number, string, string][])(
|
|
135
|
+
'should expose a status testID when deliveredToCount is %s, readBy is %s and status is %s',
|
|
136
|
+
async (deliveredToCount, readBy, status, testID) => {
|
|
137
|
+
const message = generateMessage({ user: generateUser() });
|
|
138
|
+
const { getByTestId } = renderMessageStatus({
|
|
139
|
+
deliveredToCount,
|
|
140
|
+
message: { ...message, status },
|
|
141
|
+
readBy,
|
|
142
|
+
});
|
|
143
|
+
// The icon row is hidden from screen readers in favour of the hidden text label.
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(getByTestId(testID, { includeHiddenElements: true })).toBeTruthy();
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
);
|
|
128
149
|
});
|
|
@@ -4,6 +4,7 @@ import { LayoutChangeEvent, ScrollViewProps, StyleSheet, View, useColorScheme }
|
|
|
4
4
|
import Animated from 'react-native-reanimated';
|
|
5
5
|
|
|
6
6
|
import type { FlashListProps, FlashListRef } from '@shopify/flash-list';
|
|
7
|
+
import debounce from 'lodash/debounce';
|
|
7
8
|
import type { Channel, Event, LocalMessage, MessageResponse } from 'stream-chat';
|
|
8
9
|
|
|
9
10
|
import { useMessageList } from './hooks/useMessageList';
|
|
@@ -147,7 +148,7 @@ type MessageFlashListPropsWithContext = Pick<
|
|
|
147
148
|
> &
|
|
148
149
|
Pick<
|
|
149
150
|
ThreadContextValue,
|
|
150
|
-
'loadMoreRecentThread' | 'loadMoreThread' | 'thread' | 'threadInstance'
|
|
151
|
+
'loadMoreRecentThread' | 'loadMoreThread' | 'thread' | 'threadHasMore' | 'threadInstance'
|
|
151
152
|
> & {
|
|
152
153
|
/**
|
|
153
154
|
* Besides existing (default) UX behavior of underlying FlatList of MessageList component, if you want
|
|
@@ -346,6 +347,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
346
347
|
hasPendingInitialTargetLoad,
|
|
347
348
|
targetedMessage,
|
|
348
349
|
thread,
|
|
350
|
+
threadHasMore,
|
|
349
351
|
threadInstance,
|
|
350
352
|
threadList = false,
|
|
351
353
|
} = props;
|
|
@@ -835,6 +837,16 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
835
837
|
* once per content length.
|
|
836
838
|
*/
|
|
837
839
|
|
|
840
|
+
/**
|
|
841
|
+
* How many messages the list pages through are loaded: the thread's replies in a thread list, the
|
|
842
|
+
* channel's messages otherwise.
|
|
843
|
+
*/
|
|
844
|
+
const getLoadedMessageCount = useStableCallback(() =>
|
|
845
|
+
threadList && thread?.id
|
|
846
|
+
? (channel?.state.threads[thread.id]?.length ?? 0)
|
|
847
|
+
: (channel?.state.messages.length ?? 0),
|
|
848
|
+
);
|
|
849
|
+
|
|
838
850
|
/**
|
|
839
851
|
* 1. Makes a call to `loadMore` function, which queries more older messages.
|
|
840
852
|
* 2. Ensures that we call `loadMore`, once per content length
|
|
@@ -853,8 +865,11 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
853
865
|
onStartReachedTracker.current[processedMessageList.length] = true;
|
|
854
866
|
}
|
|
855
867
|
|
|
856
|
-
const callback = () => {
|
|
868
|
+
const callback = (loadedMessageCount: number) => {
|
|
857
869
|
onStartReachedInPromise.current = null;
|
|
870
|
+
if (getLoadedMessageCount() === loadedMessageCount) {
|
|
871
|
+
onError();
|
|
872
|
+
}
|
|
858
873
|
return Promise.resolve();
|
|
859
874
|
};
|
|
860
875
|
|
|
@@ -869,12 +884,13 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
869
884
|
if (onEndReachedInPromise.current) {
|
|
870
885
|
await onEndReachedInPromise.current;
|
|
871
886
|
}
|
|
887
|
+
const loadedMessageCount = getLoadedMessageCount();
|
|
872
888
|
onStartReachedInPromise.current = (
|
|
873
889
|
threadList && !!threadInstance && loadMoreRecentThread
|
|
874
890
|
? loadMoreRecentThread({})
|
|
875
891
|
: loadMoreRecent()
|
|
876
892
|
)
|
|
877
|
-
.then(callback)
|
|
893
|
+
.then(() => callback(loadedMessageCount))
|
|
878
894
|
.catch(onError);
|
|
879
895
|
});
|
|
880
896
|
|
|
@@ -885,7 +901,10 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
885
901
|
*/
|
|
886
902
|
const maybeCallOnEndReached = useStableCallback(async () => {
|
|
887
903
|
// If onStartReached has already been called for given data length, then ignore.
|
|
888
|
-
if (
|
|
904
|
+
if (
|
|
905
|
+
(processedMessageList?.length && onEndReachedTracker.current[processedMessageList.length]) ||
|
|
906
|
+
(threadList && !threadHasMore)
|
|
907
|
+
) {
|
|
889
908
|
return;
|
|
890
909
|
}
|
|
891
910
|
|
|
@@ -893,9 +912,11 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
893
912
|
onEndReachedTracker.current[processedMessageList.length] = true;
|
|
894
913
|
}
|
|
895
914
|
|
|
896
|
-
const callback = () => {
|
|
915
|
+
const callback = (loadedMessageCount: number) => {
|
|
897
916
|
onEndReachedInPromise.current = null;
|
|
898
|
-
|
|
917
|
+
if (getLoadedMessageCount() === loadedMessageCount) {
|
|
918
|
+
onError();
|
|
919
|
+
}
|
|
899
920
|
return Promise.resolve();
|
|
900
921
|
};
|
|
901
922
|
|
|
@@ -911,8 +932,9 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
911
932
|
await onStartReachedInPromise.current;
|
|
912
933
|
}
|
|
913
934
|
|
|
935
|
+
const loadedMessageCount = getLoadedMessageCount();
|
|
914
936
|
onEndReachedInPromise.current = (threadList ? loadMoreThread() : loadMore())
|
|
915
|
-
.then(callback)
|
|
937
|
+
.then(() => callback(loadedMessageCount))
|
|
916
938
|
.catch(onError);
|
|
917
939
|
});
|
|
918
940
|
|
|
@@ -1100,6 +1122,31 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
1100
1122
|
currentListHeightRef.current = height;
|
|
1101
1123
|
});
|
|
1102
1124
|
|
|
1125
|
+
const debouncedPrefillMessages = useMemo(
|
|
1126
|
+
() =>
|
|
1127
|
+
debounce(
|
|
1128
|
+
(viewportHeight: number, contentHeight: number) => {
|
|
1129
|
+
if (viewportHeight >= contentHeight) {
|
|
1130
|
+
maybeCallOnEndReached();
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
500,
|
|
1134
|
+
{
|
|
1135
|
+
leading: false,
|
|
1136
|
+
trailing: true,
|
|
1137
|
+
},
|
|
1138
|
+
),
|
|
1139
|
+
[maybeCallOnEndReached],
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
const onContentSizeChange = useStableCallback((width: number, height: number) => {
|
|
1143
|
+
if (additionalFlashListProps?.onContentSizeChange) {
|
|
1144
|
+
additionalFlashListProps.onContentSizeChange(width, height);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
debouncedPrefillMessages(currentListHeightRef.current ?? 0, height);
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1103
1150
|
const ListFooterComponent = useCallback(() => {
|
|
1104
1151
|
if (FooterComponent) {
|
|
1105
1152
|
return <FooterComponent />;
|
|
@@ -1172,6 +1219,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) =>
|
|
|
1172
1219
|
{...additionalFlashListPropsExcludingStyle}
|
|
1173
1220
|
accessibilityActions={messageListAccessibilityActions}
|
|
1174
1221
|
onAccessibilityAction={messageListOnAccessibilityAction}
|
|
1222
|
+
onContentSizeChange={onContentSizeChange}
|
|
1175
1223
|
/>
|
|
1176
1224
|
</MessageListItemProvider>
|
|
1177
1225
|
)}
|
|
@@ -1313,7 +1361,8 @@ export const MessageFlashList = (props: MessageFlashListProps) => {
|
|
|
1313
1361
|
const { disableTypingIndicator, FlatList, myMessageTheme, shouldShowUnreadUnderlay } =
|
|
1314
1362
|
useMessagesContext();
|
|
1315
1363
|
const { loadMore, loadMoreRecent } = usePaginatedMessageListContext();
|
|
1316
|
-
const { loadMoreRecentThread, loadMoreThread, thread, threadInstance } =
|
|
1364
|
+
const { loadMoreRecentThread, loadMoreThread, thread, threadHasMore, threadInstance } =
|
|
1365
|
+
useThreadContext();
|
|
1317
1366
|
const { readEvents } = useOwnCapabilitiesContext();
|
|
1318
1367
|
const { allowSendBeforeAttachmentsUpload, messageInputFloating, messageInputHeightStore } =
|
|
1319
1368
|
useMessageInputContext();
|
|
@@ -1355,6 +1404,7 @@ export const MessageFlashList = (props: MessageFlashListProps) => {
|
|
|
1355
1404
|
shouldShowUnreadUnderlay,
|
|
1356
1405
|
targetedMessage,
|
|
1357
1406
|
thread,
|
|
1407
|
+
threadHasMore,
|
|
1358
1408
|
threadInstance,
|
|
1359
1409
|
threadList,
|
|
1360
1410
|
}}
|
|
@@ -932,6 +932,16 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
932
932
|
* once per content length.
|
|
933
933
|
*/
|
|
934
934
|
|
|
935
|
+
/**
|
|
936
|
+
* How many messages the list pages through are loaded: the thread's replies in a thread list, the
|
|
937
|
+
* channel's messages otherwise.
|
|
938
|
+
*/
|
|
939
|
+
const getLoadedMessageCount = useStableCallback(() =>
|
|
940
|
+
threadList && thread?.id
|
|
941
|
+
? (channel?.state.threads[thread.id]?.length ?? 0)
|
|
942
|
+
: (channel?.state.messages.length ?? 0),
|
|
943
|
+
);
|
|
944
|
+
|
|
935
945
|
/**
|
|
936
946
|
* 1. Makes a call to `loadMoreRecent` function, which queries more recent messages.
|
|
937
947
|
* 2. Ensures that we call `loadMoreRecent`, once per content length
|
|
@@ -950,9 +960,11 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
950
960
|
onStartReachedTracker.current[processedMessageList.length] = true;
|
|
951
961
|
}
|
|
952
962
|
|
|
953
|
-
const callback = () => {
|
|
963
|
+
const callback = (loadedMessageCount: number) => {
|
|
954
964
|
onStartReachedInPromise.current = null;
|
|
955
|
-
|
|
965
|
+
if (getLoadedMessageCount() === loadedMessageCount) {
|
|
966
|
+
onError();
|
|
967
|
+
}
|
|
956
968
|
return Promise.resolve();
|
|
957
969
|
};
|
|
958
970
|
|
|
@@ -967,12 +979,13 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
967
979
|
if (onEndReachedInPromise.current) {
|
|
968
980
|
await onEndReachedInPromise.current;
|
|
969
981
|
}
|
|
982
|
+
const loadedMessageCount = getLoadedMessageCount();
|
|
970
983
|
onStartReachedInPromise.current = (
|
|
971
984
|
threadList && !!threadInstance && loadMoreRecentThread
|
|
972
985
|
? loadMoreRecentThread({})
|
|
973
986
|
: loadMoreRecent()
|
|
974
987
|
)
|
|
975
|
-
.then(callback)
|
|
988
|
+
.then(() => callback(loadedMessageCount))
|
|
976
989
|
.catch(onError);
|
|
977
990
|
});
|
|
978
991
|
|
|
@@ -995,8 +1008,11 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
995
1008
|
onEndReachedTracker.current[processedMessageList.length] = true;
|
|
996
1009
|
}
|
|
997
1010
|
|
|
998
|
-
const callback = () => {
|
|
1011
|
+
const callback = (loadedMessageCount: number) => {
|
|
999
1012
|
onEndReachedInPromise.current = null;
|
|
1013
|
+
if (getLoadedMessageCount() === loadedMessageCount) {
|
|
1014
|
+
onError();
|
|
1015
|
+
}
|
|
1000
1016
|
return Promise.resolve();
|
|
1001
1017
|
};
|
|
1002
1018
|
|
|
@@ -1011,8 +1027,9 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => {
|
|
|
1011
1027
|
if (onStartReachedInPromise.current) {
|
|
1012
1028
|
await onStartReachedInPromise.current;
|
|
1013
1029
|
}
|
|
1030
|
+
const loadedMessageCount = getLoadedMessageCount();
|
|
1014
1031
|
onEndReachedInPromise.current = (threadList ? loadMoreThread() : loadMore())
|
|
1015
|
-
.then(callback)
|
|
1032
|
+
.then(() => callback(loadedMessageCount))
|
|
1016
1033
|
.catch(onError);
|
|
1017
1034
|
});
|
|
1018
1035
|
|
|
@@ -650,6 +650,57 @@ describe('MessageList pagination', () => {
|
|
|
650
650
|
});
|
|
651
651
|
});
|
|
652
652
|
|
|
653
|
+
it('tries loading older messages again after a load that loaded nothing', async () => {
|
|
654
|
+
const user1 = generateUser();
|
|
655
|
+
const mockedChannel = generateChannelResponse({
|
|
656
|
+
members: [generateMember({ user: user1 })],
|
|
657
|
+
messages: Array.from({ length: 100 }, (_, i) => generateMessage({ text: `message-${i}` })),
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
const chatClient = await getTestClientWithUser({ id: 'testID' });
|
|
661
|
+
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
|
|
662
|
+
const channel = chatClient.channel('messaging', mockedChannel.channel.id);
|
|
663
|
+
await channel.watch();
|
|
664
|
+
|
|
665
|
+
// resolves without loading anything, as `loadMore` does when its request fails, e.g. offline
|
|
666
|
+
const loadMore = jest.fn(() => Promise.resolve());
|
|
667
|
+
mockedHook({ loadMore });
|
|
668
|
+
|
|
669
|
+
const { getByTestId } = render(
|
|
670
|
+
<OverlayProvider>
|
|
671
|
+
<Chat client={chatClient}>
|
|
672
|
+
<Channel channel={channel}>
|
|
673
|
+
<MessageList />
|
|
674
|
+
</Channel>
|
|
675
|
+
</Chat>
|
|
676
|
+
</OverlayProvider>,
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
const scrollToEnd = () =>
|
|
680
|
+
act(() => {
|
|
681
|
+
fireEvent(getByTestId('message-flat-list'), 'momentumScrollEnd', {
|
|
682
|
+
nativeEvent: {
|
|
683
|
+
contentOffset: { y: 1900 },
|
|
684
|
+
contentSize: { height: 2000, width: 200 },
|
|
685
|
+
layoutMeasurement: { height: 400, width: 200 },
|
|
686
|
+
},
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
scrollToEnd();
|
|
691
|
+
await waitFor(() => {
|
|
692
|
+
expect(loadMore).toHaveBeenCalledTimes(1);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// released after the same 2 second backoff as a rejected load
|
|
696
|
+
await act(() => new Promise((resolve) => setTimeout(resolve, 2100)));
|
|
697
|
+
scrollToEnd();
|
|
698
|
+
|
|
699
|
+
await waitFor(() => {
|
|
700
|
+
expect(loadMore).toHaveBeenCalledTimes(2);
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
653
704
|
it('should call load latest messages when the scroll to bottom button is pressed', async () => {
|
|
654
705
|
const user1 = generateUser();
|
|
655
706
|
const messages = Array.from({ length: 10 }, (_, i) =>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ChannelAPIResponse } from 'stream-chat';
|
|
2
|
+
|
|
3
|
+
import { generateChannelResponse } from '../../mock-builders/generator/channel';
|
|
4
|
+
import { generateMessage } from '../../mock-builders/generator/message';
|
|
5
|
+
import { BetterSqlite } from '../../test-utils/BetterSqlite';
|
|
6
|
+
import { getChannels } from '../apis/getChannels';
|
|
7
|
+
import { selectMessagesForChannels } from '../apis/queries/selectMessagesForChannels';
|
|
8
|
+
import { upsertChannels } from '../apis/upsertChannels';
|
|
9
|
+
import { SqliteClient } from '../SqliteClient';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The client reads the cached page as a channel list page of `message_limit` messages, so a page of
|
|
13
|
+
* a different size makes it guess wrongly whether older messages exist, for example that a channel
|
|
14
|
+
* with a long history starts at its cached messages.
|
|
15
|
+
*/
|
|
16
|
+
describe('selectMessagesForChannels', () => {
|
|
17
|
+
let cid: string;
|
|
18
|
+
let messages: ReturnType<typeof generateMessage>[];
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
await SqliteClient.initializeDatabase();
|
|
22
|
+
await BetterSqlite.openDB();
|
|
23
|
+
|
|
24
|
+
const channelResponse = generateChannelResponse({ members: [], messages: [] });
|
|
25
|
+
cid = channelResponse.channel.cid as string;
|
|
26
|
+
messages = Array.from({ length: 30 }, (_, i) =>
|
|
27
|
+
generateMessage({ cid, timestamp: new Date(Date.UTC(2020, 0, 1, 0, i)) }),
|
|
28
|
+
);
|
|
29
|
+
channelResponse.messages = messages as unknown as typeof channelResponse.messages;
|
|
30
|
+
await upsertChannels({
|
|
31
|
+
channels: [channelResponse] as unknown as ChannelAPIResponse[],
|
|
32
|
+
isLatestMessagesSet: true,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
BetterSqlite.dropAllTables();
|
|
38
|
+
BetterSqlite.closeDB();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const ids = (rows: { id: string }[]) => rows.map((row) => row.id);
|
|
42
|
+
|
|
43
|
+
it("returns a channel's newest 25 messages, oldest first, by default", async () => {
|
|
44
|
+
const rows = await selectMessagesForChannels([cid]);
|
|
45
|
+
|
|
46
|
+
expect(ids(rows)).toEqual(ids(messages.slice(-25)));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns a channel's newest `messageLimit` messages", async () => {
|
|
50
|
+
expect(ids(await selectMessagesForChannels([cid], 10))).toEqual(ids(messages.slice(-10)));
|
|
51
|
+
expect(ids(await selectMessagesForChannels([cid], 50))).toEqual(ids(messages));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('applies the limit that getChannels is given', async () => {
|
|
55
|
+
const [channel] = await getChannels({
|
|
56
|
+
channelIds: [cid],
|
|
57
|
+
currentUserId: 'some-user',
|
|
58
|
+
messageLimit: 10,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(ids(channel.messages)).toEqual(ids(messages.slice(-10)));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -12,15 +12,18 @@ import type { TableRow, TableRowJoinedUser } from '../types';
|
|
|
12
12
|
export const getChannelMessages = async ({
|
|
13
13
|
channelIds,
|
|
14
14
|
currentUserId,
|
|
15
|
+
messageLimit,
|
|
15
16
|
}: {
|
|
16
17
|
channelIds: string[];
|
|
17
18
|
currentUserId: string;
|
|
19
|
+
messageLimit?: number;
|
|
18
20
|
}) => {
|
|
19
21
|
SqliteClient.logger?.('info', 'getChannelMessages', {
|
|
20
22
|
channelIds,
|
|
21
23
|
currentUserId,
|
|
24
|
+
messageLimit,
|
|
22
25
|
});
|
|
23
|
-
const messageRows = await selectMessagesForChannels(channelIds);
|
|
26
|
+
const messageRows = await selectMessagesForChannels(channelIds, messageLimit);
|
|
24
27
|
const messageIds = messageRows.map(({ id }) => id);
|
|
25
28
|
|
|
26
29
|
// Populate the message reactions.
|
|
@@ -16,17 +16,20 @@ import { SqliteClient } from '../SqliteClient';
|
|
|
16
16
|
* @param {Object} param
|
|
17
17
|
* @param {Array} param.channelIds List of channel ids to fetch.
|
|
18
18
|
* @param {Array} param.currentUserId Id of the current logged in user.
|
|
19
|
+
* @param {number} param.messageLimit How many of each channel's newest messages to return, 25 by default.
|
|
19
20
|
*
|
|
20
21
|
* @returns {Array} Channels with enriched state.
|
|
21
22
|
*/
|
|
22
23
|
export const getChannels = async ({
|
|
23
24
|
channelIds,
|
|
24
25
|
currentUserId,
|
|
26
|
+
messageLimit,
|
|
25
27
|
}: {
|
|
26
28
|
channelIds: string[];
|
|
27
29
|
currentUserId: string;
|
|
30
|
+
messageLimit?: number;
|
|
28
31
|
}): Promise<Omit<ChannelAPIResponse, 'duration'>[]> => {
|
|
29
|
-
SqliteClient.logger?.('info', 'getChannels', { channelIds, currentUserId });
|
|
32
|
+
SqliteClient.logger?.('info', 'getChannels', { channelIds, currentUserId, messageLimit });
|
|
30
33
|
|
|
31
34
|
const [channels, cidVsDraft, cidVsMembers, cidVsReads, cidVsMessages, cidVsActiveLocations] =
|
|
32
35
|
await Promise.all([
|
|
@@ -37,6 +40,7 @@ export const getChannels = async ({
|
|
|
37
40
|
getChannelMessages({
|
|
38
41
|
channelIds,
|
|
39
42
|
currentUserId,
|
|
43
|
+
messageLimit,
|
|
40
44
|
}),
|
|
41
45
|
getChannelActiveLocations({ channelIds }),
|
|
42
46
|
]);
|
|
@@ -2,8 +2,12 @@ import { tables } from '../../schema';
|
|
|
2
2
|
import { SqliteClient } from '../../SqliteClient';
|
|
3
3
|
import type { TableRowJoinedUser } from '../../types';
|
|
4
4
|
|
|
5
|
+
/** The `message_limit` the client assumes for a channel list query that sets none. */
|
|
6
|
+
export const DEFAULT_CHANNEL_LIST_MESSAGE_LIMIT = 25;
|
|
7
|
+
|
|
5
8
|
export const selectMessagesForChannels = async (
|
|
6
9
|
cids: string[],
|
|
10
|
+
messageLimit: number = DEFAULT_CHANNEL_LIST_MESSAGE_LIMIT,
|
|
7
11
|
): Promise<TableRowJoinedUser<'messages'>[]> => {
|
|
8
12
|
const questionMarks = Array(cids.length).fill('?').join(',');
|
|
9
13
|
const messagesColumnNames = Object.keys(tables.messages.columns)
|
|
@@ -15,6 +19,7 @@ export const selectMessagesForChannels = async (
|
|
|
15
19
|
|
|
16
20
|
SqliteClient.logger?.('info', 'selectMessagesForChannels', {
|
|
17
21
|
cids,
|
|
22
|
+
messageLimit,
|
|
18
23
|
});
|
|
19
24
|
|
|
20
25
|
const result = await SqliteClient.executeSql(
|
|
@@ -38,9 +43,9 @@ export const selectMessagesForChannels = async (
|
|
|
38
43
|
LEFT JOIN
|
|
39
44
|
users b
|
|
40
45
|
ON b.id = a.userId
|
|
41
|
-
WHERE RowNum
|
|
46
|
+
WHERE RowNum <= ?
|
|
42
47
|
ORDER BY a.createdAt ASC`,
|
|
43
|
-
cids,
|
|
48
|
+
[...cids, messageLimit],
|
|
44
49
|
);
|
|
45
50
|
|
|
46
51
|
return result.map((r) => JSON.parse(r.value));
|
package/src/version.json
CHANGED