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
17package androidx.navigation;
18
19import static org.hamcrest.CoreMatchers.is;
20import static org.hamcrest.CoreMatchers.notNullValue;
21import static org.hamcrest.CoreMatchers.nullValue;
22import static org.hamcrest.MatcherAssert.assertThat;
23
24import android.content.Context;
25import android.content.Intent;
26import android.os.Bundle;
27import android.os.Parcel;
28import android.support.test.InstrumentationRegistry;
29import android.support.test.filters.SmallTest;
30import android.support.v4.app.TaskStackBuilder;
31
32import androidx.navigation.test.R;
33import androidx.navigation.testing.TestNavigator;
34
35import org.junit.Assert;
36import org.junit.Test;
37
38@SmallTest
39public class NavControllerTest {
40    private static final String TEST_ARG = "test";
41    private static final String TEST_ARG_VALUE = "value";
42    private static final String TEST_OVERRIDDEN_VALUE_ARG = "test_overriden_value";
43    private static final String TEST_OVERRIDDEN_VALUE_ARG_VALUE = "override";
44
45    @Test
46    public void testStartDestination() {
47        NavController navController = createNavController();
48        navController.setGraph(R.navigation.nav_start_destination);
49        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
50    }
51
52    @Test(expected = IllegalStateException.class)
53    public void testMissingStartDestination() {
54        NavController navController = createNavController();
55        navController.setGraph(R.navigation.nav_missing_start_destination);
56    }
57
58    @Test(expected = IllegalArgumentException.class)
59    public void testInvalidStartDestination() {
60        NavController navController = createNavController();
61        navController.setGraph(R.navigation.nav_invalid_start_destination);
62    }
63
64    @Test
65    public void testNestedStartDestination() {
66        NavController navController = createNavController();
67        navController.setGraph(R.navigation.nav_nested_start_destination);
68        assertThat(navController.getCurrentDestination().getId(), is(R.id.nested_test));
69    }
70
71    @Test
72    public void testSetGraph() {
73        NavController navController = createNavController();
74        assertThat(navController.getGraph(), is(nullValue(NavGraph.class)));
75
76        navController.setGraph(R.navigation.nav_start_destination);
77        assertThat(navController.getGraph(), is(notNullValue(NavGraph.class)));
78        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
79    }
80
81    @Test
82    public void testNavigate() {
83        NavController navController = createNavController();
84        navController.setGraph(R.navigation.nav_simple);
85        TestNavigator navigator = navController.getNavigatorProvider()
86                .getNavigator(TestNavigator.class);
87        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
88        assertThat(navigator.mBackStack.size(), is(1));
89
90        navController.navigate(R.id.second_test);
91        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
92        assertThat(navigator.mBackStack.size(), is(2));
93    }
94
95    @Test
96    public void testSaveRestoreStateXml() {
97        Context context = InstrumentationRegistry.getTargetContext();
98        NavController navController = new NavController(context);
99        TestNavigator navigator = new TestNavigator();
100        navController.getNavigatorProvider().addNavigator(navigator);
101        navController.setGraph(R.navigation.nav_simple);
102        navController.navigate(R.id.second_test);
103
104        Bundle savedState = navController.saveState();
105        navController = new NavController(context);
106        navController.getNavigatorProvider().addNavigator(navigator);
107
108        // Restore state should automatically re-inflate the graph
109        // Since the graph has a set id
110        navController.restoreState(savedState);
111        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
112        assertThat(navigator.mBackStack.size(), is(2));
113    }
114
115    @Test
116    public void testSaveRestoreStateProgrammatic() {
117        Context context = InstrumentationRegistry.getTargetContext();
118        NavController navController = new NavController(context);
119        TestNavigator navigator = new TestNavigator();
120        navController.getNavigatorProvider().addNavigator(navigator);
121        NavGraph graph = new NavInflater(context, navController.getNavigatorProvider())
122                .inflate(R.navigation.nav_simple);
123        navController.setGraph(graph);
124        navController.navigate(R.id.second_test);
125
126        Bundle savedState = navController.saveState();
127        navController = new NavController(context);
128        navController.getNavigatorProvider().addNavigator(navigator);
129
130        // Restore state doesn't recreate any graph
131        navController.restoreState(savedState);
132        assertThat(navController.getGraph(), is(nullValue(NavGraph.class)));
133
134        // Explicitly setting a graph then restores the state
135        navController.setGraph(graph);
136        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
137        assertThat(navigator.mBackStack.size(), is(2));
138    }
139
140    @Test
141    public void testNavigateWithNoDefaultValue() {
142        Bundle returnedArgs = navigateWithArgs(null);
143
144        // Test that arguments without a default value aren't passed through at all
145        assertThat(returnedArgs.containsKey("test_no_default_value"), is(false));
146    }
147
148    @Test
149    public void testNavigateWithDefaultArgs() {
150        Bundle returnedArgs = navigateWithArgs(null);
151
152        // Test that default values are passed through
153        assertThat(returnedArgs.getString("test_default_value"), is("default"));
154    }
155
156    @Test
157    public void testNavigateWithArgs() {
158        Bundle args = new Bundle();
159        args.putString(TEST_ARG, TEST_ARG_VALUE);
160        Bundle returnedArgs = navigateWithArgs(args);
161
162        // Test that programmatically constructed arguments are passed through
163        assertThat(returnedArgs.getString(TEST_ARG), is(TEST_ARG_VALUE));
164    }
165
166    @Test
167    public void testNavigateWithOverriddenDefaultArgs() {
168        Bundle args = new Bundle();
169        args.putString(TEST_OVERRIDDEN_VALUE_ARG, TEST_OVERRIDDEN_VALUE_ARG_VALUE);
170        Bundle returnedArgs = navigateWithArgs(args);
171
172        // Test that default values can be overridden by programmatic values
173        assertThat(returnedArgs.getString(TEST_OVERRIDDEN_VALUE_ARG),
174                is(TEST_OVERRIDDEN_VALUE_ARG_VALUE));
175    }
176
177    private Bundle navigateWithArgs(Bundle args) {
178        NavController navController = createNavController();
179        navController.setGraph(R.navigation.nav_arguments);
180
181        navController.navigate(R.id.second_test, args);
182
183        TestNavigator navigator = navController.getNavigatorProvider()
184                .getNavigator(TestNavigator.class);
185        args = navigator.mBackStack.peekLast().second;
186        assertThat(args, is(notNullValue(Bundle.class)));
187
188        return args;
189    }
190
191    @Test
192    public void testNavigateThenPop() {
193        NavController navController = createNavController();
194        navController.setGraph(R.navigation.nav_simple);
195        TestNavigator navigator = navController.getNavigatorProvider()
196                .getNavigator(TestNavigator.class);
197        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
198        assertThat(navigator.mBackStack.size(), is(1));
199
200        navController.navigate(R.id.second_test);
201        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
202        assertThat(navigator.mBackStack.size(), is(2));
203
204        navController.popBackStack();
205        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
206        assertThat(navigator.mBackStack.size(), is(1));
207    }
208
209    @Test
210    public void testNavigateFromNestedThenNavigatorInstigatedPop() {
211        NavController navController = createNavController();
212        navController.setGraph(R.navigation.nav_nested_start_destination);
213        TestNavigator navigator = navController.getNavigatorProvider()
214                .getNavigator(TestNavigator.class);
215        assertThat(navController.getCurrentDestination().getId(), is(R.id.nested_test));
216        assertThat(navigator.mBackStack.size(), is(1));
217
218        navController.navigate(R.id.second_test);
219        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
220        assertThat(navigator.mBackStack.size(), is(2));
221
222        // A Navigator can pop a destination off its own back stack
223        // then inform the NavController via dispatchOnNavigatorNavigated
224        navigator.mBackStack.removeLast();
225        NavDestination newDestination = navigator.mBackStack.peekLast().first;
226        assertThat(newDestination, is(notNullValue()));
227        navigator.dispatchOnNavigatorNavigated(newDestination.getId(),
228                Navigator.BACK_STACK_DESTINATION_POPPED);
229        assertThat(navController.getCurrentDestination().getId(), is(R.id.nested_test));
230        assertThat(navigator.mBackStack.size(), is(1));
231    }
232
233    @Test
234    public void testNavigateThenNavigateUp() {
235        NavController navController = createNavController();
236        navController.setGraph(R.navigation.nav_simple);
237        TestNavigator navigator = navController.getNavigatorProvider()
238                .getNavigator(TestNavigator.class);
239        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
240        assertThat(navigator.mBackStack.size(), is(1));
241
242        navController.navigate(R.id.second_test);
243        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
244        assertThat(navigator.mBackStack.size(), is(2));
245
246        // This should function identically to popBackStack()
247        navController.navigateUp();
248        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
249        assertThat(navigator.mBackStack.size(), is(1));
250    }
251
252    @Test
253    public void testNavigateViaAction() {
254        NavController navController = createNavController();
255        navController.setGraph(R.navigation.nav_simple);
256        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
257        TestNavigator navigator = navController.getNavigatorProvider()
258                .getNavigator(TestNavigator.class);
259        assertThat(navigator.mBackStack.size(), is(1));
260
261        navController.navigate(R.id.second);
262        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
263        assertThat(navigator.mBackStack.size(), is(2));
264    }
265
266    @Test
267    public void testNavigateOptionSingleTop() {
268        NavController navController = createNavController();
269        navController.setGraph(R.navigation.nav_simple);
270        navController.navigate(R.id.second_test);
271        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
272        TestNavigator navigator = navController.getNavigatorProvider()
273                .getNavigator(TestNavigator.class);
274        assertThat(navigator.mBackStack.size(), is(2));
275
276        navController.navigate(R.id.self);
277        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
278        assertThat(navigator.mBackStack.size(), is(2));
279    }
280
281    @Test
282    public void testNavigateOptionPopUpToInAction() {
283        NavController navController = createNavController();
284        navController.setGraph(R.navigation.nav_simple);
285        navController.navigate(R.id.second_test);
286        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
287        TestNavigator navigator = navController.getNavigatorProvider()
288                .getNavigator(TestNavigator.class);
289        assertThat(navigator.mBackStack.size(), is(2));
290
291        navController.navigate(R.id.finish);
292        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
293        assertThat(navigator.mBackStack.size(), is(1));
294    }
295
296    @Test
297    public void testNavigateWithPopUpOptionsOnly() {
298        NavController navController = createNavController();
299        navController.setGraph(R.navigation.nav_simple);
300        navController.navigate(R.id.second_test);
301        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
302        TestNavigator navigator = navController.getNavigatorProvider()
303                .getNavigator(TestNavigator.class);
304        assertThat(navigator.mBackStack.size(), is(2));
305
306        NavOptions navOptions = new NavOptions.Builder().setPopUpTo(R.id.start_test, false).build();
307        // the same as to call .navigate(R.id.finish)
308        navController.navigate(0, null, navOptions);
309
310        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
311        assertThat(navigator.mBackStack.size(), is(1));
312    }
313
314    @Test
315    public void testNoDestinationNoPopUpTo() {
316        NavController navController = createNavController();
317        navController.setGraph(R.navigation.nav_simple);
318        NavOptions options = new NavOptions.Builder().build();
319        try {
320            navController.navigate(0, null, options);
321            Assert.fail("navController.navigate must throw");
322        } catch (IllegalArgumentException e) {
323            // expected exception
324        }
325    }
326
327    @Test
328    public void testNavigateOptionPopSelf() {
329        NavController navController = createNavController();
330        navController.setGraph(R.navigation.nav_simple);
331        navController.navigate(R.id.second_test);
332        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
333        TestNavigator navigator = navController.getNavigatorProvider()
334                .getNavigator(TestNavigator.class);
335        assertThat(navigator.mBackStack.size(), is(2));
336
337        navController.navigate(R.id.finish_self);
338        assertThat(navController.getCurrentDestination().getId(), is(R.id.start_test));
339        assertThat(navigator.mBackStack.size(), is(1));
340    }
341
342    @Test
343    public void testNavigateViaActionWithArgs() {
344        NavController navController = createNavController();
345        navController.setGraph(R.navigation.nav_arguments);
346
347        Bundle args = new Bundle();
348        args.putString(TEST_ARG, TEST_ARG_VALUE);
349        args.putString(TEST_OVERRIDDEN_VALUE_ARG, TEST_OVERRIDDEN_VALUE_ARG_VALUE);
350        navController.navigate(R.id.second, args);
351
352        TestNavigator navigator = navController.getNavigatorProvider()
353                .getNavigator(TestNavigator.class);
354        Bundle returnedArgs = navigator.mBackStack.peekLast().second;
355        assertThat(returnedArgs, is(notNullValue(Bundle.class)));
356
357        // Test that arguments without a default value aren't passed through at all
358        assertThat(returnedArgs.containsKey("test_no_default_value"), is(false));
359        // Test that default values are passed through
360        assertThat(returnedArgs.getString("test_default_value"), is("default"));
361        // Test that programmatically constructed arguments are passed through
362        assertThat(returnedArgs.getString(TEST_ARG), is(TEST_ARG_VALUE));
363        // Test that default values can be overridden by programmatic values
364        assertThat(returnedArgs.getString(TEST_OVERRIDDEN_VALUE_ARG),
365                is(TEST_OVERRIDDEN_VALUE_ARG_VALUE));
366    }
367
368    @Test
369    public void testDeepLinkFromNavGraph() {
370        NavController navController = createNavController();
371        navController.setGraph(R.navigation.nav_simple);
372
373        TaskStackBuilder taskStackBuilder = navController.createDeepLink()
374                .setDestination(R.id.second_test)
375                .createTaskStackBuilder();
376        assertThat(taskStackBuilder, is(notNullValue(TaskStackBuilder.class)));
377        assertThat(taskStackBuilder.getIntentCount(), is(1));
378    }
379
380    @Test
381    public void testDeepLinkIntent() {
382        NavController navController = createNavController();
383        navController.setGraph(R.navigation.nav_simple);
384
385        Bundle args = new Bundle();
386        args.putString("test", "test");
387        TaskStackBuilder taskStackBuilder = navController.createDeepLink()
388                .setDestination(R.id.second_test)
389                .setArguments(args)
390                .createTaskStackBuilder();
391
392        Intent intent = taskStackBuilder.editIntentAt(0);
393        assertThat(intent, is(notNullValue()));
394        navController.onHandleDeepLink(intent);
395
396        // The original Intent should be untouched and safely writable to a Parcel
397        Parcel p = Parcel.obtain();
398        intent.writeToParcel(p, 0);
399    }
400
401    private NavController createNavController() {
402        NavController navController = new NavController(InstrumentationRegistry.getTargetContext());
403        TestNavigator navigator = new TestNavigator();
404        navController.getNavigatorProvider().addNavigator(navigator);
405        return navController;
406    }
407}
408