diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml
new file mode 100644
index 0000000..bc2bb57
--- /dev/null
+++ b/.github/workflows/android-ci.yml
@@ -0,0 +1,61 @@
+name: Android CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout the code
+ uses: actions/checkout@v4
+
+ - name: Run lint
+ run: ./gradlew lint
+
+ - name: Upload lint report
+ uses: actions/upload-artifact@v4
+ with:
+ name: lint-report
+ path: presentation/build/reports/lint-results-debug.html
+
+ unit-test:
+ needs: [lint]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout the code
+ uses: actions/checkout@v4
+
+ - name: Run tests
+ run: ./gradlew test
+
+ - name: Upload test report
+ uses: actions/upload-artifact@v4
+ with:
+ name: unit-test-report
+ path: app/build/reports/tests/testDevelopmentDebugUnitTest/
+
+# instrumentation-test:
+# needs: [unit-test]
+# runs-on: macos-latest
+# steps:
+# - name: Checkout the code
+# uses: actions/checkout@v4
+#
+# - name: Run espresso tests
+# uses: reactivecircus/android-emulator-runner@v2
+# with:
+# api-level: 35
+# script: ./gradlew connectedCheck
+# startup-timeout: 5m
+#
+# - name: Upload test report
+# uses: actions/upload-artifact@v4
+# with:
+# name: instrumentation_test_report
+# path: app/build/reports/androidTests/connected/
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index add751b..3970d22 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -117,10 +117,10 @@ dependencies {
// Unit Test
testImplementation(libs.junit)
- testImplementation(libs.mockito)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.core.testing)
testImplementation(libs.kotlinx.coroutines.test)
+ testImplementation(libs.mockito)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockk)
testImplementation(libs.test.rules)
diff --git a/app/src/androidTest/java/com/mkdev/nimblesurvey/screen/authentication/signin/SignInScreenTest.kt b/app/src/androidTest/java/com/mkdev/nimblesurvey/screen/authentication/signin/SignInScreenTest.kt
index 9a2d346..10e7cf1 100644
--- a/app/src/androidTest/java/com/mkdev/nimblesurvey/screen/authentication/signin/SignInScreenTest.kt
+++ b/app/src/androidTest/java/com/mkdev/nimblesurvey/screen/authentication/signin/SignInScreenTest.kt
@@ -70,7 +70,6 @@ class SignInScreenTest {
composeTestRule.onNodeWithText("Password").performTextInput("12345678")
composeTestRule.onNodeWithText("Log in").performClick()
- // Wait for navigation to complete (adjust timeout as needed)
composeTestRule.waitUntil(timeoutMillis = 2000) {
navController.currentDestination?.route == HomeNavigation.ROUTE
}
@@ -94,7 +93,6 @@ class SignInScreenTest {
fun signInScreen_forgotPasswordClick_navigatesToForgotPassword() {
composeTestRule.onNodeWithText("Forgot?").performClick()
- // Wait for navigation to complete (adjust timeout as needed)
composeTestRule.waitUntil(timeoutMillis = 2000) {
navController.currentDestination?.route == ResetPasswordNavigation.ROUTE
}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d1..0000000
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 07d5da9..0000000
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 036d09b..4a4b17f 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,7 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 036d09b..4a4b17f 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,5 +1,7 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127..55344e5 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,10 +1,3 @@
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
\ No newline at end of file
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index d17851f..5abb81e 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -83,12 +83,20 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockk)
- androidTestImplementation(libs.androidx.espresso.core)
testImplementation(libs.test.rules)
- androidTestImplementation(libs.room.testing)
testImplementation(libs.androidx.junit)
testImplementation(libs.turbine)
testImplementation(libs.truth)
+
+ // UI Test
+ androidTestImplementation(libs.room.testing)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(libs.hilt.android.testing)
+ androidTestImplementation(libs.test.runner)
+ androidTestImplementation(libs.test.rules)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.turbine)
+ androidTestImplementation(libs.kotlinx.coroutines.test)
}
protobuf {
diff --git a/data/src/test/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt
similarity index 75%
rename from data/src/test/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt
rename to data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt
index d60174f..595007c 100644
--- a/data/src/test/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt
+++ b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyDaoTest.kt
@@ -1,6 +1,6 @@
package com.mkdev.data.datasource.local.database.room.dao
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import android.content.Context
import androidx.paging.PagingSource
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
@@ -8,9 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.mkdev.data.datasource.local.database.room.NimbleRoomDatabase
import com.mkdev.data.datasource.local.database.room.entity.SurveyEntity
import com.mkdev.data.factory.SurveyEntityFactory
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestDispatcher
+import com.mkdev.data.utils.TestDispatcherRule
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
@@ -19,26 +17,23 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class SurveyDaoTest {
- @get:Rule
- var instantExecutorRule = InstantTaskExecutorRule()
+ @get: Rule
+ val dispatcherRule = TestDispatcherRule()
private lateinit var database: NimbleRoomDatabase
private lateinit var surveyDao: SurveyDao
- private lateinit var testDispatcher:TestDispatcher
@Before
fun setUp() {
+ val context = ApplicationProvider.getApplicationContext()
database = Room.inMemoryDatabaseBuilder(
- ApplicationProvider.getApplicationContext(),
+ context,
NimbleRoomDatabase::class.java
- ).allowMainThreadQueries().build()
+ ).build()
surveyDao = database.surveyDao()
-
- testDispatcher = StandardTestDispatcher()
}
@After
@@ -47,7 +42,7 @@ class SurveyDaoTest {
}
@Test
- fun `insertAll should insert surveys into database`() = runTest(testDispatcher) {
+ fun insertAll_should_insert_surveys_into_database() = runTest {
// Given
val surveys = SurveyEntityFactory.createSurveyEntityList(count = 2)
@@ -63,7 +58,7 @@ class SurveyDaoTest {
}
@Test
- fun `getByPaging should return paging source of surveys`() = runTest(testDispatcher) {
+ fun getByPaging_should_return_paging_source_of_surveys() = runTest {
// Given
val surveys = SurveyEntityFactory.createSurveyEntityList(count = 2)
surveyDao.insertAll(surveys)
@@ -83,7 +78,7 @@ class SurveyDaoTest {
}
@Test
- fun `getById should return survey by id`() = runTest(testDispatcher) {
+ fun getById_should_return_survey_by_id() = runTest {
// Given
val survey = SurveyEntityFactory.createSurveyEntity()
surveyDao.insertAll(listOf(survey))
@@ -96,7 +91,7 @@ class SurveyDaoTest {
}
@Test
- fun `getById should return null when survey not found`() = runTest(testDispatcher) {
+ fun getById_should_return_null_when_survey_not_found() = runTest {
// Given
val nonexistentId = "nonexistent_id"
@@ -108,7 +103,7 @@ class SurveyDaoTest {
}
@Test
- fun `clearAll should clear all surveys from database`() = runTest(testDispatcher) {
+ fun clearAll_should_clear_all_surveys_from_database() = runTest {
// Given
val surveys = SurveyEntityFactory.createSurveyEntityList(count = 2)
surveyDao.insertAll(surveys)
diff --git a/data/src/test/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt
similarity index 77%
rename from data/src/test/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt
rename to data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt
index b860296..92cd655 100644
--- a/data/src/test/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt
+++ b/data/src/androidTest/java/com/mkdev/data/datasource/local/database/room/dao/SurveyRemoteKeyDaoTest.kt
@@ -1,32 +1,39 @@
-package com.mkdev.data.datasource.local.database.room.dao
+package com.mkdev.data.datasource.local.database.room.dao//package com.mkdev.data.datasource.local.database.room.dao
+import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.mkdev.data.datasource.local.database.room.NimbleRoomDatabase
import com.mkdev.data.datasource.local.database.room.entity.SurveyRemoteKeyEntity
import com.mkdev.data.factory.SurveyRemoteKeyEntityFactory
+import com.mkdev.data.utils.TestDispatcherRule
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SurveyRemoteKeyDaoTest {
+ @get: Rule
+ val dispatcherRule = TestDispatcherRule()
+
private lateinit var database: NimbleRoomDatabase
private lateinit var surveyRemoteKeyDao: SurveyRemoteKeyDao
private val testDispatcher = StandardTestDispatcher()
@Before
fun setUp() {
+ val context = ApplicationProvider.getApplicationContext()
database = Room.inMemoryDatabaseBuilder(
- ApplicationProvider.getApplicationContext(),
+ context,
NimbleRoomDatabase::class.java
- ).allowMainThreadQueries().build()
+ ).build()
surveyRemoteKeyDao = database.surveyRemoteKeyDao()
}
@@ -36,7 +43,7 @@ class SurveyRemoteKeyDaoTest {
}
@Test
- fun `insertAll should insert remote keys into database`() = runTest(testDispatcher) {
+ fun insertAll_should_insert_remote_keys_into_database() = runTest {
// Given
val remoteKeys = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntityList(count = 2)
@@ -49,7 +56,7 @@ class SurveyRemoteKeyDaoTest {
}
@Test
- fun `insert should insert a single remote key into database`() = runTest(testDispatcher) {
+ fun insert_should_insert_a_single_remote_key_into_database() = runTest(testDispatcher) {
// Given
val remoteKey = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntity()
@@ -62,7 +69,7 @@ class SurveyRemoteKeyDaoTest {
}
@Test
- fun `insertOrReplace should insert or replace a remote key`() = runTest(testDispatcher) {
+ fun insertOrReplace_should_insert_or_replace_a_remote_key() = runTest(testDispatcher) {
// Given
val remoteKey1 = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntity()
val remoteKey2 = remoteKey1.copy(nextPage = 3) // Updated nextPage
@@ -77,7 +84,7 @@ class SurveyRemoteKeyDaoTest {
}
@Test
- fun `remoteKeysId should return remote key by id`() = runTest(testDispatcher) {
+ fun remoteKeysId_should_return_remote_key_by_id() = runTest(testDispatcher) {
// Given
val remoteKey = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntity()
surveyRemoteKeyDao.insert(remoteKey)
@@ -90,7 +97,7 @@ class SurveyRemoteKeyDaoTest {
}
@Test
- fun `remoteKeysId should return null when remote key not found`() = runTest(testDispatcher) {
+ fun remoteKeysId_should_return_null_when_remote_key_not_found() = runTest(testDispatcher) {
// Given
val nonexistentId = "nonexistent_id"
@@ -102,7 +109,7 @@ class SurveyRemoteKeyDaoTest {
}
@Test
- fun `clearRemoteKeys should clear all remote keys from database`() = runTest(testDispatcher) {
+ fun clearRemoteKeys_should_clear_all_remote_keys_from_database() = runTest(testDispatcher) {
// Given
val remoteKeys = SurveyRemoteKeyEntityFactory.createSurveyRemoteKeyEntityList(count = 2)
surveyRemoteKeyDao.insertAll(remoteKeys)
diff --git a/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt b/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt
new file mode 100644
index 0000000..5bc0cc8
--- /dev/null
+++ b/data/src/androidTest/java/com/mkdev/data/factory/SurveyEntityFactory.kt
@@ -0,0 +1,30 @@
+package com.mkdev.data.factory
+
+import com.mkdev.data.datasource.local.database.room.entity.SurveyEntity
+
+object SurveyEntityFactory {
+
+ fun createSurveyEntity(
+ id: String = "survey_id",
+ title: String = "Survey Title",
+ description: String = "Survey Description",
+ coverImageUrl: String = "https://example.com/image.jpg",
+ isActive: Boolean = true,
+ surveyType: String = "customer_satisfaction"
+ ): SurveyEntity {
+ return SurveyEntity(
+ id = id,
+ title = title,
+ description = description,
+ coverImageUrl = coverImageUrl,
+ isActive = isActive,
+ surveyType = surveyType
+ )
+ }
+
+ fun createSurveyEntityList(count: Int = 5): List {
+ return (1..count).map {
+ createSurveyEntity(id = "survey_id_$it")
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt b/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt
new file mode 100644
index 0000000..b6b7ae2
--- /dev/null
+++ b/data/src/androidTest/java/com/mkdev/data/factory/SurveyRemoteKeyEntityFactory.kt
@@ -0,0 +1,23 @@
+package com.mkdev.data.factory
+
+import com.mkdev.data.datasource.local.database.room.entity.SurveyRemoteKeyEntity
+
+object SurveyRemoteKeyEntityFactory {
+ fun createSurveyRemoteKeyEntity(
+ surveyId: String = "survey_id",
+ prevPage: Int? = 1,
+ nextPage: Int? = 2
+ ): SurveyRemoteKeyEntity {
+ return SurveyRemoteKeyEntity(
+ surveyId = surveyId,
+ prevPage = prevPage,
+ nextPage = nextPage
+ )
+ }
+
+ fun createSurveyRemoteKeyEntityList(count: Int = 5): List {
+ return (1..count).map {
+ createSurveyRemoteKeyEntity(surveyId = "survey_id_$it")
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/src/androidTest/java/com/mkdev/data/utils/TestDispatcherRule.kt b/data/src/androidTest/java/com/mkdev/data/utils/TestDispatcherRule.kt
new file mode 100644
index 0000000..d4d26c7
--- /dev/null
+++ b/data/src/androidTest/java/com/mkdev/data/utils/TestDispatcherRule.kt
@@ -0,0 +1,23 @@
+package com.mkdev.data.utils
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class TestDispatcherRule(
+ private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+): TestWatcher() {
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
+ }
+}
\ No newline at end of file
diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts
index fca09bf..9dfe08e 100644
--- a/domain/build.gradle.kts
+++ b/domain/build.gradle.kts
@@ -6,6 +6,10 @@ plugins {
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
+
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.toString()))
+ }
}
dependencies {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 69e7dcf..1938739 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,7 +6,7 @@ agp = "8.7.3"
kotlin = "2.1.0"
coreKtx = "1.15.0"
junit = "4.13.2"
-mockito = "5.12.0"
+mockito = "5.14.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
@@ -30,7 +30,7 @@ protobufJavalite = "4.29.1"
protobuf = "0.9.4"
tinkAndroid = "1.7.0"
roomRuntime = "2.6.1"
-robolectric = "4.11.1"
+robolectric = "4.14.1"
androidxCoreTesting = "2.2.0"
testRules = "1.6.1"
kotlinxCoroutinesTest = "1.9.0"
diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts
index 046a85c..72cbb55 100644
--- a/presentation/build.gradle.kts
+++ b/presentation/build.gradle.kts
@@ -26,6 +26,13 @@ android {
compose = true
buildConfig = true
}
+
+ flavorDimensions += "environment"
+ productFlavors {
+ register("development")
+ register("staging")
+ register("production")
+ }
}
dependencies {