From 70ffac3025655026cff8ed2741da0319cbd6a20b Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 13 Jun 2021 20:52:03 +0900 Subject: [PATCH] Add some ProfileFragmentViewModel logic and tests --- app/build.gradle | 3 +- .../fragments/main/ProfileFragment.java | 2 +- .../viewmodels/ProfileFragmentViewModel.kt | 46 ++++++++++++++---- .../awais/instagrabber/LiveDataTestUtil.kt | 41 ++++++++++++++++ .../ProfileFragmentViewModelTest.kt | 47 +++++++++++++++++-- 5 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 app/src/test/java/awais/instagrabber/LiveDataTestUtil.kt diff --git a/app/build.gradle b/app/build.gradle index 88b5af11..3c520f75 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -242,9 +242,10 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' testImplementation "androidx.test.ext:junit-ktx:1.1.2" testImplementation "androidx.test:core-ktx:1.3.0" + testImplementation "androidx.arch.core:core-testing:2.1.0" testImplementation "org.robolectric:robolectric:4.5.1" - androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' + androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' androidTestImplementation 'androidx.test:core:1.3.0' androidTestImplementation 'com.android.support:support-annotations:28.0.0' androidTestImplementation 'com.android.support.test:runner:1.0.2' diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 6df0400e..70904fc8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -382,7 +382,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe shouldRefresh = false; return root; } - // appStateViewModel.getCurrentUserLiveData().observe(getViewLifecycleOwner(), user -> viewModel.setCurrentUser(user)); + appStateViewModel.getCurrentUserLiveData().observe(getViewLifecycleOwner(), user -> viewModel.setCurrentUser(user)); binding = FragmentProfileBinding.inflate(inflater, container, false); root = binding.getRoot(); profileDetailsBinding = binding.header; diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index 2efb83b2..d449c756 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -1,13 +1,12 @@ package awais.instagrabber.viewmodels import android.os.Bundle -import android.util.Log import androidx.lifecycle.* import androidx.savedstate.SavedStateRegistryOwner import awais.instagrabber.db.repositories.AccountRepository import awais.instagrabber.db.repositories.FavoriteRepository +import awais.instagrabber.models.Resource import awais.instagrabber.repositories.responses.User -import awais.instagrabber.utils.extensions.TAG import awais.instagrabber.webservices.* class ProfileFragmentViewModel( @@ -20,17 +19,44 @@ class ProfileFragmentViewModel( accountRepository: AccountRepository, favoriteRepository: FavoriteRepository, ) : ViewModel() { - private val _profile = MutableLiveData() - val profile: LiveData = _profile - val username: LiveData = Transformations.map(profile) { return@map it?.username ?: "" } + private val _profile = MutableLiveData>(Resource.loading(null)) + private val _isLoggedIn = MutableLiveData(false) - var currentUser: User? = null - var isLoggedIn = false - get() = currentUser != null - private set + val profile: LiveData> = _profile + + /** + * Username of profile without '`@`' + */ + val username: LiveData = Transformations.map(profile) { + return@map when (it.status) { + Resource.Status.LOADING, Resource.Status.ERROR -> "" + Resource.Status.SUCCESS -> it.data?.username ?: "" + } + } + val isLoggedIn: LiveData = _isLoggedIn + + var currentUser: Resource? = null + set(value) { + _isLoggedIn.postValue(value?.data != null) + // if no profile, and value is valid, set it as profile + val profileValue = profile.value + if ( + profileValue?.status != Resource.Status.LOADING + && profileValue?.data == null + && value?.status == Resource.Status.SUCCESS + && value.data != null + ) { + _profile.postValue(Resource.success(value.data)) + } + field = value + } init { - Log.d(TAG, "${state.keys()} $userRepository $friendshipRepository $storiesRepository $mediaRepository") + // Log.d(TAG, "${state.keys()} $userRepository $friendshipRepository $storiesRepository $mediaRepository") + val usernameFromState = state.get("username") + if (usernameFromState.isNullOrBlank()) { + _profile.postValue(Resource.success(null)) + } } } diff --git a/app/src/test/java/awais/instagrabber/LiveDataTestUtil.kt b/app/src/test/java/awais/instagrabber/LiveDataTestUtil.kt new file mode 100644 index 00000000..57907dac --- /dev/null +++ b/app/src/test/java/awais/instagrabber/LiveDataTestUtil.kt @@ -0,0 +1,41 @@ +package awais.instagrabber + +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +@VisibleForTesting(otherwise = VisibleForTesting.NONE) +fun LiveData.getOrAwaitValue( + time: Long = 2, + timeUnit: TimeUnit = TimeUnit.SECONDS, + afterObserve: () -> Unit = {} +): T { + var data: T? = null + val latch = CountDownLatch(1) + val observer = object : Observer { + override fun onChanged(o: T?) { + data = o + latch.countDown() + this@getOrAwaitValue.removeObserver(this) + } + } + this.observeForever(observer) + + try { + afterObserve.invoke() + + // Don't wait indefinitely if the LiveData is not set. + if (!latch.await(time, timeUnit)) { + throw TimeoutException("LiveData value was never set.") + } + + } finally { + this.removeObserver(observer) + } + + @Suppress("UNCHECKED_CAST") + return data as T +} diff --git a/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt b/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt index 4f72bfe3..f5af75ce 100644 --- a/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt +++ b/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt @@ -1,5 +1,6 @@ package awais.instagrabber.viewmodels +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.SavedStateHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import awais.instagrabber.common.* @@ -7,19 +8,49 @@ import awais.instagrabber.db.datasources.AccountDataSource import awais.instagrabber.db.datasources.FavoriteDataSource import awais.instagrabber.db.repositories.AccountRepository import awais.instagrabber.db.repositories.FavoriteRepository +import awais.instagrabber.getOrAwaitValue +import awais.instagrabber.models.Resource +import awais.instagrabber.repositories.responses.User import awais.instagrabber.webservices.* +import org.junit.Rule import org.junit.Test +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) internal class ProfileFragmentViewModelTest { + + @get:Rule + var instantExecutorRule = InstantTaskExecutorRule() + @Test fun testNoUsernameNoCurrentUser() { - val state = SavedStateHandle( - mutableMapOf( - "username" to "" - ) + val accountDataSource = AccountDataSource(AccountDaoAdapter()) + val viewModel = ProfileFragmentViewModel( + SavedStateHandle(), + UserRepository(UserServiceAdapter()), + FriendshipRepository(FriendshipServiceAdapter()), + StoriesRepository(StoriesServiceAdapter()), + MediaRepository(MediaServiceAdapter()), + GraphQLRepository(GraphQLServiceAdapter()), + AccountRepository(accountDataSource), + FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())) ) + assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue()) + assertNull(viewModel.profile.getOrAwaitValue().data) + assertEquals("", viewModel.username.getOrAwaitValue()) + viewModel.currentUser = Resource.success(null) + assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue()) + } + + @Test + fun testNoUsernameWithCurrentUser() { + // val state = SavedStateHandle( + // mutableMapOf( + // "username" to "test" + // ) + // ) val userRepository = UserRepository(UserServiceAdapter()) val friendshipRepository = FriendshipRepository(FriendshipServiceAdapter()) val storiesRepository = StoriesRepository(StoriesServiceAdapter()) @@ -29,7 +60,7 @@ internal class ProfileFragmentViewModelTest { val accountRepository = AccountRepository(accountDataSource) val favoriteRepository = FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())) val viewModel = ProfileFragmentViewModel( - state, + SavedStateHandle(), userRepository, friendshipRepository, storiesRepository, @@ -38,5 +69,11 @@ internal class ProfileFragmentViewModelTest { accountRepository, favoriteRepository ) + assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue()) + assertNull(viewModel.profile.getOrAwaitValue().data) + val user = User() + viewModel.currentUser = Resource.success(user) + assertEquals(true, viewModel.isLoggedIn.getOrAwaitValue()) + assertEquals(user, viewModel.profile.getOrAwaitValue().data) } } \ No newline at end of file