diff --git a/app/build.gradle b/app/build.gradle index 645bcb05..f16f9675 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,6 +137,7 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion" implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofitVersion" + implementation "at.connyduck:kotlin-result-calladapter:1.0.0" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion" @@ -176,8 +177,8 @@ dependencies { testImplementation "androidx.test.ext:junit:1.1.3" testImplementation "org.robolectric:robolectric:4.4" - testImplementation "org.mockito:mockito-inline:3.6.28" - testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + testImplementation "org.mockito:mockito-inline:4.4.0" + testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" androidTestImplementation "androidx.room:room-testing:$roomVersion" diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 5c7901c5..a934ff9a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -682,18 +682,15 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } } - private fun fetchUserInfo() { - mastodonApi.accountVerifyCredentials() - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this, Lifecycle.Event.ON_DESTROY) - .subscribe( - { userInfo -> - onFetchUserInfoSuccess(userInfo) - }, - { throwable -> - Log.e(TAG, "Failed to fetch user info. " + throwable.message) - } - ) + private fun fetchUserInfo() = lifecycleScope.launch { + mastodonApi.accountVerifyCredentials().fold( + { userInfo -> + onFetchUserInfoSuccess(userInfo) + }, + { throwable -> + Log.e(TAG, "Failed to fetch user info. " + throwable.message) + } + ) } private fun onFetchUserInfoSuccess(me: Account) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt index d1ae0b9e..10dc303f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt @@ -35,6 +35,7 @@ import com.keylesspalace.tusky.util.Resource import com.keylesspalace.tusky.util.RxAwareViewModel import com.keylesspalace.tusky.util.Success import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.rx3.rxSingle import javax.inject.Inject class AnnouncementsViewModel @Inject constructor( @@ -56,8 +57,9 @@ class AnnouncementsViewModel @Inject constructor( appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) .map> { Either.Left(it) } .onErrorResumeNext { - mastodonApi.getInstance() - .map { Either.Right(it) } + rxSingle { + mastodonApi.getInstance().getOrThrow() + }.map { Either.Right(it) } } ) { emojis, either -> either.asLeftOrNull()?.copy(emojiList = emojis) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 66dacfb4..08df6dc9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -48,6 +48,7 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.Disposable import kotlinx.coroutines.launch +import kotlinx.coroutines.rx3.rxSingle import java.util.Locale import javax.inject.Inject @@ -105,7 +106,10 @@ class ComposeViewModel @Inject constructor( init { Single.zip( - api.getCustomEmojis(), api.getInstance() + api.getCustomEmojis(), + rxSingle { + api.getInstance().getOrThrow() + } ) { emojis, instance -> InstanceEntity( instance = accountManager.activeAccount?.domain!!, @@ -291,7 +295,7 @@ class ComposeViewModel @Inject constructor( ): LiveData { val deletionObservable = if (isEditingScheduledToot) { - api.deleteScheduledStatus(scheduledTootId.toString()).toObservable().map { } + rxSingle { api.deleteScheduledStatus(scheduledTootId.toString()) }.toObservable().map { } } else { Observable.just(Unit) }.toLiveData() diff --git a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt index cc2bd776..4df7abc1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/login/LoginActivity.kt @@ -33,7 +33,6 @@ import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ActivityLoginBinding import com.keylesspalace.tusky.di.Injectable -import com.keylesspalace.tusky.entity.AppCredentials import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.getNonNullString import com.keylesspalace.tusky.util.rickRoll @@ -166,32 +165,33 @@ class LoginActivity : BaseActivity(), Injectable { setLoading(true) lifecycleScope.launch { - val credentials: AppCredentials = try { - mastodonApi.authenticateApp( - domain, getString(R.string.app_name), oauthRedirectUri, - OAUTH_SCOPES, getString(R.string.tusky_website) - ) - } catch (e: Exception) { - binding.loginButton.isEnabled = true - binding.domainTextInputLayout.error = - getString(R.string.error_failed_app_registration) - setLoading(false) - Log.e(TAG, Log.getStackTraceString(e)) - return@launch - } + mastodonApi.authenticateApp( + domain, getString(R.string.app_name), oauthRedirectUri, + OAUTH_SCOPES, getString(R.string.tusky_website) + ).fold( + { credentials -> + // Before we open browser page we save the data. + // Even if we don't open other apps user may go to password manager or somewhere else + // and we will need to pick up the process where we left off. + // Alternatively we could pass it all as part of the intent and receive it back + // but it is a bit of a workaround. + preferences.edit() + .putString(DOMAIN, domain) + .putString(CLIENT_ID, credentials.clientId) + .putString(CLIENT_SECRET, credentials.clientSecret) + .apply() - // Before we open browser page we save the data. - // Even if we don't open other apps user may go to password manager or somewhere else - // and we will need to pick up the process where we left off. - // Alternatively we could pass it all as part of the intent and receive it back - // but it is a bit of a workaround. - preferences.edit() - .putString(DOMAIN, domain) - .putString(CLIENT_ID, credentials.clientId) - .putString(CLIENT_SECRET, credentials.clientSecret) - .apply() - - redirectUserToAuthorizeAndLogin(domain, credentials.clientId) + redirectUserToAuthorizeAndLogin(domain, credentials.clientId) + }, + { e -> + binding.loginButton.isEnabled = true + binding.domainTextInputLayout.error = + getString(R.string.error_failed_app_registration) + setLoading(false) + Log.e(TAG, Log.getStackTraceString(e)) + return@launch + } + ) } } @@ -224,29 +224,28 @@ class LoginActivity : BaseActivity(), Injectable { setLoading(true) - val accessToken = try { - mastodonApi.fetchOAuthToken( - domain, clientId, clientSecret, oauthRedirectUri, code, - "authorization_code" - ) - } catch (e: Exception) { - setLoading(false) - binding.domainTextInputLayout.error = - getString(R.string.error_retrieving_oauth_token) - Log.e( - TAG, - "%s %s".format(getString(R.string.error_retrieving_oauth_token), e.message), - ) - return - } + mastodonApi.fetchOAuthToken( + domain, clientId, clientSecret, oauthRedirectUri, code, "authorization_code" + ).fold( + { accessToken -> + accountManager.addAccount(accessToken.accessToken, domain) - accountManager.addAccount(accessToken.accessToken, domain) - - val intent = Intent(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(intent) - finish() - overridePendingTransition(R.anim.explode, R.anim.explode) + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + finish() + overridePendingTransition(R.anim.explode, R.anim.explode) + }, + { e -> + setLoading(false) + binding.domainTextInputLayout.error = + getString(R.string.error_retrieving_oauth_token) + Log.e( + TAG, + "%s %s".format(getString(R.string.error_retrieving_oauth_token), e.message), + ) + } + ) } private fun setLoading(loadingState: Boolean) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt index cd3e5ac0..766ed44a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledStatusViewModel.kt @@ -25,7 +25,6 @@ import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.entity.ScheduledStatus import com.keylesspalace.tusky.network.MastodonApi import kotlinx.coroutines.launch -import kotlinx.coroutines.rx3.await import javax.inject.Inject class ScheduledStatusViewModel @Inject constructor( @@ -43,12 +42,14 @@ class ScheduledStatusViewModel @Inject constructor( fun deleteScheduledStatus(status: ScheduledStatus) { viewModelScope.launch { - try { - mastodonApi.deleteScheduledStatus(status.id).await() - pagingSourceFactory.remove(status) - } catch (throwable: Throwable) { - Log.w("ScheduledTootViewModel", "Error deleting scheduled status", throwable) - } + mastodonApi.deleteScheduledStatus(status.id).fold( + { + pagingSourceFactory.remove(status) + }, + { throwable -> + Log.w("ScheduledTootViewModel", "Error deleting scheduled status", throwable) + } + ) } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt index 7bda6ef7..d927c299 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -19,6 +19,7 @@ import android.content.Context import android.content.SharedPreferences import android.os.Build import android.text.Spanned +import at.connyduck.calladapter.kotlinresult.KotlinResultCallAdapterFactory import com.google.gson.Gson import com.google.gson.GsonBuilder import com.keylesspalace.tusky.BuildConfig @@ -111,6 +112,7 @@ class NetworkModule { .client(httpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) + .addCallAdapterFactory(KotlinResultCallAdapterFactory.create()) .build() } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 28d83eca..111cad56 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -80,7 +80,7 @@ interface MastodonApi { fun getCustomEmojis(): Single> @GET("api/v1/instance") - fun getInstance(): Single + suspend fun getInstance(): Result @GET("api/v1/filters") fun getFilters(): Single> @@ -249,12 +249,12 @@ interface MastodonApi { ): Single> @DELETE("api/v1/scheduled_statuses/{id}") - fun deleteScheduledStatus( + suspend fun deleteScheduledStatus( @Path("id") scheduledStatusId: String - ): Single + ): Result @GET("api/v1/accounts/verify_credentials") - fun accountVerifyCredentials(): Single + suspend fun accountVerifyCredentials(): Result @FormUrlEncoded @PATCH("api/v1/accounts/update_credentials") @@ -265,7 +265,7 @@ interface MastodonApi { @Multipart @PATCH("api/v1/accounts/update_credentials") - fun accountUpdateCredentials( + suspend fun accountUpdateCredentials( @Part(value = "display_name") displayName: RequestBody?, @Part(value = "note") note: RequestBody?, @Part(value = "locked") locked: RequestBody?, @@ -279,7 +279,7 @@ interface MastodonApi { @Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?, @Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?, @Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody? - ): Call + ): Result @GET("api/v1/accounts/search") fun searchAccounts( @@ -447,7 +447,7 @@ interface MastodonApi { @Field("redirect_uris") redirectUris: String, @Field("scopes") scopes: String, @Field("website") website: String - ): AppCredentials + ): Result @FormUrlEncoded @POST("oauth/token") @@ -458,7 +458,7 @@ interface MastodonApi { @Field("redirect_uri") redirectUri: String, @Field("code") code: String, @Field("grant_type") grantType: String - ): AccessToken + ): Result @FormUrlEncoded @POST("api/v1/lists") diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt index f3539f8d..17aa38c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt @@ -20,6 +20,7 @@ import android.net.Uri import androidx.core.net.toUri import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.ProfileEditedEvent import com.keylesspalace.tusky.entity.Account @@ -31,8 +32,7 @@ import com.keylesspalace.tusky.util.Loading import com.keylesspalace.tusky.util.Resource import com.keylesspalace.tusky.util.Success import com.keylesspalace.tusky.util.randomAlphanumericString -import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.kotlin.addTo +import kotlinx.coroutines.launch import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody @@ -40,9 +40,7 @@ import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONException import org.json.JSONObject -import retrofit2.Call -import retrofit2.Callback -import retrofit2.Response +import retrofit2.HttpException import java.io.File import javax.inject.Inject @@ -63,24 +61,20 @@ class EditProfileViewModel @Inject constructor( private var oldProfileData: Account? = null - private val disposables = CompositeDisposable() - - fun obtainProfile() { + fun obtainProfile() = viewModelScope.launch { if (profileData.value == null || profileData.value is Error) { profileData.postValue(Loading()) - mastodonApi.accountVerifyCredentials() - .subscribe( - { profile -> - oldProfileData = profile - profileData.postValue(Success(profile)) - }, - { - profileData.postValue(Error()) - } - ) - .addTo(disposables) + mastodonApi.accountVerifyCredentials().fold( + { profile -> + oldProfileData = profile + profileData.postValue(Success(profile)) + }, + { + profileData.postValue(Error()) + } + ) } } @@ -151,34 +145,34 @@ class EditProfileViewModel @Inject constructor( return } - mastodonApi.accountUpdateCredentials( - displayName, note, locked, avatar, header, - field1?.first, field1?.second, field2?.first, field2?.second, field3?.first, field3?.second, field4?.first, field4?.second - ).enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - val newProfileData = response.body() - if (!response.isSuccessful || newProfileData == null) { - val errorResponse = response.errorBody()?.string() - val errorMsg = if (!errorResponse.isNullOrBlank()) { - try { - JSONObject(errorResponse).optString("error", null) - } catch (e: JSONException) { + viewModelScope.launch { + mastodonApi.accountUpdateCredentials( + displayName, note, locked, avatar, header, + field1?.first, field1?.second, field2?.first, field2?.second, field3?.first, field3?.second, field4?.first, field4?.second + ).fold( + { newProfileData -> + saveData.postValue(Success()) + eventHub.dispatch(ProfileEditedEvent(newProfileData)) + }, + { throwable -> + if (throwable is HttpException) { + val errorResponse = throwable.response()?.errorBody()?.string() + val errorMsg = if (!errorResponse.isNullOrBlank()) { + try { + JSONObject(errorResponse).optString("error", "") + } catch (e: JSONException) { + null + } + } else { null } + saveData.postValue(Error(errorMessage = errorMsg)) } else { - null + saveData.postValue(Error()) } - saveData.postValue(Error(errorMessage = errorMsg)) - return } - saveData.postValue(Success()) - eventHub.dispatch(ProfileEditedEvent(newProfileData)) - } - - override fun onFailure(call: Call, t: Throwable) { - saveData.postValue(Error()) - } - }) + ) + } } // cache activity state for rotation change @@ -208,15 +202,11 @@ class EditProfileViewModel @Inject constructor( return File(application.cacheDir, filename) } - override fun onCleared() { - disposables.dispose() - } - - fun obtainInstance() { + fun obtainInstance() = viewModelScope.launch { if (instanceData.value == null || instanceData.value is Error) { instanceData.postValue(Loading()) - mastodonApi.getInstance().subscribe( + mastodonApi.getInstance().fold( { instance -> instanceData.postValue(Success(instance)) }, @@ -224,7 +214,6 @@ class EditProfileViewModel @Inject constructor( instanceData.postValue(Error()) } ) - .addTo(disposables) } } } diff --git a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt index ef6d2632..ff208823 100644 --- a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt @@ -16,15 +16,11 @@ package com.keylesspalace.tusky import android.text.SpannedString -import android.widget.LinearLayout import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import com.google.android.material.bottomsheet.BottomSheetBehavior import com.keylesspalace.tusky.entity.SearchResult import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.entity.TimelineAccount import com.keylesspalace.tusky.network.MastodonApi -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock import io.reactivex.rxjava3.android.plugins.RxAndroidPlugins import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.plugins.RxJavaPlugins @@ -39,8 +35,8 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mockito.eq -import org.mockito.Mockito.mock -import java.util.ArrayList +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import java.util.Date import java.util.concurrent.TimeUnit @@ -306,7 +302,7 @@ class BottomSheetActivityTest { init { mastodonApi = api @Suppress("UNCHECKED_CAST") - bottomSheet = mock(BottomSheetBehavior::class.java) as BottomSheetBehavior + bottomSheet = mock() } override fun openLink(url: String) { diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt index e7b3a1a9..dc4a412f 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt @@ -24,8 +24,6 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT import com.keylesspalace.tusky.components.compose.DEFAULT_MAXIMUM_URL_LENGTH -import com.keylesspalace.tusky.components.compose.MediaUploader -import com.keylesspalace.tusky.components.drafts.DraftHelper import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase @@ -37,18 +35,16 @@ import com.keylesspalace.tusky.entity.Instance import com.keylesspalace.tusky.entity.InstanceConfiguration import com.keylesspalace.tusky.entity.StatusConfiguration import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.service.ServiceClient -import com.nhaarman.mockitokotlin2.any import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleObserver import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.`when` -import org.mockito.Mockito.mock +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.robolectric.Robolectric import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config @@ -94,44 +90,47 @@ class ComposeActivityTest { val controller = Robolectric.buildActivity(ComposeActivity::class.java) activity = controller.get() - accountManagerMock = mock(AccountManager::class.java) - `when`(accountManagerMock.activeAccount).thenReturn(account) + accountManagerMock = mock { + on { activeAccount } doReturn account + } - apiMock = mock(MastodonApi::class.java) - `when`(apiMock.getCustomEmojis()).thenReturn(Single.just(emptyList())) - `when`(apiMock.getInstance()).thenReturn(object : Single() { - override fun subscribeActual(observer: SingleObserver) { - val instance = instanceResponseCallback?.invoke() + apiMock = mock { + on { getCustomEmojis() } doReturn Single.just(emptyList()) + onBlocking { getInstance() } doReturn instanceResponseCallback?.invoke().let { instance -> if (instance == null) { - observer.onError(Throwable()) + Result.failure(Throwable()) } else { - observer.onSuccess(instance) + Result.success(instance) } } - }) + } - val instanceDaoMock = mock(InstanceDao::class.java) - `when`(instanceDaoMock.loadMetadataForInstance(any())).thenReturn( - Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null)) - ) + val instanceDaoMock: InstanceDao = mock { + on { loadMetadataForInstance(any()) } doReturn + Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null)) + on { loadMetadataForInstance(any()) } doReturn + Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null, null, null, null)) + } - val dbMock = mock(AppDatabase::class.java) - `when`(dbMock.instanceDao()).thenReturn(instanceDaoMock) + val dbMock: AppDatabase = mock { + on { instanceDao() } doReturn instanceDaoMock + } val viewModel = ComposeViewModel( apiMock, accountManagerMock, - mock(MediaUploader::class.java), - mock(ServiceClient::class.java), - mock(DraftHelper::class.java), + mock(), + mock(), + mock(), dbMock ) activity.intent = Intent(activity, ComposeActivity::class.java).apply { putExtra(ComposeActivity.COMPOSE_OPTIONS_EXTRA, composeOptions) } - val viewModelFactoryMock = mock(ViewModelFactory::class.java) - `when`(viewModelFactoryMock.create(ComposeViewModel::class.java)).thenReturn(viewModel) + val viewModelFactoryMock: ViewModelFactory = mock { + on { create(ComposeViewModel::class.java) } doReturn viewModel + } activity.accountManager = accountManagerMock activity.viewModelFactory = viewModelFactoryMock @@ -490,7 +489,7 @@ class ComposeActivityTest { ) } - fun getCustomInstanceConfiguration(maximumStatusCharacters: Int? = null, charactersReservedPerUrl: Int? = null): InstanceConfiguration { + private fun getCustomInstanceConfiguration(maximumStatusCharacters: Int? = null, charactersReservedPerUrl: Int? = null): InstanceConfiguration { return InstanceConfiguration( statuses = StatusConfiguration( maxCharacters = maximumStatusCharacters, diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt index 03fff5ee..d5063943 100644 --- a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt @@ -8,12 +8,12 @@ import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.PollOption import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.FilterModel -import com.nhaarman.mockitokotlin2.mock import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.mock import org.robolectric.annotation.Config import java.util.ArrayList import java.util.Date @@ -22,7 +22,7 @@ import java.util.Date @RunWith(AndroidJUnit4::class) class FilterTest { - lateinit var filterModel: FilterModel + private lateinit var filterModel: FilterModel @Before fun setup() { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt index 462b0a4a..2778f8c2 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/CachedTimelineRemoteMediatorTest.kt @@ -17,9 +17,6 @@ import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.Converters import com.keylesspalace.tusky.db.TimelineStatusWithAccount -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock import io.reactivex.rxjava3.core.Single import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking @@ -31,6 +28,9 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config import retrofit2.HttpException diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt index 2e67c6fe..60dda419 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelinePagingSourceTest.kt @@ -3,11 +3,11 @@ package com.keylesspalace.tusky.components.timeline import androidx.paging.PagingSource import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelinePagingSource import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.mock import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock class NetworkTimelinePagingSourceTest { diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt index 74d0fe25..eabf744c 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/NetworkTimelineRemoteMediatorTest.kt @@ -12,11 +12,6 @@ import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineView import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.viewdata.StatusViewData -import com.nhaarman.mockitokotlin2.anyOrNull -import com.nhaarman.mockitokotlin2.doReturn -import com.nhaarman.mockitokotlin2.doThrow -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.verify import kotlinx.coroutines.runBlocking import okhttp3.Headers import okhttp3.ResponseBody.Companion.toResponseBody @@ -24,6 +19,11 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify import org.robolectric.annotation.Config import retrofit2.HttpException import retrofit2.Response @@ -331,7 +331,6 @@ class NetworkTimelineRemoteMediatorTest { mockStatusViewData("2"), mockStatusViewData("1"), ) - verify(timelineViewModel).nextKey = "0" assertTrue(result is RemoteMediator.MediatorResult.Success) assertEquals(false, (result as RemoteMediator.MediatorResult.Success).endOfPaginationReached)