Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI journey tests #31

Merged
merged 6 commits into from
Apr 26, 2024
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
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ android {

resourceConfigurations += setOf("en")

testInstrumentationRunner = "com.rwmobi.dazncodechallenge.ui.utils.CustomTestRunner"
testInstrumentationRunner = "com.rwmobi.dazncodechallenge.ui.test.CustomTestRunner"
vectorDrawables {
useSupportLibrary = true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2024. Ryan Wong
* https://github.com/ryanw-mobile
* Sponsored by RW MobiMedia UK Limited
*
*/

package com.rwmobi.dazncodechallenge.data.repository

import com.rwmobi.dazncodechallenge.domain.model.Event
import com.rwmobi.dazncodechallenge.domain.model.Schedule
import com.rwmobi.dazncodechallenge.domain.repository.Repository
import javax.inject.Inject

class FakeUITestRepository @Inject constructor() : Repository {
private var localEvents: List<Event> = emptyList()
private var localSchedules: List<Schedule> = emptyList()
private var remoteEvents: List<Event> = emptyList()
private var remoteSchedules: List<Schedule> = emptyList()
private var exception: Throwable? = null

override suspend fun getEvents(): Result<List<Event>> {
return exception?.let {
Result.failure(it)
} ?: Result.success(localEvents) // Note: we do not sort by date
}

override suspend fun getSchedule(): Result<List<Schedule>> {
return exception?.let {
Result.failure(it)
} ?: Result.success(localSchedules) // Note: we do not sort by date
}

override suspend fun refreshEvents(): Result<Unit> {
return exception?.let {
Result.failure(it)
} ?: run {
localEvents = remoteEvents
Result.success(Unit)
}
}

override suspend fun refreshSchedule(): Result<Unit> {
return exception?.let {
Result.failure(it)
} ?: run {
localSchedules = remoteSchedules
Result.success(Unit)
}
}

fun setExceptionForTest(exception: Throwable) {
this.exception = exception
}

fun setLocalEventsForTest(events: List<Event>) {
localEvents = events
}

fun setLocalSchedulesForTest(schedule: List<Schedule>) {
localSchedules = schedule
}

fun setRemoteEventsForTest(events: List<Event>) {
remoteEvents = events
}

fun setRemoteSchedulesForTest(schedule: List<Schedule>) {
remoteSchedules = schedule
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
/*
* Copyright (c) 2021. Ryan Wong (hello@ryanwong.co.uk)
* Copyright (c) 2024. Ryan Wong
* https://github.com/ryanw-mobile
* Sponsored by RW MobiMedia UK Limited
*
*/

package com.rwmobi.dazncodechallenge.di

// @Module
// @TestInstallIn(
// components = [SingletonComponent::class],
// replaces = [RepositoryModule::class],
// )
// object FakeBaseRepositoryModule {
// @Provides
// @Singleton
// fun provideFakeRepository(): Repository {
// synchronized(this) {
// return FakeRepository()
// }
// }
// }
import com.rwmobi.dazncodechallenge.data.repository.FakeUITestRepository
import com.rwmobi.dazncodechallenge.domain.repository.Repository
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import javax.inject.Singleton

@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [RepositoryModule::class],
)
object FakeBaseRepositoryModule {
@Provides
@Singleton
fun provideFakeRepository(): Repository {
return FakeUITestRepository()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2024. Ryan Wong
* https://github.com/ryanw-mobile
* Sponsored by RW MobiMedia UK Limited
*
*/

package com.rwmobi.dazncodechallenge.di

import android.content.Context
import coil.ImageLoader
import com.rwmobi.dazncodechallenge.ui.test.FakeImageLoader
import dagger.Module
import dagger.Provides
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import javax.inject.Singleton

@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [CoilModule::class],
)
object FakeCoilModule {
@Singleton
@Provides
fun provideFakeCoilImageLoader(@ApplicationContext context: Context): ImageLoader {
return FakeImageLoader(context = context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright (c) 2024. Ryan Wong
* https://github.com/ryanw-mobile
* Sponsored by RW MobiMedia UK Limited
*
*/

package com.rwmobi.dazncodechallenge.ui

import androidx.compose.ui.semantics.Role
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.printToLog
import com.rwmobi.dazncodechallenge.R
import com.rwmobi.dazncodechallenge.ui.navigation.AppNavItem
import com.rwmobi.dazncodechallenge.ui.test.DaznCodeChallengeTestRule
import com.rwmobi.dazncodechallenge.ui.test.withRole

internal class MainActivityTestRobot(
private val composeTestRule: DaznCodeChallengeTestRule,
) {
init {
printSemanticTree()
assertNavigationBarIsDisplayed()
assertNavigationItemsAreDisplayed()
}

fun printSemanticTree() {
composeTestRule.onRoot().printToLog("SemanticTree")
}

fun tapNavigationEvents() {
with(composeTestRule) {
onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(R.string.events))),
).performClick()
}
}

fun tapNavigationSchedule() {
with(composeTestRule) {
onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(R.string.schedule))),
).performClick()
}
}

fun waitUntilPlayerDismissed() {
with(composeTestRule) {
waitUntil(timeoutMillis = 2_000) {
!onNodeWithContentDescription(label = activity.getString(R.string.content_description_video_player)).isDisplayed()
}
}
}

fun assertNavigationBarIsDisplayed() {
with(composeTestRule) {
onNodeWithContentDescription(label = activity.getString(R.string.content_description_navigation_bar)).assertIsDisplayed()
}
}

fun assertNavigationBarIsNotDisplayed() {
with(composeTestRule) {
onNodeWithContentDescription(label = activity.getString(R.string.content_description_navigation_bar)).assertDoesNotExist()
}
}

fun assertNavigationRailIsDisplayed() {
with(composeTestRule) {
onNodeWithContentDescription(label = activity.getString(R.string.content_description_navigation_rail)).assertIsDisplayed()
}
}

fun assertNavigationItemsAreDisplayed() {
with(composeTestRule) {
for (navigationItem in AppNavItem.navBarItems) {
onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(navigationItem.titleResId))),
).assertIsDisplayed()
}
}
}

fun assertEventsTabIsSelected() {
with(composeTestRule) {
onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(R.string.events))),
).assertIsSelected()

onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(R.string.schedule))),
).assertIsNotSelected()
}
}

fun assertScheduleTabIsSelected() {
with(composeTestRule) {
onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(R.string.events))),
).assertIsNotSelected()

onNode(
matcher = withRole(Role.Tab).and(hasContentDescription(value = activity.getString(R.string.schedule))),
).assertIsSelected()
}
}

fun assertExoPlayerIsDisplayed() {
with(composeTestRule) {
onNodeWithContentDescription(label = activity.getString(R.string.content_description_video_player)).assertIsDisplayed()
}
}

fun assertExoPlayerIsNotDisplayed() {
with(composeTestRule) {
onNodeWithContentDescription(label = activity.getString(R.string.content_description_video_player)).assertDoesNotExist()
}
}
}
Loading