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

Enable wear app tests #580

Merged
merged 2 commits into from Apr 10, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Expand Up @@ -97,6 +97,7 @@ koin-workmanager = { module = "io.insert-koin:koin-androidx-workmanager", versio
koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin-android-compose" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin-core" }
koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin-core" }
koin-test-junit4 = { module = "io.insert-koin:koin-test-junit4", version.ref = "koin-core" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test" }
kmm-viewmodel = { module = "com.rickclephas.kmm:kmm-viewmodel-core", version.ref = "kmm-viewmodel" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
Expand Down
Expand Up @@ -4,6 +4,7 @@ import com.apollographql.apollo3.ApolloCall
import com.apollographql.apollo3.api.Error
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.CacheMissException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.runningFold
Expand All @@ -22,6 +23,8 @@ object ClientQuery {

if (next.data != null) {
QueryResult.Success(mapper(next.data!!))
} else if (apolloException is CacheMissException) {
previous
} else if (apolloException != null && previous is QueryResult.Loading) {
QueryResult.Error(apolloException)
} else {
Expand Down
1 change: 1 addition & 0 deletions wearApp/build.gradle.kts
Expand Up @@ -186,6 +186,7 @@ dependencies {
testImplementation(libs.robolectric)
testImplementation(libs.compose.ui.test.junit4)
testImplementation(libs.koin.test)
testImplementation(libs.koin.test.junit4)
testImplementation(libs.accompanist.testharness)
testImplementation(libs.snapshot.android)
testImplementation(libs.snapshot.jvm)
Expand Down
@@ -1,9 +1,12 @@
@file:OptIn(KoinInternalApi::class)

package dev.johnoreilly.confetti.wear

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavHostController
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
Expand All @@ -16,6 +19,10 @@ import dev.johnoreilly.confetti.wear.conferences.navigation.ConferencesDestinati
import dev.johnoreilly.confetti.wear.sessiondetails.navigation.SessionDetailsDestination
import dev.johnoreilly.confetti.wear.ui.ConfettiApp
import org.koin.android.ext.android.inject
import org.koin.compose.LocalKoinApplication
import org.koin.compose.LocalKoinScope
import org.koin.core.annotation.KoinInternalApi
import org.koin.mp.KoinPlatformTools

class MainActivity : ComponentActivity() {
lateinit var navController: NavHostController
Expand All @@ -27,14 +34,21 @@ class MainActivity : ComponentActivity() {
setContent {
navController = rememberSwipeDismissableNavController()

ConfettiApp(navController, intent)
// This shouldn't be needed, but allows robolectric tests to run successfully
// TODO remove once a solution is found or a fix in koin?
CompositionLocalProvider(
LocalKoinScope provides KoinPlatformTools.defaultContext().get().scopeRegistry.rootScope,
LocalKoinApplication provides KoinPlatformTools.defaultContext().get()
) {
ConfettiApp(navController, intent)

LaunchedEffect(Unit) {
navigateFromTileLaunch()
}
LaunchedEffect(Unit) {
navigateFromTileLaunch()
}

LaunchedEffect(Unit) {
logNavigationEvents()
LaunchedEffect(Unit) {
logNavigationEvents()
}
}
}
}
Expand Down
Expand Up @@ -11,7 +11,6 @@ import coil.request.SuccessResult
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.tools.coil.FakeImageLoader
import dev.johnoreilly.confetti.navigation.SessionDetailsKey
import dev.johnoreilly.confetti.utils.AndroidDateService
import dev.johnoreilly.confetti.utils.QueryResult
import dev.johnoreilly.confetti.wear.preview.TestFixtures.JohnUrl
import dev.johnoreilly.confetti.wear.preview.TestFixtures.MartinUrl
Expand Down
@@ -1,11 +1,8 @@
@file:OptIn(ExperimentalHorologistApi::class)
@file:Suppress("UnstableApiUsage")

package dev.johnoreilly.confetti.wear

import androidx.core.graphics.drawable.toDrawable
import androidx.wear.compose.material.Colors
import androidx.wear.compose.material.MaterialTheme
import coil.decode.DataSource
import coil.request.SuccessResult
import com.google.android.horologist.annotations.ExperimentalHorologistApi
Expand Down
@@ -1,28 +1,46 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package dev.johnoreilly.confetti.wear.app

import androidx.compose.ui.test.onNodeWithText
import dev.johnoreilly.confetti.wear.conferences.navigation.ConferencesDestination
import dev.johnoreilly.confetti.wear.preview.TestFixtures
import dev.johnoreilly.confetti.wear.startup.navigation.StartHomeDestination
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test

@Ignore("Disable while multiple app tests cause failures")
class AppTest : BaseAppTest() {

@Test
fun launchHome() {
fun launchHomeWithNoConference() {
val activity = rule.activity

val navController = activity.navController

assertEquals("conference_route/{conference}", navController.currentDestination?.route)
assertEquals(StartHomeDestination.route, navController.currentDestination?.route)

// We got navigated to the conferences screen to select a screen
rule.onNodeWithText("Conferences")
.assertExists()

assertEquals("", getConference())
assertEquals(ConferencesDestination.route, navController.currentDestination?.route)
}

private fun getConference(): String {
return runBlocking { appSettings.getConference() }
@Test
fun launchHomeWithConference() {
runBlocking {
appSettings.setConference(TestFixtures.kotlinConf2023.id)
}

val activity = rule.activity

val navController = activity.navController

assertEquals(StartHomeDestination.route, navController.currentDestination?.route)

rule.onNodeWithText("Conference Days")
.assertExists()

assertEquals(StartHomeDestination.route, navController.currentDestination?.route)
}
}
@@ -1,8 +1,5 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package dev.johnoreilly.confetti.wear.app

import android.os.Looper
import android.util.Log
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.work.Configuration
Expand All @@ -12,24 +9,21 @@ import com.apollographql.apollo3.cache.normalized.sql.ApolloInitializer
import dev.johnoreilly.confetti.AppSettings
import dev.johnoreilly.confetti.wear.MainActivity
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.runner.RunWith
import org.koin.core.context.stopKoin
import org.koin.test.KoinTest
import org.koin.test.AutoCloseKoinTest
import org.koin.test.get
import org.koin.test.inject
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode


@RunWith(RobolectricTestRunner::class)
@Config(application = KoinTestApp::class, sdk = [30])
@GraphicsMode(GraphicsMode.Mode.NATIVE)
abstract class BaseAppTest : KoinTest {
abstract class BaseAppTest : AutoCloseKoinTest() {
@get:Rule
val rule = createAndroidComposeRule(MainActivity::class.java)

Expand All @@ -45,11 +39,4 @@ abstract class BaseAppTest : KoinTest {

ApolloInitializer().create(get())
}

@After
fun after() {
Shadows.shadowOf(Looper.getMainLooper()).idle()

stopKoin()
}
}
@@ -1,55 +1,16 @@
package dev.johnoreilly.confetti.wear.app

import androidx.core.net.toUri
import dev.johnoreilly.confetti.wear.conferences.navigation.ConferencesDestination
import dev.johnoreilly.confetti.wear.preview.TestFixtures
import dev.johnoreilly.confetti.wear.startup.navigation.StartHomeDestination
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test

class NavigationTest : BaseAppTest() {
@Test
@Ignore("multiple fails")
fun launchWithoutConference() {
val activity = rule.activity

val navController = activity.navController

assertEquals(StartHomeDestination.route, navController.currentDestination?.route)

rule.waitUntil {
navController.currentDestination?.route == ConferencesDestination.route
}
}

@Test
// @Ignore("multiple fails")
fun launchWithConference() {
runBlocking {
appSettings.setConference(TestFixtures.kotlinConf2023.id)
}

val activity = rule.activity

val navController = activity.navController

assertEquals(StartHomeDestination.route, navController.currentDestination?.route)

rule.waitUntil {
navController.currentDestination?.route == StartHomeDestination.route
}
}

@Test
@Ignore("multiple fails")
fun deeplinks() {
val activity = rule.activity

val navController = activity.navController

navController.navigate("confetti://confetti/signInPrompt".toUri())
navController.navigate("confetti://confetti/signIn".toUri())
navController.navigate("confetti://confetti/signOut".toUri())
navController.navigate("confetti://confetti/settings".toUri())
Expand Down
@@ -1,17 +1,13 @@
@file:OptIn(ExperimentalCoroutinesApi::class)

package dev.johnoreilly.confetti.wear.app

import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.printToString
import dev.johnoreilly.confetti.wear.home.navigation.ConferenceHomeDestination
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assert.assertEquals
import org.junit.Ignore
import org.junit.Test
import org.robolectric.shadows.ShadowSettings

@Ignore("Disable while multiple app tests cause failures")
class OfflineTest : BaseAppTest() {

@Test
Expand All @@ -20,7 +16,7 @@ class OfflineTest : BaseAppTest() {

val navController = activity.navController

assertEquals("conference_route/{conference}", navController.currentDestination?.route)
assertEquals("start_route/{conference}", navController.currentDestination?.route)

ShadowSettings.setAirplaneMode(true)

Expand Down
Expand Up @@ -8,7 +8,6 @@ import androidx.work.impl.utils.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import com.apollographql.apollo3.cache.normalized.sql.ApolloInitializer
import dev.johnoreilly.confetti.ApolloClientCache
import dev.johnoreilly.confetti.ConfettiRepository
import dev.johnoreilly.confetti.GetConferencesQuery
import dev.johnoreilly.confetti.GetSessionsQuery
import dev.johnoreilly.confetti.fragment.SessionDetails
Expand All @@ -35,7 +34,6 @@ import org.robolectric.annotation.GraphicsMode
@Ignore("For manual runs")
class FetchDataTest : KoinTest {

val confettiRepository: ConfettiRepository by inject()
val apolloClientCache: ApolloClientCache by inject()

@Before
Expand All @@ -54,13 +52,11 @@ class FetchDataTest : KoinTest {
stopKoin()
}

fun GetConferencesQuery.Conference.inspect() {
"Conference(\"${id}\", listOf(${
days.joinToString(",") {
"LocalDate.parse(\"$it\")"
}
}), \"${name}\")"
}
fun GetConferencesQuery.Conference.inspect() = "Conference(\"${id}\", listOf(${
days.joinToString(",") {
"LocalDate.parse(\"$it\")"
}
}), \"${name}\")"

@Test
fun fetchConferences() = runTest {
Expand Down Expand Up @@ -113,7 +109,7 @@ class FetchDataTest : KoinTest {
sessionDescription = ""${""}"$sessionDescription""${""}",
language = ${language.quoted()},
speakers = listOf(
${speakers.joinToString(",\n") { it.inspect() } }
${speakers.joinToString(",\n") { it.inspect() }}
),
room = SessionDetails.Room(name = "${room?.name}"),
tags = listOf(${tags.joinToString(", ") { it }}),
Expand Down
Expand Up @@ -47,12 +47,12 @@ import com.quickbird.snapshot.Snapshotting
import com.quickbird.snapshot.fileSnapshotting
import com.quickbird.snapshot.snapshot
import dev.johnoreilly.confetti.screenshot.SnapshotTransformer
import dev.johnoreilly.confetti.screenshot.a11y.A11ySnapshotTransformer
import dev.johnoreilly.confetti.screenshot.bitmapWithTolerance
import dev.johnoreilly.confetti.screenshot.highlightWithRed
import dev.johnoreilly.confetti.wear.FixedTimeSource
import dev.johnoreilly.confetti.wear.preview.TestFixtures
import dev.johnoreilly.confetti.screenshot.a11y.A11ySnapshotTransformer
import dev.johnoreilly.confetti.wear.app.KoinTestApp
import dev.johnoreilly.confetti.wear.preview.TestFixtures
import dev.johnoreilly.confetti.wear.proto.Theme
import dev.johnoreilly.confetti.wear.ui.ConfettiTheme
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -184,9 +184,11 @@ abstract class ScreenshotTest : KoinTest {
colorDiffing = Diffing.highlightWithRed
),
snapshot = { node: SemanticsNodeInteraction ->
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888).apply {
view.draw(Canvas(this))
}
val bitmap =
Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
.apply {
view.draw(Canvas(this))
}
snapshotTransformer.transform(node, bitmap)
}
).fileSnapshotting
Expand Down