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