Skip to content

Commit

Permalink
#17 in progress
Browse files Browse the repository at this point in the history
- espresso tests for treasure editor screen
  • Loading branch information
mjureczko committed Dec 6, 2024
1 parent 6f0982b commit f041c66
Show file tree
Hide file tree
Showing 16 changed files with 267 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ abstract class AbstractUITest {
hiltRule.inject()
}

fun performClick(contentDescription: String) {
fun performTap(contentDescription: String) {
composeRule
.onNodeWithContentDescription(contentDescription)
.assertExists()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import dagger.hilt.android.testing.UninstallModules
import org.junit.After
import org.junit.Test
import pl.marianjureczko.poszukiwacz.model.Route
import pl.marianjureczko.poszukiwacz.model.RouteArranger
import pl.marianjureczko.poszukiwacz.screen.main.CONFIRM_ROUTE_NAME_BUTTON
import pl.marianjureczko.poszukiwacz.screen.main.DELETE_ROUTE_BUTTON
import pl.marianjureczko.poszukiwacz.screen.main.EDIT_ROUTE_BUTTON
Expand All @@ -25,19 +24,17 @@ import java.time.LocalDateTime
import java.time.ZoneOffset


//Prepared for Pixel 6a API 34
@UninstallModules(PortsModule::class)
@HiltAndroidTest
class MainScreenTest : UiTest() {

var route: Route = getRouteFromStorage()
var route: Route = TestPortsModule.getRouteFromStorage()

@After
fun restoreRoute() {
if(TestPortsModule.storage.routes.isEmpty()) {
val newRoute = RouteArranger.routeWithoutTipFiles()
TestPortsModule.storage.routes[newRoute.name] = newRoute
}
route = getRouteFromStorage()
TestPortsModule.assureRouteIsPresentInStorage()
route = TestPortsModule.getRouteFromStorage()
}

@Test
Expand All @@ -59,7 +56,7 @@ class MainScreenTest : UiTest() {
.performTextInput(routeName)
composeRule.waitForIdle()

performClick(CONFIRM_ROUTE_NAME_BUTTON)
performTap(CONFIRM_ROUTE_NAME_BUTTON)

getNode(TOPBAR_SCREEN_TITLE)
.assertTextEquals("${context.getString(R.string.route)} $routeName")
Expand All @@ -75,10 +72,9 @@ class MainScreenTest : UiTest() {
// // Your composable function that renders the screen
// MainScreen { }()
// }
composeRule.waitForIdle()

//when
performClick(EDIT_ROUTE_BUTTON + route.name)
goToTreasuresEditorScreen(route.name)

//then
val screenTitle: SemanticsNodeInteraction = getNode(TOPBAR_SCREEN_TITLE)
Expand All @@ -95,13 +91,11 @@ class MainScreenTest : UiTest() {
composeRule.waitForIdle()

//when
performClick(DELETE_ROUTE_BUTTON + route.name)
performClick(YES_BUTTON)
performTap(DELETE_ROUTE_BUTTON + route.name)
performTap(YES_BUTTON)

//then
getNode(EDIT_ROUTE_BUTTON + route.name)
.assertDoesNotExist()
}

private fun getRouteFromStorage() = TestPortsModule.storage.routes.values.first()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.mockito.Mockito.mock
import pl.marianjureczko.poszukiwacz.model.RouteArranger
import pl.marianjureczko.poszukiwacz.shared.port.CameraPort
import pl.marianjureczko.poszukiwacz.shared.port.LocationPort
import pl.marianjureczko.poszukiwacz.shared.port.StorageHelper
Expand All @@ -17,8 +18,8 @@ object TestPortsModule {

val storage = TestStoragePort()
val locationClient = mock<FusedLocationProviderClient>()
val location = mock<LocationPort>()
val camera = mock<CameraPort>()
val location = TestLocationPort()
val camera = TestCameraPort()

@Singleton
@Provides
Expand All @@ -43,4 +44,15 @@ object TestPortsModule {
fun storageHelper(): StorageHelper {
return storage
}



fun getRouteFromStorage() = storage.routes.values.first()

fun assureRouteIsPresentInStorage() {
if (storage.routes.isEmpty()) {
val newRoute = RouteArranger.routeWithoutTipFiles()
storage.routes[newRoute.name] = newRoute
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package pl.marianjureczko.poszukiwacz

import androidx.compose.ui.test.assertTextEquals
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import org.junit.Assert.assertEquals
import org.junit.Test
import pl.marianjureczko.poszukiwacz.model.Route
import pl.marianjureczko.poszukiwacz.screen.treasureseditor.ADD_TREASURE_BUTTON
import pl.marianjureczko.poszukiwacz.screen.treasureseditor.DO_PHOTO_TIP_BUTTON
import pl.marianjureczko.poszukiwacz.screen.treasureseditor.RECORD_SOUND_TIP_BUTTON
import pl.marianjureczko.poszukiwacz.screen.treasureseditor.STOP_RECORDING_BUTTON
import pl.marianjureczko.poszukiwacz.screen.treasureseditor.TREASURE_ITEM_TEXT
import pl.marianjureczko.poszukiwacz.shared.di.PortsModule
import pl.marianjureczko.poszukiwacz.ui.components.NO_BUTTON
import pl.marianjureczko.poszukiwacz.ui.components.YES_BUTTON

@UninstallModules(PortsModule::class)
@HiltAndroidTest
class TreasureEditorScreenTest : UiTest() {

var route: Route = TestPortsModule.getRouteFromStorage()
//
// @After
// fun restoreRoute() {
// TestPortsModule.assureRouteIsPresentInStorage()
// route = TestPortsModule.getRouteFromStorage()
// }

@Test
fun shouldShowAllTreasuresFromRoute_whenEditingTheRoute() {
//given
composeRule.waitForIdle()

//when
goToTreasuresEditorScreen(route.name)

//then
route.treasures.forEach { td ->
getNode("$TREASURE_ITEM_TEXT ${td.id}")
.assertTextEquals("[${td.id}] ${td.latitude} ${td.longitude}")
}
}

@Test
fun shouldAddTreasure_whenTapOnPlus() {
//given
val newTreasureId = route.nextId()
goToTreasuresEditorScreen(route.name)
val lat = 1.0
val lng = 2.0
TestPortsModule.location.updateLocation(lat, lng)
composeRule.waitForIdle()

//when
performTap(ADD_TREASURE_BUTTON)

//then
val treasureNode = getNode("$TREASURE_ITEM_TEXT $newTreasureId")
treasureNode.assertTextEquals("[$newTreasureId] $lat $lng")

val newTreasure = TestPortsModule.getRouteFromStorage().treasures.find { it.id == newTreasureId }!!
assertEquals(lat, newTreasure.latitude, 0.001)
assertEquals(lng, newTreasure.longitude, 0.001)
assertEquals(null, newTreasure.tipFileName)
assertEquals(null, newTreasure.photoFileName)
}

@Test
fun shouldRecordAudioTip_whenTapOnMicrophone() {
//given
composeRule.waitForIdle()
goToTreasuresEditorScreen(route.name)

//when
performTap("$RECORD_SOUND_TIP_BUTTON ${route.treasures[0].id}")

//then
Thread.sleep(100)
performTap(STOP_RECORDING_BUTTON)
TestPortsModule.storage.fileNotEmpty = true
performTap("$RECORD_SOUND_TIP_BUTTON ${route.treasures[0].id}")
getNode(YES_BUTTON).assertExists()
performTap(NO_BUTTON)
getNode(YES_BUTTON).assertDoesNotExist()
}

@Test
fun shouldSavePhotoTip_whenTapOnCamera() {
//given
TestPortsModule.storage.fileNotEmpty = false
composeRule.waitForIdle()
goToTreasuresEditorScreen(route.name)
TestPortsModule.camera.counter = 0

//when
performTap("$DO_PHOTO_TIP_BUTTON ${route.treasures[0].id}")

//then
TestPortsModule.storage.fileNotEmpty = true
assertEquals(1, TestPortsModule.camera.counter)
composeRule.waitForIdle()
performTap("$DO_PHOTO_TIP_BUTTON ${route.treasures[0].id}")
getNode(YES_BUTTON).assertExists()
performTap(NO_BUTTON)
getNode(YES_BUTTON).assertDoesNotExist()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package pl.marianjureczko.poszukiwacz

import pl.marianjureczko.poszukiwacz.screen.main.EDIT_ROUTE_BUTTON

open class UiTest: AbstractUITest() {

fun goToTreasuresEditorScreen(routeName: String) {
composeRule.waitForIdle()
performTap(EDIT_ROUTE_BUTTON + routeName)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
Expand All @@ -27,6 +29,8 @@ import pl.marianjureczko.poszukiwacz.R
import pl.marianjureczko.poszukiwacz.activity.treasureseditor.formatTime
import pl.marianjureczko.poszukiwacz.ui.components.AbstractDialog

const val STOP_RECORDING_BUTTON = "stop recording button"

@Composable
fun RecordingDialog(
fileName: String,
Expand Down Expand Up @@ -81,11 +85,16 @@ fun RecordingDialog(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
TextButton(onClick = {
isRecording.value = false
stopRecording(recorder, context)
onDismiss(closedAt.value)
}) {
TextButton(
onClick = {
isRecording.value = false
stopRecording(recorder, context)
onDismiss(closedAt.value)
},
modifier = Modifier.semantics {
contentDescription = STOP_RECORDING_BUTTON
}
) {
Text(text = context.getString(R.string.stop_recording))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
import pl.marianjureczko.poszukiwacz.R
import pl.marianjureczko.poszukiwacz.model.TreasureDescription
import pl.marianjureczko.poszukiwacz.permissions.RequirementsForDoingTipPhoto
import pl.marianjureczko.poszukiwacz.permissions.RequirementsForRecordingSound
import pl.marianjureczko.poszukiwacz.shared.GoToFacebook
Expand Down Expand Up @@ -44,18 +45,18 @@ fun TreasureEditorScreen(
},
content = { _ ->
TreasureEditorScreenBody(
state,
cameraPermission.status.isGranted,
microphonePermission?.status?.isGranted ?: false,
{ viewModel.hideOverridePhotoDialog() },
{ viewModel.showOverridePhotoDialog() },
{ viewModel.hideOverrideSoundTipDialog() },
{ viewModel.showOverrideSoundTipDialog() },
{ td -> viewModel.showSoundRecordingDialog(td) },
{ viewModel.hideSoundRecordingDialog() },
{ viewModel.addTreasure() },
{ id -> viewModel.removeTreasure(id) },
viewModel
state = state,
cameraPermissionGranted = cameraPermission.status.isGranted,
recordingPermissionGranted = microphonePermission?.status?.isGranted ?: false,
hideOverrideSoundTipDialog = { viewModel.hideOverrideSoundTipDialog() },
hideOverridePhotoDialog = { viewModel.hideOverridePhotoDialog() },
showOverridePhotoDialog = { td: TreasureDescription -> viewModel.showOverridePhotoDialog(td) },
showOverrideSoundTipDialog = { td: TreasureDescription -> viewModel.showOverrideSoundTipDialog(td) },
showSoundRecordingDialog = { td: TreasureDescription -> viewModel.showSoundRecordingDialog(td) },
hideSoundRecordingDialog = { viewModel.hideSoundRecordingDialog() },
addTreasure = { viewModel.addTreasure() },
removeTreasure = { id -> viewModel.removeTreasure(id) },
getDoTipPhoto = viewModel
)
}
)
Expand Down
Loading

0 comments on commit f041c66

Please sign in to comment.