1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.navigation.compose 18 19 import android.window.BackEvent 20 import androidx.activity.BackEventCompat 21 import androidx.activity.OnBackPressedDispatcher 22 import androidx.activity.compose.LocalOnBackPressedDispatcherOwner 23 import androidx.compose.foundation.text.BasicText 24 import androidx.compose.ui.test.junit4.createComposeRule 25 import androidx.lifecycle.Lifecycle 26 import androidx.navigation.NavHostController 27 import androidx.test.ext.junit.runners.AndroidJUnit4 28 import androidx.test.filters.LargeTest 29 import com.google.common.truth.Truth.assertThat 30 import org.junit.Rule 31 import org.junit.Test 32 import org.junit.runner.RunWith 33 34 @LargeTest 35 @RunWith(AndroidJUnit4::class) 36 class NavHostPredictiveBackTest { 37 @get:Rule val composeTestRule = createComposeRule() 38 39 @Test testNavHostAnimationsnull40 fun testNavHostAnimations() { 41 lateinit var navController: NavHostController 42 lateinit var backPressedDispatcher: OnBackPressedDispatcher 43 composeTestRule.setContent { 44 navController = rememberNavController() 45 backPressedDispatcher = 46 LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher 47 NavHost(navController, startDestination = first) { 48 composable(first) { BasicText(first) } 49 composable(second) { BasicText(second) } 50 } 51 } 52 53 val firstEntry = navController.currentBackStackEntry 54 55 composeTestRule.runOnIdle { 56 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.RESUMED) 57 } 58 59 composeTestRule.runOnIdle { navController.navigate(second) } 60 61 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.CREATED) 62 assertThat(navController.currentBackStackEntry?.lifecycle?.currentState) 63 .isEqualTo(Lifecycle.State.STARTED) 64 65 composeTestRule.runOnIdle { 66 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.CREATED) 67 assertThat(navController.currentBackStackEntry?.lifecycle?.currentState) 68 .isEqualTo(Lifecycle.State.RESUMED) 69 } 70 71 val secondEntry = navController.currentBackStackEntry 72 73 composeTestRule.runOnIdle { 74 backPressedDispatcher.dispatchOnBackStarted( 75 BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT) 76 ) 77 assertThat(navController.currentBackStackEntry?.lifecycle?.currentState) 78 .isEqualTo(Lifecycle.State.STARTED) 79 assertThat(navController.previousBackStackEntry?.lifecycle?.currentState) 80 .isEqualTo(Lifecycle.State.STARTED) 81 backPressedDispatcher.dispatchOnBackProgressed( 82 BackEventCompat(0.1F, 0.1F, 0.5F, BackEvent.EDGE_LEFT) 83 ) 84 } 85 86 composeTestRule.waitForIdle() 87 88 composeTestRule.runOnIdle { 89 backPressedDispatcher.dispatchOnBackProgressed( 90 BackEventCompat(0.1F, 0.1F, 0.5F, BackEvent.EDGE_LEFT) 91 ) 92 } 93 94 assertThat(navController.currentBackStackEntry?.lifecycle?.currentState) 95 .isEqualTo(Lifecycle.State.STARTED) 96 assertThat(secondEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.STARTED) 97 98 composeTestRule.runOnIdle { backPressedDispatcher.dispatchOnBackCancelled() } 99 100 composeTestRule.runOnIdle { 101 assertThat(secondEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.RESUMED) 102 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.CREATED) 103 } 104 } 105 106 @Test testDisabledInSameFramePredictiveBacknull107 fun testDisabledInSameFramePredictiveBack() { 108 lateinit var navController: NavHostController 109 lateinit var backPressedDispatcher: OnBackPressedDispatcher 110 composeTestRule.setContent { 111 navController = rememberNavController() 112 backPressedDispatcher = 113 LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher 114 NavHost(navController, startDestination = first) { 115 composable(first) { BasicText(first) } 116 composable(second) { BasicText(second) } 117 } 118 } 119 120 val firstEntry = navController.currentBackStackEntry 121 122 composeTestRule.runOnIdle { 123 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.RESUMED) 124 } 125 126 composeTestRule.runOnIdle { navController.navigate(second) } 127 128 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.CREATED) 129 assertThat(navController.currentBackStackEntry?.lifecycle?.currentState) 130 .isEqualTo(Lifecycle.State.STARTED) 131 132 composeTestRule.runOnIdle { 133 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.CREATED) 134 assertThat(navController.currentBackStackEntry?.lifecycle?.currentState) 135 .isEqualTo(Lifecycle.State.RESUMED) 136 } 137 138 val secondEntry = navController.currentBackStackEntry 139 140 composeTestRule.mainClock.autoAdvance = false 141 142 composeTestRule.runOnIdle { 143 navController.popBackStack() 144 backPressedDispatcher.dispatchOnBackStarted( 145 BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT) 146 ) 147 backPressedDispatcher.onBackPressed() 148 } 149 150 composeTestRule.mainClock.autoAdvance = true 151 152 composeTestRule.runOnIdle { 153 assertThat(firstEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.RESUMED) 154 assertThat(secondEntry?.lifecycle?.currentState).isEqualTo(Lifecycle.State.DESTROYED) 155 } 156 } 157 } 158 159 private const val first = "first" 160 private const val second = "second" 161