1 /* 2 * Copyright 2019 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 18 19 import android.net.Uri 20 import androidx.navigation.test.intArgument 21 import androidx.navigation.test.nullableStringArgument 22 import androidx.test.filters.SmallTest 23 import com.google.common.truth.Truth.assertThat 24 import com.google.common.truth.Truth.assertWithMessage 25 import org.junit.Test 26 27 @SmallTest 28 class NavGraphAndroidTest { 29 companion object { 30 const val DESTINATION_ID = 1 31 const val DESTINATION_ROUTE = "destination_route" 32 const val DESTINATION_LABEL = "test_label" 33 const val GRAPH_ID = 2 34 const val GRAPH_ROUTE = "graph_route" 35 const val GRAPH_LABEL = "graph_label" 36 } 37 38 @Test matchDeepLinknull39 fun matchDeepLink() { 40 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 41 val graph = 42 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 43 44 val idArgument = NavArgument.Builder().setType(NavType.IntType).build() 45 graph.addArgument("id", idArgument) 46 graph.addDeepLink("www.example.com/users/{id}") 47 48 val match = graph.matchDeepLink(Uri.parse("https://www.example.com/users/43")) 49 50 assertWithMessage("Deep link should match").that(match).isNotNull() 51 52 assertWithMessage("Deep link should extract id argument correctly") 53 .that(match?.matchingArgs?.getInt("id")) 54 .isEqualTo(43) 55 } 56 57 @Test matchDeepLinkBestMatchExactnull58 fun matchDeepLinkBestMatchExact() { 59 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 60 val graph = 61 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 62 63 graph.addDeepLink("www.example.com/users/index.html") 64 65 graph.addArgument("id", nullableStringArgument(null)) 66 graph.addDeepLink("www.example.com/users/{id}") 67 68 val match = graph.matchDeepLink(Uri.parse("https://www.example.com/users/index.html")) 69 70 assertWithMessage("Deep link should match").that(match).isNotNull() 71 assertWithMessage("Deep link should pick the exact match") 72 .that(match?.matchingArgs?.size()) 73 .isEqualTo(0) 74 } 75 76 @Test matchDotStarnull77 fun matchDotStar() { 78 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 79 val graph = 80 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 81 82 graph.addDeepLink("www.example.com/.*") 83 graph.addDeepLink("www.example.com/{name}") 84 85 val match = graph.matchDeepLink(Uri.parse("https://www.example.com/foo")) 86 assertWithMessage("Deep link should match").that(match).isNotNull() 87 assertWithMessage("Deep link should pick name over .*") 88 .that(match?.matchingArgs?.size()) 89 .isEqualTo(1) 90 } 91 92 @Test matchDeepLinkBestMatchnull93 fun matchDeepLinkBestMatch() { 94 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 95 val graph = 96 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 97 98 val idArgument = NavArgument.Builder().setType(NavType.IntType).build() 99 graph.addArgument("id", idArgument) 100 graph.addDeepLink("www.example.com/users/{id}") 101 102 val postIdArgument = NavArgument.Builder().setType(NavType.IntType).build() 103 graph.addArgument("postId", postIdArgument) 104 graph.addDeepLink("www.example.com/users/{id}/posts/{postId}") 105 106 val match = graph.matchDeepLink(Uri.parse("https://www.example.com/users/43/posts/99")) 107 108 assertWithMessage("Deep link should match").that(match).isNotNull() 109 110 assertWithMessage("Deep link should pick the argument with more matching arguments") 111 .that(match?.matchingArgs?.size()) 112 .isEqualTo(2) 113 assertWithMessage("Deep link should extract id argument correctly") 114 .that(match?.matchingArgs?.getInt("id")) 115 .isEqualTo(43) 116 assertWithMessage("Deep link should extract postId argument correctly") 117 .that(match?.matchingArgs?.getInt("postId")) 118 .isEqualTo(99) 119 } 120 121 @Test matchDeepLinkBestMatchPathAndQuerynull122 fun matchDeepLinkBestMatchPathAndQuery() { 123 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 124 val graph = 125 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 126 127 graph.addArgument("code", nullableStringArgument(null)) 128 graph.addDeepLink("www.example.com/users?code={code}") 129 130 graph.addArgument("id", nullableStringArgument(null)) 131 graph.addDeepLink("www.example.com/users?id={id}") 132 133 val match = graph.matchDeepLink(Uri.parse("https://www.example.com/users?id=1234")) 134 135 assertWithMessage("Deep link should match").that(match).isNotNull() 136 137 assertWithMessage("Deep link should pick the argument with given values") 138 .that(match?.matchingArgs?.size()) 139 .isEqualTo(1) 140 assertWithMessage("Deep link should extract id argument correctly") 141 .that(match?.matchingArgs?.getString("id")) 142 .isEqualTo("1234") 143 } 144 145 @Test matchDeepLinkBestMatchChildrennull146 fun matchDeepLinkBestMatchChildren() { 147 val navigatorProvider = 148 NavigatorProvider().apply { 149 addNavigator(NavGraphNavigator(this)) 150 addNavigator(NoOpNavigator()) 151 } 152 val graph = 153 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 154 155 val userDestination = 156 navigatorProvider.getNavigator(NoOpNavigator::class.java).createDestination() 157 userDestination.id = 1 158 val idArgument = NavArgument.Builder().setType(NavType.IntType).build() 159 userDestination.addArgument("id", idArgument) 160 userDestination.addDeepLink("www.example.com/users/{id}") 161 graph.addDestination(userDestination) 162 163 val postDestination = 164 navigatorProvider.getNavigator(NoOpNavigator::class.java).createDestination() 165 postDestination.id = 2 166 val postIdArgument = NavArgument.Builder().setType(NavType.IntType).build() 167 postDestination.addArgument("id", idArgument) 168 postDestination.addArgument("postId", postIdArgument) 169 postDestination.addDeepLink("www.example.com/users/{id}/posts/{postId}") 170 graph.addDestination(postDestination) 171 172 val match = graph.matchDeepLink(Uri.parse("https://www.example.com/users/43/posts/99")) 173 174 assertWithMessage("Deep link should match").that(match).isNotNull() 175 176 assertWithMessage("Deep link should point to correct destination") 177 .that(match?.destination) 178 .isSameInstanceAs(postDestination) 179 assertWithMessage("Deep link should extract id argument correctly") 180 .that(match?.matchingArgs?.getInt("id")) 181 .isEqualTo(43) 182 assertWithMessage("Deep link should extract postId argument correctly") 183 .that(match?.matchingArgs?.getInt("postId")) 184 .isEqualTo(99) 185 } 186 187 @Test matchDeepLinkBestMatchPathWildcardnull188 fun matchDeepLinkBestMatchPathWildcard() { 189 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 190 val graph = 191 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 192 193 val testAction = "test.action" 194 val navDeepLink1 = navDeepLink { 195 action = testAction 196 uriPattern = "www.example.com/{id}" 197 } 198 val navDeepLink2 = navDeepLink { 199 action = testAction 200 uriPattern = "www.example.com/{id}/.*" 201 } 202 graph.addDeepLink(navDeepLink1) 203 graph.addDeepLink(navDeepLink2) 204 graph.addArgument("id", intArgument(defaultValue = -1)) 205 206 val intArg = 2 207 val deepLinkRequest = 208 NavDeepLinkRequest( 209 Uri.parse("https://www.example.com/$intArg/wildCardMatch"), 210 testAction, 211 null 212 ) 213 val match = graph.matchDeepLink(deepLinkRequest) 214 215 assertWithMessage("Deep link should match").that(match).isNotNull() 216 assertThat(match?.matchingArgs).isNotNull() 217 assertThat(match?.matchingArgs?.getInt("id")).isEqualTo(intArg) 218 } 219 220 @Test matchDeepLinkSharedActionBestMatchnull221 fun matchDeepLinkSharedActionBestMatch() { 222 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 223 val graph = 224 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 225 226 val testAction = "test.action" 227 val navDeepLink1 = navDeepLink { 228 action = testAction 229 uriPattern = "www.example.com/{id}" 230 } 231 val navDeepLink2 = navDeepLink { 232 action = testAction 233 uriPattern = "www.differentUrl.com" 234 } 235 graph.addDeepLink(navDeepLink1) 236 graph.addDeepLink(navDeepLink2) 237 graph.addArgument("id", intArgument(defaultValue = -1)) 238 239 val intArg = 2 240 val deepLinkRequest = 241 NavDeepLinkRequest(Uri.parse("https://www.example.com/$intArg"), testAction, null) 242 val match = graph.matchDeepLink(deepLinkRequest) 243 244 assertWithMessage("Deep link should match").that(match).isNotNull() 245 assertThat(match?.matchingArgs).isNotNull() 246 assertThat(match?.matchingArgs?.getInt("id")).isEqualTo(intArg) 247 } 248 249 @Test matchDeepLinkSharedActionBestMatchPathOverQuerynull250 fun matchDeepLinkSharedActionBestMatchPathOverQuery() { 251 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 252 val graph = 253 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 254 255 val navDeepLink1 = navDeepLink { uriPattern = "www.example.com/path/token={id}" } 256 val navDeepLink2 = navDeepLink { uriPattern = "www.example.com/path?token={id}" } 257 graph.addDeepLink(navDeepLink1) 258 graph.addDeepLink(navDeepLink2) 259 graph.addArgument("id", intArgument(defaultValue = -1)) 260 261 val intArg = 2 262 val deepLinkRequest = 263 NavDeepLinkRequest(Uri.parse("https://www.example.com/path/token=$intArg"), null, null) 264 val match = graph.matchDeepLink(deepLinkRequest) 265 266 assertWithMessage("Deep link should match").that(match).isNotNull() 267 assertThat(match?.matchingArgs).isNotNull() 268 assertThat(match?.matchingArgs?.getInt("id")).isEqualTo(intArg) 269 } 270 271 @Test matchDeepLinkSharedActionBestMatchPathWildCardnull272 fun matchDeepLinkSharedActionBestMatchPathWildCard() { 273 val navigatorProvider = NavigatorProvider().apply { addNavigator(NavGraphNavigator(this)) } 274 val graph = 275 navigatorProvider.getNavigator(NavGraphNavigator::class.java).createDestination() 276 277 val testAction = "test.action" 278 val navDeepLink1 = navDeepLink { 279 action = testAction 280 uriPattern = "www.example.com/path?token={id}" 281 } 282 val navDeepLink2 = navDeepLink { 283 action = testAction 284 uriPattern = "www.example.com/path/.*token={id}" 285 } 286 graph.addDeepLink(navDeepLink1) 287 graph.addDeepLink(navDeepLink2) 288 graph.addArgument("id", intArgument(defaultValue = -1)) 289 290 val intArg = 2 291 val deepLinkRequest = 292 NavDeepLinkRequest( 293 Uri.parse("https://www.example.com/path/wildCard?token=$intArg"), 294 testAction, 295 null 296 ) 297 val match = graph.matchDeepLink(deepLinkRequest) 298 299 assertWithMessage("Deep link should match").that(match).isNotNull() 300 assertThat(match?.matchingArgs).isNotNull() 301 assertThat(match?.matchingArgs?.getInt("id")).isEqualTo(intArg) 302 } 303 304 @Test toStringStartDestIdOnlynull305 fun toStringStartDestIdOnly() { 306 val navigatorProvider = 307 NavigatorProvider().apply { 308 addNavigator(NavGraphNavigator(this)) 309 addNavigator(NoOpNavigator()) 310 } 311 val graph = 312 navigatorProvider 313 .getNavigator(NavGraphNavigator::class.java) 314 .createDestination() 315 .apply { 316 id = GRAPH_ID 317 label = GRAPH_LABEL 318 setStartDestination(DESTINATION_ID) 319 } 320 val expected = 321 "NavGraph(0x${GRAPH_ID.toString(16)}) label=$GRAPH_LABEL " + 322 "startDestination=0x${DESTINATION_ID.toString(16)}" 323 assertThat(graph.toString()).isEqualTo(expected) 324 } 325 326 @Test toStringStartDestRouteOnlynull327 fun toStringStartDestRouteOnly() { 328 val navigatorProvider = 329 NavigatorProvider().apply { 330 addNavigator(NavGraphNavigator(this)) 331 addNavigator(NoOpNavigator()) 332 } 333 val graph = 334 navigatorProvider 335 .getNavigator(NavGraphNavigator::class.java) 336 .createDestination() 337 .apply { 338 route = GRAPH_ROUTE 339 id = GRAPH_ID 340 label = GRAPH_LABEL 341 setStartDestination(DESTINATION_ROUTE) 342 } 343 val expected = 344 "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " + 345 "label=$GRAPH_LABEL startDestination=$DESTINATION_ROUTE" 346 assertThat(graph.toString()).isEqualTo(expected) 347 } 348 349 @Test startDestDisplayNameWithRoutenull350 fun startDestDisplayNameWithRoute() { 351 val navigatorProvider = 352 NavigatorProvider().apply { 353 addNavigator(NavGraphNavigator(this)) 354 addNavigator(NoOpNavigator()) 355 } 356 val graph = 357 navigatorProvider 358 .getNavigator(NavGraphNavigator::class.java) 359 .createDestination() 360 .apply { 361 route = GRAPH_ROUTE 362 id = GRAPH_ID 363 label = GRAPH_LABEL 364 setStartDestination(DESTINATION_ROUTE) 365 } 366 assertThat(graph.startDestDisplayName).isEqualTo(DESTINATION_ROUTE) 367 } 368 369 @Test toStringStartDestInNodesnull370 fun toStringStartDestInNodes() { 371 val navigatorProvider = 372 NavigatorProvider().apply { 373 addNavigator(NavGraphNavigator(this)) 374 addNavigator(NoOpNavigator()) 375 } 376 val destination = 377 navigatorProvider.getNavigator(NoOpNavigator::class.java).createDestination().apply { 378 id = DESTINATION_ID 379 label = DESTINATION_LABEL 380 } 381 val graph = 382 navigatorProvider 383 .getNavigator(NavGraphNavigator::class.java) 384 .createDestination() 385 .apply { 386 id = GRAPH_ID 387 label = GRAPH_LABEL 388 setStartDestination(DESTINATION_ID) 389 addDestination(destination) 390 } 391 val expected = 392 "NavGraph(0x${GRAPH_ID.toString(16)}) label=$GRAPH_LABEL " + 393 "startDestination={NavDestination(0x${DESTINATION_ID.toString(16)}) " + 394 "label=$DESTINATION_LABEL}" 395 assertThat(graph.toString()).isEqualTo(expected) 396 } 397 398 @Test toStringStartDestInNodesRoutenull399 fun toStringStartDestInNodesRoute() { 400 val navigatorProvider = 401 NavigatorProvider().apply { 402 addNavigator(NavGraphNavigator(this)) 403 addNavigator(NoOpNavigator()) 404 } 405 val destination = 406 navigatorProvider.getNavigator(NoOpNavigator::class.java).createDestination().apply { 407 route = DESTINATION_ROUTE 408 id = DESTINATION_ID 409 label = DESTINATION_LABEL 410 } 411 val graph = 412 navigatorProvider 413 .getNavigator(NavGraphNavigator::class.java) 414 .createDestination() 415 .apply { 416 route = GRAPH_ROUTE 417 id = GRAPH_ID 418 label = GRAPH_LABEL 419 setStartDestination(DESTINATION_ROUTE) 420 setStartDestination(DESTINATION_ID) 421 addDestination(destination) 422 } 423 val expected = 424 "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " + 425 "label=$GRAPH_LABEL " + 426 "startDestination={NavDestination(0x${DESTINATION_ID.toString(16)}) " + 427 "route=$DESTINATION_ROUTE label=$DESTINATION_LABEL}" 428 assertThat(graph.toString()).isEqualTo(expected) 429 } 430 431 @Test toStringStartDestInNodesRouteWithStartDestIDnull432 fun toStringStartDestInNodesRouteWithStartDestID() { 433 val navigatorProvider = 434 NavigatorProvider().apply { 435 addNavigator(NavGraphNavigator(this)) 436 addNavigator(NoOpNavigator()) 437 } 438 val destination = 439 navigatorProvider.getNavigator(NoOpNavigator::class.java).createDestination().apply { 440 route = DESTINATION_ROUTE 441 label = DESTINATION_LABEL 442 } 443 val graph = 444 navigatorProvider 445 .getNavigator(NavGraphNavigator::class.java) 446 .createDestination() 447 .apply { 448 route = GRAPH_ROUTE 449 id = GRAPH_ID 450 label = GRAPH_LABEL 451 setStartDestination(DESTINATION_ROUTE) 452 setStartDestination(DESTINATION_ID) 453 addDestination(destination) 454 } 455 val expected = 456 "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " + 457 "label=$GRAPH_LABEL startDestination=0x${DESTINATION_ID.toString(16)}" 458 assertThat(graph.toString()).isEqualTo(expected) 459 } 460 461 @Test toStringStartDestInNodesRouteWithIDnull462 fun toStringStartDestInNodesRouteWithID() { 463 val navigatorProvider = 464 NavigatorProvider().apply { 465 addNavigator(NavGraphNavigator(this)) 466 addNavigator(NoOpNavigator()) 467 } 468 val destination = 469 navigatorProvider.getNavigator(NoOpNavigator::class.java).createDestination().apply { 470 route = DESTINATION_ROUTE 471 id = DESTINATION_ID 472 label = DESTINATION_LABEL 473 } 474 val graph = 475 navigatorProvider 476 .getNavigator(NavGraphNavigator::class.java) 477 .createDestination() 478 .apply { 479 route = GRAPH_ROUTE 480 id = GRAPH_ID 481 label = GRAPH_LABEL 482 setStartDestination(DESTINATION_ROUTE) 483 addDestination(destination) 484 } 485 // even though id was set after route, it should still be able to find the destination 486 // based on route 487 val expected = 488 "NavGraph(0x${GRAPH_ID.toString(16)}) route=$GRAPH_ROUTE " + 489 "label=$GRAPH_LABEL startDestination={$destination}" 490 assertThat(graph.toString()).isEqualTo(expected) 491 } 492 } 493