Tusky-App-Android/app/src/test/java/com/keylesspalace/tusky/MainActivityTest.kt

146 lines
5.2 KiB
Kotlin
Raw Normal View History

package com.keylesspalace.tusky
import android.app.Activity
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Intent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.viewpager2.widget.ViewPager2
import androidx.work.testing.WorkManagerTestInitHelper
import at.connyduck.calladapter.networkresult.NetworkResult
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.components.accountlist.AccountListActivity
import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.db.AccountEntity
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.entity.TimelineAccount
import java.util.Date
Replace RxJava3 code with coroutines (#4290) This pull request removes the remaining RxJava code and replaces it with coroutine-equivalent implementations. - Remove all duplicate methods in `MastodonApi`: - Methods returning a RxJava `Single` have been replaced by suspending methods returning a `NetworkResult` in order to be consistent with the new code. - _sync_/_async_ method variants are replaced with the _async_ version only (suspending method), and `runBlocking{}` is used to make the async variant synchronous. - Create a custom coroutine-based implementation of `Single` for usage in Java code where launching a coroutine is not possible. This class can be deleted after remaining Java code has been converted to Kotlin. - `NotificationsFragment.java` can subscribe to `EventHub` events by calling the new lifecycle-aware `EventHub.subscribe()` method. This allows using the `SharedFlow` as single source of truth for all events. - Rx Autodispose is replaced by `lifecycleScope.launch()` which will automatically cancel the coroutine when the Fragment view/Activity is destroyed. - Background work is launched in the existing injectable `externalScope`, since using `GlobalScope` is discouraged. `externalScope` has been changed to be a `@Singleton` and to use the main dispatcher by default. - Transform `ShareShortcutHelper` to an injectable utility class so it can use the application `Context` and `externalScope` as provided dependencies to launch a background coroutine. - Implement a custom Glide extension method `RequestBuilder.submitAsync()` to do the same thing as `RequestBuilder.submit().get()` in a non-blocking way. This way there is no need to switch to a background dispatcher and block a background thread, and cancellation is supported out-of-the-box. - An utility method `Fragment.updateRelativeTimePeriodically()` has been added to remove duplicate logic in `TimelineFragment` and `NotificationsFragment`, and the logic is now implemented using a simple coroutine instead of `Observable.interval()`. Note that the periodic update now happens between onStart and onStop instead of between onResume and onPause, since the Fragment is not interactive but is still visible in the started state. - Rewrite `BottomSheetActivityTest` using coroutines tests. - Remove all RxJava library dependencies.
2024-02-29 15:28:48 +01:00
import kotlinx.coroutines.test.TestScope
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.robolectric.Robolectric
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.util.concurrent.BackgroundExecutor.runInBackground
import org.robolectric.annotation.Config
@Config(sdk = [28])
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val account = Account(
id = "1",
localUsername = "",
username = "",
displayName = "",
createdAt = Date(),
note = "",
url = "",
avatar = "",
header = ""
)
private val accountEntity = AccountEntity(
id = 1,
domain = "test.domain",
accessToken = "fakeToken",
clientId = "fakeId",
clientSecret = "fakeSecret",
isActive = true
)
@Before
fun setup() {
WorkManagerTestInitHelper.initializeTestWorkManager(context)
}
@After
fun teardown() {
WorkManagerTestInitHelper.closeWorkDatabase()
}
@Test
fun `clicking notification of type FOLLOW shows notification tab`() {
val intent = showNotification(Notification.Type.FOLLOW)
val activity = startMainActivity(intent)
val currentTab = activity.findViewById<ViewPager2>(R.id.viewPager).currentItem
val notificationTab = defaultTabs().indexOfFirst { it.id == NOTIFICATIONS }
assertEquals(currentTab, notificationTab)
}
@Test
fun `clicking notification of type FOLLOW_REQUEST shows follow requests`() {
val intent = showNotification(Notification.Type.FOLLOW_REQUEST)
val activity = startMainActivity(intent)
val nextActivity = shadowOf(activity).peekNextStartedActivity()
assertNotNull(nextActivity)
assertEquals(ComponentName(context, AccountListActivity::class.java.name), nextActivity.component)
assertEquals(AccountListActivity.Type.FOLLOW_REQUESTS, nextActivity.getSerializableExtra("type"))
}
private fun showNotification(type: Notification.Type): Intent {
val notificationManager = context.getSystemService(NotificationManager::class.java)
val shadowNotificationManager = shadowOf(notificationManager)
NotificationHelper.createNotificationChannelsForAccount(accountEntity, context)
runInBackground {
val notification = NotificationHelper.make(
context,
notificationManager,
Notification(
type = type,
id = "id",
account = TimelineAccount(
id = "1",
localUsername = "connyduck",
username = "connyduck@mastodon.example",
displayName = "Conny Duck",
note = "This is their bio",
url = "https://mastodon.example/@ConnyDuck",
avatar = "https://mastodon.example/system/accounts/avatars/000/150/486/original/ab27d7ddd18a10ea.jpg"
),
status = null,
report = null
),
accountEntity,
true
)
notificationManager.notify("id", 1, notification)
}
val notification = shadowNotificationManager.allNotifications.first()
return shadowOf(notification.contentIntent).savedIntent
}
private fun startMainActivity(intent: Intent): Activity {
val controller = Robolectric.buildActivity(MainActivity::class.java, intent)
val activity = controller.get()
activity.eventHub = EventHub()
activity.accountManager = mock {
on { activeAccount } doReturn accountEntity
}
activity.draftsAlert = mock {}
Replace RxJava3 code with coroutines (#4290) This pull request removes the remaining RxJava code and replaces it with coroutine-equivalent implementations. - Remove all duplicate methods in `MastodonApi`: - Methods returning a RxJava `Single` have been replaced by suspending methods returning a `NetworkResult` in order to be consistent with the new code. - _sync_/_async_ method variants are replaced with the _async_ version only (suspending method), and `runBlocking{}` is used to make the async variant synchronous. - Create a custom coroutine-based implementation of `Single` for usage in Java code where launching a coroutine is not possible. This class can be deleted after remaining Java code has been converted to Kotlin. - `NotificationsFragment.java` can subscribe to `EventHub` events by calling the new lifecycle-aware `EventHub.subscribe()` method. This allows using the `SharedFlow` as single source of truth for all events. - Rx Autodispose is replaced by `lifecycleScope.launch()` which will automatically cancel the coroutine when the Fragment view/Activity is destroyed. - Background work is launched in the existing injectable `externalScope`, since using `GlobalScope` is discouraged. `externalScope` has been changed to be a `@Singleton` and to use the main dispatcher by default. - Transform `ShareShortcutHelper` to an injectable utility class so it can use the application `Context` and `externalScope` as provided dependencies to launch a background coroutine. - Implement a custom Glide extension method `RequestBuilder.submitAsync()` to do the same thing as `RequestBuilder.submit().get()` in a non-blocking way. This way there is no need to switch to a background dispatcher and block a background thread, and cancellation is supported out-of-the-box. - An utility method `Fragment.updateRelativeTimePeriodically()` has been added to remove duplicate logic in `TimelineFragment` and `NotificationsFragment`, and the logic is now implemented using a simple coroutine instead of `Observable.interval()`. Note that the periodic update now happens between onStart and onStop instead of between onResume and onPause, since the Fragment is not interactive but is still visible in the started state. - Rewrite `BottomSheetActivityTest` using coroutines tests. - Remove all RxJava library dependencies.
2024-02-29 15:28:48 +01:00
activity.shareShortcutHelper = mock {}
activity.externalScope = TestScope()
activity.mastodonApi = mock {
onBlocking { accountVerifyCredentials() } doReturn NetworkResult.success(account)
onBlocking { listAnnouncements(false) } doReturn NetworkResult.success(emptyList())
}
controller.create().start()
return activity
}
}