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.fragment;
18
19import static org.hamcrest.CoreMatchers.is;
20import static org.hamcrest.CoreMatchers.notNullValue;
21import static org.hamcrest.MatcherAssert.assertThat;
22
23import android.app.Instrumentation;
24import android.content.ComponentName;
25import android.content.Intent;
26import android.net.Uri;
27import android.os.Bundle;
28import android.support.annotation.IdRes;
29import android.support.annotation.NavigationRes;
30import android.support.test.InstrumentationRegistry;
31import android.support.test.filters.SmallTest;
32import android.support.test.rule.ActivityTestRule;
33import android.support.v4.app.TaskStackBuilder;
34
35import androidx.navigation.NavController;
36import androidx.navigation.NavDeepLinkBuilder;
37import androidx.navigation.fragment.test.BaseNavigationActivity;
38import androidx.navigation.fragment.test.R;
39import androidx.navigation.testing.TestNavigator;
40
41import org.junit.Before;
42import org.junit.Rule;
43import org.junit.Test;
44
45@SmallTest
46public 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