Dev: add simple automated UI test setup (#1027)

## Description
This PR is mostly a proof-of-concept for setting up automated UI
testing. The tests use Roboelectric so they can run without an emulator.
There is also a sample instrumented test for running on emulators or
devices for future tests.

It adds two basic automated UI tests for adding a server. These are sort
integration tests because they use the actual implementations for the
view model and services like `ServerRepository` and `JellyfinServerDao`.

Also, moves the bulk of `MainActivity`'s compose code into a function
for future tests.

### Related issues
N/A

### Testing
Local testing

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-03-03 13:46:46 -05:00 committed by GitHub
parent 8935067c5a
commit 0004701296
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 990 additions and 163 deletions

View file

@ -17,7 +17,7 @@ import javax.inject.Singleton
class SetupNavigationManager
@Inject
constructor() {
var backStack: MutableList<NavKey> = mutableStateListOf(SetupDestination.Loading)
var backStack: MutableList<SetupDestination> = mutableStateListOf(SetupDestination.Loading)
/**
* Go to the specified [SetupDestination]

View file

@ -18,6 +18,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -49,6 +50,14 @@ annotation class IoCoroutineScope
@Retention(AnnotationRetention.BINARY)
annotation class DefaultCoroutineScope
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DefaultDispatcher
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@ -62,12 +71,6 @@ object AppModule {
version = BuildConfig.VERSION_NAME,
)
@Provides
@Singleton
fun deviceInfo(
@ApplicationContext context: Context,
): DeviceInfo = androidDevice(context)
@StandardOkHttpClient
@Provides
@Singleton
@ -177,15 +180,29 @@ object AppModule {
}
}
@Provides
@Singleton
@IoDispatcher
fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO
@Provides
@Singleton
@IoCoroutineScope
fun ioCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun ioCoroutineScope(
@IoDispatcher dispatcher: CoroutineDispatcher,
): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
@Provides
@Singleton
@DefaultDispatcher
fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
@Provides
@Singleton
@DefaultCoroutineScope
fun defaultCoroutineScope(): CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
fun defaultCoroutineScope(
@DefaultDispatcher dispatcher: CoroutineDispatcher,
): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
@Provides
@Singleton
@ -199,3 +216,13 @@ object AppModule {
@StandardOkHttpClient okHttpClient: OkHttpClient,
) = SeerrApi(okHttpClient)
}
@Module
@InstallIn(SingletonComponent::class)
object DeviceModule {
@Provides
@Singleton
fun deviceInfo(
@ApplicationContext context: Context,
): DeviceInfo = androidDevice(context)
}