Skip to content

Commit 7726fa2

Browse files
Rename and add tests for fix
1 parent 1a8bb41 commit 7726fa2

File tree

2 files changed

+135
-7
lines changed

2 files changed

+135
-7
lines changed

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistModalActivity.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,15 +201,15 @@ class GistModalActivity : AppCompatActivity(), ModalInAppMessageViewCallback, Tr
201201
// Only dispatch dismiss if THIS activity's message is still the currently displayed message.
202202
// This prevents a race condition where Activity1 finishes while Activity2 is already showing,
203203
// which would cause Activity1's onDestroy to incorrectly dismiss Activity2's message.
204-
val ourMessage = activityMessage
205-
val displayedMessage = currentMessageState?.message
204+
val displayedMessage = activityMessage
205+
val messageInQueue = currentMessageState?.message
206206
val inAppManager = inAppMessagingManager
207207

208-
if (ourMessage != null && inAppManager != null && ourMessage.queueId == displayedMessage?.queueId) {
209-
if (!isPersistentMessage(ourMessage)) {
210-
inAppManager.dispatch(InAppMessagingAction.DismissMessage(message = ourMessage))
208+
if (displayedMessage != null && inAppManager != null && displayedMessage.queueId == messageInQueue?.queueId) {
209+
if (!isPersistentMessage(displayedMessage)) {
210+
inAppManager.dispatch(InAppMessagingAction.DismissMessage(message = displayedMessage))
211211
} else {
212-
inAppManager.dispatch(InAppMessagingAction.DismissMessage(message = ourMessage, shouldLog = false))
212+
inAppManager.dispatch(InAppMessagingAction.DismissMessage(message = displayedMessage, shouldLog = false))
213213
}
214214
}
215215
super.onDestroy()

messaginginapp/src/test/java/io/customer/messaginginapp/gist/presentation/GistModalActivityTest.kt

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,20 @@ import io.customer.messaginginapp.gist.GistEnvironment
2020
import io.customer.messaginginapp.gist.data.listeners.GistQueue
2121
import io.customer.messaginginapp.gist.data.model.Message
2222
import io.customer.messaginginapp.gist.data.model.MessagePosition
23+
import io.customer.messaginginapp.gist.utilities.ModalMessageExtras
2324
import io.customer.messaginginapp.gist.utilities.ModalMessageParser
2425
import io.customer.messaginginapp.state.InAppMessagingAction
2526
import io.customer.messaginginapp.state.InAppMessagingManager
27+
import io.customer.messaginginapp.state.MessageBuilderMock
28+
import io.customer.messaginginapp.state.ModalMessageState
2629
import io.customer.messaginginapp.testutils.core.IntegrationTest
2730
import io.customer.sdk.core.di.SDKComponent
2831
import io.customer.sdk.core.util.DispatchersProvider
2932
import io.customer.sdk.core.util.Logger
3033
import io.customer.sdk.core.util.ScopeProvider
3134
import io.customer.sdk.data.model.Region
3235
import io.mockk.coEvery
36+
import io.mockk.every
3337
import io.mockk.mockk
3438
import io.mockk.spyk
3539
import io.mockk.verify
@@ -171,7 +175,129 @@ class GistModalActivityTest : IntegrationTest() {
171175
scenario.close()
172176
}
173177

174-
private fun initializeModuleMessagingInApp() {
178+
// region onDestroy race condition prevention tests
179+
180+
@Test
181+
fun onDestroy_givenActivityMessageMatchesMessageInQueue_expectDismissDispatched() {
182+
// Initialize ModuleMessagingInApp
183+
val messagingManager = initializeModuleMessagingInApp()
184+
val testMessage = MessageBuilderMock.createMessage(
185+
queueId = "test-queue-id",
186+
persistent = false
187+
)
188+
189+
// Setup parser to return the test message
190+
coEvery { mockMessageParser.parseExtras(any()) } returns ModalMessageExtras(
191+
message = testMessage,
192+
messagePosition = MessagePosition.CENTER
193+
)
194+
195+
// Set the modal state to Displayed with the SAME message (matching queueId)
196+
every { messagingManager.getCurrentState() } returns mockk(relaxed = true) {
197+
every { modalMessageState } returns ModalMessageState.Displayed(testMessage)
198+
}
199+
200+
val intent = createActivityIntent(testMessage)
201+
val scenario = ActivityScenario.launch<GistModalActivity>(intent)
202+
flushCoroutines(scopeProviderStub.inAppLifecycleScope)
203+
204+
// Destroy the activity
205+
scenario.moveToState(Lifecycle.State.DESTROYED)
206+
207+
// Verify dismiss was dispatched since the activity's message matches the one in queue
208+
assertCalledOnce {
209+
messagingManager.dispatch(
210+
match<InAppMessagingAction.DismissMessage> {
211+
it.message.queueId == testMessage.queueId && it.shouldLog
212+
}
213+
)
214+
}
215+
216+
scenario.close()
217+
}
218+
219+
@Test
220+
fun onDestroy_givenActivityMessageDifferentFromMessageInQueue_expectDismissNotDispatched() {
221+
// Initialize ModuleMessagingInApp
222+
val messagingManager = initializeModuleMessagingInApp()
223+
val activityMessage = MessageBuilderMock.createMessage(
224+
queueId = "activity-queue-id",
225+
persistent = false
226+
)
227+
val queueMessage = MessageBuilderMock.createMessage(
228+
queueId = "different-queue-id",
229+
persistent = false
230+
)
231+
232+
// Setup parser to return the activity's message
233+
coEvery { mockMessageParser.parseExtras(any()) } returns ModalMessageExtras(
234+
message = activityMessage,
235+
messagePosition = MessagePosition.CENTER
236+
)
237+
238+
// Set the modal state to Displayed with a DIFFERENT message (different queueId)
239+
// This simulates the race condition where Activity2 is already showing
240+
every { messagingManager.getCurrentState() } returns mockk(relaxed = true) {
241+
every { modalMessageState } returns ModalMessageState.Displayed(queueMessage)
242+
}
243+
244+
val intent = createActivityIntent(activityMessage)
245+
val scenario = ActivityScenario.launch<GistModalActivity>(intent)
246+
flushCoroutines(scopeProviderStub.inAppLifecycleScope)
247+
248+
// Destroy the activity
249+
scenario.moveToState(Lifecycle.State.DESTROYED)
250+
251+
// Verify dismiss was NOT dispatched since the messages don't match (race condition prevention)
252+
verify(exactly = 0) {
253+
messagingManager.dispatch(match<InAppMessagingAction.DismissMessage> { true })
254+
}
255+
256+
scenario.close()
257+
}
258+
259+
@Test
260+
fun onDestroy_givenPersistentMessage_expectDismissDispatchedWithShouldLogFalse() {
261+
// Initialize ModuleMessagingInApp
262+
val messagingManager = initializeModuleMessagingInApp()
263+
val testMessage = MessageBuilderMock.createMessage(
264+
queueId = "test-queue-id",
265+
persistent = true // Persistent message
266+
)
267+
268+
// Setup parser to return the test message
269+
coEvery { mockMessageParser.parseExtras(any()) } returns ModalMessageExtras(
270+
message = testMessage,
271+
messagePosition = MessagePosition.CENTER
272+
)
273+
274+
// Set the modal state to Displayed with the SAME message
275+
every { messagingManager.getCurrentState() } returns mockk(relaxed = true) {
276+
every { modalMessageState } returns ModalMessageState.Displayed(testMessage)
277+
}
278+
279+
val intent = createActivityIntent(testMessage)
280+
val scenario = ActivityScenario.launch<GistModalActivity>(intent)
281+
flushCoroutines(scopeProviderStub.inAppLifecycleScope)
282+
283+
// Destroy the activity
284+
scenario.moveToState(Lifecycle.State.DESTROYED)
285+
286+
// Verify dismiss was dispatched with shouldLog = false for persistent message
287+
assertCalledOnce {
288+
messagingManager.dispatch(
289+
match<InAppMessagingAction.DismissMessage> {
290+
it.message.queueId == testMessage.queueId && !it.shouldLog
291+
}
292+
)
293+
}
294+
295+
scenario.close()
296+
}
297+
298+
// endregion
299+
300+
private fun initializeModuleMessagingInApp(): InAppMessagingManager {
175301
val moduleConfig = MessagingInAppModuleConfig.Builder(
176302
siteId = "test-site-id",
177303
region = Region.US
@@ -188,6 +314,8 @@ class GistModalActivityTest : IntegrationTest() {
188314
environment = GistEnvironment.LOCAL
189315
)
190316
).flushCoroutines(scopeProviderStub.inAppLifecycleScope)
317+
318+
return messagingManager
191319
}
192320

193321
private fun createTestMessage(): Message = Message(

0 commit comments

Comments
 (0)