Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/SampleApp/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2821,7 +2821,7 @@ PODS:
- SDWebImageWebPCoder (0.15.0):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- stream-chat-react-native (9.6.1):
- stream-chat-react-native (9.7.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
Expand Down Expand Up @@ -3252,7 +3252,7 @@ SPEC CHECKSUMS:
GoogleAppMeasurement: 57270ccc2b77472d7e85c4cbe45972564eff78bb
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
hermes-engine: 26897e4ae07f28dde638ffab1d25a07f556f7e3e
hermes-engine: d8f4d6c98c2ea2858468ad58fbf6b3a0b33e4da6
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Expand All @@ -3270,7 +3270,7 @@ SPEC CHECKSUMS:
React: e2dc35338068bbd299c66f043ae0d7f25de8499e
React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48
React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0
React-Core-prebuilt: 81cf85599e5cd4ab9bbbbdf4db08ab654f52e760
React-Core-prebuilt: 07da8c06ca047a4047eb44266cd74e1f422d7655
React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146
React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716
React-debug: 9af1e96a6069c996e3d9f1e493603e74bc9f1593
Expand Down Expand Up @@ -3342,7 +3342,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2
ReactCodegen: 7016a2114079361a2f1536b3c91a15ceb8eb7ca4
ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f
ReactNativeDependencies: 08dba4ab0913ea4d2255e4fc63538742aa3e7ac5
ReactNativeDependencies: b494f9d4ef665da25093dffcdd0b1a110475f404
RNCClipboard: 7a7d4557bfd3370b35c99dfecd92ae7b9fc4948a
RNFastImage: 14580cef91660b889645fb9e87f58a53621db993
RNFBApp: 3b942e786ca88524ba17df665a1a360fb3eee525
Expand All @@ -3359,7 +3359,7 @@ SPEC CHECKSUMS:
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
stream-chat-react-native: fdb9929071f737e85eee5b8dbdb6bc4e120ac5c4
stream-chat-react-native: b8f70a8fb5382c4a6a6a6dc16d52dd7243bab3af
Teleport: 58dccc8594d74a77cdc2a2191b60656f9aaac743
Yoga: e240fec777ff1f21ef42ccebbb44b6d262125855

Expand Down
8 changes: 4 additions & 4 deletions package/src/__tests__/offline-support/offline-feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ import { BetterSqlite } from '../../test-utils/BetterSqlite';
const ChannelPreviewComponent = ({ channel }: { channel: ChannelLLC }) => (
<View accessibilityLabel='list-item' testID={channel.cid}>
<Text>{channel.data?.name}</Text>
<Text>{channel.state?.messages?.[0]?.text}</Text>
<Text>{channel.messagePaginator?.headItems?.[0]?.text}</Text>
</View>
);

Expand Down Expand Up @@ -731,7 +731,7 @@ export const Generic = () => {
expect(matchingRows.length).toBe(1);
expect(matchingRows[0].hidden).toBeTruthy();
expect(matchingMessagesRows.length).toBe(
chatClient.activeChannels[hiddenChannel.cid].state.messages.length,
chatClient.activeChannels[hiddenChannel.cid].messagePaginator.headItems.length,
);
});
});
Expand Down Expand Up @@ -766,7 +766,7 @@ export const Generic = () => {
expect(matchingRows.length).toBe(1);
expect(matchingRows[0].hidden).toBeTruthy();
expect(matchingMessagesRows.length).toBe(
chatClient.activeChannels[hiddenChannel.cid].state.messages.length,
chatClient.activeChannels[hiddenChannel.cid].messagePaginator.headItems.length,
);
});

Expand All @@ -792,7 +792,7 @@ export const Generic = () => {
expect(matchingRows.length).toBe(1);
expect(matchingRows[0].hidden).toBeFalsy();
expect(matchingMessagesRows.length).toBe(
chatClient.activeChannels[hiddenChannel.cid].state.messages.length,
chatClient.activeChannels[hiddenChannel.cid].messagePaginator.headItems.length,
);
});
});
Expand Down
40 changes: 21 additions & 19 deletions package/src/__tests__/offline-support/optimistic-update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export const OptimisticUpdates = () => {
describe('send reaction', () => {
it('pending task should exist if sendReaction request fails', async () => {
const reaction = generateReaction();
const targetMessage = channel.state.messages[0];
const targetMessage = channel.messagePaginator.headItems[0];

render(
<Chat client={chatClient} enableOfflineSupport>
Expand Down Expand Up @@ -333,7 +333,7 @@ export const OptimisticUpdates = () => {

it('pending task should be cleared if sendReaction request is successful', async () => {
const reaction = generateReaction();
const targetMessage = channel.state.messages[0];
const targetMessage = channel.messagePaginator.headItems[0];

render(
<Chat client={chatClient} enableOfflineSupport>
Expand Down Expand Up @@ -429,7 +429,7 @@ export const OptimisticUpdates = () => {
describe('delete reaction', () => {
it('pending task should exist if deleteReaction request fails', async () => {
const reaction = generateReaction();
const targetMessage = channel.state.messages[0];
const targetMessage = channel.messagePaginator.headItems[0];

render(
<Chat client={chatClient} enableOfflineSupport>
Expand Down Expand Up @@ -461,7 +461,7 @@ export const OptimisticUpdates = () => {

it('pending task should be cleared if deleteReaction request is successful', async () => {
const reaction = generateReaction();
const targetMessage = channel.state.messages[0];
const targetMessage = channel.messagePaginator.headItems[0];

render(
<Chat client={chatClient} enableOfflineSupport>
Expand All @@ -488,7 +488,7 @@ export const OptimisticUpdates = () => {

describe('edit message', () => {
it('should keep the optimistic edit in state and DB if the LLC queues the edit', async () => {
const message = channel.state.messages[0];
const message = channel.messagePaginator.headItems[0];
const editedText = 'edited while offline';

render(
Expand Down Expand Up @@ -536,7 +536,7 @@ export const OptimisticUpdates = () => {
await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy());

await waitFor(async () => {
const updatedMessage = channel.state.findMessage(message.id);
const updatedMessage = channel.messagePaginator.getItem(message.id);
const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks');
const dbMessages = await BetterSqlite.selectFromTable('messages');
const dbMessage = dbMessages.find((row) => row.id === message.id);
Expand All @@ -551,7 +551,7 @@ export const OptimisticUpdates = () => {
});

it('should keep the optimistic edit if the request fails', async () => {
const message = channel.state.messages[0];
const message = channel.messagePaginator.headItems[0];
const editedText = 'should stay optimistic';

render(
Expand Down Expand Up @@ -588,7 +588,7 @@ export const OptimisticUpdates = () => {
await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy());

await waitFor(async () => {
const updatedMessage = channel.state.findMessage(message.id);
const updatedMessage = channel.messagePaginator.getItem(message.id);
const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks');
const dbMessages = await BetterSqlite.selectFromTable('messages');
const dbMessage = dbMessages.find((row) => row.id === message.id);
Expand All @@ -600,7 +600,7 @@ export const OptimisticUpdates = () => {
});

it('should not set message_text_updated_at during optimistic edit of a failed message', async () => {
const message = channel.state.messages[0];
const message = channel.messagePaginator.headItems[0];
const optimisticStateSpy = jest.fn();

render(
Expand All @@ -609,7 +609,7 @@ export const OptimisticUpdates = () => {
channel={channel}
doUpdateMessageRequest={
(() => {
const optimisticMessage = channel.state.findMessage(message.id);
const optimisticMessage = channel.messagePaginator.getItem(message.id);
optimisticStateSpy(optimisticMessage);

return {
Expand Down Expand Up @@ -649,7 +649,7 @@ export const OptimisticUpdates = () => {
});

it('should keep the optimistic edit for attachment updates without auto-queueing', async () => {
const message = channel.state.messages[0];
const message = channel.messagePaginator.headItems[0];
const editedText = 'edited attachment message';
const localUri = 'file://edited-attachment.png';

Expand Down Expand Up @@ -698,7 +698,7 @@ export const OptimisticUpdates = () => {
await waitFor(() => expect(screen.getByTestId('children')).toBeTruthy());

await waitFor(async () => {
const updatedMessage = channel.state.findMessage(message.id);
const updatedMessage = channel.messagePaginator.getItem(message.id);
const pendingTasksRows = await BetterSqlite.selectFromTable('pendingTasks');
const dbMessages = await BetterSqlite.selectFromTable('messages');
const dbMessage = dbMessages.find((row) => row.id === message.id);
Expand All @@ -715,7 +715,7 @@ export const OptimisticUpdates = () => {

describe('pending task execution', () => {
it('pending task should be executed after connection is recovered', async () => {
const message = channel.state.messages[0];
const message = channel.messagePaginator.headItems[0];
const reaction = generateReaction();

render(
Expand Down Expand Up @@ -855,22 +855,24 @@ export const OptimisticUpdates = () => {
pendingTask = pendingTasks[0];
});

expect(channel.state.messages.some((message) => message.id === localMessage.id)).toBe(true);
expect(
channel.messagePaginator.headItems.some((message) => message.id === localMessage.id),
).toBe(true);

jest
.spyOn(channel, 'watch')
.mockResolvedValue({} as Awaited<ReturnType<typeof channel.watch>>);

channel.state.removeMessage(localMessage);
channel.state.addMessageSorted(serverMessage, true);
channel.messagePaginator.removeItem({ id: localMessage.id });
channel.messagePaginator.ingestItem(channel.state.formatMessage(serverMessage));
await getOfflineDb(chatClient).deletePendingTask({ id: pendingTask!.id });

await act(async () => {
await getOfflineDb(chatClient).syncManager.invokeSyncStatusListeners(true);
});

await waitFor(() => {
const matchingMessages = channel.state.messages.filter(
const matchingMessages = channel.messagePaginator.headItems.filter(
(message) => message.text === localMessage.text,
);

Expand Down Expand Up @@ -922,15 +924,15 @@ export const OptimisticUpdates = () => {
.spyOn(channel, 'watch')
.mockResolvedValue({} as Awaited<ReturnType<typeof channel.watch>>);

channel.state.removeMessage(localMessage);
channel.messagePaginator.removeItem({ id: localMessage.id });
await getOfflineDb(chatClient).deletePendingTask({ id: pendingTask!.id });

await act(async () => {
await getOfflineDb(chatClient).syncManager.invokeSyncStatusListeners(true);
});

await waitFor(() => {
const matchingMessages = channel.state.messages.filter(
const matchingMessages = channel.messagePaginator.headItems.filter(
(message) => message.id === localMessage.id,
);

Expand Down
10 changes: 7 additions & 3 deletions package/src/components/Channel/Channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,10 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
// of loaded messages, and the paginator otherwise defaults to a 100-message page.
channel.messagePaginator.pageSize = DEFAULT_MESSAGE_LIST_PAGE_SIZE;

if ((!channel.initialized || !channel.state.isUpToDate) && initializeOnMount) {
if (
(!channel.initialized || !channel.messagePaginator.isActiveIntervalAtHead) &&
initializeOnMount
) {
try {
await channel?.watch();
} catch (err) {
Expand Down Expand Up @@ -703,7 +706,9 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
.filter(
(message) =>
message.status === MessageStatusTypes.FAILED &&
!channel.state.findMessage(message.id, message.parent_id),
!(message.parent_id
? threadInstance?.messagePaginator.getItem(message.id)
: channel.messagePaginator.getItem(message.id)),
)
.map(parseMessage);

Expand Down Expand Up @@ -735,7 +740,6 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
if (atLatest) {
await markRead();
}
channel.state.setIsUpToDate(atLatest);
} else if (threadInstance) {
await threadInstance.reload();

Expand Down
14 changes: 7 additions & 7 deletions package/src/components/Channel/__tests__/Channel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,9 @@ describe('Channel initial load useEffect', () => {

const loadMessageIntoState = jest.fn(() => {
const newMessages = getElementsAround(messages, 'id', messageToSearch.id);
channel.state.messages = newMessages;
newMessages.forEach((m) =>
channel.messagePaginator.ingestItem(channel.state.formatMessage(m)),
);
});

channel.state = {
Expand Down Expand Up @@ -619,19 +621,17 @@ describe('Channel initial load useEffect', () => {
});

await waitFor(() => {
channel.state.addMessagesSorted(
Array.from({ length: 10 }, (_, i) =>
generateMessage({ status: 'failed', text: `message-${i}` }),
),
);
Array.from({ length: 10 }, (_, i) =>
generateMessage({ status: 'failed', text: `message-${i}` }),
).forEach((m) => channel.messagePaginator.ingestItem(channel.state.formatMessage(m)));
});

await waitFor(() => {
act(() => dispatchConnectionChanged(chatClient));
});

await waitFor(() => {
expect(channel.state.messages.length).toBe(20);
expect(channel.messagePaginator.headItems.length).toBe(20);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jest.mock('../../ChannelPreview/ChannelSwipableWrapper', () => ({
const ChannelPreviewComponent = ({ channel }: { channel: ChannelType }) => (
<View accessibilityLabel='list-item' testID={channel.id}>
<Text>{(channel.data as { name?: string } | undefined)?.name}</Text>
<Text>{channel.state.messages[0]?.text}</Text>
<Text>{channel.messagePaginator.headItems[0]?.text}</Text>
</View>
);

Expand Down Expand Up @@ -261,7 +261,7 @@ describe('ChannelList', () => {
const channel = generateChannel({
data: { name: id },
id,
state: { latestMessages: [], members: {}, messages: [], setIsUpToDate: jest.fn() },
state: { members: {} },
} as unknown as Parameters<typeof generateChannel>[0]) as unknown as {
countUnread: () => number;
messageComposer: { registerDraftEventSubscriptions: () => () => void };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ const initChannelFromData = async (

channel.countUnread = jest.fn().mockReturnValue(0);
channel.initialized = true;
channel.lastMessage = jest.fn().mockReturnValue(generateMessage());
channel.muteStatus = jest.fn().mockReturnValue({ muted: false });
channel.state.messages = [generateMessage()];
channel.messagePaginator.ingestItem(channel.state.formatMessage(generateMessage()));

return channel;
};
Expand Down
Loading
Loading