Skip to content

Commit fe8fb0c

Browse files
Fix thread messages not grouped properly (#5985)
* Fix thread messages not grouped properly before receiving confirmation from server. * Update CHANGELOG.md. --------- Co-authored-by: André Mion <[email protected]>
1 parent 437cd60 commit fe8fb0c

File tree

3 files changed

+381
-1
lines changed

3 files changed

+381
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
## stream-chat-android-ui-common
4747
### 🐞 Fixed
48+
- Fix thread messages not grouped properly. [#5985](https://github.com/GetStream/stream-chat-android/pull/5985)
4849

4950
### ⬆️ Improved
5051

stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/utils/extensions/MessageFooterVisibility.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private fun isFooterVisibleWithTimeDifference(
7272
return when {
7373
isLastMessageInGroup -> true
7474
message.isDeleted() -> false
75-
message.user != nextMessage?.user ||
75+
message.user.id != nextMessage?.user?.id ||
7676
nextMessage.isDeleted() ||
7777
(nextMessage.getCreatedAtOrDefault(NEVER).time) -
7878
(message.getCreatedAtOrDefault(NEVER).time) >
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
/*
2+
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.ui.common.utils.extensions
18+
19+
import io.getstream.chat.android.models.Message
20+
import io.getstream.chat.android.models.User
21+
import io.getstream.chat.android.randomMessage
22+
import io.getstream.chat.android.ui.common.state.messages.list.MessageFooterVisibility
23+
import org.amshove.kluent.`should be equal to`
24+
import org.junit.jupiter.api.Test
25+
import java.util.Date
26+
27+
internal class MessageFooterVisibilityTest {
28+
29+
private val user1 = User(id = "user1", name = "User 1")
30+
private val user2 = User(id = "user2", name = "User 2")
31+
32+
// Test MessageFooterVisibility.Always
33+
34+
@Test
35+
fun `Always should show footer when nextMessage is null`() {
36+
val message = createMessage(user1)
37+
38+
val result = MessageFooterVisibility.Always.shouldShowMessageFooter(
39+
message = message,
40+
isLastMessageInGroup = false,
41+
nextMessage = null,
42+
)
43+
44+
result `should be equal to` true
45+
}
46+
47+
@Test
48+
fun `Always should show footer when nextMessage is not null`() {
49+
val message = createMessage(user1)
50+
val nextMessage = createMessage(user1)
51+
52+
val result = MessageFooterVisibility.Always.shouldShowMessageFooter(
53+
message = message,
54+
isLastMessageInGroup = false,
55+
nextMessage = nextMessage,
56+
)
57+
58+
result `should be equal to` true
59+
}
60+
61+
@Test
62+
fun `Always should show footer when isLastMessageInGroup is true`() {
63+
val message = createMessage(user1)
64+
val nextMessage = createMessage(user1)
65+
66+
val result = MessageFooterVisibility.Always.shouldShowMessageFooter(
67+
message = message,
68+
isLastMessageInGroup = true,
69+
nextMessage = nextMessage,
70+
)
71+
72+
result `should be equal to` true
73+
}
74+
75+
// Test MessageFooterVisibility.Never
76+
77+
@Test
78+
fun `Never should not show footer when nextMessage is null`() {
79+
val message = createMessage(user1)
80+
81+
val result = MessageFooterVisibility.Never.shouldShowMessageFooter(
82+
message = message,
83+
isLastMessageInGroup = false,
84+
nextMessage = null,
85+
)
86+
87+
result `should be equal to` false
88+
}
89+
90+
@Test
91+
fun `Never should not show footer when nextMessage is not null`() {
92+
val message = createMessage(user1)
93+
val nextMessage = createMessage(user1)
94+
95+
val result = MessageFooterVisibility.Never.shouldShowMessageFooter(
96+
message = message,
97+
isLastMessageInGroup = false,
98+
nextMessage = nextMessage,
99+
)
100+
101+
result `should be equal to` false
102+
}
103+
104+
@Test
105+
fun `Never should not show footer when isLastMessageInGroup is true`() {
106+
val message = createMessage(user1)
107+
val nextMessage = createMessage(user1)
108+
109+
val result = MessageFooterVisibility.Never.shouldShowMessageFooter(
110+
message = message,
111+
isLastMessageInGroup = true,
112+
nextMessage = nextMessage,
113+
)
114+
115+
result `should be equal to` false
116+
}
117+
118+
@Test
119+
fun `Never should not show footer when message is edited`() {
120+
val message = createMessage(user1, messageTextUpdatedAt = Date())
121+
122+
val result = MessageFooterVisibility.Never.shouldShowMessageFooter(
123+
message = message,
124+
isLastMessageInGroup = false,
125+
nextMessage = createMessage(user1),
126+
)
127+
128+
result `should be equal to` false
129+
}
130+
131+
// Test MessageFooterVisibility.LastInGroup
132+
133+
@Test
134+
fun `LastInGroup should show footer when nextMessage is null`() {
135+
val message = createMessage(user1)
136+
137+
val result = MessageFooterVisibility.LastInGroup.shouldShowMessageFooter(
138+
message = message,
139+
isLastMessageInGroup = false,
140+
nextMessage = null,
141+
)
142+
143+
result `should be equal to` true
144+
}
145+
146+
@Test
147+
fun `LastInGroup should show footer when isLastMessageInGroup is true`() {
148+
val message = createMessage(user1)
149+
val nextMessage = createMessage(user1)
150+
151+
val result = MessageFooterVisibility.LastInGroup.shouldShowMessageFooter(
152+
message = message,
153+
isLastMessageInGroup = true,
154+
nextMessage = nextMessage,
155+
)
156+
157+
result `should be equal to` true
158+
}
159+
160+
@Test
161+
fun `LastInGroup should not show footer when isLastMessageInGroup is false`() {
162+
val message = createMessage(user1)
163+
val nextMessage = createMessage(user1)
164+
165+
val result = MessageFooterVisibility.LastInGroup.shouldShowMessageFooter(
166+
message = message,
167+
isLastMessageInGroup = false,
168+
nextMessage = nextMessage,
169+
)
170+
171+
result `should be equal to` false
172+
}
173+
174+
@Test
175+
fun `LastInGroup should show footer when message is edited`() {
176+
val message = createMessage(user1, messageTextUpdatedAt = Date())
177+
val nextMessage = createMessage(user1)
178+
179+
val result = MessageFooterVisibility.LastInGroup.shouldShowMessageFooter(
180+
message = message,
181+
isLastMessageInGroup = false,
182+
nextMessage = nextMessage,
183+
)
184+
185+
result `should be equal to` true
186+
}
187+
188+
// Test MessageFooterVisibility.WithTimeDifference
189+
190+
@Test
191+
fun `WithTimeDifference should show footer when nextMessage is null`() {
192+
val message = createMessage(user1)
193+
194+
val result = MessageFooterVisibility.WithTimeDifference().shouldShowMessageFooter(
195+
message = message,
196+
isLastMessageInGroup = false,
197+
nextMessage = null,
198+
)
199+
200+
result `should be equal to` true
201+
}
202+
203+
@Test
204+
fun `WithTimeDifference should show footer when isLastMessageInGroup is true`() {
205+
val message = createMessage(user1, createdAt = Date(1000))
206+
val nextMessage = createMessage(user1, createdAt = Date(2000))
207+
208+
val result = MessageFooterVisibility.WithTimeDifference().shouldShowMessageFooter(
209+
message = message,
210+
isLastMessageInGroup = true,
211+
nextMessage = nextMessage,
212+
)
213+
214+
result `should be equal to` true
215+
}
216+
217+
@Test
218+
fun `WithTimeDifference should not show footer when message is deleted`() {
219+
val message = createMessage(user1, createdAt = Date(1000), deletedAt = Date(1500))
220+
val nextMessage = createMessage(user1, createdAt = Date(2000))
221+
222+
val result = MessageFooterVisibility.WithTimeDifference().shouldShowMessageFooter(
223+
message = message,
224+
isLastMessageInGroup = false,
225+
nextMessage = nextMessage,
226+
)
227+
228+
result `should be equal to` false
229+
}
230+
231+
@Test
232+
fun `WithTimeDifference should show footer when users are different`() {
233+
val message = createMessage(user1, createdAt = Date(1000))
234+
val nextMessage = createMessage(user2, createdAt = Date(2000))
235+
236+
val result = MessageFooterVisibility.WithTimeDifference().shouldShowMessageFooter(
237+
message = message,
238+
isLastMessageInGroup = false,
239+
nextMessage = nextMessage,
240+
)
241+
242+
result `should be equal to` true
243+
}
244+
245+
@Test
246+
fun `WithTimeDifference should show footer when next message is deleted`() {
247+
val message = createMessage(user1, createdAt = Date(1000))
248+
val nextMessage = createMessage(user1, createdAt = Date(2000), deletedAt = Date(2500))
249+
250+
val result = MessageFooterVisibility.WithTimeDifference().shouldShowMessageFooter(
251+
message = message,
252+
isLastMessageInGroup = false,
253+
nextMessage = nextMessage,
254+
)
255+
256+
result `should be equal to` true
257+
}
258+
259+
@Test
260+
fun `WithTimeDifference should show footer when time difference exceeds threshold`() {
261+
val timeDiffMillis = 60_000L // 1 minute
262+
val message = createMessage(user1, createdAt = Date(1000))
263+
val nextMessage = createMessage(user1, createdAt = Date(62_000)) // More than 1 minute later
264+
265+
val result = MessageFooterVisibility.WithTimeDifference(timeDiffMillis).shouldShowMessageFooter(
266+
message = message,
267+
isLastMessageInGroup = false,
268+
nextMessage = nextMessage,
269+
)
270+
271+
result `should be equal to` true
272+
}
273+
274+
@Test
275+
fun `WithTimeDifference should not show footer when time difference is within threshold`() {
276+
val timeDiffMillis = 60_000L // 1 minute
277+
val message = createMessage(user1, createdAt = Date(1000))
278+
val nextMessage = createMessage(user1, createdAt = Date(30_000)) // 29 seconds later
279+
280+
val result = MessageFooterVisibility.WithTimeDifference(timeDiffMillis).shouldShowMessageFooter(
281+
message = message,
282+
isLastMessageInGroup = false,
283+
nextMessage = nextMessage,
284+
)
285+
286+
result `should be equal to` false
287+
}
288+
289+
@Test
290+
fun `WithTimeDifference should show footer when time difference exactly equals threshold`() {
291+
val timeDiffMillis = 60_000L // 1 minute
292+
val message = createMessage(user1, createdAt = Date(1000))
293+
val nextMessage = createMessage(user1, createdAt = Date(61_001)) // Exactly 60 seconds + 1ms later
294+
295+
val result = MessageFooterVisibility.WithTimeDifference(timeDiffMillis).shouldShowMessageFooter(
296+
message = message,
297+
isLastMessageInGroup = false,
298+
nextMessage = nextMessage,
299+
)
300+
301+
result `should be equal to` true
302+
}
303+
304+
@Test
305+
fun `WithTimeDifference should show footer when message is edited`() {
306+
val message = createMessage(user1, createdAt = Date(1000), messageTextUpdatedAt = Date(1500))
307+
val nextMessage = createMessage(user1, createdAt = Date(2000))
308+
309+
val result = MessageFooterVisibility.WithTimeDifference().shouldShowMessageFooter(
310+
message = message,
311+
isLastMessageInGroup = false,
312+
nextMessage = nextMessage,
313+
)
314+
315+
result `should be equal to` true
316+
}
317+
318+
@Test
319+
fun `WithTimeDifference should not show footer when all conditions are favorable to hide`() {
320+
val timeDiffMillis = 60_000L // 1 minute
321+
val message = createMessage(user1, createdAt = Date(1000))
322+
val nextMessage = createMessage(user1, createdAt = Date(10_000)) // 9 seconds later
323+
324+
val result = MessageFooterVisibility.WithTimeDifference(timeDiffMillis).shouldShowMessageFooter(
325+
message = message,
326+
isLastMessageInGroup = false,
327+
nextMessage = nextMessage,
328+
)
329+
330+
result `should be equal to` false
331+
}
332+
333+
// Test edited messages
334+
335+
@Test
336+
fun `Always should show footer when message is edited`() {
337+
val message = createMessage(user1, messageTextUpdatedAt = Date())
338+
val nextMessage = createMessage(user1)
339+
340+
val result = MessageFooterVisibility.Always.shouldShowMessageFooter(
341+
message = message,
342+
isLastMessageInGroup = false,
343+
nextMessage = nextMessage,
344+
)
345+
346+
result `should be equal to` true
347+
}
348+
349+
@Test
350+
fun `LastInGroup should show footer when message is edited even if not last in group`() {
351+
val message = createMessage(user1, messageTextUpdatedAt = Date())
352+
val nextMessage = createMessage(user1)
353+
354+
val result = MessageFooterVisibility.LastInGroup.shouldShowMessageFooter(
355+
message = message,
356+
isLastMessageInGroup = false,
357+
nextMessage = nextMessage,
358+
)
359+
360+
result `should be equal to` true
361+
}
362+
363+
// Helper functions
364+
365+
private fun createMessage(
366+
user: User,
367+
createdAt: Date? = null,
368+
deletedAt: Date? = null,
369+
messageTextUpdatedAt: Date? = null,
370+
): Message {
371+
return randomMessage(
372+
user = user,
373+
createdAt = createdAt,
374+
deletedAt = deletedAt,
375+
messageTextUpdatedAt = messageTextUpdatedAt,
376+
deletedForMe = false,
377+
)
378+
}
379+
}

0 commit comments

Comments
 (0)