diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 437cebda..9243ab54 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -43,6 +43,7 @@ kotlin { val commonTest by getting { dependencies { implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") implementation("com.russhwolf:multiplatform-settings-test:"+extra["multiplatformSettings.version"]) } } diff --git a/shared/src/androidUnitTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt b/shared/src/androidUnitTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt index 041f302e..542082ba 100644 --- a/shared/src/androidUnitTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt +++ b/shared/src/androidUnitTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt @@ -1,21 +1,11 @@ package eu.baroncelli.dkmpsample.shared +import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import com.russhwolf.settings.MapSettings -import app.cash.sqldelight.sqlite.driver.JdbcSqliteDriver import eu.baroncelli.dkmpsample.shared.datalayer.Repository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.runBlocking import mylocal.db.LocalDb -import java.util.concurrent.Executors -import kotlin.coroutines.CoroutineContext -actual val testCoroutineContext: CoroutineContext = - Executors.newSingleThreadExecutor().asCoroutineDispatcher() -actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit) = - runBlocking(testCoroutineContext) { this.block() } - -actual fun getTestRepository() : Repository { +actual suspend fun getTestRepository(): Repository { val sqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) LocalDb.Schema.create(sqlDriver) return Repository(sqlDriver, MapSettings(), false) diff --git a/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Navigation.kt b/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Navigation.kt index 736fa4ed..352fc989 100644 --- a/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Navigation.kt +++ b/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Navigation.kt @@ -2,6 +2,7 @@ package eu.baroncelli.dkmpsample.shared.viewmodel import eu.baroncelli.dkmpsample.shared.viewmodel.screens.CallOnInitValues import eu.baroncelli.dkmpsample.shared.viewmodel.screens.navigationSettings +import kotlinx.coroutines.Job data class NavigationState ( val currentLevel1ScreenIdentifier : ScreenIdentifier, @@ -143,11 +144,11 @@ class Navigation(val stateManager : StateManager) { // ADD SCREEN TO BACKSTACK - fun addScreenToBackstack(screenIdentifier: ScreenIdentifier) { + fun addScreenToBackstack(screenIdentifier: ScreenIdentifier): Job? { debugLogger.log("addScreenToBackstack: "+screenIdentifier.URI) stateManager.currentVerticalBackstack.add(screenIdentifier) stateManager.currentVerticalNavigationLevelsMap[screenIdentifier.screen.navigationLevel] = screenIdentifier - stateManager.initScreen(screenIdentifier) + return stateManager.initScreen(screenIdentifier) } diff --git a/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/StateManager.kt b/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/StateManager.kt index 39f90f61..2d671837 100644 --- a/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/StateManager.kt +++ b/shared/src/commonMain/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/StateManager.kt @@ -31,7 +31,7 @@ class StateManager(repo: Repository) { // INIT SCREEN - fun initScreen(screenIdentifier: ScreenIdentifier) { + fun initScreen(screenIdentifier: ScreenIdentifier): Job? { debugLogger.log("initScreen: "+screenIdentifier.URI) val screenInitSettings = screenIdentifier.getScreenInitSettings(this) if (screenScopesMap[screenIdentifier.URI] == null || !screenScopesMap[screenIdentifier.URI]!!.isActive) { @@ -43,22 +43,23 @@ class StateManager(repo: Repository) { firstInit = true screenStatesMap[screenIdentifier.URI] = MutableStateFlow(screenInitSettings.initState(screenIdentifier)) } else if (screenInitSettings.callOnInitAtEachNavigation == CallOnInitValues.DONT_CALL) { - return // in case: the state is already in the map + return null // in case: the state is already in the map // AND "callOnInitAtEachNavigation" is set to DONT_CALL // => we don't need to run the "callOnInit" function } - runCallOnInit(screenIdentifier, screenInitSettings, firstInit) + return runCallOnInit(screenIdentifier, screenInitSettings, firstInit) } fun isInTheStatesMap(screenIdentifier: ScreenIdentifier) : Boolean { return screenStatesMap.containsKey(screenIdentifier.URI) } - fun runCallOnInit(screenIdentifier: ScreenIdentifier, screenInitSettings: ScreenInitSettings, firstInit : Boolean = false) { - if (!firstInit && screenInitSettings.callOnInitAtEachNavigation == CallOnInitValues.CALL_BEFORE_SHOWING_SCREEN) { + fun runCallOnInit(screenIdentifier: ScreenIdentifier, screenInitSettings: ScreenInitSettings, firstInit : Boolean = false) : Job? { + return if (!firstInit && screenInitSettings.callOnInitAtEachNavigation == CallOnInitValues.CALL_BEFORE_SHOWING_SCREEN) { runBlocking { screenInitSettings.callOnInit(this@StateManager) } + null } else { runInScreenScope(screenIdentifier) { screenInitSettings.callOnInit(this@StateManager) @@ -122,10 +123,10 @@ class StateManager(repo: Repository) { } // we run each event function on a Dispatchers.Main coroutine - fun runInScreenScope (screenIdentifier: ScreenIdentifier? = null, block: suspend () -> Unit) { + fun runInScreenScope (screenIdentifier: ScreenIdentifier? = null, block: suspend () -> Unit): Job? { val URI = screenIdentifier?.URI ?: currentScreenIdentifier.URI val screenScope = screenScopesMap[URI] - screenScope?.launch { + return screenScope?.launch { block() } } diff --git a/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt b/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt index 28b3a7e9..55dd6bed 100644 --- a/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt +++ b/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt @@ -4,7 +4,4 @@ import eu.baroncelli.dkmpsample.shared.datalayer.Repository import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext -expect fun runBlockingTest(block: suspend CoroutineScope.()-> Unit) -expect val testCoroutineContext: CoroutineContext - -expect fun getTestRepository() : Repository \ No newline at end of file +expect suspend fun getTestRepository() : Repository \ No newline at end of file diff --git a/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Tests.kt b/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Tests.kt index 0affd07d..9951435c 100644 --- a/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Tests.kt +++ b/shared/src/commonTest/kotlin/eu/baroncelli/dkmpsample/shared/viewmodel/Tests.kt @@ -1,44 +1,62 @@ package eu.baroncelli.dkmpsample.shared.viewmodel import eu.baroncelli.dkmpsample.shared.datalayer.objects.CountryExtraData +import eu.baroncelli.dkmpsample.shared.datalayer.objects.CountryListData +import eu.baroncelli.dkmpsample.shared.datalayer.sources.localdb.countries.setCountriesList import eu.baroncelli.dkmpsample.shared.getTestRepository -import eu.baroncelli.dkmpsample.shared.viewmodel.screens.Screen.* -import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrydetail.CountryDetailState -import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrieslist.* +import eu.baroncelli.dkmpsample.shared.viewmodel.screens.Screen.CountriesList +import eu.baroncelli.dkmpsample.shared.viewmodel.screens.Screen.CountryDetail +import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrieslist.CountriesListParams +import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrieslist.CountriesListState +import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrieslist.CountriesListType import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrydetail.CountryDetailParams +import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrydetail.CountryDetailState import eu.baroncelli.dkmpsample.shared.viewmodel.screens.countrydetail.CountryInfo +import kotlinx.coroutines.test.runTest +import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertTrue class ViewModelTests { - val vm = DKMPViewModel(getTestRepository()) - val navigation = vm.navigation - val stateProvider = navigation.stateProvider - val stateManager = navigation.stateManager + lateinit var vm: DKMPViewModel + val navigation: Navigation + get() = vm.navigation + val stateProvider: StateProvider + get() = navigation.stateProvider + val stateManager: StateManager + get() = navigation.stateManager + @BeforeTest + fun setUp() = runTest { + vm = DKMPViewModel(getTestRepository()) + } + @Test - fun testCountriesListStateUpdate() { - val screenIdentifier = ScreenIdentifier.get(CountriesList,CountriesListParams(CountriesListType.ALL)) - val screenInitSettings = screenIdentifier.getScreenInitSettings(navigation) - stateManager.addScreen(screenIdentifier, screenInitSettings) + fun testCountriesListStateUpdate() = runTest { + val screenIdentifier = ScreenIdentifier.get(CountriesList, CountriesListParams(CountriesListType.ALL)) + navigation.addScreenToBackstack(screenIdentifier)!!.join() stateManager.updateScreen(CountriesListState::class) { it.copy(favoriteCountries = mapOf("Italy" to true)) } - val screenState = stateProvider.get(screenIdentifier) as CountriesListState + val screenState = stateProvider.getScreenState(screenIdentifier).value assertTrue(screenState.favoriteCountries.containsKey("Italy")) } @Test - fun testCountryDetailStateUpdate() { + fun testCountryDetailStateUpdate() = runTest { + stateManager.dataRepository.localDb.setCountriesList( + listOf( + CountryListData(name = "Germany") + ) + ) val screenIdentifier = ScreenIdentifier.get(CountryDetail, CountryDetailParams("Germany")) - val screenInitSettings = screenIdentifier.getScreenInitSettings(navigation) - stateManager.addScreen(screenIdentifier, screenInitSettings) + navigation.addScreenToBackstack(screenIdentifier)!!.join() stateManager.updateScreen(CountryDetailState::class) { it.copy(countryInfo = CountryInfo(_extraData = CountryExtraData(vaccines = "Pfizer, Moderna, AstraZeneca"))) } - val screenState = stateProvider.get(screenIdentifier) as CountryDetailState + val screenState = stateProvider.getScreenState(screenIdentifier).value assertTrue(screenState.countryInfo.vaccinesList!!.contains("Pfizer")) } diff --git a/shared/src/desktopTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt b/shared/src/desktopTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt index 51fd1000..ddf53164 100644 --- a/shared/src/desktopTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt +++ b/shared/src/desktopTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt @@ -1,22 +1,12 @@ package eu.baroncelli.dkmpsample.shared +import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import com.russhwolf.settings.MapSettings import eu.baroncelli.dkmpsample.shared.datalayer.Repository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.runBlocking -import kotlin.coroutines.CoroutineContext -import app.cash.sqldelight.sqlite.driver.JdbcSqliteDriver import mylocal.db.LocalDb -actual val testCoroutineContext: CoroutineContext = - newSingleThreadContext("testRunner") - -actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit) = - runBlocking(testCoroutineContext) { this.block() } - -actual fun getTestRepository() : Repository { +actual suspend fun getTestRepository(): Repository { val sqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) - LocalDb.Schema.create(sqlDriver) + LocalDb.Schema.create(sqlDriver).await() return Repository(sqlDriver, MapSettings(), false) } \ No newline at end of file diff --git a/shared/src/iosTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt b/shared/src/iosTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt index ee2cc52f..569074e5 100644 --- a/shared/src/iosTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt +++ b/shared/src/iosTest/kotlin/eu/baroncelli/dkmpsample/shared/TestUtils.kt @@ -1,21 +1,11 @@ package eu.baroncelli.dkmpsample.shared +import app.cash.sqldelight.driver.native.NativeSqliteDriver import com.russhwolf.settings.MapSettings import eu.baroncelli.dkmpsample.shared.datalayer.Repository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.runBlocking -import kotlin.coroutines.CoroutineContext -import app.cash.sqldelight.drivers.native.NativeSqliteDriver import mylocal.db.LocalDb -actual val testCoroutineContext: CoroutineContext = - newSingleThreadContext("testRunner") - -actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit) = - runBlocking(testCoroutineContext) { this.block() } - -actual fun getTestRepository() : Repository { +actual suspend fun getTestRepository(): Repository { val sqlDriver = NativeSqliteDriver(LocalDb.Schema, "test.db") return Repository(sqlDriver, MapSettings(), false) } \ No newline at end of file