1 /* 2 * Copyright 2017 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.fragment; 18 19 import static org.hamcrest.CoreMatchers.is; 20 import static org.hamcrest.CoreMatchers.notNullValue; 21 import static org.hamcrest.MatcherAssert.assertThat; 22 23 import android.app.Instrumentation; 24 import android.content.ComponentName; 25 import android.content.Intent; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.support.annotation.IdRes; 29 import android.support.annotation.NavigationRes; 30 import android.support.test.InstrumentationRegistry; 31 import android.support.test.filters.SmallTest; 32 import android.support.test.rule.ActivityTestRule; 33 import android.support.v4.app.TaskStackBuilder; 34 35 import androidx.navigation.NavController; 36 import androidx.navigation.NavDeepLinkBuilder; 37 import androidx.navigation.fragment.test.BaseNavigationActivity; 38 import androidx.navigation.fragment.test.R; 39 import androidx.navigation.testing.TestNavigator; 40 41 import org.junit.Before; 42 import org.junit.Rule; 43 import org.junit.Test; 44 45 @SmallTest 46 public abstract class BaseNavControllerTest<A extends BaseNavigationActivity> { 47 private static final String TEST_ARG = "test"; 48 private static final String TEST_ARG_VALUE = "value"; 49 private static final String TEST_DEEP_LINK_ACTION = "deep_link"; 50 51 /** 52 * @return The concrete Activity class under test 53 */ 54 protected abstract Class<A> getActivityClass(); 55 56 @Rule 57 public ActivityTestRule<A> mActivityRule = 58 new ActivityTestRule<>(getActivityClass(), false, false); 59 60 private Instrumentation mInstrumentation; 61 62 @Before 63 public void getInstrumentation() { 64 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 65 } 66 67 @Test 68 public void testStartDestinationDeeplink() throws Throwable { 69 assertDeeplink(R.id.start_test, 1); 70 } 71 72 @Test 73 public void testDeeplink() throws Throwable { 74 assertDeeplink(R.id.deep_link_test, 2); 75 } 76 77 @Test 78 public void testNestedStartDestinationDeeplink() throws Throwable { 79 assertDeeplink(R.id.nested_start_test, 2); 80 } 81 82 @Test 83 public void testNestedDeeplink() throws Throwable { 84 assertDeeplink(R.id.nested_deep_link_test, 3); 85 } 86 87 @Test 88 public void testDoubleNestedStartDestinationDeeplink() throws Throwable { 89 assertDeeplink(R.id.double_nested_start_test, 2); 90 } 91 92 @Test 93 public void testDoubleNestedDeeplink() throws Throwable { 94 assertDeeplink(R.id.double_nested_deep_link_test, 3); 95 } 96 97 private void assertDeeplink(@IdRes int destId, int expectedStackSize) throws Throwable { 98 BaseNavigationActivity activity = launchDeepLink(R.navigation.nav_deep_link, 99 destId, null); 100 NavController navController = activity.getNavController(); 101 102 assertThat(navController.getCurrentDestination().getId(), is(destId)); 103 TestNavigator navigator = navController.getNavigatorProvider() 104 .getNavigator(TestNavigator.class); 105 assertThat(navigator.mBackStack.size(), is(expectedStackSize)); 106 107 // Test that the deep link Intent was passed through even though we don't pass in any args 108 //noinspection ConstantConditions 109 Intent deepLinkIntent = navigator.mBackStack.peekLast().second 110 .getParcelable(NavController.KEY_DEEP_LINK_INTENT); 111 assertThat(deepLinkIntent, is(notNullValue(Intent.class))); 112 assertThat(deepLinkIntent.getAction(), is(TEST_DEEP_LINK_ACTION)); 113 } 114 115 @Test 116 public void testStartDestinationDeeplinkWithArgs() throws Throwable { 117 assertDeepLinkWithArgs(R.id.start_test, 1); 118 } 119 120 @Test 121 public void testDeeplinkWithArgs() throws Throwable { 122 assertDeepLinkWithArgs(R.id.deep_link_test, 2); 123 } 124 125 @Test 126 public void testNestedStartDestinationDeeplinkWithArgs() throws Throwable { 127 assertDeepLinkWithArgs(R.id.nested_start_test, 2); 128 } 129 130 @Test 131 public void testNestedDeeplinkWithArgs() throws Throwable { 132 assertDeepLinkWithArgs(R.id.nested_deep_link_test, 3); 133 } 134 135 @Test 136 public void testDoubleNestedStartDestinationDeeplinkWithArgs() throws Throwable { 137 assertDeepLinkWithArgs(R.id.double_nested_start_test, 2); 138 } 139 140 @Test 141 public void testDoubleNestedDeeplinkWithArgs() throws Throwable { 142 assertDeepLinkWithArgs(R.id.double_nested_deep_link_test, 3); 143 } 144 145 private void assertDeepLinkWithArgs(@IdRes int destId, int expectedStackSize) throws Throwable { 146 Bundle args = new Bundle(); 147 args.putString(TEST_ARG, TEST_ARG_VALUE); 148 BaseNavigationActivity activity = launchDeepLink(R.navigation.nav_deep_link, 149 destId, args); 150 NavController navController = activity.getNavController(); 151 152 assertThat(navController.getCurrentDestination().getId(), is(destId)); 153 TestNavigator navigator = navController.getNavigatorProvider() 154 .getNavigator(TestNavigator.class); 155 assertThat(navigator.mBackStack.size(), is(expectedStackSize)); 156 //noinspection ConstantConditions 157 assertThat(navigator.mBackStack.peekLast().second.getString(TEST_ARG), is(TEST_ARG_VALUE)); 158 159 // Test that the deep link Intent was passed in alongside our args 160 //noinspection ConstantConditions 161 Intent deepLinkIntent = navigator.mBackStack.peekLast().second 162 .getParcelable(NavController.KEY_DEEP_LINK_INTENT); 163 assertThat(deepLinkIntent, is(notNullValue(Intent.class))); 164 assertThat(deepLinkIntent.getAction(), is(TEST_DEEP_LINK_ACTION)); 165 } 166 167 @Test 168 public void testStartDestinationUriDeepLink() throws Throwable { 169 assertUriDeepLink("start", 170 R.id.start_test, 1); 171 } 172 173 @Test 174 public void testUriDeepLink() throws Throwable { 175 assertUriDeepLink("deep_link", R.id.deep_link_test, 2); 176 } 177 178 @Test 179 public void testNestedStartDestinationUriDeepLink() throws Throwable { 180 assertUriDeepLink("nested_start", R.id.nested_start_test, 2); 181 } 182 183 @Test 184 public void testNestedUriDeepLink() throws Throwable { 185 assertUriDeepLink("nested_deep_link", R.id.nested_deep_link_test, 3); 186 } 187 188 @Test 189 public void testDoubleNestedStartDestinationUriDeepLink() throws Throwable { 190 assertUriDeepLink("double_nested_start", R.id.double_nested_start_test, 2); 191 } 192 193 @Test 194 public void testDoubleNestedUriDeepLink() throws Throwable { 195 assertUriDeepLink("double_nested_deep_link", R.id.double_nested_deep_link_test, 3); 196 } 197 198 private void assertUriDeepLink(String path, @IdRes int destId, int expectedStackSize) 199 throws Throwable { 200 Uri deepLinkUri = Uri.parse("http://www.example.com/" + path + "/" + TEST_ARG_VALUE); 201 Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUri) 202 .setComponent(new ComponentName(mInstrumentation.getContext(), 203 getActivityClass())) 204 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 205 BaseNavigationActivity activity = launchActivity(intent); 206 NavController navController = activity.getNavController(); 207 navController.setGraph(R.navigation.nav_deep_link); 208 209 assertThat(navController.getCurrentDestination().getId(), is(destId)); 210 TestNavigator navigator = navController.getNavigatorProvider() 211 .getNavigator(TestNavigator.class); 212 assertThat(navigator.mBackStack.size(), is(expectedStackSize)); 213 //noinspection ConstantConditions 214 assertThat(navigator.mBackStack.peekLast().second.getString(TEST_ARG), is(TEST_ARG_VALUE)); 215 216 // Test that the deep link Intent was passed in alongside our args 217 //noinspection ConstantConditions 218 Intent deepLinkIntent = navigator.mBackStack.peekLast().second 219 .getParcelable(NavController.KEY_DEEP_LINK_INTENT); 220 assertThat(deepLinkIntent, is(notNullValue(Intent.class))); 221 assertThat(deepLinkIntent.getData(), is(deepLinkUri)); 222 } 223 224 private BaseNavigationActivity launchActivity(Intent intent) throws Throwable { 225 BaseNavigationActivity activity = mActivityRule.launchActivity(intent); 226 mInstrumentation.waitForIdleSync(); 227 NavController navController = activity.getNavController(); 228 assertThat(navController, is(notNullValue(NavController.class))); 229 TestNavigator navigator = new TestNavigator(); 230 navController.getNavigatorProvider().addNavigator(navigator); 231 return activity; 232 } 233 234 private BaseNavigationActivity launchDeepLink(@NavigationRes int graphId, @IdRes int destId, 235 Bundle args) throws Throwable { 236 TaskStackBuilder intents = new NavDeepLinkBuilder(mInstrumentation.getTargetContext()) 237 .setGraph(graphId) 238 .setDestination(destId) 239 .setArguments(args) 240 .createTaskStackBuilder(); 241 Intent intent = intents.editIntentAt(0); 242 intent.setAction(TEST_DEEP_LINK_ACTION); 243 244 // Now launch the deeplink Intent 245 BaseNavigationActivity deeplinkActivity = launchActivity(intent); 246 NavController navController = deeplinkActivity.getNavController(); 247 navController.setGraph(graphId); 248 249 return deeplinkActivity; 250 } 251 } 252