@@ -14,9 +14,14 @@ import com.slack.circuit.backstack.SaveableBackStack
1414import com.slack.circuit.foundation.Circuit
1515import com.slack.circuit.foundation.CircuitCompositionLocals
1616import com.slack.circuit.foundation.NavigableCircuitContent
17+ import com.slack.circuit.runtime.CircuitContext
18+ import com.slack.circuit.runtime.InternalCircuitApi
1719import com.slack.circuit.test.FakeNavigator
1820import com.slack.circuit.test.test
1921import com.slack.circuitx.android.IntentScreen
22+ import io.mockk.coEvery
23+ import io.mockk.coVerify
24+ import io.mockk.coVerifyAll
2025import io.mockk.every
2126import io.mockk.mockk
2227import io.mockk.verify
@@ -28,13 +33,16 @@ import kotlin.test.assertIs
2833import kotlin.test.assertNotNull
2934import kotlin.test.assertTrue
3035import kotlinx.coroutines.ExperimentalCoroutinesApi
36+ import kotlinx.coroutines.channels.Channel
3137import kotlinx.coroutines.flow.MutableStateFlow
3238import kotlinx.coroutines.flow.flowOf
3339import kotlinx.coroutines.test.TestScope
3440import kotlinx.coroutines.test.UnconfinedTestDispatcher
3541import kotlinx.coroutines.test.runTest
3642import org.ccci.gto.android.common.androidx.compose.ui.platform.AndroidUiDispatcherUtil
43+ import org.ccci.gto.android.common.sync.SyncTracker
3744import org.ccci.gto.android.common.util.content.equalsIntent
45+ import org.ccci.gto.support.turbine.awaitItemMatching
3846import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent
3947import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.ACTION_OPEN_LESSON
4048import org.cru.godtools.analytics.model.OpenAnalyticsActionEvent.Companion.SOURCE_LESSONS
@@ -49,6 +57,9 @@ import org.cru.godtools.model.Tool
4957import org.cru.godtools.model.Translation
5058import org.cru.godtools.model.randomTool
5159import org.cru.godtools.model.randomTranslation
60+ import org.cru.godtools.sync.GodToolsSyncService
61+ import org.cru.godtools.ui.dashboard.SyncTaskRegistry
62+ import org.cru.godtools.ui.dashboard.SyncTaskRegistry.Companion.syncTaskRegistry
5263import org.cru.godtools.ui.dashboard.filters.FilterMenu
5364import org.cru.godtools.ui.dashboard.lessons.LessonsPresenter.UiEvent
5465import org.cru.godtools.ui.dashboard.lessons.LessonsPresenter.UiState
@@ -65,13 +76,19 @@ import org.robolectric.annotation.Config
6576@OptIn(ExperimentalCoroutinesApi ::class )
6677class LessonsPresenterTest {
6778 private val appLangFlow = MutableStateFlow (Locale .ENGLISH )
79+ private val countryFlow = MutableStateFlow <String ?>(" US" )
6880 private val lessonsFlow = MutableStateFlow (emptyList<Tool >())
6981 private val enLessonsFlow = MutableStateFlow (emptyList<Tool >())
7082 private val languagesFlow = MutableStateFlow (emptyList<Language >())
7183 private val translationsFlow = MutableStateFlow (emptyList<Translation >())
84+ private val toolOrderSync = Channel <Boolean >()
7285 private var isPersonalizationEnabled = true
7386
7487 private val testScope = TestScope ()
88+ @OptIn(InternalCircuitApi ::class )
89+ private val circuitContext = CircuitContext (null ).apply {
90+ syncTaskRegistry = SyncTaskRegistry (SyncTracker (testScope.backgroundScope))
91+ }
7592 private val context: Context = ApplicationProvider .getApplicationContext()
7693 private val eventBus: EventBus = mockk(relaxUnitFun = true )
7794 private val remoteConfig: FirebaseRemoteConfig = mockk {
@@ -83,6 +100,10 @@ class LessonsPresenterTest {
83100 }
84101 private val settings: Settings = mockk {
85102 every { appLanguageFlow } returns appLangFlow
103+ every { getCountrySettingFlow() } returns countryFlow
104+ }
105+ private val syncService: GodToolsSyncService = mockk {
106+ coEvery { syncToolOrder(any(), any(), any()) } coAnswers { toolOrderSync.receive() }
86107 }
87108 private val lessonsFlowProducer: LessonsFlowProducer = mockk {
88109 every { getFlow(any(), any()) } returns flowOf(emptyList())
@@ -105,10 +126,12 @@ class LessonsPresenterTest {
105126 lessonsFlowProducer = lessonsFlowProducer,
106127 remoteConfig = remoteConfig,
107128 settings = settings,
129+ syncService = syncService,
108130 toolCardPresenter = FakeToolCardPresenter (),
109131 toolsRepository = toolsRepository,
110132 translationsRepository = translationsRepository,
111133 ioDispatcher = UnconfinedTestDispatcher (testScope.testScheduler),
134+ circuitContext = circuitContext,
112135 navigator = navigator,
113136 )
114137
@@ -409,4 +432,53 @@ class LessonsPresenterTest {
409432 verify { eventBus.post(OpenAnalyticsActionEvent (ACTION_OPEN_LESSON , " lesson2" , SOURCE_LESSONS )) }
410433 }
411434 // endregion State.lessons
435+
436+ // region SideEffect - RegisterSyncTask
437+ @Test
438+ fun `SideEffect - RegisterSyncTask - Triggers initial sync` () = testScope.runTest {
439+ presenter.test {
440+ awaitItem()
441+ toolOrderSync.send(true )
442+ coVerifyAll { syncService.syncToolOrder(Locale .ENGLISH , " US" , false ) }
443+ }
444+ }
445+
446+ @Test
447+ fun `SideEffect - RegisterSyncTask - uses locale from language filter` () = testScope.runTest {
448+ appLangFlow.value = Locale .FRENCH
449+ presenter.test {
450+ awaitItem()
451+ toolOrderSync.send(true )
452+ coVerifyAll { syncService.syncToolOrder(Locale .FRENCH , " US" , false ) }
453+ }
454+ }
455+
456+ @Test
457+ fun `SideEffect - RegisterSyncTask - re-syncs when locale changes` () = testScope.runTest {
458+ presenter.test {
459+ val initialState = awaitItem()
460+ toolOrderSync.send(true )
461+ coVerify { syncService.syncToolOrder(Locale .ENGLISH , " US" , false ) }
462+
463+ initialState.languageFilter.eventSink(FilterMenu .Event .SelectItem (Language (Locale .FRENCH )))
464+ awaitItemMatching { it.languageFilter.selectedItem?.code == Locale .FRENCH }
465+ toolOrderSync.send(true )
466+ coVerify { syncService.syncToolOrder(Locale .FRENCH , " US" , false ) }
467+ cancelAndIgnoreRemainingEvents()
468+ }
469+ }
470+
471+ @Test
472+ fun `SideEffect - RegisterSyncTask - passes force on triggered sync` () = testScope.runTest {
473+ presenter.test {
474+ awaitItem()
475+ toolOrderSync.send(true )
476+ coVerify { syncService.syncToolOrder(Locale .ENGLISH , " US" , false ) }
477+
478+ circuitContext.syncTaskRegistry!! .triggerSyncTasks(force = true )
479+ toolOrderSync.send(true )
480+ coVerify { syncService.syncToolOrder(Locale .ENGLISH , " US" , true ) }
481+ }
482+ }
483+ // endregion SideEffect - RegisterSyncTask
412484}
0 commit comments