Skip to content

Commit 7cc3b9e

Browse files
author
sm-deployer
authored
Merge pull request #446 from salemove/fix_display_history_messages
2 parents b7f20a6 + 072fd4f commit 7cc3b9e

File tree

4 files changed

+298
-7
lines changed

4 files changed

+298
-7
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ ext {
5757
testLibraryVersion = '1.1.3'
5858
androidXTestVersion = '1.1.3'
5959
mockitoVersion = '4.3.1'
60+
mockitoKotlinVersion = '4.1.0'
6061
archCoreVersion = '2.1.0'
6162

6263
//kotlin

widgetssdk/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ dependencies {
6969
implementation "androidx.core:core-ktx:$coreKtxVersion"
7070

7171
testImplementation "junit:junit:$junitVersion"
72+
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
7273
testImplementation "org.mockito:mockito-core:$mockitoVersion"
7374
testImplementation "org.mockito:mockito-inline:$mockitoVersion"
7475
testImplementation "androidx.arch.core:core-testing:$archCoreVersion"

widgetssdk/src/main/java/com/glia/widgets/chat/controller/ChatController.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.glia.widgets.chat.controller
22

33
import android.net.Uri
4+
import androidx.annotation.VisibleForTesting
45
import androidx.recyclerview.widget.RecyclerView
56
import com.glia.androidsdk.GliaException
67
import com.glia.androidsdk.Operator
@@ -276,7 +277,7 @@ class ChatController(
276277

277278
private fun onMessage(messageInternal: ChatMessageInternal) {
278279
val message = messageInternal.chatMessage
279-
if (!isNewMessage(message)) {
280+
if (!isNewMessage(chatState.chatItems, message)) {
280281
return
281282
}
282283
val isUnsentMessage =
@@ -1257,18 +1258,22 @@ class ChatController(
12571258
.createChatState()
12581259
}
12591260

1260-
private fun removeDuplicates(
1261+
@VisibleForTesting
1262+
fun removeDuplicates(
12611263
oldHistory: List<ChatItem>?, newHistory: List<ChatMessageInternal>?
12621264
): List<ChatMessageInternal>? {
12631265
return if (newHistory.isNullOrEmpty() || oldHistory.isNullOrEmpty()) {
12641266
newHistory
1265-
} else newHistory.filter { isNewMessage(it.chatMessage) }
1267+
} else newHistory.filter { isNewMessage(oldHistory, it.chatMessage) }
12661268
}
12671269

1268-
private val oldMessageIdsHash: MutableSet<String> by lazy { mutableSetOf() }
1269-
1270-
private fun isNewMessage(newMessage: ChatMessage): Boolean =
1271-
oldMessageIdsHash.add(newMessage.id)
1270+
@VisibleForTesting
1271+
fun isNewMessage(oldHistory: List<ChatItem>?, newMessage: ChatMessage): Boolean =
1272+
oldHistory
1273+
?.filterIsInstance<LinkedChatItem>()
1274+
?.any { oldMessage -> oldMessage.messageId != null && oldMessage.messageId == newMessage.id }
1275+
?.not()
1276+
?: true
12721277

12731278
private fun error(error: Throwable?) {
12741279
error?.also { error(it.toString()) }
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
package com.glia.widgets.chat.controller
2+
3+
import com.glia.androidsdk.chat.ChatMessage
4+
import com.glia.widgets.chat.ChatViewCallback
5+
import com.glia.widgets.chat.domain.*
6+
import com.glia.widgets.chat.model.history.ChatItem
7+
import com.glia.widgets.chat.model.history.LinkedChatItem
8+
import com.glia.widgets.core.dialog.DialogController
9+
import com.glia.widgets.core.dialog.domain.IsShowOverlayPermissionRequestDialogUseCase
10+
import com.glia.widgets.core.engagement.domain.GetEngagementStateFlowableUseCase
11+
import com.glia.widgets.core.engagement.domain.GliaEndEngagementUseCase
12+
import com.glia.widgets.core.engagement.domain.GliaOnEngagementEndUseCase
13+
import com.glia.widgets.core.engagement.domain.GliaOnEngagementUseCase
14+
import com.glia.widgets.core.engagement.domain.model.ChatMessageInternal
15+
import com.glia.widgets.core.fileupload.domain.*
16+
import com.glia.widgets.core.mediaupgradeoffer.MediaUpgradeOfferRepository
17+
import com.glia.widgets.core.notification.domain.RemoveCallNotificationUseCase
18+
import com.glia.widgets.core.notification.domain.ShowAudioCallNotificationUseCase
19+
import com.glia.widgets.core.notification.domain.ShowVideoCallNotificationUseCase
20+
import com.glia.widgets.core.operator.domain.AddOperatorMediaStateListenerUseCase
21+
import com.glia.widgets.core.queue.domain.GliaCancelQueueTicketUseCase
22+
import com.glia.widgets.core.queue.domain.GliaQueueForChatEngagementUseCase
23+
import com.glia.widgets.core.queue.domain.QueueTicketStateChangeToUnstaffedUseCase
24+
import com.glia.widgets.core.survey.domain.GliaSurveyUseCase
25+
import com.glia.widgets.filepreview.domain.usecase.DownloadFileUseCase
26+
import com.glia.widgets.helper.TimeCounter
27+
import com.glia.widgets.view.MessagesNotSeenHandler
28+
import com.glia.widgets.view.MinimizeHandler
29+
import junit.framework.TestCase.*
30+
import org.junit.Before
31+
import org.junit.Test
32+
import org.mockito.kotlin.mock
33+
import org.mockito.kotlin.whenever
34+
35+
class ChatControllerTest {
36+
private lateinit var chatViewCallback: ChatViewCallback
37+
private lateinit var mediaUpgradeOfferRepository: MediaUpgradeOfferRepository
38+
private lateinit var callTimer: TimeCounter
39+
private lateinit var minimizeHandler: MinimizeHandler
40+
private lateinit var dialogController: DialogController
41+
private lateinit var messagesNotSeenHandler: MessagesNotSeenHandler
42+
private lateinit var showAudioCallNotificationUseCase: ShowAudioCallNotificationUseCase
43+
private lateinit var showVideoCallNotificationUseCase: ShowVideoCallNotificationUseCase
44+
private lateinit var removeCallNotificationUseCase: RemoveCallNotificationUseCase
45+
private lateinit var loadHistoryUseCase: GliaLoadHistoryUseCase
46+
private lateinit var queueForChatEngagementUseCase: GliaQueueForChatEngagementUseCase
47+
private lateinit var getEngagementUseCase: GliaOnEngagementUseCase
48+
private lateinit var engagementEndUseCase: GliaOnEngagementEndUseCase
49+
private lateinit var onMessageUseCase: GliaOnMessageUseCase
50+
private lateinit var onOperatorTypingUseCase: GliaOnOperatorTypingUseCase
51+
private lateinit var sendMessagePreviewUseCase: GliaSendMessagePreviewUseCase
52+
private lateinit var sendMessageUseCase: GliaSendMessageUseCase
53+
private lateinit var addOperatorMediaStateListenerUseCase: AddOperatorMediaStateListenerUseCase
54+
private lateinit var cancelQueueTicketUseCase: GliaCancelQueueTicketUseCase
55+
private lateinit var endEngagementUseCase: GliaEndEngagementUseCase
56+
private lateinit var addFileToAttachmentAndUploadUseCase: AddFileToAttachmentAndUploadUseCase
57+
private lateinit var addFileAttachmentsObserverUseCase: AddFileAttachmentsObserverUseCase
58+
private lateinit var removeFileAttachmentObserverUseCase: RemoveFileAttachmentObserverUseCase
59+
private lateinit var getFileAttachmentsUseCase: GetFileAttachmentsUseCase
60+
private lateinit var removeFileAttachmentUseCase: RemoveFileAttachmentUseCase
61+
private lateinit var supportedFileCountCheckUseCase: SupportedFileCountCheckUseCase
62+
private lateinit var isShowSendButtonUseCase: IsShowSendButtonUseCase
63+
private lateinit var isShowOverlayPermissionRequestDialogUseCase: IsShowOverlayPermissionRequestDialogUseCase
64+
private lateinit var downloadFileUseCase: DownloadFileUseCase
65+
private lateinit var isEnableChatEditTextUseCase: IsEnableChatEditTextUseCase
66+
private lateinit var siteInfoUseCase: SiteInfoUseCase
67+
private lateinit var surveyUseCase: GliaSurveyUseCase
68+
private lateinit var getGliaEngagementStateFlowableUseCase: GetEngagementStateFlowableUseCase
69+
private lateinit var isFromCallScreenUseCase: IsFromCallScreenUseCase
70+
private lateinit var updateFromCallScreenUseCase: UpdateFromCallScreenUseCase
71+
private lateinit var customCardAdapterTypeUseCase: CustomCardAdapterTypeUseCase
72+
private lateinit var customCardTypeUseCase: CustomCardTypeUseCase
73+
private lateinit var customCardInteractableUseCase: CustomCardInteractableUseCase
74+
private lateinit var customCardShouldShowUseCase: CustomCardShouldShowUseCase
75+
private lateinit var ticketStateChangeToUnstaffedUseCase: QueueTicketStateChangeToUnstaffedUseCase
76+
77+
private lateinit var chatController: ChatController
78+
79+
@Before
80+
fun setUp() {
81+
chatViewCallback = mock()
82+
mediaUpgradeOfferRepository = mock()
83+
callTimer = mock()
84+
minimizeHandler = mock()
85+
dialogController = mock()
86+
messagesNotSeenHandler = mock()
87+
showAudioCallNotificationUseCase = mock()
88+
showVideoCallNotificationUseCase = mock()
89+
removeCallNotificationUseCase = mock()
90+
loadHistoryUseCase = mock()
91+
queueForChatEngagementUseCase = mock()
92+
getEngagementUseCase = mock()
93+
engagementEndUseCase = mock()
94+
onMessageUseCase = mock()
95+
onOperatorTypingUseCase = mock()
96+
sendMessagePreviewUseCase = mock()
97+
sendMessageUseCase = mock()
98+
addOperatorMediaStateListenerUseCase = mock()
99+
cancelQueueTicketUseCase = mock()
100+
endEngagementUseCase = mock()
101+
addFileToAttachmentAndUploadUseCase = mock()
102+
addFileAttachmentsObserverUseCase = mock()
103+
removeFileAttachmentObserverUseCase = mock()
104+
getFileAttachmentsUseCase = mock()
105+
removeFileAttachmentUseCase = mock()
106+
supportedFileCountCheckUseCase = mock()
107+
isShowSendButtonUseCase = mock()
108+
isShowOverlayPermissionRequestDialogUseCase = mock()
109+
downloadFileUseCase = mock()
110+
isEnableChatEditTextUseCase = mock()
111+
siteInfoUseCase = mock()
112+
surveyUseCase = mock()
113+
getGliaEngagementStateFlowableUseCase = mock()
114+
isFromCallScreenUseCase = mock()
115+
updateFromCallScreenUseCase = mock()
116+
customCardAdapterTypeUseCase = mock()
117+
customCardTypeUseCase = mock()
118+
customCardInteractableUseCase = mock()
119+
customCardShouldShowUseCase = mock()
120+
ticketStateChangeToUnstaffedUseCase = mock()
121+
122+
chatController = ChatController(chatViewCallback, mediaUpgradeOfferRepository, callTimer,
123+
minimizeHandler, dialogController, messagesNotSeenHandler, showAudioCallNotificationUseCase,
124+
showVideoCallNotificationUseCase, removeCallNotificationUseCase, loadHistoryUseCase,
125+
queueForChatEngagementUseCase, getEngagementUseCase, engagementEndUseCase, onMessageUseCase,
126+
onOperatorTypingUseCase, sendMessagePreviewUseCase, sendMessageUseCase,
127+
addOperatorMediaStateListenerUseCase, cancelQueueTicketUseCase, endEngagementUseCase,
128+
addFileToAttachmentAndUploadUseCase, addFileAttachmentsObserverUseCase,
129+
removeFileAttachmentObserverUseCase, getFileAttachmentsUseCase, removeFileAttachmentUseCase,
130+
supportedFileCountCheckUseCase, isShowSendButtonUseCase,
131+
isShowOverlayPermissionRequestDialogUseCase, downloadFileUseCase, isEnableChatEditTextUseCase,
132+
siteInfoUseCase, surveyUseCase, getGliaEngagementStateFlowableUseCase, isFromCallScreenUseCase,
133+
updateFromCallScreenUseCase, customCardAdapterTypeUseCase, customCardTypeUseCase,
134+
customCardInteractableUseCase, customCardShouldShowUseCase, ticketStateChangeToUnstaffedUseCase)
135+
}
136+
137+
@Test
138+
fun removeDuplicates_returnsNull_whenNewMessagesIsNull() {
139+
val oldHistory = listOf<ChatItem>(
140+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id1") },
141+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id2") },
142+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id3") }
143+
)
144+
145+
assertNull(chatController.removeDuplicates(oldHistory, null))
146+
}
147+
148+
@Test
149+
fun removeDuplicates_returnsNewMessages_whenOldMessagesIsNull() {
150+
val newMessages = listOf<ChatMessage>(
151+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id1") },
152+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id2") },
153+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id3") }
154+
).map { chatItem ->
155+
mock<ChatMessageInternal>().also { whenever(it.chatMessage).thenReturn(chatItem) }
156+
}
157+
158+
val result = chatController.removeDuplicates(null, newMessages)
159+
assertEquals(newMessages, result)
160+
}
161+
162+
@Test
163+
fun removeDuplicates_returnsAllMessages_whenAllNewMessagesAreNotContainInHistory() {
164+
val oldHistory = listOf<ChatItem>(
165+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id1") },
166+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id2") },
167+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id3") }
168+
)
169+
val newMessages = listOf<ChatMessage>(
170+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id4") },
171+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id5") },
172+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id6") }
173+
).map { chatItem ->
174+
mock<ChatMessageInternal>().also { whenever(it.chatMessage).thenReturn(chatItem) }
175+
}
176+
177+
val result = chatController.removeDuplicates(oldHistory, newMessages)!!.map { it.chatMessage.id }
178+
assertEquals(listOf("Id4", "Id5", "Id6"), result)
179+
}
180+
181+
@Test
182+
fun removeDuplicates_returnsOnlyNewMessages_whenSomeNewMessagesAreContainInHistory() {
183+
val oldHistory = listOf<ChatItem>(
184+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id1") },
185+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id2") },
186+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id3") }
187+
)
188+
val newMessages = listOf<ChatMessage>(
189+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id3") },
190+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id4") },
191+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id5") }
192+
).map { chatItem ->
193+
mock<ChatMessageInternal>().also { whenever(it.chatMessage).thenReturn(chatItem) }
194+
}
195+
196+
val result = chatController.removeDuplicates(oldHistory, newMessages)!!.map { it.chatMessage.id }
197+
assertEquals(listOf("Id4", "Id5"), result)
198+
}
199+
200+
@Test
201+
fun removeDuplicates_removesAllMessages_whenAllNewMessagesAreContainInHistory() {
202+
val oldHistory = listOf<ChatItem>(
203+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id1") },
204+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id2") },
205+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("Id3") }
206+
)
207+
val newMessages = listOf<ChatMessage>(
208+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id1") },
209+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id2") },
210+
mock<ChatMessage>().also { whenever(it.id).thenReturn("Id3") }
211+
).map { chatItem ->
212+
mock<ChatMessageInternal>().also { whenever(it.chatMessage).thenReturn(chatItem) }
213+
}
214+
215+
assertTrue(chatController.removeDuplicates(oldHistory, newMessages)!!.isEmpty())
216+
}
217+
218+
@Test
219+
fun isNewMessage_returnsTrue_whenOldMessagesEmpty() {
220+
val oldHistory = listOf<ChatItem>()
221+
val newMessage = mock<ChatMessage>()
222+
223+
assertTrue(chatController.isNewMessage(oldHistory, newMessage))
224+
}
225+
226+
@Test
227+
fun isNewMessage_returnsTrue_whenOldMessagesIsNull() {
228+
val newMessage = mock<ChatMessage>()
229+
230+
assertTrue(chatController.isNewMessage(null, newMessage))
231+
}
232+
233+
@Test
234+
fun isNewMessage_returnsTrue_whenOldMessagesDoesNotContainNewOne() {
235+
val oldHistory = listOf<ChatItem>(
236+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId1") },
237+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId2") },
238+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId3") }
239+
)
240+
val newMessage = mock<ChatMessage>()
241+
whenever(newMessage.id).thenReturn("newId")
242+
243+
assertTrue(chatController.isNewMessage(oldHistory, newMessage))
244+
}
245+
246+
@Test
247+
fun isNewMessage_returnsFalse_whenOldMessagesContainsNewOne() {
248+
val oldHistory = listOf<ChatItem>(
249+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId1") },
250+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId2") },
251+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId3") }
252+
)
253+
val newMessage = mock<ChatMessage>()
254+
whenever(newMessage.id).thenReturn("oldId2")
255+
256+
assertFalse(chatController.isNewMessage(oldHistory, newMessage))
257+
}
258+
259+
@Test
260+
fun isNewMessage_filterNullId_whenOldMessageDoesNotHaveId() {
261+
val oldHistory = listOf<ChatItem>(
262+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId1") },
263+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn(null) },
264+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn(null) }
265+
)
266+
val newMessage = mock<ChatMessage>()
267+
whenever(newMessage.id).thenReturn("oldId1")
268+
269+
assertFalse(chatController.isNewMessage(oldHistory, newMessage))
270+
}
271+
272+
@Test
273+
fun isNewMessage_filterLinkedChatItem_whenOldMessageHasIncorrectType() {
274+
val oldHistory = listOf<ChatItem>(
275+
mock<LinkedChatItem>().also { whenever(it.messageId).thenReturn("oldId1") },
276+
mock<ChatItem>().also { whenever(it.id).thenReturn("oldId2") },
277+
mock<ChatItem>().also { whenever(it.id).thenReturn("oldId3") }
278+
)
279+
val newMessage = mock<ChatMessage>()
280+
whenever(newMessage.id).thenReturn("oldId2")
281+
282+
assertTrue(chatController.isNewMessage(oldHistory, newMessage))
283+
}
284+
}

0 commit comments

Comments
 (0)