From 3b736d2b35bb606471e35aadb4a864d5cb5a45ba Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:05:05 +0200 Subject: [PATCH 1/2] offline: Keep persisted channel lastMessageAt consistent with its latest message --- .../domain/channel/internal/ChannelMapper.kt | 4 +- .../channel/internal/ChannelMapperTest.kt | 45 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapper.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapper.kt index faddd5e3728..6b3c315e138 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapper.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapper.kt @@ -16,8 +16,10 @@ package io.getstream.chat.android.offline.repository.domain.channel.internal +import io.getstream.chat.android.client.extensions.getCreatedAtOrNull import io.getstream.chat.android.client.extensions.internal.lastMessage import io.getstream.chat.android.client.extensions.syncUnreadCountWithReads +import io.getstream.chat.android.core.utils.date.max import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.ChannelUserRead import io.getstream.chat.android.models.DraftMessage @@ -53,7 +55,7 @@ internal fun Channel.toEntity(): ChannelEntity { memberCount = memberCount, reads = read.map(ChannelUserRead::toEntity).associateBy(ChannelUserReadEntity::userId).toMutableMap(), lastMessageId = lastMessage?.id, - lastMessageAt = lastMessageAt, + lastMessageAt = max(lastMessageAt, lastMessage?.getCreatedAtOrNull()), createdByUserId = createdBy.id, watcherIds = watchers.map(User::id), watcherCount = watcherCount, diff --git a/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapperTest.kt b/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapperTest.kt index b797c1f4a2b..d1a83235b68 100644 --- a/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapperTest.kt +++ b/stream-chat-android-offline/src/test/java/io/getstream/chat/android/offline/repository/domain/channel/internal/ChannelMapperTest.kt @@ -37,6 +37,7 @@ import io.getstream.chat.android.randomUser import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import java.util.Date internal class ChannelMapperTest { @@ -86,7 +87,7 @@ internal class ChannelMapperTest { memberCount = channel.memberCount, reads = reads.map { it.toEntity() }.associateBy { it.userId }.toMutableMap(), lastMessageId = lastMessage.id, - lastMessageAt = channel.lastMessageAt, + lastMessageAt = listOfNotNull(channel.lastMessageAt, lastMessage.createdAt).maxOrNull(), createdByUserId = createdByUser.id, watcherIds = watchers.map { it.id }, watcherCount = channel.watcherCount, @@ -102,6 +103,48 @@ internal class ChannelMapperTest { assertEquals(expectedChannelEntity, result) } + @Test + fun `toEntity should advance lastMessageAt to the newest message when the stored value is stale`() = runTest { + val newest = randomMessage( + createdAt = Date(2000), + createdLocallyAt = null, + parentId = null, + deletedAt = null, + deletedForMe = false, + ) + val channel = randomChannel(messages = listOf(newest), lastMessageAt = Date(1000)) + + val entity = channel.toEntity() + + assertEquals(Date(2000), entity.lastMessageAt) + assertEquals(newest.id, entity.lastMessageId) + } + + @Test + fun `toEntity should keep lastMessageAt when it is newer than the last message`() = runTest { + val older = randomMessage( + createdAt = Date(1000), + createdLocallyAt = null, + parentId = null, + deletedAt = null, + deletedForMe = false, + ) + val channel = randomChannel(messages = listOf(older), lastMessageAt = Date(3000)) + + val entity = channel.toEntity() + + assertEquals(Date(3000), entity.lastMessageAt) + } + + @Test + fun `toEntity should keep lastMessageAt when there is no last message`() = runTest { + val channel = randomChannel(messages = emptyList(), lastMessageAt = Date(1000)) + + val entity = channel.toEntity() + + assertEquals(Date(1000), entity.lastMessageAt) + } + @Test @Suppress("LongMethod") fun `Should map ChannelEntity to Channel correctly`() = runTest { From fc609c0e4bfa4ff34cbf60a21c76ddeff9511ec7 Mon Sep 17 00:00:00 2001 From: Gian <47775302+gpunto@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:17:10 +0200 Subject: [PATCH 2/2] e2e: Wait for the message delivery before the participant reads it --- .../chat/android/compose/tests/MessageDeliveryStatusTests.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt index 9317a213698..d335a28f095 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageDeliveryStatusTests.kt @@ -80,7 +80,9 @@ class MessageDeliveryStatusTests : StreamTestCase() { userRobot.login().openChannel() } step("AND user successfully sends a new message") { - userRobot.sendMessage(sampleText) + userRobot + .sendMessage(sampleText) + .assertMessageDeliveryStatus(MessageDeliveryStatus.SENT) // await for message to be successfully sent } step("WHEN participant reads the user's message") { participantRobot.readMessage()