From 3baf1a0e544909bc4b8c54a426a64ff1124faa5b Mon Sep 17 00:00:00 2001 From: Emma Simon Date: Thu, 10 Oct 2024 14:05:22 -0400 Subject: [PATCH 1/5] build(deps): Bump Sentry without upgrading kotlin (#463) --- iosApp/Podfile.lock | 12 ++++++------ shared/shared.podspec | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index 482906be3..33d70cc4a 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - Sentry (8.26.0): - - Sentry/Core (= 8.26.0) - - Sentry/Core (8.26.0) + - Sentry (8.36.0): + - Sentry/Core (= 8.36.0) + - Sentry/Core (8.36.0) - shared (1.0): - - Sentry (~> 8.26.0) + - Sentry (~> 8.36.0) DEPENDENCIES: - shared (from `../shared/`) @@ -17,8 +17,8 @@ EXTERNAL SOURCES: :path: "../shared/" SPEC CHECKSUMS: - Sentry: 74a073c71c998117edb08f56f443c83570a31bed - shared: 3feb3c866b91cde11fd8e459d237a2cc925cd032 + Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 + shared: 19e2aa6dcc6627fddba6b8c32232f932ea8b8cc2 PODFILE CHECKSUM: 32413a7fdb16d40c9f57cf8da0c74d4507ff5f90 diff --git a/shared/shared.podspec b/shared/shared.podspec index 7d6bbb6d8..b045cbcc2 100644 --- a/shared/shared.podspec +++ b/shared/shared.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |spec| spec.vendored_frameworks = 'build/cocoapods/framework/shared.framework' spec.libraries = 'c++' spec.ios.deployment_target = '15.0' - spec.dependency 'Sentry', '~> 8.26.0' + spec.dependency 'Sentry', '~> 8.36.0' if !Dir.exist?('build/cocoapods/framework/shared.framework') || Dir.empty?('build/cocoapods/framework/shared.framework') raise " From c71d3db62deeeede6ae62c03837a74a7dc1fa77f Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Thu, 10 Oct 2024 13:14:46 -0600 Subject: [PATCH 2/5] fix(iOS): display Service Change in alert header (#462) --- iosApp/iosApp/Localizable.xcstrings | 3 +++ iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift | 1 + 2 files changed, 4 insertions(+) diff --git a/iosApp/iosApp/Localizable.xcstrings b/iosApp/iosApp/Localizable.xcstrings index 86ddb3d90..8459eb55c 100644 --- a/iosApp/iosApp/Localizable.xcstrings +++ b/iosApp/iosApp/Localizable.xcstrings @@ -350,6 +350,9 @@ }, "select location" : { + }, + "Service Change" : { + "comment" : "Possible alert effect" }, "Service ended" : { diff --git a/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift b/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift index a06bfc7ce..b6a4f3477 100644 --- a/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift +++ b/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift @@ -32,6 +32,7 @@ struct AlertDetails: View { switch alert.effect { case .detour: NSLocalizedString("Detour", comment: "Possible alert effect") case .dockClosure: NSLocalizedString("Dock Closure", comment: "Possible alert effect") + case .serviceChange: NSLocalizedString("Service Change", comment: "Possible alert effect") case .shuttle: NSLocalizedString("Shuttle", comment: "Possible alert effect") case .stationClosure: NSLocalizedString("Station Closure", comment: "Possible alert effect") case .stopClosure: NSLocalizedString("Stop Closure", comment: "Possible alert effect") From a297274ef7b3fada48721a049d2540241facfe90 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Fri, 11 Oct 2024 10:19:30 -0600 Subject: [PATCH 3/5] refactor: unify stop details with nearby transit (#465) * refactor: unify stop details with nearby transit * test(NearbyStaticData.withRealtimeInfo): unit tests for new fields --------- Co-authored-by: KaylaBrady <31781298+KaylaBrady@users.noreply.github.com> --- .../tid/mbta_app/model/NearbyStaticData.kt | 74 +++-- .../mbta/tid/mbta_app/model/PatternSorting.kt | 8 +- .../mbta/tid/mbta_app/model/PatternsByStop.kt | 9 +- .../mbta_app/model/StopDetailsDepartures.kt | 286 ++---------------- .../tid/mbta_app/model/NearbyResponseTest.kt | 185 ++++++++++- .../model/StopDetailsDeparturesTest.kt | 2 +- .../mbta_app/model/TemporaryTerminalTest.kt | 213 ++++++++----- 7 files changed, 397 insertions(+), 380 deletions(-) diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/NearbyStaticData.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/NearbyStaticData.kt index dcf4f66be..c173b0024 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/NearbyStaticData.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/NearbyStaticData.kt @@ -8,6 +8,7 @@ import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse import com.mbta.tid.mbta_app.model.response.ScheduleResponse import com.mbta.tid.mbta_app.utils.resolveParentId import io.github.dellisd.spatialk.geojson.Position +import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes import kotlinx.datetime.Instant @@ -449,19 +450,22 @@ data class NearbyStaticData(val data: List) { /** * Attaches [schedules] and [predictions] to the route, stop, and routePattern to which they apply. - * Removes non-typical route patterns which are not predicted within 90 minutes of [filterAtTime]. - * Sorts routes by subway first then nearest stop, stops by distance, and headsigns by route pattern - * sort order. + * Removes non-typical route patterns which are not happening either at all or between + * [filterAtTime] and [filterAtTime] + [hideNonTypicalPatternsBeyondNext]. Sorts routes by subway + * first then nearest stop, stops by distance, and headsigns by route pattern sort order. * * Runs static data and predictions through [TemporaryTerminalRewriter]. */ fun NearbyStaticData.withRealtimeInfo( globalData: GlobalResponse?, - sortByDistanceFrom: Position, + sortByDistanceFrom: Position?, schedules: ScheduleResponse?, predictions: PredictionsStreamDataResponse?, alerts: AlertsStreamDataResponse?, filterAtTime: Instant, + showAllPatternsWhileLoading: Boolean, + hideNonTypicalPatternsBeyondNext: Duration?, + filterCancellations: Boolean, pinnedRoutes: Set ): List { val activeRelevantAlerts = @@ -543,11 +547,26 @@ fun NearbyStaticData.withRealtimeInfo( ) .orEmpty() - val cutoffTime = filterAtTime.plus(90.minutes) + val cutoffTime = hideNonTypicalPatternsBeyondNext?.let { filterAtTime + it } val hasSchedulesTodayByPattern = NearbyStaticData.getSchedulesTodayByPattern(schedules) - val upcomingTripsMap: UpcomingTripsMap = - (upcomingTripsByRoutePatternAndStop + upcomingTripsByDirectionAndStop) + val upcomingTripsMap: UpcomingTripsMap? = + (upcomingTripsByRoutePatternAndStop + upcomingTripsByDirectionAndStop).takeUnless { + schedules == null && predictions == null + } + + fun UpcomingTripsMap.maybeFilterCancellations(isSubway: Boolean) = + if (filterCancellations) this.filterCancellations(isSubway) else this + + fun RealtimePatterns.shouldShow(): Boolean { + if (!allDataLoaded && showAllPatternsWhileLoading) return true + val isUpcoming = + when (cutoffTime) { + null -> this.isUpcoming() + else -> this.isUpcomingWithin(filterAtTime, cutoffTime) + } + return (isTypical() || isUpcoming) && !isArrivalOnly() + } fun List.filterEmptyAndSort(): List { return this.filterNot { it.patterns.isEmpty() } @@ -562,14 +581,13 @@ fun NearbyStaticData.withRealtimeInfo( StopsAssociated.WithRoute( transit.route, transit.patternsByStop - .map { + .map { stopPatterns -> PatternsByStop( - it, - upcomingTripsMap.filterCancellations( + stopPatterns, + upcomingTripsMap?.maybeFilterCancellations( transit.route.type.isSubway() ), - filterAtTime, - cutoffTime, + { it.shouldShow() }, activeRelevantAlerts, hasSchedulesTodayByPattern, allDataLoaded @@ -582,14 +600,13 @@ fun NearbyStaticData.withRealtimeInfo( transit.line, transit.routes, transit.patternsByStop - .map { + .map { stopPatterns -> PatternsByStop( - it, - upcomingTripsMap.filterCancellations( + stopPatterns, + upcomingTripsMap?.maybeFilterCancellations( transit.routes.min().type.isSubway() ), - filterAtTime, - cutoffTime, + { it.shouldShow() }, activeRelevantAlerts, hasSchedulesTodayByPattern, allDataLoaded @@ -604,6 +621,29 @@ fun NearbyStaticData.withRealtimeInfo( .sortedWith(PatternSorting.compareStopsAssociated(pinnedRoutes, sortByDistanceFrom)) } +fun NearbyStaticData.withRealtimeInfo( + globalData: GlobalResponse?, + sortByDistanceFrom: Position, + schedules: ScheduleResponse?, + predictions: PredictionsStreamDataResponse?, + alerts: AlertsStreamDataResponse?, + filterAtTime: Instant, + pinnedRoutes: Set +): List { + return this.withRealtimeInfo( + globalData, + sortByDistanceFrom, + schedules, + predictions, + alerts, + filterAtTime, + showAllPatternsWhileLoading = false, + hideNonTypicalPatternsBeyondNext = 90.minutes, + filterCancellations = true, + pinnedRoutes + ) +} + class NearbyStaticDataBuilder { val data = mutableListOf() diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternSorting.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternSorting.kt index d28b66053..e4b6bb165 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternSorting.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternSorting.kt @@ -72,13 +72,17 @@ object PatternSorting { fun compareStopsAssociated( pinnedRoutes: Set, - sortByDistanceFrom: Position + sortByDistanceFrom: Position? ): Comparator = compareBy( { pinnedRouteBucket(it.sortRoute(), pinnedRoutes) }, { patternServiceBucket(it.patternsByStop.first().patterns.first()) }, { subwayBucket(it.sortRoute()) }, - { it.distanceFrom(sortByDistanceFrom) }, + if (sortByDistanceFrom != null) { + { it.distanceFrom(sortByDistanceFrom) } + } else { + { 0 } + }, { it.sortRoute() }, ) } diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternsByStop.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternsByStop.kt index a71860f0f..9e56abc7b 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternsByStop.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/PatternsByStop.kt @@ -4,7 +4,6 @@ import com.mbta.tid.mbta_app.model.response.GlobalResponse import io.github.dellisd.spatialk.geojson.Position import io.github.dellisd.spatialk.turf.ExperimentalTurfApi import io.github.dellisd.spatialk.turf.distance -import kotlinx.datetime.Instant /** * @property patterns [RealtimePatterns]s serving the stop grouped by headsign or direction. The @@ -24,8 +23,7 @@ data class PatternsByStop( constructor( staticData: NearbyStaticData.StopPatterns, upcomingTripsMap: UpcomingTripsMap?, - filterTime: Instant, - cutoffTime: Instant, + patternsPredicate: (RealtimePatterns) -> Boolean, alerts: Collection?, hasSchedulesTodayByPattern: Map?, allDataLoaded: Boolean, @@ -62,10 +60,7 @@ data class PatternsByStop( ) } } - .filter { - (it.isTypical() || it.isUpcomingWithin(filterTime, cutoffTime)) && - !it.isArrivalOnly() - } + .filter(patternsPredicate) .sortedWith(PatternSorting.compareRealtimePatterns()), staticData.directions ) diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDepartures.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDepartures.kt index 1ac5412e7..2245d08b9 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDepartures.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDepartures.kt @@ -2,9 +2,9 @@ package com.mbta.tid.mbta_app.model import com.mbta.tid.mbta_app.model.response.AlertsStreamDataResponse import com.mbta.tid.mbta_app.model.response.GlobalResponse +import com.mbta.tid.mbta_app.model.response.NearbyResponse import com.mbta.tid.mbta_app.model.response.PredictionsStreamDataResponse import com.mbta.tid.mbta_app.model.response.ScheduleResponse -import com.mbta.tid.mbta_app.utils.resolveParentId import kotlinx.datetime.Instant data class StopDetailsDepartures(val routes: List) { @@ -18,73 +18,29 @@ data class StopDetailsDepartures(val routes: List) { filterAtTime: Instant ) : this( global.run { - val loading = schedules == null || predictions == null - val tripMapByHeadsign = tripMapByHeadsign(stops, schedules, predictions, filterAtTime) - val allStopIds = if (patternIdsByStop.containsKey(stop.id)) { - setOf(stop.id) + listOf(stop.id) } else { - stop.childStopIds.toSet() - } - - val patternsByRoute = - allStopIds - .flatMap { patternIdsByStop[it] ?: emptyList() } - .map { patternId -> routePatterns.getValue(patternId) } - .groupBy { routes.getValue(it.routeId) } - - val touchedLines: MutableSet = mutableSetOf() - - val activeRelevantAlerts = - alerts?.alerts?.values?.filter { - it.isActive(filterAtTime) && it.significance >= AlertSignificance.Minor + stop.childStopIds.filter { global.stops.containsKey(it) } } - val hasSchedulesTodayByPattern = NearbyStaticData.getSchedulesTodayByPattern(schedules) - - patternsByRoute - .mapNotNull { (route, routePatterns) -> - if (touchedLines.contains(route.lineId)) { - return@mapNotNull null - } else if (NearbyStaticData.groupedLines.contains(route.lineId)) { - val line = global.lines[route.lineId] ?: return@mapNotNull null - touchedLines.add(line.id) - return@mapNotNull patternsByStopForLine( - stop, - line, - patternsByRoute, - tripMapByHeadsignOrDirection( - stops, - tripMapByHeadsign, - schedules, - predictions, - filterAtTime - ), - allStopIds, - loading, - global, - activeRelevantAlerts, - hasSchedulesTodayByPattern - ) - } + val staticData = NearbyStaticData(global, NearbyResponse(allStopIds)) - return@mapNotNull patternsByStopForRoute( - stop, - route, - routePatterns, - tripMapByHeadsign, - allStopIds, - loading, - global, - activeRelevantAlerts, - hasSchedulesTodayByPattern - ) - } - .filterNot { it.patterns.isEmpty() } - .sortedWith( - PatternSorting.comparePatternsByStop(pinnedRoutes, sortByDistanceFrom = null) + staticData + .withRealtimeInfo( + global, + null, + schedules, + predictions, + alerts, + filterAtTime, + showAllPatternsWhileLoading = true, + hideNonTypicalPatternsBeyondNext = null, + filterCancellations = false, + pinnedRoutes ) + .flatMap { it.patternsByStop } } ) @@ -100,212 +56,4 @@ data class StopDetailsDepartures(val routes: List) { val direction = directions.first() return StopDetailsFilter(route.routeIdentifier, direction) } - - companion object { - - private fun tripMapByHeadsign( - stops: Map, - schedules: ScheduleResponse?, - predictions: PredictionsStreamDataResponse?, - filterAtTime: Instant - ): Map>? { - return UpcomingTrip.tripsMappedBy( - stops, - schedules, - predictions, - scheduleKey = { schedule, scheduleData -> - val trip = scheduleData.trips.getValue(schedule.tripId) - RealtimePatterns.UpcomingTripKey.ByRoutePattern( - schedule.routeId, - trip.routePatternId, - stops.resolveParentId(schedule.stopId) - ) - }, - predictionKey = { prediction, streamData -> - val trip = streamData.trips.getValue(prediction.tripId) - RealtimePatterns.UpcomingTripKey.ByRoutePattern( - prediction.routeId, - trip.routePatternId, - stops.resolveParentId(prediction.stopId) - ) - }, - filterAtTime - ) - } - - private fun tripMapByHeadsignOrDirection( - stops: Map, - tripMapByRoutePattern: - Map>?, - schedules: ScheduleResponse?, - predictions: PredictionsStreamDataResponse?, - filterAtTime: Instant - ): Map>? { - val tripMapByDirection = - UpcomingTrip.tripsMappedBy( - stops, - schedules, - predictions, - scheduleKey = { schedule, scheduleData -> - val trip = scheduleData.trips.getValue(schedule.tripId) - RealtimePatterns.UpcomingTripKey.ByDirection( - schedule.routeId, - trip.directionId, - stops.resolveParentId(schedule.stopId) - ) - }, - predictionKey = { prediction, streamData -> - val trip = streamData.trips.getValue(prediction.tripId) - RealtimePatterns.UpcomingTripKey.ByDirection( - prediction.routeId, - trip.directionId, - stops.resolveParentId(prediction.stopId) - ) - }, - filterAtTime - ) - - return if (tripMapByRoutePattern != null || tripMapByDirection != null) { - (tripMapByRoutePattern ?: emptyMap()) + (tripMapByDirection ?: emptyMap()) - } else { - null - } - } - - private fun patternsByStopForRoute( - stop: Stop, - route: Route, - routePatterns: List, - tripMap: Map>?, - allStopIds: Set, - loading: Boolean, - global: GlobalResponse, - alerts: Collection?, - hasSchedulesTodayByPattern: Map?, - ): PatternsByStop { - global.run { - val allDataLoaded = !loading - val patternsByHeadsign = - routePatterns.groupBy { trips.getValue(it.representativeTripId).headsign } - return PatternsByStop( - listOf(route), - null, - stop, - patternsByHeadsign - .map { (headsign, patterns) -> - val stopIdsOnPatterns = - NearbyStaticData.filterStopsByPatterns(patterns, global, allStopIds) - val upcomingTrips = - if (tripMap != null) { - patterns - .mapNotNull { pattern -> - tripMap[ - RealtimePatterns.UpcomingTripKey.ByRoutePattern( - route.id, - pattern.id, - stop.id - )] - } - .flatten() - .sorted() - } else { - null - } - ?.filterCancellations(route.type.isSubway()) - RealtimePatterns.ByHeadsign( - route, - headsign, - null, - patterns, - upcomingTrips, - alerts?.let { - RealtimePatterns.applicableAlerts( - routes = listOf(route), - stopIds = stopIdsOnPatterns, - alerts = alerts - ) - }, - RealtimePatterns.hasSchedulesToday( - hasSchedulesTodayByPattern, - patterns - ), - allDataLoaded - ) - } - .filter { - loading || ((it.isTypical() || it.isUpcoming()) && !it.isArrivalOnly()) - } - .sortedWith(PatternSorting.compareRealtimePatterns()), - Direction.getDirections(global, stop, route, routePatterns) - ) - } - } - - private fun patternsByStopForLine( - stop: Stop, - line: Line, - patternsByRoute: Map>, - tripMap: Map>?, - allStopIds: Set, - loading: Boolean, - global: GlobalResponse, - alerts: Collection?, - hasSchedulesTodayByPattern: Map?, - ): PatternsByStop { - global.run { - val allDataLoaded = !loading - val groupedPatternsByRoute = patternsByRoute.filter { it.key.lineId == line.id } - - val staticPatterns = - NearbyStaticData.buildStopPatternsForLine( - stop, - groupedPatternsByRoute, - line, - allStopIds, - global - ) - - val realtimePatterns = - staticPatterns.patterns - .map { - when (it) { - is NearbyStaticData.StaticPatterns.ByHeadsign -> - RealtimePatterns.ByHeadsign( - it, - tripMap, - stop.id, - alerts, - hasSchedulesTodayByPattern, - allDataLoaded - ) - is NearbyStaticData.StaticPatterns.ByDirection -> - RealtimePatterns.ByDirection( - it, - tripMap, - stop.id, - alerts, - hasSchedulesTodayByPattern, - allDataLoaded - ) - } - } - .filter { - loading || ((it.isTypical() || it.isUpcoming()) && !it.isArrivalOnly()) - } - .sortedWith(PatternSorting.compareRealtimePatterns()) - - return PatternsByStop( - groupedPatternsByRoute.map { it.key }, - line, - stop, - realtimePatterns, - Direction.getDirectionsForLine( - global, - stop, - realtimePatterns.flatMap { it.patterns } - ) - ) - } - } - } } diff --git a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/NearbyResponseTest.kt b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/NearbyResponseTest.kt index 7ef3784ed..956ac7c07 100644 --- a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/NearbyResponseTest.kt +++ b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/NearbyResponseTest.kt @@ -1221,7 +1221,7 @@ class NearbyResponseTest { "Typical Out", null, listOf(typicalOutbound), - emptyList(), + null, allDataLoaded = false ), RealtimePatterns.ByHeadsign( @@ -1229,7 +1229,7 @@ class NearbyResponseTest { "Typical In", null, listOf(typicalInbound), - emptyList(), + null, allDataLoaded = false ), ) @@ -1249,6 +1249,187 @@ class NearbyResponseTest { ) } + @Test + fun `withRealtimeInfo hideNonTypicalPatternsBeyondNext when null doesn't filter`() { + val objects = ObjectCollectionBuilder() + + val stop1 = objects.stop() + + val route1 = objects.route() + + // should be included because typical and has prediction + val typicalOutbound = + objects.routePattern(route1) { + directionId = 0 + sortOrder = 1 + typicality = RoutePattern.Typicality.Typical + representativeTrip { headsign = "Typical Out" } + } + + // should be included because hideNonTypicalPatternsBeyondNext is null + val deviationInbound = + objects.routePattern(route1) { + directionId = 1 + sortOrder = 4 + typicality = RoutePattern.Typicality.Deviation + representativeTrip { headsign = "Deviation In" } + } + + val staticData = + NearbyStaticData.build { + route(route1) { + stop(stop1) { + headsign("Typical Out", listOf(typicalOutbound)) + headsign("Deviation In", listOf(deviationInbound)) + } + } + } + + val time = Instant.parse("2024-02-22T12:08:19-05:00") + + val typicalOutboundPrediction = + objects.prediction { + departureTime = time + routeId = route1.id + stopId = stop1.id + tripId = typicalOutbound.representativeTripId + } + + val deviationInboundPrediction = + objects.prediction { + departureTime = time + 95.minutes + routeId = route1.id + stopId = stop1.id + tripId = deviationInbound.representativeTripId + } + + assertEquals( + listOf( + StopsAssociated.WithRoute( + route1, + listOf( + PatternsByStop( + route1, + stop1, + listOf( + RealtimePatterns.ByHeadsign( + route1, + "Typical Out", + null, + listOf(typicalOutbound), + listOf(objects.upcomingTrip(typicalOutboundPrediction)), + allDataLoaded = false + ), + RealtimePatterns.ByHeadsign( + route1, + "Deviation In", + null, + listOf(deviationInbound), + listOf(objects.upcomingTrip(deviationInboundPrediction)), + allDataLoaded = false + ), + ) + ) + ) + ) + ), + staticData.withRealtimeInfo( + globalData = GlobalResponse(objects), + sortByDistanceFrom = stop1.position, + schedules = null, + predictions = PredictionsStreamDataResponse(objects), + alerts = null, + filterAtTime = time, + showAllPatternsWhileLoading = false, + hideNonTypicalPatternsBeyondNext = null, + filterCancellations = false, + pinnedRoutes = setOf(), + ) + ) + } + + @Test + fun `withRealtimeInfo includes cancellations when filterCancellations false`() { + val objects = ObjectCollectionBuilder() + val stop1 = objects.stop() + val route1 = objects.route() + + // should be included because typical and has cancelled prediction + val typicalOutbound = + objects.routePattern(route1) { + directionId = 0 + sortOrder = 1 + typicality = RoutePattern.Typicality.Typical + representativeTrip { headsign = "Typical Out" } + } + + val staticData = + NearbyStaticData.build { + route(route1) { stop(stop1) { headsign("Typical Out", listOf(typicalOutbound)) } } + } + + val time = Instant.parse("2024-02-22T12:08:19-05:00") + + val typicalOutboundSchedule = + objects.schedule { + routeId = route1.id + tripId = typicalOutbound.representativeTripId + stopId = stop1.id + arrivalTime = time + departureTime = time + } + + val typicalOutboundPrediction = + objects.prediction { + departureTime = null + routeId = route1.id + stopId = stop1.id + tripId = typicalOutbound.representativeTripId + scheduleRelationship = Prediction.ScheduleRelationship.Cancelled + } + + assertEquals( + listOf( + StopsAssociated.WithRoute( + route1, + listOf( + PatternsByStop( + route1, + stop1, + listOf( + RealtimePatterns.ByHeadsign( + route1, + "Typical Out", + null, + listOf(typicalOutbound), + listOf( + objects.upcomingTrip( + typicalOutboundSchedule, + typicalOutboundPrediction + ) + ), + allDataLoaded = true + ), + ) + ) + ) + ) + ), + staticData.withRealtimeInfo( + globalData = GlobalResponse(objects), + sortByDistanceFrom = stop1.position, + schedules = ScheduleResponse(objects), + predictions = PredictionsStreamDataResponse(objects), + alerts = null, + filterAtTime = time, + showAllPatternsWhileLoading = false, + hideNonTypicalPatternsBeyondNext = null, + filterCancellations = false, + pinnedRoutes = setOf(), + ) + ) + } + @Test fun `withRealtimeInfo sorts subway first then by distance`() { val objects = ObjectCollectionBuilder() diff --git a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDeparturesTest.kt b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDeparturesTest.kt index 3b3ceccc8..0392f08ae 100644 --- a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDeparturesTest.kt +++ b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/StopDetailsDeparturesTest.kt @@ -265,7 +265,7 @@ class StopDetailsDeparturesTest { ) ), ), - listOf(Direction("West", null, 0), directionEast) + listOf(directionWest, directionEast) ) ) ), diff --git a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/TemporaryTerminalTest.kt b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/TemporaryTerminalTest.kt index fb3eea5a0..a66c98a40 100644 --- a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/TemporaryTerminalTest.kt +++ b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/model/TemporaryTerminalTest.kt @@ -309,35 +309,36 @@ class TemporaryTerminalTest { @Test fun `shows only temporary terminals when outside shuttle`() { - assertEquals( + val expected = expected( - harvard.station, - RealtimePatterns.ByHeadsign( - red, - "Kendall/MIT", - null, - listOf(redAlewifeBraintree, redAlewifeAshmont, redAlewifeKendall), - listOf( - UpcomingTrip(tripAlewifeKendall, scheduleAlewifeKendall), - UpcomingTrip(tripAlewifeBraintree1, predictionAlewifeBraintree1), - UpcomingTrip(tripAlewifeBraintree2, predictionAlewifeBraintree2), - UpcomingTrip(tripAlewifeBraintree3, predictionAlewifeBraintree3) - ), - emptyList() + harvard.station, + RealtimePatterns.ByHeadsign( + red, + "Kendall/MIT", + null, + listOf(redAlewifeBraintree, redAlewifeAshmont, redAlewifeKendall), + listOf( + UpcomingTrip(tripAlewifeKendall, scheduleAlewifeKendall), + UpcomingTrip(tripAlewifeBraintree1, predictionAlewifeBraintree1), + UpcomingTrip(tripAlewifeBraintree2, predictionAlewifeBraintree2), + UpcomingTrip(tripAlewifeBraintree3, predictionAlewifeBraintree3) + ), + emptyList() + ), + RealtimePatterns.ByHeadsign( + red, + "Alewife", + null, + listOf(redBraintreeAlewife, redAshmontAlewife, redKendallAlewife), + listOf( + UpcomingTrip(tripBraintreeAlewife1, predictionBraintreeAlewife1), + UpcomingTrip(tripKendallAlewife, scheduleKendallAlewife) ), - RealtimePatterns.ByHeadsign( - red, - "Alewife", - null, - listOf(redBraintreeAlewife, redAshmontAlewife, redKendallAlewife), - listOf( - UpcomingTrip(tripBraintreeAlewife1, predictionBraintreeAlewife1), - UpcomingTrip(tripKendallAlewife, scheduleKendallAlewife) - ), - emptyList() - ) + emptyList() ) - .condensed(), + ) + assertEquals( + expected.condensed(), NearbyStaticData(globalData, nearbyOutsideShuttle) .withRealtimeInfo( globalData, @@ -350,39 +351,54 @@ class TemporaryTerminalTest { ) .condensed() ) + assertEquals( + expected.condensed(), + StopDetailsDepartures( + harvard.station, + globalData, + schedules, + predictions, + alerts, + emptySet(), + now + ) + .asNearby() + .condensed() + ) } @Test fun `shows only regular terminals when inside shuttle`() { - assertEquals( + val expected = expected( - parkStreet.station, - RealtimePatterns.ByHeadsign( - red, - "Braintree", - null, - listOf(redAlewifeBraintree), - emptyList(), - listOf(alert) - ), - RealtimePatterns.ByHeadsign( - red, - "Ashmont", - null, - listOf(redAlewifeAshmont), - emptyList(), - listOf(alert) - ), - RealtimePatterns.ByHeadsign( - red, - "Alewife", - null, - listOf(redBraintreeAlewife, redAshmontAlewife), - emptyList(), - listOf(alert) - ) + parkStreet.station, + RealtimePatterns.ByHeadsign( + red, + "Braintree", + null, + listOf(redAlewifeBraintree), + emptyList(), + listOf(alert) + ), + RealtimePatterns.ByHeadsign( + red, + "Ashmont", + null, + listOf(redAlewifeAshmont), + emptyList(), + listOf(alert) + ), + RealtimePatterns.ByHeadsign( + red, + "Alewife", + null, + listOf(redBraintreeAlewife, redAshmontAlewife), + emptyList(), + listOf(alert) ) - .condensed(), + ) + assertEquals( + expected.condensed(), NearbyStaticData(globalData, nearbyInsideShuttle) .withRealtimeInfo( globalData, @@ -395,40 +411,55 @@ class TemporaryTerminalTest { ) .condensed() ) + assertEquals( + expected.condensed(), + StopDetailsDepartures( + parkStreet.station, + globalData, + schedules, + predictions, + alerts, + emptySet(), + now + ) + .asNearby() + .condensed() + ) } @Test fun `shows correct set of terminals when at boundary of shuttle`() { - assertEquals( + val expected = expected( - jfkUmass.station, - RealtimePatterns.ByHeadsign( - red, - "Braintree", - null, - listOf(redAlewifeBraintree, redJfkBraintree), - listOf(UpcomingTrip(tripJfkBraintree, scheduleJfkBraintree)), - emptyList() - ), - RealtimePatterns.ByHeadsign( - red, - "Ashmont", - null, - listOf(redAlewifeAshmont, redJfkAshmont), - listOf(UpcomingTrip(tripJfkAshmont, scheduleJfkAshmont)), - emptyList() - ), - RealtimePatterns.ByHeadsign( - red, - "Alewife", - null, - listOf(redBraintreeAlewife, redAshmontAlewife), - emptyList(), - listOf(alert) - ), - // JFK/UMass filtered out because arrival only - ) - .condensed(), + jfkUmass.station, + RealtimePatterns.ByHeadsign( + red, + "Braintree", + null, + listOf(redAlewifeBraintree, redJfkBraintree), + listOf(UpcomingTrip(tripJfkBraintree, scheduleJfkBraintree)), + emptyList() + ), + RealtimePatterns.ByHeadsign( + red, + "Ashmont", + null, + listOf(redAlewifeAshmont, redJfkAshmont), + listOf(UpcomingTrip(tripJfkAshmont, scheduleJfkAshmont)), + emptyList() + ), + RealtimePatterns.ByHeadsign( + red, + "Alewife", + null, + listOf(redBraintreeAlewife, redAshmontAlewife), + emptyList(), + listOf(alert) + ), + // JFK/UMass filtered out because arrival only + ) + assertEquals( + expected.condensed(), NearbyStaticData(globalData, nearbyAtShuttleEdge) .withRealtimeInfo( globalData, @@ -441,8 +472,26 @@ class TemporaryTerminalTest { ) .condensed() ) + assertEquals( + expected.condensed(), + StopDetailsDepartures( + jfkUmass.station, + globalData, + schedules, + predictions, + alerts, + emptySet(), + now + ) + .asNearby() + .condensed() + ) } + // for easier assertions about stop details + fun StopDetailsDepartures.asNearby(): List = + listOf(StopsAssociated.WithRoute(red, this.routes)) + // for more legible diffs fun List.condensed(): String { fun Instant.condensed() = toLocalDateTime(TimeZone.of("America/New_York")).time.toString() From 32dd8804f29d615e87650550323a8a3f961ef569 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Tue, 15 Oct 2024 08:22:08 -0600 Subject: [PATCH 4/5] feat(iOS): forget predictions if backgrounded for over ten minutes (#461) * feat: forget predictions if backgrounded for over ten minutes * fix the tests * fix a test that was working locally --------- Co-authored-by: Kayla Brady <31781298+KaylaBrady@users.noreply.github.com> --- .../nearbyTransit/NearbyTransitPageTest.kt | 2 ++ .../nearbyTransit/NearbyTransitViewTest.kt | 2 ++ .../stopDetails/StopDetailsViewTest.kt | 2 ++ .../util/SubscribeToPredictionsTest.kt | 2 ++ .../NearbyTransit/NearbyTransitView.swift | 9 ++++- .../Pages/StopDetails/StopDetailsPage.swift | 19 ++++++++--- .../Pages/TripDetails/TripDetailsPage.swift | 21 ++++++++---- .../TripDetails/TripDetailsPageTests.swift | 4 +++ .../mbta_app/endToEnd/EndToEndRepositories.kt | 4 +++ .../repositories/PredictionsRepository.kt | 9 +++++ .../repositories/TripPredictionsRepository.kt | 9 +++++ .../PredictionsRepositoryTests.kt | 34 +++++++++++++++++++ 12 files changed, 105 insertions(+), 12 deletions(-) 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 043b6827b..13c1cec63 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 @@ -195,6 +195,8 @@ class NearbyTransitPageTest : KoinTest { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { /* no-op */ } diff --git a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitViewTest.kt b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitViewTest.kt index 2a5ed4700..199fa233b 100644 --- a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitViewTest.kt +++ b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/nearbyTransit/NearbyTransitViewTest.kt @@ -189,6 +189,8 @@ class NearbyTransitViewTest : KoinTest { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { /* no-op */ } diff --git a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/stopDetails/StopDetailsViewTest.kt b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/stopDetails/StopDetailsViewTest.kt index 0889a5f13..3dc169b64 100644 --- a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/stopDetails/StopDetailsViewTest.kt +++ b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/stopDetails/StopDetailsViewTest.kt @@ -135,6 +135,8 @@ class StopDetailsViewTest { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { /* no-op */ } diff --git a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/util/SubscribeToPredictionsTest.kt b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/util/SubscribeToPredictionsTest.kt index 2b9864f06..0cf397918 100644 --- a/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/util/SubscribeToPredictionsTest.kt +++ b/androidApp/src/androidTest/java/com/mbta/tid/mbta_app/android/util/SubscribeToPredictionsTest.kt @@ -59,6 +59,8 @@ class SubscribeToPredictionsTest { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { check(isConnected) { "called disconnect when not connected" } isConnected = false diff --git a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift index b2c788d51..3a227ca66 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift @@ -88,7 +88,14 @@ struct NearbyTransitView: View { } } .withScenePhaseHandlers( - onActive: { joinPredictions(state.nearbyByRouteAndStop?.stopIds()) }, + onActive: { + if let predictionsByStop, + predictionsRepository + .shouldForgetPredictions(predictionCount: predictionsByStop.predictionQuantity()) { + self.predictionsByStop = nil + } + joinPredictions(state.nearbyByRouteAndStop?.stopIds()) + }, onInactive: leavePredictions, onBackground: { leavePredictions() diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift index f4a30c8fd..3e1b9b5cd 100644 --- a/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift +++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift @@ -106,11 +106,20 @@ struct StopDetailsPage: View { .onDisappear { leavePredictions() } - .withScenePhaseHandlers(onActive: { joinPredictions(stop) }, - onInactive: leavePredictions, - onBackground: { leavePredictions() - isReturningFromBackground = true - }) + .withScenePhaseHandlers( + onActive: { + if let predictionsByStop, + predictionsRepository + .shouldForgetPredictions(predictionCount: predictionsByStop.predictionQuantity()) { + self.predictionsByStop = nil + } + joinPredictions(stop) + }, + onInactive: leavePredictions, + onBackground: { leavePredictions() + isReturningFromBackground = true + } + ) } } diff --git a/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift b/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift index a4855596f..38601b21a 100644 --- a/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift +++ b/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift @@ -130,12 +130,21 @@ struct TripDetailsPage: View { joinVehicle(vehicleId: vehicleId) } .onReceive(inspection.notice) { inspection.visit(self, $0) } - .withScenePhaseHandlers(onActive: joinRealtime, - onInactive: leaveRealtime, - onBackground: { - leaveRealtime() - isReturningFromBackground = true - }) + .withScenePhaseHandlers( + onActive: { + if let tripPredictions, + tripPredictionsRepository + .shouldForgetPredictions(predictionCount: tripPredictions.predictionQuantity()) { + self.tripPredictions = nil + } + joinRealtime() + }, + onInactive: leaveRealtime, + onBackground: { + leaveRealtime() + isReturningFromBackground = true + } + ) } private func loadEverything() { diff --git a/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift b/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift index 807dade76..55abaee1f 100644 --- a/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift +++ b/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift @@ -655,6 +655,10 @@ final class TripDetailsPageTests: XCTestCase { var lastUpdated: Instant? + func shouldForgetPredictions(predictionCount _: Int32) -> Bool { + false + } + func disconnect() { onDisconnect?() } diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/endToEnd/EndToEndRepositories.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/endToEnd/EndToEndRepositories.kt index be5cee66e..d706c0f80 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/endToEnd/EndToEndRepositories.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/endToEnd/EndToEndRepositories.kt @@ -133,6 +133,8 @@ fun endToEndModule(): Module { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun connectV2( stopIds: List, onJoin: (ApiResult) -> Unit, @@ -199,6 +201,8 @@ fun endToEndModule(): Module { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { TODO("Not yet implemented") } diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepository.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepository.kt index 5ae0f8a11..013fddb27 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepository.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepository.kt @@ -10,6 +10,7 @@ import com.mbta.tid.mbta_app.network.PhoenixMessage import com.mbta.tid.mbta_app.network.PhoenixPushStatus import com.mbta.tid.mbta_app.network.PhoenixSocket import com.mbta.tid.mbta_app.phoenix.PredictionsForStopsChannel +import kotlin.time.Duration.Companion.minutes import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.koin.core.component.KoinComponent @@ -28,6 +29,8 @@ interface IPredictionsRepository { var lastUpdated: Instant? + fun shouldForgetPredictions(predictionCount: Int): Boolean + fun disconnect() } @@ -38,6 +41,10 @@ class PredictionsRepository(private val socket: PhoenixSocket) : override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = + (Clock.System.now() - (lastUpdated ?: Instant.DISTANT_FUTURE)) > 10.minutes && + predictionCount > 0 + override fun connect( stopIds: List, onReceive: (ApiResult) -> Unit @@ -221,6 +228,8 @@ class MockPredictionsRepository( override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { onDisconnect() } diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/TripPredictionsRepository.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/TripPredictionsRepository.kt index 90cce9842..dc06582e9 100644 --- a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/TripPredictionsRepository.kt +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/repositories/TripPredictionsRepository.kt @@ -8,6 +8,7 @@ import com.mbta.tid.mbta_app.network.PhoenixMessage import com.mbta.tid.mbta_app.network.PhoenixPushStatus import com.mbta.tid.mbta_app.network.PhoenixSocket import com.mbta.tid.mbta_app.phoenix.PredictionsForStopsChannel +import kotlin.time.Duration.Companion.minutes import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.koin.core.component.KoinComponent @@ -17,6 +18,8 @@ interface ITripPredictionsRepository { var lastUpdated: Instant? + fun shouldForgetPredictions(predictionCount: Int): Boolean + fun disconnect() } @@ -27,6 +30,10 @@ class TripPredictionsRepository(private val socket: PhoenixSocket) : override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = + (Clock.System.now() - (lastUpdated ?: Instant.DISTANT_FUTURE)) > 10.minutes && + predictionCount > 0 + override fun connect( tripId: String, onReceive: (ApiResult) -> Unit @@ -89,6 +96,8 @@ class MockTripPredictionsRepository : ITripPredictionsRepository { override var lastUpdated: Instant? = null + override fun shouldForgetPredictions(predictionCount: Int) = false + override fun disconnect() { /* no-op */ } diff --git a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepositoryTests.kt b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepositoryTests.kt index ac2beab9d..398055b75 100644 --- a/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepositoryTests.kt +++ b/shared/src/commonTest/kotlin/com/mbta/tid/mbta_app/repositories/PredictionsRepositoryTests.kt @@ -18,9 +18,14 @@ import dev.mokkery.mock import dev.mokkery.verify import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.minutes +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import org.koin.test.KoinTest class PredictionsRepositoryTests : KoinTest { @@ -416,4 +421,33 @@ class PredictionsRepositoryTests : KoinTest { } ) } + + @Test + fun `shouldForgetPredictions false when never updated`() { + val predictionsRepo = PredictionsRepository(mock(MockMode.autofill)) + predictionsRepo.lastUpdated = null + // there will not, in practice, be ten predictions and no last updated time + assertFalse(predictionsRepo.shouldForgetPredictions(10)) + } + + @Test + fun `shouldForgetPredictions false when no predictions`() { + val predictionsRepo = PredictionsRepository(mock(MockMode.autofill)) + predictionsRepo.lastUpdated = Instant.DISTANT_PAST + assertFalse(predictionsRepo.shouldForgetPredictions(0)) + } + + @Test + fun `shouldForgetPredictions false when within ten minutes`() { + val predictionsRepo = PredictionsRepository(mock(MockMode.autofill)) + predictionsRepo.lastUpdated = Clock.System.now() - 9.9.minutes + assertFalse(predictionsRepo.shouldForgetPredictions(10)) + } + + @Test + fun `shouldForgetPredictions true when old and nonempty`() { + val predictionsRepo = PredictionsRepository(mock(MockMode.autofill)) + predictionsRepo.lastUpdated = Clock.System.now() - 10.1.minutes + assertTrue(predictionsRepo.shouldForgetPredictions(10)) + } } From 14bce935bd15e014a3bb98064970d89183327d51 Mon Sep 17 00:00:00 2001 From: Kayla Brady <31781298+KaylaBrady@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:12:23 -0400 Subject: [PATCH 5/5] chore: increase test waits to address flakiness (#471) --- iosApp/iosAppTests/Views/ErrorBannerTests.swift | 2 +- iosApp/iosAppTests/Views/NearbyTransitViewTests.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iosApp/iosAppTests/Views/ErrorBannerTests.swift b/iosApp/iosAppTests/Views/ErrorBannerTests.swift index 040efd86b..e637ac54b 100644 --- a/iosApp/iosAppTests/Views/ErrorBannerTests.swift +++ b/iosApp/iosAppTests/Views/ErrorBannerTests.swift @@ -30,7 +30,7 @@ final class ErrorBannerTests: XCTestCase { let stateSetPublisher = PassthroughSubject() - let showedState = sut.inspection.inspect(onReceive: stateSetPublisher, after: 0.2) { view in + let showedState = sut.inspection.inspect(onReceive: stateSetPublisher, after: 0.5) { view in XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Updated \(minutesAgo) minutes ago") try view.find(ViewType.Button.self).tap() diff --git a/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift b/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift index 8ed0ca795..5ab0fde7b 100644 --- a/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift +++ b/iosApp/iosAppTests/Views/NearbyTransitViewTests.swift @@ -463,7 +463,7 @@ final class NearbyTransitViewTests: XCTestCase { nearbyVM: .init() ) - let exp = sut.inspection.inspect(onReceive: globalLoadedPublisher, after: 0.2) { view in + let exp = sut.inspection.inspect(onReceive: globalLoadedPublisher, after: 0.5) { view in let stops = view.findAll(NearbyStopView.self) XCTAssertEqual(stops[0].findAll(DestinationRowView.self).count, 3) @@ -710,7 +710,7 @@ final class NearbyTransitViewTests: XCTestCase { nearbyVM: .init() ) - sut.inspection.inspect(after: 0.2) { view in + sut.inspection.inspect(after: 0.5) { view in XCTAssertNotNil(try view.view(NearbyTransitView.self) .find(text: "Error loading data")) }