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