diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad1e48d7..2517d228 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master ] +env: + CI: true + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/review-suggest.yml b/.github/workflows/review-suggest.yml index d06d0ddd..444bdfee 100644 --- a/.github/workflows/review-suggest.yml +++ b/.github/workflows/review-suggest.yml @@ -2,6 +2,10 @@ name: reviewdog-suggester on: pull_request: types: [opened, synchronize, reopened] + +env: + CI: true + jobs: kotlin: name: runner / suggester / spotless diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 9ad2da3b..1615b60c 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master ] +env: + CI: true + jobs: build: runs-on: ubuntu-latest diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ce97a058..b9c4f02c 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -14,6 +14,7 @@ - \ No newline at end of file + diff --git a/.idea/misc.xml b/.idea/misc.xml index 076b3e47..74a53cfc 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,6 +9,7 @@ + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f73e6abe..6d8e5bc6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { implementation(domain) implementation(data) implementation(core) + implementation(coreUi) implementation(featureMain) implementation(featureAdd) implementation(featureSearch) diff --git a/app/src/main/java/com/hoc/flowmvi/core/CoreModule.kt b/app/src/main/java/com/hoc/flowmvi/core/CoreModule.kt index 088c9ac8..bacf7866 100644 --- a/app/src/main/java/com/hoc/flowmvi/core/CoreModule.kt +++ b/app/src/main/java/com/hoc/flowmvi/core/CoreModule.kt @@ -1,7 +1,7 @@ package com.hoc.flowmvi.core import com.hoc.flowmvi.core.dispatchers.CoroutineDispatchers -import com.hoc.flowmvi.core.navigator.Navigator +import com.hoc.flowmvi.core_ui.navigator.Navigator import org.koin.dsl.module val coreModule = module { diff --git a/app/src/main/java/com/hoc/flowmvi/core/NavigatorImpl.kt b/app/src/main/java/com/hoc/flowmvi/core/NavigatorImpl.kt index 130f6c2d..0bfd900e 100644 --- a/app/src/main/java/com/hoc/flowmvi/core/NavigatorImpl.kt +++ b/app/src/main/java/com/hoc/flowmvi/core/NavigatorImpl.kt @@ -1,8 +1,8 @@ package com.hoc.flowmvi.core import android.content.Context -import com.hoc.flowmvi.core.navigator.IntentProviders -import com.hoc.flowmvi.core.navigator.Navigator +import com.hoc.flowmvi.core_ui.navigator.IntentProviders +import com.hoc.flowmvi.core_ui.navigator.Navigator class NavigatorImpl( private val add: IntentProviders.Add, diff --git a/buildSrc/src/main/kotlin/deps.kt b/buildSrc/src/main/kotlin/deps.kt index 1cbfc9cf..3e969885 100644 --- a/buildSrc/src/main/kotlin/deps.kt +++ b/buildSrc/src/main/kotlin/deps.kt @@ -1,5 +1,6 @@ @file:Suppress("unused", "ClassName", "SpellCheckingInspection") +import org.gradle.api.Project import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.kotlin.dsl.project import org.gradle.plugin.use.PluginDependenciesSpec @@ -97,12 +98,14 @@ inline val PDsS.kotlinKapt: PDS get() = id("kotlin-kapt") inline val DependencyHandler.domain get() = project(":domain") inline val DependencyHandler.core get() = project(":core") +inline val DependencyHandler.coreUi get() = project(":core-ui") inline val DependencyHandler.data get() = project(":data") inline val DependencyHandler.featureMain get() = project(":feature-main") inline val DependencyHandler.featureAdd get() = project(":feature-add") inline val DependencyHandler.featureSearch get() = project(":feature-search") inline val DependencyHandler.mviBase get() = project(":mvi-base") inline val DependencyHandler.mviTesting get() = project(":mvi-testing") +inline val DependencyHandler.testUtils get() = project(":test-utils") fun DependencyHandler.addUnitTest(testImplementation: Boolean = true) { val configName = if (testImplementation) "testImplementation" else "implementation" @@ -112,3 +115,8 @@ fun DependencyHandler.addUnitTest(testImplementation: Boolean = true) { add(configName, deps.test.kotlinJUnit) add(configName, deps.coroutines.test) } + +val Project.isCiBuild: Boolean + get() = providers.environmentVariable("CI") + .forUseAtConfigurationTime() + .orNull == "true" diff --git a/core-ui/.gitignore b/core-ui/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/core-ui/.gitignore @@ -0,0 +1 @@ +/build diff --git a/core-ui/build.gradle.kts b/core-ui/build.gradle.kts new file mode 100644 index 00000000..019ff854 --- /dev/null +++ b/core-ui/build.gradle.kts @@ -0,0 +1,55 @@ +plugins { + androidLib + kotlinAndroid +} + +android { + compileSdk = appConfig.compileSdkVersion + buildToolsVersion = appConfig.buildToolsVersion + + defaultConfig { + minSdk = appConfig.minSdkVersion + targetSdk = appConfig.targetSdkVersion + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } + + testOptions { + unitTests.isIncludeAndroidResources = true + unitTests.isReturnDefaultValues = true + } +} + +dependencies { + implementation(deps.coroutines.core) + implementation(deps.coroutines.android) + + implementation(deps.androidx.coreKtx) + implementation(deps.androidx.swipeRefreshLayout) + implementation(deps.androidx.recyclerView) + implementation(deps.androidx.material) + + implementation(deps.lifecycle.commonJava8) + implementation(deps.lifecycle.runtimeKtx) + + implementation(deps.timber) + + addUnitTest() +} diff --git a/core/consumer-rules.pro b/core-ui/consumer-rules.pro similarity index 100% rename from core/consumer-rules.pro rename to core-ui/consumer-rules.pro diff --git a/core/proguard-rules.pro b/core-ui/proguard-rules.pro similarity index 100% rename from core/proguard-rules.pro rename to core-ui/proguard-rules.pro diff --git a/core-ui/src/androidTest/java/com/hoc/flowmvi/core_ui/ExampleInstrumentedTest.kt b/core-ui/src/androidTest/java/com/hoc/flowmvi/core_ui/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..bd932593 --- /dev/null +++ b/core-ui/src/androidTest/java/com/hoc/flowmvi/core_ui/ExampleInstrumentedTest.kt @@ -0,0 +1 @@ +package com.hoc.flowmvi.core_ui diff --git a/core/src/main/AndroidManifest.xml b/core-ui/src/main/AndroidManifest.xml similarity index 87% rename from core/src/main/AndroidManifest.xml rename to core-ui/src/main/AndroidManifest.xml index baf271f3..069f7323 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core-ui/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="com.hoc.flowmvi.core_ui"> diff --git a/core/src/main/java/com/hoc/flowmvi/core/CollectIn.kt b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/CollectIn.kt similarity index 97% rename from core/src/main/java/com/hoc/flowmvi/core/CollectIn.kt rename to core-ui/src/main/java/com/hoc/flowmvi/core_ui/CollectIn.kt index c80ff5fe..669e251d 100644 --- a/core/src/main/java/com/hoc/flowmvi/core/CollectIn.kt +++ b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/CollectIn.kt @@ -1,4 +1,4 @@ -package com.hoc.flowmvi.core +package com.hoc.flowmvi.core_ui import androidx.fragment.app.Fragment import androidx.lifecycle.Lifecycle diff --git a/core/src/main/java/com/hoc/flowmvi/core/FlowBinding.kt b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/FlowBinding.kt similarity index 99% rename from core/src/main/java/com/hoc/flowmvi/core/FlowBinding.kt rename to core-ui/src/main/java/com/hoc/flowmvi/core_ui/FlowBinding.kt index 29528fb0..73d57c17 100644 --- a/core/src/main/java/com/hoc/flowmvi/core/FlowBinding.kt +++ b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/FlowBinding.kt @@ -1,4 +1,4 @@ -package com.hoc.flowmvi.core +package com.hoc.flowmvi.core_ui import android.content.Context import android.os.Looper diff --git a/core/src/main/java/com/hoc/flowmvi/core/SwipeLeftToDeleteCallback.kt b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/SwipeLeftToDeleteCallback.kt similarity index 98% rename from core/src/main/java/com/hoc/flowmvi/core/SwipeLeftToDeleteCallback.kt rename to core-ui/src/main/java/com/hoc/flowmvi/core_ui/SwipeLeftToDeleteCallback.kt index 9c68ce9f..71ae822e 100644 --- a/core/src/main/java/com/hoc/flowmvi/core/SwipeLeftToDeleteCallback.kt +++ b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/SwipeLeftToDeleteCallback.kt @@ -1,4 +1,4 @@ -package com.hoc.flowmvi.core +package com.hoc.flowmvi.core_ui import android.content.Context import android.graphics.Canvas diff --git a/core/src/main/java/com/hoc/flowmvi/core/navigator/Navigator.kt b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/navigator/Navigator.kt similarity index 88% rename from core/src/main/java/com/hoc/flowmvi/core/navigator/Navigator.kt rename to core-ui/src/main/java/com/hoc/flowmvi/core_ui/navigator/Navigator.kt index 4749440c..d0a6b1db 100644 --- a/core/src/main/java/com/hoc/flowmvi/core/navigator/Navigator.kt +++ b/core-ui/src/main/java/com/hoc/flowmvi/core_ui/navigator/Navigator.kt @@ -1,4 +1,4 @@ -package com.hoc.flowmvi.core.navigator +package com.hoc.flowmvi.core_ui.navigator import android.content.Context import android.content.Intent diff --git a/core/src/main/res/drawable/ic_baseline_delete_white_24.xml b/core-ui/src/main/res/drawable/ic_baseline_delete_white_24.xml similarity index 100% rename from core/src/main/res/drawable/ic_baseline_delete_white_24.xml rename to core-ui/src/main/res/drawable/ic_baseline_delete_white_24.xml diff --git a/core/src/main/res/font/noto_sans.xml b/core-ui/src/main/res/font/noto_sans.xml similarity index 100% rename from core/src/main/res/font/noto_sans.xml rename to core-ui/src/main/res/font/noto_sans.xml diff --git a/core/src/main/res/values/colors.xml b/core-ui/src/main/res/values/colors.xml similarity index 100% rename from core/src/main/res/values/colors.xml rename to core-ui/src/main/res/values/colors.xml diff --git a/core/src/main/res/values/font_certs.xml b/core-ui/src/main/res/values/font_certs.xml similarity index 100% rename from core/src/main/res/values/font_certs.xml rename to core-ui/src/main/res/values/font_certs.xml diff --git a/core/src/main/res/values/preloaded_fonts.xml b/core-ui/src/main/res/values/preloaded_fonts.xml similarity index 100% rename from core/src/main/res/values/preloaded_fonts.xml rename to core-ui/src/main/res/values/preloaded_fonts.xml diff --git a/core/src/main/res/values/strings.xml b/core-ui/src/main/res/values/strings.xml similarity index 100% rename from core/src/main/res/values/strings.xml rename to core-ui/src/main/res/values/strings.xml diff --git a/core/src/main/res/values/styles.xml b/core-ui/src/main/res/values/styles.xml similarity index 100% rename from core/src/main/res/values/styles.xml rename to core-ui/src/main/res/values/styles.xml diff --git a/core-ui/src/test/java/com/hoc/flowmvi/core_ui/ExampleUnitTest.kt b/core-ui/src/test/java/com/hoc/flowmvi/core_ui/ExampleUnitTest.kt new file mode 100644 index 00000000..bd932593 --- /dev/null +++ b/core-ui/src/test/java/com/hoc/flowmvi/core_ui/ExampleUnitTest.kt @@ -0,0 +1 @@ +package com.hoc.flowmvi.core_ui diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 351e1704..cf13ffe4 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,55 +1,8 @@ plugins { - androidLib - kotlinAndroid -} - -android { - compileSdk = appConfig.compileSdkVersion - buildToolsVersion = appConfig.buildToolsVersion - - defaultConfig { - minSdk = appConfig.minSdkVersion - targetSdk = appConfig.targetSdkVersion - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") - } - - buildTypes { - release { - isMinifyEnabled = true - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } - - testOptions { - unitTests.isIncludeAndroidResources = true - unitTests.isReturnDefaultValues = true - } + kotlin } dependencies { implementation(deps.coroutines.core) - implementation(deps.coroutines.android) - - implementation(deps.androidx.coreKtx) - implementation(deps.androidx.swipeRefreshLayout) - implementation(deps.androidx.recyclerView) - implementation(deps.androidx.material) - - implementation(deps.lifecycle.commonJava8) - implementation(deps.lifecycle.runtimeKtx) - - implementation(deps.timber) - addUnitTest() } diff --git a/core/src/androidTest/java/com/hoc/flowmvi/core/ExampleInstrumentedTest.kt b/core/src/androidTest/java/com/hoc/flowmvi/core/ExampleInstrumentedTest.kt deleted file mode 100644 index dc6f3377..00000000 --- a/core/src/androidTest/java/com/hoc/flowmvi/core/ExampleInstrumentedTest.kt +++ /dev/null @@ -1 +0,0 @@ -package com.hoc.flowmvi.core diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 412a1f59..7bcd2199 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -18,7 +18,7 @@ android { buildTypes { release { - isMinifyEnabled = true + isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -55,4 +55,5 @@ dependencies { implementation(deps.timber) addUnitTest() + testImplementation(testUtils) } diff --git a/data/src/test/java/com/hoc/flowmvi/data/UserRepositoryImplTest.kt b/data/src/test/java/com/hoc/flowmvi/data/UserRepositoryImplTest.kt index eb0d8e05..18541457 100644 --- a/data/src/test/java/com/hoc/flowmvi/data/UserRepositoryImplTest.kt +++ b/data/src/test/java/com/hoc/flowmvi/data/UserRepositoryImplTest.kt @@ -4,12 +4,13 @@ import arrow.core.Either import arrow.core.getOrHandle import arrow.core.identity import com.hoc.flowmvi.core.Mapper -import com.hoc.flowmvi.core.dispatchers.CoroutineDispatchers import com.hoc.flowmvi.data.remote.UserApiService import com.hoc.flowmvi.data.remote.UserBody import com.hoc.flowmvi.data.remote.UserResponse import com.hoc.flowmvi.domain.entity.User import com.hoc.flowmvi.domain.repository.UserError +import com.hoc.flowmvi.test_utils.TestCoroutineDispatcherRule +import com.hoc.flowmvi.test_utils.TestDispatchers import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify @@ -19,17 +20,13 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import io.mockk.verifySequence -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain +import org.junit.Rule import java.io.IOException import kotlin.test.AfterTest import kotlin.test.BeforeTest @@ -95,16 +92,12 @@ private val USERS = listOf( ), ) -@ExperimentalCoroutinesApi -class TestDispatchersImpl(testDispatcher: TestCoroutineDispatcher) : CoroutineDispatchers { - override val main: CoroutineDispatcher = testDispatcher - override val io: CoroutineDispatcher = testDispatcher -} - @ExperimentalCoroutinesApi @ExperimentalTime class UserRepositoryImplTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutineRule = TestCoroutineDispatcherRule() + private val testDispatcher get() = coroutineRule.testCoroutineDispatcher private lateinit var repo: UserRepositoryImpl private lateinit var userApiService: UserApiService @@ -114,8 +107,6 @@ class UserRepositoryImplTest { @BeforeTest fun setup() { - Dispatchers.setMain(testDispatcher) - userApiService = mockk() responseToDomain = mockk() domainToBody = mockk() @@ -123,7 +114,7 @@ class UserRepositoryImplTest { repo = UserRepositoryImpl( userApiService = userApiService, - dispatchers = TestDispatchersImpl(testDispatcher), + dispatchers = TestDispatchers(coroutineRule.testCoroutineDispatcher), responseToDomain = responseToDomain, domainToBody = domainToBody, errorMapper = errorMapper @@ -132,9 +123,6 @@ class UserRepositoryImplTest { @AfterTest fun tearDown() { - testDispatcher.cleanupTestCoroutines() - Dispatchers.resetMain() - confirmVerified( userApiService, responseToDomain, diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 60b79fc4..a3d82c44 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -8,4 +8,5 @@ dependencies { implementation(deps.arrow.core) addUnitTest() + testImplementation(testUtils) } diff --git a/domain/src/test/java/com/hoc/flowmvi/domain/UseCaseTest.kt b/domain/src/test/java/com/hoc/flowmvi/domain/UseCaseTest.kt index f396b5b4..d35c840e 100644 --- a/domain/src/test/java/com/hoc/flowmvi/domain/UseCaseTest.kt +++ b/domain/src/test/java/com/hoc/flowmvi/domain/UseCaseTest.kt @@ -10,6 +10,7 @@ import com.hoc.flowmvi.domain.usecase.GetUsersUseCase import com.hoc.flowmvi.domain.usecase.RefreshGetUsersUseCase import com.hoc.flowmvi.domain.usecase.RemoveUserUseCase import com.hoc.flowmvi.domain.usecase.SearchUsersUseCase +import com.hoc.flowmvi.test_utils.TestCoroutineDispatcherRule import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify @@ -20,8 +21,8 @@ import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.runBlockingTest +import org.junit.Rule import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test @@ -53,7 +54,9 @@ private val USERS = listOf( @ExperimentalCoroutinesApi class UseCaseTest { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutineRule = TestCoroutineDispatcherRule() + private val testDispatcher get() = coroutineRule.testCoroutineDispatcher private lateinit var userRepository: UserRepository private lateinit var getUsersUseCase: GetUsersUseCase diff --git a/feature-add/build.gradle.kts b/feature-add/build.gradle.kts index b55d001e..60e57890 100644 --- a/feature-add/build.gradle.kts +++ b/feature-add/build.gradle.kts @@ -17,7 +17,7 @@ android { buildTypes { release { - isMinifyEnabled = true + isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -41,6 +41,7 @@ android { dependencies { implementation(domain) implementation(core) + implementation(coreUi) implementation(mviBase) implementation(deps.androidx.appCompat) diff --git a/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt b/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt index e59f17b8..90b18744 100644 --- a/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt +++ b/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddActivity.kt @@ -6,11 +6,11 @@ import android.view.MenuItem import androidx.core.view.isInvisible import androidx.transition.AutoTransition import androidx.transition.TransitionManager -import com.hoc.flowmvi.core.clicks -import com.hoc.flowmvi.core.firstChange -import com.hoc.flowmvi.core.navigator.IntentProviders -import com.hoc.flowmvi.core.textChanges -import com.hoc.flowmvi.core.toast +import com.hoc.flowmvi.core_ui.clicks +import com.hoc.flowmvi.core_ui.firstChange +import com.hoc.flowmvi.core_ui.navigator.IntentProviders +import com.hoc.flowmvi.core_ui.textChanges +import com.hoc.flowmvi.core_ui.toast import com.hoc.flowmvi.mvi_base.AbstractMviActivity import com.hoc.flowmvi.ui.add.databinding.ActivityAddBinding import com.hoc081098.flowext.mapTo diff --git a/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddModule.kt b/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddModule.kt index 227df3f6..ca1f178d 100644 --- a/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddModule.kt +++ b/feature-add/src/main/java/com/hoc/flowmvi/ui/add/AddModule.kt @@ -1,6 +1,6 @@ package com.hoc.flowmvi.ui.add -import com.hoc.flowmvi.core.navigator.IntentProviders +import com.hoc.flowmvi.core_ui.navigator.IntentProviders import kotlinx.coroutines.ExperimentalCoroutinesApi import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module diff --git a/feature-main/build.gradle.kts b/feature-main/build.gradle.kts index 2532aed4..50614bc0 100644 --- a/feature-main/build.gradle.kts +++ b/feature-main/build.gradle.kts @@ -17,7 +17,7 @@ android { buildTypes { release { - isMinifyEnabled = true + isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -48,6 +48,7 @@ android { dependencies { implementation(domain) implementation(core) + implementation(coreUi) implementation(mviBase) implementation(deps.androidx.appCompat) diff --git a/feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainActivity.kt b/feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainActivity.kt index f59257e9..18e99ffa 100644 --- a/feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainActivity.kt +++ b/feature-main/src/main/java/com/hoc/flowmvi/ui/main/MainActivity.kt @@ -7,11 +7,11 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.hoc.flowmvi.core.SwipeLeftToDeleteCallback -import com.hoc.flowmvi.core.clicks -import com.hoc.flowmvi.core.navigator.Navigator -import com.hoc.flowmvi.core.refreshes -import com.hoc.flowmvi.core.toast +import com.hoc.flowmvi.core_ui.SwipeLeftToDeleteCallback +import com.hoc.flowmvi.core_ui.clicks +import com.hoc.flowmvi.core_ui.navigator.Navigator +import com.hoc.flowmvi.core_ui.refreshes +import com.hoc.flowmvi.core_ui.toast import com.hoc.flowmvi.domain.repository.UserError import com.hoc.flowmvi.mvi_base.AbstractMviActivity import com.hoc.flowmvi.ui.main.databinding.ActivityMainBinding diff --git a/feature-search/build.gradle.kts b/feature-search/build.gradle.kts index 0b0b5c7e..fdd552b4 100644 --- a/feature-search/build.gradle.kts +++ b/feature-search/build.gradle.kts @@ -18,7 +18,7 @@ android { buildTypes { release { - isMinifyEnabled = true + isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" @@ -42,6 +42,7 @@ android { dependencies { implementation(domain) implementation(core) + implementation(coreUi) implementation(mviBase) implementation(deps.androidx.appCompat) diff --git a/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchActivity.kt b/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchActivity.kt index e0908242..8524cd48 100644 --- a/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchActivity.kt +++ b/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchActivity.kt @@ -10,11 +10,11 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager -import com.hoc.flowmvi.core.SearchViewQueryTextEvent -import com.hoc.flowmvi.core.clicks -import com.hoc.flowmvi.core.navigator.IntentProviders -import com.hoc.flowmvi.core.queryTextEvents -import com.hoc.flowmvi.core.toast +import com.hoc.flowmvi.core_ui.SearchViewQueryTextEvent +import com.hoc.flowmvi.core_ui.clicks +import com.hoc.flowmvi.core_ui.navigator.IntentProviders +import com.hoc.flowmvi.core_ui.queryTextEvents +import com.hoc.flowmvi.core_ui.toast import com.hoc.flowmvi.domain.repository.UserError import com.hoc.flowmvi.mvi_base.AbstractMviActivity import com.hoc.flowmvi.ui.search.databinding.ActivitySearchBinding diff --git a/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchModule.kt b/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchModule.kt index 38b1c6d7..64895147 100644 --- a/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchModule.kt +++ b/feature-search/src/main/java/com/hoc/flowmvi/ui/search/SearchModule.kt @@ -1,6 +1,6 @@ package com.hoc.flowmvi.ui.search -import com.hoc.flowmvi.core.navigator.IntentProviders +import com.hoc.flowmvi.core_ui.navigator.IntentProviders import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import org.koin.androidx.viewmodel.dsl.viewModel diff --git a/mvi/mvi-base/build.gradle.kts b/mvi/mvi-base/build.gradle.kts index 8773f946..220af562 100644 --- a/mvi/mvi-base/build.gradle.kts +++ b/mvi/mvi-base/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { implementation(deps.lifecycle.runtimeKtx) implementation(deps.coroutines.core) - implementation(core) + implementation(coreUi) implementation(deps.timber) addUnitTest() diff --git a/mvi/mvi-base/src/main/java/com/hoc/flowmvi/mvi_base/AbstractMviActivity.kt b/mvi/mvi-base/src/main/java/com/hoc/flowmvi/mvi_base/AbstractMviActivity.kt index cbb29495..3ccbe35a 100644 --- a/mvi/mvi-base/src/main/java/com/hoc/flowmvi/mvi_base/AbstractMviActivity.kt +++ b/mvi/mvi-base/src/main/java/com/hoc/flowmvi/mvi_base/AbstractMviActivity.kt @@ -5,7 +5,7 @@ import androidx.annotation.CallSuper import androidx.annotation.LayoutRes import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope -import com.hoc.flowmvi.core.collectIn +import com.hoc.flowmvi.core_ui.collectIn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/mvi/mvi-testing/build.gradle.kts b/mvi/mvi-testing/build.gradle.kts index 97b4be58..1c467baf 100644 --- a/mvi/mvi-testing/build.gradle.kts +++ b/mvi/mvi-testing/build.gradle.kts @@ -16,12 +16,28 @@ android { } buildTypes { + debug { + (!isCiBuild).let { + buildConfigField( + type = it::class.java.simpleName, + name = "ENABLE_LOG_TEST", + value = it.toString(), + ) + } + } release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + false.let { + buildConfigField( + type = it::class.java.simpleName, + name = "ENABLE_LOG_TEST", + value = it.toString(), + ) + } } } compileOptions { @@ -36,6 +52,7 @@ dependencies { implementation(deps.coroutines.core) implementation(mviBase) + implementation(testUtils) implementation(deps.timber) implementation(deps.arrow.core) diff --git a/mvi/mvi-testing/src/main/java/com/flowmvi/mvi_testing/BaseMviViewModelTest.kt b/mvi/mvi-testing/src/main/java/com/flowmvi/mvi_testing/BaseMviViewModelTest.kt index fd62354a..4dbb5fa1 100644 --- a/mvi/mvi-testing/src/main/java/com/flowmvi/mvi_testing/BaseMviViewModelTest.kt +++ b/mvi/mvi-testing/src/main/java/com/flowmvi/mvi_testing/BaseMviViewModelTest.kt @@ -7,19 +7,18 @@ import com.hoc.flowmvi.mvi_base.MviIntent import com.hoc.flowmvi.mvi_base.MviSingleEvent import com.hoc.flowmvi.mvi_base.MviViewModel import com.hoc.flowmvi.mvi_base.MviViewState +import com.hoc.flowmvi.test_utils.TestCoroutineDispatcherRule import io.mockk.clearAllMocks import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.test.setMain +import org.junit.Rule import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.assertEquals @@ -34,12 +33,20 @@ abstract class BaseMviViewModelTest< E : MviSingleEvent, VM : MviViewModel, > { - private val testDispatcher = TestCoroutineDispatcher() + @get:Rule + val coroutineRule = TestCoroutineDispatcherRule() + + protected val testDispatcher get() = coroutineRule.testCoroutineDispatcher @CallSuper @BeforeTest open fun setup() { - Dispatchers.setMain(testDispatcher) + } + + @CallSuper + @AfterTest + open fun tearDown() { + clearAllMocks() } protected fun test( @@ -48,12 +55,21 @@ abstract class BaseMviViewModelTest< expectedStates: List Unit, S>>, expectedEvents: List Unit, E>>, delayAfterDispatchingIntents: Duration = Duration.ZERO, - logging: Boolean = true, + logging: Boolean = BuildConfig.ENABLE_LOG_TEST, intentsBeforeCollecting: Flow? = null, otherAssertions: (suspend () -> Unit)? = null, ) = testDispatcher.runBlockingTest { + fun logIfEnabled(s: () -> String) = if (logging) println(s()) else Unit + val vm = vmProducer() - intentsBeforeCollecting?.collect { vm.processIntent(it) } + intentsBeforeCollecting + ?.onCompletion { logIfEnabled { "---------------" } } + ?.collect { + vm.processIntent(it) + logIfEnabled { "[BEFORE] Dispatch $it -> $vm" } + } + + logIfEnabled { "[START] $vm" } val states = mutableListOf() val events = mutableListOf() @@ -61,13 +77,15 @@ abstract class BaseMviViewModelTest< val stateJob = launch(start = CoroutineStart.UNDISPATCHED) { vm.viewState.toList(states) } val eventJob = launch(start = CoroutineStart.UNDISPATCHED) { vm.singleEvent.toList(events) } - intents.collect { vm.processIntent(it) } + intents.collect { + vm.processIntent(it) + logIfEnabled { "[DISPATCH] Dispatch $it -> $vm" } + } delay(delayAfterDispatchingIntents) + logIfEnabled { "---------------" } - if (logging) { - println(states) - println(events) - } + logIfEnabled { "[DONE] states=${states.joinToStringWithIndex()}" } + logIfEnabled { "[DONE] events=${events.joinToStringWithIndex()}" } assertEquals(expectedStates.size, states.size, "States size") expectedStates.withIndex().zip(states).forEach { (indexedValue, state) -> @@ -103,14 +121,16 @@ abstract class BaseMviViewModelTest< stateJob.cancel() eventJob.cancel() } - - @CallSuper - @AfterTest - open fun tearDown() { - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() - clearAllMocks() - } } fun Iterable.mapRight(): List Unit, T>> = map { it.right() } + +private fun List.joinToStringWithIndex(): String { + return withIndex().joinToString( + separator = ",\n", + prefix = "[\n", + postfix = "]", + ) { (i, v) -> + " [$i]: $v" + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b7dfdfed..cf8a3884 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,6 +7,8 @@ include(":feature-search") include(":domain") include(":data") include(":core") +include(":core-ui") +include(":test-utils") includeProject(":mvi-base", "mvi/mvi-base") includeProject(":mvi-testing", "mvi/mvi-testing") diff --git a/test-utils/.gitignore b/test-utils/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/test-utils/.gitignore @@ -0,0 +1 @@ +/build diff --git a/test-utils/build.gradle.kts b/test-utils/build.gradle.kts new file mode 100644 index 00000000..bef2a2c2 --- /dev/null +++ b/test-utils/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + kotlin +} + +dependencies { + implementation(deps.coroutines.core) + implementation(core) + + addUnitTest(testImplementation = false) +} diff --git a/test-utils/src/main/java/com/hoc/flowmvi/test_utils/TestCoroutineDispatcherRule.kt b/test-utils/src/main/java/com/hoc/flowmvi/test_utils/TestCoroutineDispatcherRule.kt new file mode 100644 index 00000000..1b25e277 --- /dev/null +++ b/test-utils/src/main/java/com/hoc/flowmvi/test_utils/TestCoroutineDispatcherRule.kt @@ -0,0 +1,22 @@ +package com.hoc.flowmvi.test_utils + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@ExperimentalCoroutinesApi +class TestCoroutineDispatcherRule(val testCoroutineDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : + TestWatcher() { + override fun starting(description: Description) { + Dispatchers.setMain(testCoroutineDispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + testCoroutineDispatcher.cleanupTestCoroutines() + } +} diff --git a/test-utils/src/main/java/com/hoc/flowmvi/test_utils/TestDispatchers.kt b/test-utils/src/main/java/com/hoc/flowmvi/test_utils/TestDispatchers.kt new file mode 100644 index 00000000..5faa6744 --- /dev/null +++ b/test-utils/src/main/java/com/hoc/flowmvi/test_utils/TestDispatchers.kt @@ -0,0 +1,13 @@ +package com.hoc.flowmvi.test_utils + +import com.hoc.flowmvi.core.dispatchers.CoroutineDispatchers +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher + +@ExperimentalCoroutinesApi +class TestDispatchers(testCoroutineDispatcher: TestCoroutineDispatcher) : + CoroutineDispatchers { + override val main: CoroutineDispatcher = testCoroutineDispatcher + override val io: CoroutineDispatcher = testCoroutineDispatcher +}