Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/flipcash/shared/invite/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ android {

dependencies {
implementation(project(":apps:flipcash:shared:shareable"))

testImplementation(kotlin("test"))
testImplementation(libs.bundles.unit.testing)
testImplementation(libs.robolectric)
testImplementation(testFixtures(project(":libs:coroutines")))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import com.flipcash.app.invite.InviteController
import com.flipcash.app.invite.internal.InternalInviteController
import com.flipcash.app.shareable.ShareSheetController
import com.flipcash.libs.coroutines.DispatcherProvider
import com.getcode.util.resources.ResourceHelper
import dagger.Module
import dagger.Provides
Expand All @@ -22,9 +23,11 @@ object InviteModule {
@ApplicationContext context: Context,
resources: ResourceHelper,
shareController: ShareSheetController,
dispatchers: DispatcherProvider,
): InviteController = InternalInviteController(
context = context,
resources = resources,
shareController = shareController,
dispatchers = dispatchers,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,37 @@ import com.flipcash.app.invite.InviteController
import com.flipcash.app.shareable.ShareSheetController
import com.flipcash.app.shareable.Shareable
import com.flipcash.shared.invite.R
import com.flipcash.libs.coroutines.DispatcherProvider
import com.getcode.util.resources.ResourceHelper
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

internal class InternalInviteController(
@param:ApplicationContext
private val context: Context,
private val resources: ResourceHelper,
private val shareController: ShareSheetController,
dispatchers: DispatcherProvider,
) : InviteController {

private val _channels = MutableStateFlow(resolveChannels())
// Hilt constructs this on the main thread when MainActivity is injected, and loading
// other apps' labels and icons reads their APKs, which ANRs on slow devices.
private val scope = CoroutineScope(SupervisorJob() + dispatchers.IO)

private val _channels = MutableStateFlow<List<InviteChannel>>(emptyList())
override val channels: StateFlow<List<InviteChannel>> = _channels

init {
refresh()
}

override fun refresh() {
_channels.value = resolveChannels()
scope.launch { _channels.value = resolveChannels() }
}

private fun resolveChannels(): List<InviteChannel> = buildList {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.flipcash.app.invite.internal

import android.content.Context
import android.content.pm.PackageManager
import com.flipcash.app.invite.InviteChannelType
import com.flipcash.libs.coroutines.TestDispatcherProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.test.assertEquals
import kotlin.test.assertTrue

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(RobolectricTestRunner::class)
class InternalInviteControllerTest {

private val packageManager: PackageManager = mockk {
every { queryIntentActivities(any(), any<Int>()) } returns emptyList()
every { getApplicationInfo(any<String>(), any<Int>()) } throws PackageManager.NameNotFoundException()
}
private val context: Context = mockk {
every { packageManager } returns this@InternalInviteControllerTest.packageManager
}

@Test
fun `construction does not query the package manager`() = runTest {
val dispatcher = StandardTestDispatcher(testScheduler)

val controller = controller(dispatcher)

assertTrue(controller.channels.value.isEmpty())
verify(exactly = 0) { context.packageManager }
}

@Test
fun `channels resolve on the io dispatcher`() = runTest {
val controller = controller(StandardTestDispatcher(testScheduler))

advanceUntilIdle()

assertEquals(
listOf(InviteChannelType.Sms, InviteChannelType.More),
controller.channels.value.map { it.type },
)
}

private fun controller(dispatcher: TestDispatcher) = InternalInviteController(
context = context,
resources = mockk(relaxed = true),
shareController = mockk(relaxed = true),
dispatchers = TestDispatcherProvider(dispatcher),
)
}
Loading