diff --git a/albumlist/build.gradle b/albumlist/build.gradle index d51b157..e59dcbe 100644 --- a/albumlist/build.gradle +++ b/albumlist/build.gradle @@ -3,4 +3,6 @@ apply from: '../core.gradle' dependencies { implementation project(':base') + testImplementation project(':testing') + androidTestImplementation project(':testing') } \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index feaac15..7b3f366 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,6 +37,10 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + testOptions { unitTests.returnDefaultValues = true } @@ -51,4 +55,6 @@ dependencies { implementation project(":userlist") implementation project(":albumlist") implementation project(":photolist") + testImplementation project(':testing') + androidTestImplementation project(':testing') } diff --git a/app/src/main/java/com/hako/friendlists/MainApplication.kt b/app/src/main/java/com/hako/friendlists/MainApplication.kt index 14faa3d..92db1b3 100644 --- a/app/src/main/java/com/hako/friendlists/MainApplication.kt +++ b/app/src/main/java/com/hako/friendlists/MainApplication.kt @@ -1,10 +1,8 @@ package com.hako.friendlists import android.app.Application -import com.hako.albumlist.di.albumListModules import com.hako.userlist.di.userlistModules import com.hako.friendlists.di.appModules -import com.hako.photolist.di.photoListModules import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import timber.log.Timber @@ -28,9 +26,7 @@ class MainApplication : Application() { modules( listOf( appModules, - userlistModules, - albumListModules, - photoListModules + userlistModules ) ) } diff --git a/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt b/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt index aa1db70..09e853a 100644 --- a/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt +++ b/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt @@ -4,19 +4,35 @@ import android.os.Bundle import androidx.annotation.IdRes import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import com.hako.albumlist.di.albumListModules import com.hako.albumlist.feature.ALBUMLIST_FRAGMENT_BUNDLE_USER_ID import com.hako.albumlist.navigation.AlbumlistNavigation import com.hako.base.extensions.buildNavigation import com.hako.base.navigation.NavigationEvent import com.hako.friendlists.R +import com.hako.photolist.di.photoListModules import com.hako.photolist.feature.PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID import com.hako.userlist.navigation.UserlistNavigation +import org.koin.core.context.loadKoinModules // This sets the fragment title, it's referenced in every navigation const val FRAGMENT_TITLE = "actionTitle" class NavigationViewmodel : ViewModel() { + // Load koin modules dynamically ;) + private val albums by lazy { + loadKoinModules(albumListModules) + } + + private val photos by lazy { + loadKoinModules(photoListModules) + } + + private fun injectAlbums() = albums + + private fun injectPhotos() = photos + val navigate = MutableLiveData>() fun onNavigationEvent(event: NavigationEvent) { @@ -28,6 +44,8 @@ class NavigationViewmodel : ViewModel() { } private fun handleUserlistNavigation(event: UserlistNavigation) { + injectAlbums() + when (event) { is UserlistNavigation.ClickedOnUser -> navigate.postValue( buildNavigation(R.id.action_userlistFragment_to_albumlistFragment, Bundle().apply { @@ -42,6 +60,8 @@ class NavigationViewmodel : ViewModel() { } private fun handleAlbumlistNavigation(event: AlbumlistNavigation) { + injectPhotos() + when (event) { is AlbumlistNavigation.ClickedOnAlbum -> navigate.postValue( buildNavigation(R.id.action_albumlistFragment_to_photolistFragment, Bundle().apply { diff --git a/base/build.gradle b/base/build.gradle index 440ca03..dd32c8e 100644 --- a/base/build.gradle +++ b/base/build.gradle @@ -52,12 +52,6 @@ dependencies { api deps.timber api deps.lottie api deps.picasso - //Testing - api deps.testing.junit - api deps.testing.koin - api deps.testing.core - api deps.testing.rules - api deps.testing.runner - api deps.testing.ext - api deps.testing.espresso + testImplementation project(':testing') + androidTestImplementation project(':testing') } diff --git a/base/src/main/java/com/hako/base/domain/database/dao/AlbumDao.kt b/base/src/main/java/com/hako/base/domain/database/dao/AlbumDao.kt index 6d22584..54abdad 100644 --- a/base/src/main/java/com/hako/base/domain/database/dao/AlbumDao.kt +++ b/base/src/main/java/com/hako/base/domain/database/dao/AlbumDao.kt @@ -15,9 +15,6 @@ interface AlbumDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun saveAll(entities: List) - @get:Query("SELECT * FROM ${AlbumEntity.TABLE_NAME}") - val all: List - @Query("SELECT * FROM ${AlbumEntity.TABLE_NAME} WHERE userId = :userId ORDER BY id ASC") fun getAlbums(userId: Int): List diff --git a/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt b/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt index 4ad3019..f6a2623 100644 --- a/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt +++ b/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt @@ -15,9 +15,6 @@ interface PhotoDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun saveAll(entities: List) - @get:Query("SELECT * FROM ${PhotoEntity.TABLE_NAME}") - val all: List - @Query("SELECT * FROM ${PhotoEntity.TABLE_NAME} WHERE albumId = :albumId ORDER BY id ASC") fun getPhotos(albumId: Int): List diff --git a/base/src/main/java/com/hako/base/domain/database/dao/UserDao.kt b/base/src/main/java/com/hako/base/domain/database/dao/UserDao.kt index 9bda2a6..f373651 100644 --- a/base/src/main/java/com/hako/base/domain/database/dao/UserDao.kt +++ b/base/src/main/java/com/hako/base/domain/database/dao/UserDao.kt @@ -18,9 +18,6 @@ interface UserDao { @Query("UPDATE ${UserEntity.TABLE_NAME} SET isFavorite = :favorite WHERE id = :id") fun saveFavorite(id: Int, favorite: Boolean): Int - @get:Query("SELECT * FROM ${UserEntity.TABLE_NAME}") - val all: List - @Query("SELECT * FROM ${UserEntity.TABLE_NAME} ORDER BY id ASC") fun getAllUsers(): List diff --git a/base/src/main/java/com/hako/base/domain/database/entities/User.kt b/base/src/main/java/com/hako/base/domain/database/entities/User.kt index 14e5be0..6756461 100644 --- a/base/src/main/java/com/hako/base/domain/database/entities/User.kt +++ b/base/src/main/java/com/hako/base/domain/database/entities/User.kt @@ -13,7 +13,7 @@ data class UserEntity( val email: String, val phone: String, val website: String, - val isFavorite: Boolean = true + val isFavorite: Boolean = false ) { companion object { const val TABLE_NAME = "users" diff --git a/core.gradle b/core.gradle index e0be423..db3b581 100644 --- a/core.gradle +++ b/core.gradle @@ -19,6 +19,10 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + testOptions { unitTests.returnDefaultValues = true } diff --git a/photolist/build.gradle b/photolist/build.gradle index d51b157..e59dcbe 100644 --- a/photolist/build.gradle +++ b/photolist/build.gradle @@ -3,4 +3,6 @@ apply from: '../core.gradle' dependencies { implementation project(':base') + testImplementation project(':testing') + androidTestImplementation project(':testing') } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 469cf91..e43c830 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':app', ':base', ':userlist', ':albumlist', ':photolist' +include ':app', ':base', ':userlist', ':albumlist', ':photolist', ':testing' rootProject.name='Friendlists' diff --git a/testing/.gitignore b/testing/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/testing/.gitignore @@ -0,0 +1 @@ +/build diff --git a/testing/build.gradle b/testing/build.gradle new file mode 100644 index 0000000..c3282a1 --- /dev/null +++ b/testing/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'com.android.library' +apply from: '../core.gradle' +apply from: 'versions.gradle' + +dependencies { + api deps.testing.junit + api deps.testing.koin + api deps.testing.core + api deps.testing.rules + api deps.testing.runner + api deps.testing.ext + api deps.testing.espresso + api deps.testing.fragments +} \ No newline at end of file diff --git a/testing/consumer-rules.pro b/testing/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/testing/proguard-rules.pro b/testing/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/testing/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/testing/src/main/AndroidManifest.xml b/testing/src/main/AndroidManifest.xml new file mode 100644 index 0000000..02de5b0 --- /dev/null +++ b/testing/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/testing/src/main/java/com/hako/testing/TestingExtensions.kt b/testing/src/main/java/com/hako/testing/TestingExtensions.kt new file mode 100644 index 0000000..50dd567 --- /dev/null +++ b/testing/src/main/java/com/hako/testing/TestingExtensions.kt @@ -0,0 +1,10 @@ +package com.hako.testing + +import androidx.annotation.RestrictTo +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.assertion.ViewAssertions.matches + +@RestrictTo(RestrictTo.Scope.TESTS) +fun String.isTextDisplayed() = onView(withText(this)).check(matches(ViewMatchers.isDisplayed())) \ No newline at end of file diff --git a/testing/src/main/res/values/strings.xml b/testing/src/main/res/values/strings.xml new file mode 100644 index 0000000..3a5fe33 --- /dev/null +++ b/testing/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + testing + diff --git a/testing/versions.gradle b/testing/versions.gradle new file mode 100644 index 0000000..13b6f02 --- /dev/null +++ b/testing/versions.gradle @@ -0,0 +1,24 @@ +ext.deps = [:] + +def versions = [:] +versions.koin = "2.0.1" +versions.junit = "4.13" +versions.test = "1.2.0" +versions.test_ext = "1.1.1" +versions.espresso = "3.2.0" +versions.fragments = "1.2.0" + +def deps = [:] + +def testing = [:] +testing.junit = "junit:junit:$versions.junit" +testing.core = "androidx.test:core:$versions.test" +testing.rules = "androidx.test:rules:$versions.test" +testing.runner = "androidx.test:runner:$versions.test" +testing.ext = "androidx.test.ext:junit:$versions.test_ext" +testing.koin = "org.koin:koin-test:$versions.koin" +testing.espresso = "androidx.test.espresso:espresso-core:$versions.espresso" +testing.fragments = "androidx.fragment:fragment-testing:$versions.fragments" +deps.testing = testing + +ext.deps = deps diff --git a/tools/detekt.yml b/tools/detekt.yml index 0d5938a..3bcf79d 100644 --- a/tools/detekt.yml +++ b/tools/detekt.yml @@ -108,7 +108,7 @@ empty-blocks: EmptyForBlock: active: true EmptyFunctionBlock: - active: true + active: false ignoreOverridden: false EmptyIfBlock: active: true diff --git a/userlist/build.gradle b/userlist/build.gradle index 298aaa6..4bf1bc9 100644 --- a/userlist/build.gradle +++ b/userlist/build.gradle @@ -3,4 +3,6 @@ apply from: '../core.gradle' dependencies { implementation project(':base') + testImplementation project(':testing') + androidTestImplementation project(':testing') } diff --git a/userlist/src/androidTest/java/com/hako/userlist/feature/Mocks.kt b/userlist/src/androidTest/java/com/hako/userlist/feature/Mocks.kt new file mode 100644 index 0000000..a6c771f --- /dev/null +++ b/userlist/src/androidTest/java/com/hako/userlist/feature/Mocks.kt @@ -0,0 +1,29 @@ +package com.hako.userlist.feature + +import com.hako.base.domain.database.dao.UserDao +import com.hako.base.domain.database.entities.UserEntity +import com.hako.userlist.domain.datasource.UserlistRemoteApi +import com.hako.userlist.model.User +import io.reactivex.Single + +class MockUserDao(private val userList: List) : UserDao { + override fun save(entity: UserEntity) {} + + override fun saveAll(entities: List) {} + + override fun saveFavorite(id: Int, favorite: Boolean) = 1 + + override fun getAllUsers() = userList + + override fun getFavoriteUsers() = userList.filter { it.isFavorite } + + override fun count() = userList.count() + + override fun nukeDatabase() {} +} + +class MockUserApi(private val userList: List) : UserlistRemoteApi { + override fun getUsers() = Single.fromCallable { getAllUsers() } + + private fun getAllUsers() = userList +} \ No newline at end of file diff --git a/userlist/src/androidTest/java/com/hako/userlist/feature/UserlistFragmentTest.kt b/userlist/src/androidTest/java/com/hako/userlist/feature/UserlistFragmentTest.kt new file mode 100644 index 0000000..77c91d2 --- /dev/null +++ b/userlist/src/androidTest/java/com/hako/userlist/feature/UserlistFragmentTest.kt @@ -0,0 +1,146 @@ +package com.hako.userlist.feature + +import androidx.fragment.app.testing.launchFragmentInContainer +import androidx.test.platform.app.InstrumentationRegistry +import com.hako.base.domain.database.dao.UserDao +import com.hako.base.domain.database.entities.UserEntity +import com.hako.testing.isTextDisplayed +import com.hako.userlist.domain.datasource.UserlistRemoteApi +import com.hako.userlist.domain.usecase.GetFavoriteUsers +import com.hako.userlist.domain.usecase.GetUsers +import com.hako.userlist.domain.usecase.SetFavoriteStatus +import com.hako.userlist.model.User +import com.hako.userlist.model.toUserEntity +import com.hako.userlist.viewmodel.UserlistViewmodel +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.context.loadKoinModules +import org.koin.core.context.startKoin +import org.koin.core.context.stopKoin +import org.koin.dsl.module + +class UserlistFragmentTest { + + @Before + fun setupKoin() { + startKoin { + InstrumentationRegistry.getInstrumentation().targetContext + modules(module { + factory { GetUsers(get()) } + factory { GetFavoriteUsers(get()) } + factory { SetFavoriteStatus(get()) } + viewModel { UserlistViewmodel() } + }) + } + } + + @After + fun killKoin() { + stopKoin() + } + + @Test + fun shouldShowUserlist_withAllUsers() { + userlist { + withTwoBasicUsers() + } should { + showTwoBasicUsers() + } + } + + @Test + fun shouldShowOnlyFavoriteUserlist_withAllUsers() { + userlist { + withTwoBasicOneFavoriteUsers() + } should { + showOnlyOneFavorite() + } + } + + private fun userlist(func: UserlistRobot.() -> Unit) = + UserlistRobot().apply { + func() + } +} + +class UserlistRobot { + + fun withTwoBasicUsers() { + loadKoinModules( + module { + factory { MockUserDao(loadTwoBasicUsers().map { it.toUserEntity() }) } + factory { MockUserApi(loadTwoBasicUsers()) } + } + ) + launchFragmentInContainer() + } + + fun withTwoBasicOneFavoriteUsers() { + loadKoinModules( + module { + factory { MockUserDao(loadTwoUsersOneFavorite()) } + factory { MockUserApi(loadTwoBasicUsers()) } + } + ) + launchFragmentInContainer() + } + + infix fun should(func: UserlistResult.() -> Unit) { + UserlistResult().apply { func() } + } + + private fun loadTwoBasicUsers() = listOf( + User( + 1, + "Marian Arriaga", + "mariancita", + "test@gmail.com", + "+56873912", + "www.test.com" + ), + User( + 2, + "Carlos Martinez", + "carlitos", + "test2@gmail.com", + "+56873912", + "www.test2.com" + ) + ) + + private fun loadTwoUsersOneFavorite() = listOf( + UserEntity( + 1, + "Marian Arriaga", + "mariancita", + "test@gmail.com", + "+56873912", + "www.test.com", + true + ), + UserEntity( + 2, + "Carlos Martinez", + "carlitos", + "test2@gmail.com", + "+56873912", + "www.test2.com" + ) + ) +} + +class UserlistResult { + fun showTwoBasicUsers() { + "Marian Arriaga".isTextDisplayed() + "mariancita".isTextDisplayed() + "Carlos Martinez".isTextDisplayed() + "carlitos".isTextDisplayed() + } + + fun showOnlyOneFavorite() { + "Marian Arriaga".isTextDisplayed() + "mariancita".isTextDisplayed() + } +} \ No newline at end of file