Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-14 14:47:19 +05:00
parent d792ac6304
commit f61a462cc3
14 changed files with 305 additions and 44 deletions

View file

@ -0,0 +1,27 @@
package com.tangem.features.send.api.navigation
/**
* Tracks the previously active route of a send-flow child stack to detect returns from edit screens.
*
* A Confirm screen receives the parent's state snapshot via its constructor only when it is freshly
* Confirm instance is reused, so the flow component must re-push the parent's current state into it.
* The gate for that re-push must read the route that was active *before* the current one the
* Confirm route itself always has `isEditMode = false` ([REDACTED_TASK_KEY]).
*
* @param isEditRoute returns whether the given route is an edit screen route
*/
class EditReturnTracker<R : Any>(private val isEditRoute: (R) -> Boolean) {
private var previousRoute: R? = null
/**
* Registers [route] as the currently active route and returns `true` if the route
* that was active before it was an edit route.
*/
fun onRouteActivated(route: R): Boolean {
val isReturnedFromEdit = previousRoute?.let(isEditRoute) == true
previousRoute = route
return isReturnedFromEdit
}
}

View file

@ -0,0 +1,108 @@
package com.tangem.features.send.api.navigation
import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test
internal class EditReturnTrackerTest {
private sealed interface TestRoute {
val isEditMode: Boolean
data class Amount(override val isEditMode: Boolean) : TestRoute
data class Destination(override val isEditMode: Boolean) : TestRoute
data object Confirm : TestRoute {
override val isEditMode: Boolean = false
}
data object Success : TestRoute {
override val isEditMode: Boolean = false
}
}
private val tracker = EditReturnTracker<TestRoute> { it.isEditMode }
@Test
fun `GIVEN no previous route WHEN first route activated THEN no edit return detected`() {
val isReturnedFromEdit = tracker.onRouteActivated(TestRoute.Amount(isEditMode = false))
assertThat(isReturnedFromEdit).isFalse()
}
@Test
fun `GIVEN linear flow WHEN confirm activated first time THEN no edit return detected`() {
// Arrange
tracker.onRouteActivated(TestRoute.Amount(isEditMode = false))
tracker.onRouteActivated(TestRoute.Destination(isEditMode = false))
// Act
val isReturnedFromEdit = tracker.onRouteActivated(TestRoute.Confirm)
// Assert
assertThat(isReturnedFromEdit).isFalse()
}
@Test
fun `GIVEN amount edited from confirm WHEN popped back to confirm THEN edit return detected`() {
// Arrange: [REDACTED_TASK_KEY] reproduction — Amount -> Destination -> Confirm -> Amount(edit) -> Confirm
tracker.onRouteActivated(TestRoute.Amount(isEditMode = false))
tracker.onRouteActivated(TestRoute.Destination(isEditMode = false))
tracker.onRouteActivated(TestRoute.Confirm)
tracker.onRouteActivated(TestRoute.Amount(isEditMode = true))
// Act
val isReturnedFromEdit = tracker.onRouteActivated(TestRoute.Confirm)
// Assert
assertThat(isReturnedFromEdit).isTrue()
}
@Test
fun `GIVEN destination edited from confirm WHEN popped back to confirm THEN edit return detected`() {
// Arrange
tracker.onRouteActivated(TestRoute.Confirm)
tracker.onRouteActivated(TestRoute.Destination(isEditMode = true))
// Act
val isReturnedFromEdit = tracker.onRouteActivated(TestRoute.Confirm)
// Assert
assertThat(isReturnedFromEdit).isTrue()
}
@Test
fun `GIVEN edit return consumed WHEN next route activated THEN no edit return detected`() {
// Arrange
tracker.onRouteActivated(TestRoute.Confirm)
tracker.onRouteActivated(TestRoute.Amount(isEditMode = true))
tracker.onRouteActivated(TestRoute.Confirm)
// Act
val isReturnedFromEdit = tracker.onRouteActivated(TestRoute.Success)
// Assert
assertThat(isReturnedFromEdit).isFalse()
}
@Test
fun `GIVEN consecutive edits WHEN each pops back to confirm THEN each return detected independently`() {
tracker.onRouteActivated(TestRoute.Confirm)
assertThat(tracker.onRouteActivated(TestRoute.Amount(isEditMode = true))).isFalse()
assertThat(tracker.onRouteActivated(TestRoute.Confirm)).isTrue()
assertThat(tracker.onRouteActivated(TestRoute.Destination(isEditMode = true))).isFalse()
assertThat(tracker.onRouteActivated(TestRoute.Confirm)).isTrue()
}
@Test
fun `GIVEN confirm re-entered from non-edit route WHEN confirm activated THEN no edit return detected`() {
// Arrange: back from Confirm to Destination step, then Next re-pushes Confirm
tracker.onRouteActivated(TestRoute.Destination(isEditMode = false))
tracker.onRouteActivated(TestRoute.Confirm)
tracker.onRouteActivated(TestRoute.Destination(isEditMode = false))
// Act
val isReturnedFromEdit = tracker.onRouteActivated(TestRoute.Confirm)
// Assert
assertThat(isReturnedFromEdit).isFalse()
}
}