stream-chat-react-native-core 9.0.0-beta.23 → 9.0.0-beta.24
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 +11 -10
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/Channel/Channel.js +11 -10
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/offline-support/optimistic-update.js +125 -0
- package/src/components/Channel/Channel.tsx +11 -8
- package/src/version.json +1 -1
|
@@ -720,6 +720,131 @@ export const OptimisticUpdates = () => {
|
|
|
720
720
|
expect(sendMessageSpy).toHaveBeenCalled();
|
|
721
721
|
});
|
|
722
722
|
});
|
|
723
|
+
|
|
724
|
+
it('should not re-add a failed local message after reconnect when its pending send task was resolved', async () => {
|
|
725
|
+
const localMessage = generateMessage({
|
|
726
|
+
status: MessageStatusTypes.SENDING,
|
|
727
|
+
text: 'offline resend',
|
|
728
|
+
user: chatClient.user,
|
|
729
|
+
userId: chatClient.userID,
|
|
730
|
+
});
|
|
731
|
+
const serverMessage = generateMessage({
|
|
732
|
+
id: localMessage.id,
|
|
733
|
+
text: localMessage.text,
|
|
734
|
+
user: chatClient.user,
|
|
735
|
+
userId: chatClient.userID,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({
|
|
739
|
+
localMessage,
|
|
740
|
+
message: localMessage,
|
|
741
|
+
options: {},
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
render(
|
|
745
|
+
<Chat client={chatClient} enableOfflineSupport>
|
|
746
|
+
<Channel channel={channel} initialValue={localMessage.text}>
|
|
747
|
+
<CallbackEffectWithContext
|
|
748
|
+
callback={async ({ sendMessage }) => {
|
|
749
|
+
useMockedApis(chatClient, [erroredPostApi()]);
|
|
750
|
+
await sendMessage();
|
|
751
|
+
}}
|
|
752
|
+
context={MessageInputContext}
|
|
753
|
+
>
|
|
754
|
+
<View testID='children' />
|
|
755
|
+
</CallbackEffectWithContext>
|
|
756
|
+
</Channel>
|
|
757
|
+
</Chat>,
|
|
758
|
+
);
|
|
759
|
+
await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy());
|
|
760
|
+
|
|
761
|
+
let pendingTask;
|
|
762
|
+
await waitFor(async () => {
|
|
763
|
+
const pendingTasks = await chatClient.offlineDb.getPendingTasks();
|
|
764
|
+
expect(pendingTasks).toHaveLength(1);
|
|
765
|
+
pendingTask = pendingTasks[0];
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
expect(channel.state.messages.some((message) => message.id === localMessage.id)).toBe(true);
|
|
769
|
+
|
|
770
|
+
jest.spyOn(channel, 'watch').mockResolvedValue({});
|
|
771
|
+
|
|
772
|
+
channel.state.removeMessage(localMessage);
|
|
773
|
+
channel.state.addMessageSorted(serverMessage, true);
|
|
774
|
+
await chatClient.offlineDb.deletePendingTask({ id: pendingTask.id });
|
|
775
|
+
|
|
776
|
+
await act(async () => {
|
|
777
|
+
await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
await waitFor(() => {
|
|
781
|
+
const matchingMessages = channel.state.messages.filter(
|
|
782
|
+
(message) => message.text === localMessage.text,
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
expect(matchingMessages).toHaveLength(1);
|
|
786
|
+
expect(matchingMessages[0].id).toBe(serverMessage.id);
|
|
787
|
+
expect(matchingMessages[0].status).not.toBe(MessageStatusTypes.FAILED);
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it('should re-add a failed local message after reconnect when fresh state still does not contain it', async () => {
|
|
792
|
+
const localMessage = generateMessage({
|
|
793
|
+
status: MessageStatusTypes.SENDING,
|
|
794
|
+
text: 'offline resend unresolved',
|
|
795
|
+
user: chatClient.user,
|
|
796
|
+
userId: chatClient.userID,
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
jest.spyOn(channel.messageComposer, 'compose').mockResolvedValue({
|
|
800
|
+
localMessage,
|
|
801
|
+
message: localMessage,
|
|
802
|
+
options: {},
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
render(
|
|
806
|
+
<Chat client={chatClient} enableOfflineSupport>
|
|
807
|
+
<Channel channel={channel} initialValue={localMessage.text}>
|
|
808
|
+
<CallbackEffectWithContext
|
|
809
|
+
callback={async ({ sendMessage }) => {
|
|
810
|
+
useMockedApis(chatClient, [erroredPostApi()]);
|
|
811
|
+
await sendMessage();
|
|
812
|
+
}}
|
|
813
|
+
context={MessageInputContext}
|
|
814
|
+
>
|
|
815
|
+
<View testID='children' />
|
|
816
|
+
</CallbackEffectWithContext>
|
|
817
|
+
</Channel>
|
|
818
|
+
</Chat>,
|
|
819
|
+
);
|
|
820
|
+
await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy());
|
|
821
|
+
|
|
822
|
+
let pendingTask;
|
|
823
|
+
await waitFor(async () => {
|
|
824
|
+
const pendingTasks = await chatClient.offlineDb.getPendingTasks();
|
|
825
|
+
expect(pendingTasks).toHaveLength(1);
|
|
826
|
+
pendingTask = pendingTasks[0];
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
jest.spyOn(channel, 'watch').mockResolvedValue({});
|
|
830
|
+
|
|
831
|
+
channel.state.removeMessage(localMessage);
|
|
832
|
+
await chatClient.offlineDb.deletePendingTask({ id: pendingTask.id });
|
|
833
|
+
|
|
834
|
+
await act(async () => {
|
|
835
|
+
await chatClient.offlineDb.syncManager.invokeSyncStatusListeners(true);
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
await waitFor(() => {
|
|
839
|
+
const matchingMessages = channel.state.messages.filter(
|
|
840
|
+
(message) => message.id === localMessage.id,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
expect(matchingMessages).toHaveLength(1);
|
|
844
|
+
expect(matchingMessages[0].status).toBe(MessageStatusTypes.FAILED);
|
|
845
|
+
expect(matchingMessages[0].text).toBe(localMessage.text);
|
|
846
|
+
});
|
|
847
|
+
});
|
|
723
848
|
});
|
|
724
849
|
});
|
|
725
850
|
};
|
|
@@ -1167,6 +1167,15 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
|
|
|
1167
1167
|
updated_at: message.updated_at?.toString(),
|
|
1168
1168
|
}) as unknown as MessageResponse;
|
|
1169
1169
|
|
|
1170
|
+
const getRecoverableFailedMessages = (messages: LocalMessage[] = []) =>
|
|
1171
|
+
messages
|
|
1172
|
+
.filter(
|
|
1173
|
+
(message) =>
|
|
1174
|
+
message.status === MessageStatusTypes.FAILED &&
|
|
1175
|
+
!channel.state.findMessage(message.id, message.parent_id),
|
|
1176
|
+
)
|
|
1177
|
+
.map(parseMessage);
|
|
1178
|
+
|
|
1170
1179
|
try {
|
|
1171
1180
|
if (channelMessagesState?.messages) {
|
|
1172
1181
|
await channel?.watch({
|
|
@@ -1181,9 +1190,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
|
|
|
1181
1190
|
if (!thread) {
|
|
1182
1191
|
copyChannelState();
|
|
1183
1192
|
|
|
1184
|
-
const failedMessages = channelMessagesState.messages
|
|
1185
|
-
?.filter((message) => message.status === MessageStatusTypes.FAILED)
|
|
1186
|
-
.map(parseMessage);
|
|
1193
|
+
const failedMessages = getRecoverableFailedMessages(channelMessagesState.messages);
|
|
1187
1194
|
if (failedMessages?.length) {
|
|
1188
1195
|
channel.state.addMessagesSorted(failedMessages);
|
|
1189
1196
|
}
|
|
@@ -1192,11 +1199,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
|
|
|
1192
1199
|
} else {
|
|
1193
1200
|
await reloadThread();
|
|
1194
1201
|
|
|
1195
|
-
const failedThreadMessages = thread
|
|
1196
|
-
? threadMessages
|
|
1197
|
-
.filter((message) => message.status === MessageStatusTypes.FAILED)
|
|
1198
|
-
.map(parseMessage)
|
|
1199
|
-
: [];
|
|
1202
|
+
const failedThreadMessages = thread ? getRecoverableFailedMessages(threadMessages) : [];
|
|
1200
1203
|
if (failedThreadMessages.length) {
|
|
1201
1204
|
channel.state.addMessagesSorted(failedThreadMessages);
|
|
1202
1205
|
setThreadMessages([...channel.state.threads[thread.id]]);
|
package/src/version.json
CHANGED