From 68d199cbc03335a7ca92fd474f6d426f0207be6b Mon Sep 17 00:00:00 2001 From: Jack Curtis Date: Mon, 23 Dec 2024 10:30:12 -0500 Subject: [PATCH] testing for fully implemented search components, fix bug in debounce (#603) --- .../nearbyTransit/NearbyTransitPageTest.kt | 1 + .../android/search/SearchBarOverlayTest.kt | 99 +++++++++++++++++++ .../android/state/GetSearchResultTest.kt | 64 ++++++++++++ .../android/state/getSearchResults.kt | 9 ++ 4 files changed, 173 insertions(+) create mode 100644 androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/search/SearchBarOverlayTest.kt create mode 100644 androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/state/GetSearchResultTest.kt diff --git a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitPageTest.kt b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitPageTest.kt index 602ba09d5..1ee1534d9 100644 --- a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitPageTest.kt +++ b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitPageTest.kt @@ -296,6 +296,7 @@ class NearbyTransitPageTest : KoinTest { composeTestRule.waitUntilDoesNotExist(hasText("Loading...")) composeTestRule.onNodeWithText("Nearby Transit").assertIsDisplayed() + composeTestRule.onNodeWithText("Search by stop").assertIsDisplayed() composeTestRule.onNodeWithText("Green Line Long Name").assertExists() composeTestRule.onNodeWithText("Green Line Stop").assertExists() diff --git a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/search/SearchBarOverlayTest.kt b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/search/SearchBarOverlayTest.kt new file mode 100644 index 000000000..a40cce9f1 --- /dev/null +++ b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/search/SearchBarOverlayTest.kt @@ -0,0 +1,99 @@ +package com.mbta.tid.mbta_app.android.search + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.test.requestFocus +import com.mbta.tid.mbta_app.model.RouteType +import com.mbta.tid.mbta_app.model.SearchResults +import com.mbta.tid.mbta_app.model.StopResult +import com.mbta.tid.mbta_app.model.StopResultRoute +import com.mbta.tid.mbta_app.model.response.ApiResult +import com.mbta.tid.mbta_app.repositories.ISearchResultRepository +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import org.koin.compose.KoinContext +import org.koin.dsl.koinApplication +import org.koin.dsl.module +import org.koin.test.KoinTest + +@ExperimentalTestApi +@ExperimentalMaterial3Api +class SearchBarOverlayTest : KoinTest { + val koinApplication = koinApplication { + modules( + module { + single { + object : ISearchResultRepository { + override suspend fun getSearchResults( + query: String + ): ApiResult? { + return ApiResult.Ok( + SearchResults( + routes = emptyList(), + stops = + listOf( + StopResult( + id = "stopId", + rank = 2, + name = "stopName", + zone = "stopZone", + isStation = false, + routes = + listOf( + StopResultRoute( + type = RouteType.BUS, + icon = "routeIcon", + ) + ) + ) + ) + ) + ) + } + } + } + } + ) + } + + @get:Rule var composeTestRule = createComposeRule() + + @Test + fun testSearchBarOverlayBehavesCorrectly() = runTest { + val navigated = mutableStateOf(false) + + composeTestRule.setContent { + KoinContext(koinApplication.koin) { + val focusRequester = remember { FocusRequester() } + SearchBarOverlay( + onStopNavigation = { navigated.value = true }, + currentNavEntry = null, + inputFieldFocusRequester = focusRequester, + ) { + Text("Content") + } + } + } + + composeTestRule.onNodeWithText("Content").assertExists() + val searchNode = composeTestRule.onNodeWithText("Search by stop") + searchNode.assertExists() + searchNode.requestFocus() + composeTestRule.awaitIdle() + + searchNode.performTextInput("sto") + composeTestRule.waitUntilAtLeastOneExists(hasText("stopName")) + composeTestRule.onNodeWithText("stopName").performClick() + composeTestRule.waitUntil { navigated.value } + } +} diff --git a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/state/GetSearchResultTest.kt b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/state/GetSearchResultTest.kt new file mode 100644 index 000000000..336e77272 --- /dev/null +++ b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/state/GetSearchResultTest.kt @@ -0,0 +1,64 @@ +package com.mbta.tid.mbta_app.android.state + +import androidx.compose.ui.test.junit4.createComposeRule +import com.mbta.tid.mbta_app.model.RouteType +import com.mbta.tid.mbta_app.model.SearchResults +import com.mbta.tid.mbta_app.model.StopResult +import com.mbta.tid.mbta_app.model.StopResultRoute +import com.mbta.tid.mbta_app.model.response.ApiResult +import com.mbta.tid.mbta_app.repositories.ISearchResultRepository +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class GetSearchResultTest { + val searchResults = + SearchResults( + routes = emptyList(), + stops = + listOf( + StopResult( + id = "stopId", + rank = 2, + name = "stopName", + zone = "stopZone", + isStation = false, + routes = + listOf( + StopResultRoute( + type = RouteType.BUS, + icon = "routeIcon", + ) + ) + ) + ) + ) + + @get:Rule val composeTestRule = createComposeRule() + + @Test + fun testSearchResults() = runTest { + var actualSearchResultsViewModel: SearchResultsViewModel? = null + + composeTestRule.setContent { + actualSearchResultsViewModel = + getSearchResultsVm( + object : ISearchResultRepository { + override suspend fun getSearchResults( + query: String + ): ApiResult? { + return ApiResult.Ok(searchResults) + } + } + ) + } + + composeTestRule.waitUntil { actualSearchResultsViewModel != null } + + actualSearchResultsViewModel?.getSearchResults("query") + + composeTestRule.waitUntil { actualSearchResultsViewModel?.searchResults?.value != null } + + assert(actualSearchResultsViewModel?.searchResults?.value == searchResults) + } +} diff --git a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/state/getSearchResults.kt b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/state/getSearchResults.kt index c16d156a4..0ba24c5c2 100644 --- a/androidApp/src/main/java/com/mbta/tid/mbta_app/android/state/getSearchResults.kt +++ b/androidApp/src/main/java/com/mbta/tid/mbta_app/android/state/getSearchResults.kt @@ -36,6 +36,15 @@ class SearchResultsViewModel( null -> {} } } + } else if (lastClickTime == null) { + job = + CoroutineScope(Dispatchers.IO).launch { + when (val data = searchResultRepository.getSearchResults(query)) { + is ApiResult.Ok -> _searchResults.emit(data.data) + is ApiResult.Error -> _searchResults.emit(null) + null -> {} + } + } } lastClickTime = currentTime }