fix(android): add tests for backstack and fix deeplink navigation

This commit is contained in:
Harsh Shandilya
2026-01-12 21:29:41 +05:30
parent a44e74788d
commit b29785a5fc
4 changed files with 93 additions and 10 deletions
+4
View File
@@ -6,6 +6,8 @@
*/
@file:Suppress("UnstableApiUsage")
import dev.msfjarvis.claw.gradle.addTestDependencies
plugins {
id("dev.msfjarvis.claw.android-application")
id("dev.msfjarvis.claw.rename-artifacts")
@@ -118,4 +120,6 @@ dependencies {
compileOnly(libs.androidx.compose.glance.preview)
runtimeOnly(libs.androidx.profileinstaller)
addTestDependencies(project)
}
@@ -79,6 +79,7 @@ class MainActivity(
windowSizeClass = windowSizeClass,
setWebUri = { url -> webUri = url },
deepLinkDestination = deepLinkDestination,
clearDeepLink = { deepLinkDestination = null },
)
}
}
@@ -8,6 +8,7 @@ package dev.msfjarvis.claw.android.ui.screens
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalActivity
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
@@ -87,6 +88,7 @@ fun LobstersPostsScreen(
setWebUri: (String?) -> Unit,
modifier: Modifier = Modifier,
deepLinkDestination: NavKey? = null,
clearDeepLink: () -> Unit = {},
viewModel: ClawViewModel = metroViewModel(),
tagFilterViewModel: TagFilterViewModel = metroViewModel(key = "tag_filter"),
) {
@@ -113,7 +115,8 @@ fun LobstersPostsScreen(
LaunchedEffect(deepLinkDestination) {
if (deepLinkDestination != null) {
navigateTo(backStack, deepLinkDestination)
navigateTo(backStack, deepLinkDestination, allowStacking = true)
clearDeepLink()
}
}
@@ -296,7 +299,8 @@ fun LobstersPostsScreen(
}
}
private fun navigateTo(
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun navigateTo(
backStack: MutableList<NavKey>,
destination: NavKey,
allowStacking: Boolean = false,
@@ -306,24 +310,27 @@ private fun navigateTo(
if (destination != Hottest) {
backStack.add(Hottest)
}
backStack.add(destination)
return
}
if (destination is NonStackable) {
if (allowStacking && destination is Comments) {
val lastItem = backStack.lastOrNull()
if (lastItem is Comments && lastItem.postId == destination.postId) {
return
}
backStack.add(destination)
return
}
val existingEntry =
backStack.firstOrNull {
it is NonStackable && it::class.java.isAssignableFrom(destination::class.java)
}
if (existingEntry != null) {
val existingIndex = backStack.indexOf(existingEntry)
backStack.remove(existingEntry)
if (allowStacking && destination is Comments && existingEntry is Comments) {
if (destination.postId != existingEntry.postId) {
backStack.add(existingIndex, destination)
return
}
}
}
}
@@ -0,0 +1,71 @@
/*
* Copyright © Harsh Shandilya.
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
package dev.msfjarvis.claw.android.ui.screens
import androidx.navigation3.runtime.NavKey
import com.google.common.truth.Truth.assertThat
import dev.msfjarvis.claw.android.ui.navigation.Comments
import dev.msfjarvis.claw.android.ui.navigation.Hottest
import dev.msfjarvis.claw.android.ui.navigation.Newest
import dev.msfjarvis.claw.android.ui.navigation.Saved
import org.junit.jupiter.api.Test
class BackStackBehaviorTest {
private fun makeBackStack(vararg keys: NavKey) = mutableListOf(*keys)
@Test
fun `navigating from between top level destinations prevents stacking`() {
val backStack = makeBackStack(Hottest)
navigateTo(backStack, Newest)
assertThat(backStack).containsExactly(Hottest, Newest).inOrder()
navigateTo(backStack, Saved)
assertThat(backStack).containsExactly(Hottest, Saved).inOrder()
}
@Test
fun `NonStackable destinations do not stack by default`() {
val backStack = makeBackStack(Hottest, Comments("abc123"))
navigateTo(backStack, Comments("def456"))
assertThat(backStack).containsExactly(Hottest, Comments("def456")).inOrder()
}
@Test
fun `NonStackable destinations stack when allowStacking = true`() {
val backStack = makeBackStack(Hottest, Comments("abc123"))
navigateTo(backStack, Comments("def456"), allowStacking = true)
assertThat(backStack).containsExactly(Hottest, Comments("abc123"), Comments("def456")).inOrder()
}
@Test
fun `Same destination cannot be stacked on itself`() {
val backStack = makeBackStack(Hottest, Comments("abc123"))
navigateTo(backStack, Comments("abc123"), allowStacking = true)
assertThat(backStack).containsExactly(Hottest, Comments("abc123")).inOrder()
}
@Test
fun `Same destination can be stacked with a gap in between`() {
val backStack = makeBackStack(Hottest, Comments("abc123"), Comments("def456"))
navigateTo(backStack, Comments("abc123"), allowStacking = true)
assertThat(backStack)
.containsExactly(Hottest, Comments("abc123"), Comments("def456"), Comments("abc123"))
.inOrder()
}
}