SearchFragmentTest.java revision dd1dc22dcc48309ee44ebf7ce0f9f3649108584a
1/*
2 * Copyright (C) 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
18package com.android.settings.search;
19
20import android.app.LoaderManager;
21import android.content.Context;
22import android.content.Intent;
23import android.content.Loader;
24import android.os.Bundle;
25import android.util.Pair;
26import android.view.View;
27
28import com.android.internal.logging.nano.MetricsProto;
29import com.android.settings.R;
30import com.android.settings.SettingsActivity;
31import com.android.settings.testutils.SettingsRobolectricTestRunner;
32import com.android.settings.TestConfig;
33import com.android.settings.testutils.DatabaseTestUtils;
34import com.android.settings.testutils.FakeFeatureFactory;
35import com.android.settings.testutils.shadow.SettingsShadowResources;
36
37import org.junit.After;
38import org.junit.Before;
39import org.junit.Test;
40import org.junit.runner.RunWith;
41import org.mockito.Answers;
42import org.mockito.ArgumentCaptor;
43import org.mockito.ArgumentMatcher;
44import org.mockito.Captor;
45import org.mockito.Mock;
46import org.mockito.MockitoAnnotations;
47import org.robolectric.Robolectric;
48import org.robolectric.RuntimeEnvironment;
49import org.robolectric.annotation.Config;
50import org.robolectric.util.ActivityController;
51import org.robolectric.util.ReflectionHelpers;
52
53import java.util.Set;
54
55import static com.google.common.truth.Truth.assertThat;
56import static org.mockito.ArgumentMatchers.nullable;
57import static org.mockito.Matchers.any;
58import static org.mockito.Matchers.anyInt;
59import static org.mockito.Matchers.anyString;
60import static org.mockito.Matchers.argThat;
61import static org.mockito.Matchers.eq;
62import static org.mockito.Mockito.mock;
63import static org.mockito.Mockito.never;
64import static org.mockito.Mockito.spy;
65import static org.mockito.Mockito.times;
66import static org.mockito.Mockito.verify;
67import static org.mockito.Mockito.when;
68
69@RunWith(SettingsRobolectricTestRunner.class)
70@Config(manifest = TestConfig.MANIFEST_PATH,
71        sdk = TestConfig.SDK_VERSION,
72        shadows = {
73                SettingsShadowResources.class,
74                SettingsShadowResources.SettingsShadowTheme.class,
75        })
76public class SearchFragmentTest {
77
78    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
79    private Context mContext;
80    @Mock
81    private DatabaseResultLoader mDatabaseResultLoader;
82    @Mock
83    private InstalledAppResultLoader mInstalledAppResultLoader;
84
85    @Mock
86    private SavedQueryLoader mSavedQueryLoader;
87    @Mock
88    private SavedQueryController mSavedQueryController;
89    @Mock
90    private SearchResultsAdapter mSearchResultsAdapter;
91    @Captor
92    private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
93
94    private FakeFeatureFactory mFeatureFactory;
95
96    @Before
97    public void setUp() {
98        MockitoAnnotations.initMocks(this);
99
100        mFeatureFactory = FakeFeatureFactory.setupForTest(mContext);
101    }
102
103    @After
104    public void tearDown() {
105        DatabaseTestUtils.clearDb(RuntimeEnvironment.application);
106    }
107
108    @Test
109    public void screenRotate_shouldPersistQuery() {
110        when(mFeatureFactory.searchFeatureProvider
111                .getDatabaseSearchLoader(any(Context.class), anyString()))
112                .thenReturn(mDatabaseResultLoader);
113        when(mFeatureFactory.searchFeatureProvider
114                .getInstalledAppSearchLoader(any(Context.class), anyString()))
115                .thenReturn(mInstalledAppResultLoader);
116        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
117                .thenReturn(mSavedQueryLoader);
118
119        final Bundle bundle = new Bundle();
120        final String testQuery = "test";
121        ActivityController<SearchActivity> activityController =
122                Robolectric.buildActivity(SearchActivity.class);
123        activityController.setup();
124        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
125                .findFragmentById(R.id.main_content);
126
127        ReflectionHelpers.setField(fragment, "mShowingSavedQuery", false);
128        fragment.mQuery = testQuery;
129
130        activityController.saveInstanceState(bundle).pause().stop().destroy();
131
132        activityController = Robolectric.buildActivity(SearchActivity.class);
133        activityController.setup(bundle);
134
135        assertThat(fragment.mQuery).isEqualTo(testQuery);
136    }
137
138    @Test
139    public void screenRotateEmptyString_ShouldNotCrash() {
140        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
141                .thenReturn(mSavedQueryLoader);
142
143        final Bundle bundle = new Bundle();
144        ActivityController<SearchActivity> activityController =
145                Robolectric.buildActivity(SearchActivity.class);
146        activityController.setup();
147        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
148                .findFragmentById(R.id.main_content);
149        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
150                .thenReturn(true);
151
152        fragment.mQuery = "";
153
154        activityController.saveInstanceState(bundle).pause().stop().destroy();
155
156        activityController = Robolectric.buildActivity(SearchActivity.class);
157        activityController.setup(bundle);
158
159        verify(mFeatureFactory.searchFeatureProvider, never())
160                .getDatabaseSearchLoader(any(Context.class), anyString());
161        verify(mFeatureFactory.searchFeatureProvider, never())
162                .getInstalledAppSearchLoader(any(Context.class), anyString());
163    }
164
165    @Test
166    public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
167        when(mFeatureFactory.searchFeatureProvider
168                .getDatabaseSearchLoader(any(Context.class), anyString()))
169                .thenReturn(mDatabaseResultLoader);
170        when(mFeatureFactory.searchFeatureProvider
171                .getInstalledAppSearchLoader(any(Context.class), anyString()))
172                .thenReturn(mInstalledAppResultLoader);
173        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
174                .thenReturn(mSavedQueryLoader);
175
176        final String testQuery = "test";
177        ActivityController<SearchActivity> activityController =
178                Robolectric.buildActivity(SearchActivity.class);
179        activityController.setup();
180        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
181                .findFragmentById(R.id.main_content);
182        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
183                .thenReturn(true);
184
185        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
186        fragment.onQueryTextChange(testQuery);
187        activityController.get().onBackPressed();
188
189        activityController.pause().stop().destroy();
190
191        verify(mFeatureFactory.metricsFeatureProvider, never()).action(
192                any(Context.class),
193                eq(MetricsProto.MetricsEvent.ACTION_LEAVE_SEARCH_RESULT_WITHOUT_QUERY));
194        verify(mFeatureFactory.metricsFeatureProvider).histogram(
195                any(Context.class), eq(SearchFragment.RESULT_CLICK_COUNT), eq(0));
196        verify(mFeatureFactory.searchFeatureProvider)
197                .getDatabaseSearchLoader(any(Context.class), anyString());
198        verify(mFeatureFactory.searchFeatureProvider)
199                .getInstalledAppSearchLoader(any(Context.class), anyString());
200        verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
201        assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
202    }
203
204    @Test
205    public void onSearchResultsDisplayed_noResult_shouldShowNoResultView() {
206        ActivityController<SearchActivity> activityController =
207                Robolectric.buildActivity(SearchActivity.class);
208        activityController.setup();
209        SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
210                .findFragmentById(R.id.main_content));
211        fragment.onSearchResultsDisplayed(0 /* count */);
212
213        assertThat(fragment.mNoResultsView.getVisibility()).isEqualTo(View.VISIBLE);
214        verify(mFeatureFactory.metricsFeatureProvider).visible(
215                any(Context.class),
216                anyInt(),
217                eq(MetricsProto.MetricsEvent.SETTINGS_SEARCH_NO_RESULT));
218    }
219
220    @Test
221    public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
222        when(mFeatureFactory.searchFeatureProvider
223                .getDatabaseSearchLoader(any(Context.class), anyString()))
224                .thenReturn(mDatabaseResultLoader);
225        when(mFeatureFactory.searchFeatureProvider
226                .getInstalledAppSearchLoader(any(Context.class), anyString()))
227                .thenReturn(mInstalledAppResultLoader);
228        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
229                .thenReturn(mSavedQueryLoader);
230        ActivityController<SearchActivity> activityController =
231                Robolectric.buildActivity(SearchActivity.class);
232        activityController.setup();
233        SearchFragment fragment = spy((SearchFragment) activityController.get().getFragmentManager()
234                .findFragmentById(R.id.main_content));
235        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
236                .thenReturn(true);
237        ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
238        ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
239        fragment.mQuery = "123";
240
241        fragment.onQueryTextChange("");
242
243        verify(mFeatureFactory.searchFeatureProvider, never())
244                .getDatabaseSearchLoader(any(Context.class), anyString());
245        verify(mFeatureFactory.searchFeatureProvider, never())
246                .getInstalledAppSearchLoader(any(Context.class), anyString());
247        verify(mSavedQueryController).loadSavedQueries();
248        verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
249    }
250
251    @Test
252    public void updateIndex_TriggerOnCreate() {
253        when(mFeatureFactory.searchFeatureProvider
254                .getDatabaseSearchLoader(any(Context.class), anyString()))
255                .thenReturn(mDatabaseResultLoader);
256        when(mFeatureFactory.searchFeatureProvider
257                .getInstalledAppSearchLoader(any(Context.class), anyString()))
258                .thenReturn(mInstalledAppResultLoader);
259        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
260                .thenReturn(mSavedQueryLoader);
261
262        ActivityController<SearchActivity> activityController =
263                Robolectric.buildActivity(SearchActivity.class);
264        activityController.setup();
265        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
266                .findFragmentById(R.id.main_content);
267        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
268                .thenReturn(true);
269
270        fragment.onAttach(null);
271        verify(mFeatureFactory.searchFeatureProvider).updateIndex(any(Context.class),
272                any(IndexingCallback.class));
273    }
274
275    @Test
276    public void syncLoaders_MergeWhenAllLoadersDone() {
277        when(mFeatureFactory.searchFeatureProvider
278                .getDatabaseSearchLoader(any(Context.class), anyString()))
279                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
280        when(mFeatureFactory.searchFeatureProvider
281                .getInstalledAppSearchLoader(any(Context.class), anyString()))
282                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
283        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
284                .thenReturn(mSavedQueryLoader);
285
286        ActivityController<SearchActivity> activityController =
287                Robolectric.buildActivity(SearchActivity.class);
288        activityController.setup();
289
290        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
291                .findFragmentById(R.id.main_content));
292        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
293                .thenReturn(true);
294
295        fragment.onQueryTextChange("non-empty");
296
297        Robolectric.flushForegroundThreadScheduler();
298
299        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(Set.class));
300    }
301
302    @Test
303    public void whenNoQuery_HideFeedbackIsCalled() {
304        when(mFeatureFactory.searchFeatureProvider
305                .getDatabaseSearchLoader(any(Context.class), anyString()))
306                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
307        when(mFeatureFactory.searchFeatureProvider
308                .getInstalledAppSearchLoader(any(Context.class), anyString()))
309                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
310        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
311                .thenReturn(mSavedQueryLoader);
312
313        ActivityController<SearchActivity> activityController =
314                Robolectric.buildActivity(SearchActivity.class);
315        activityController.setup();
316        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
317                .findFragmentById(R.id.main_content));
318        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
319                .thenReturn(true);
320        when(fragment.getLoaderManager()).thenReturn(mock(LoaderManager.class));
321
322        fragment.onQueryTextChange("");
323        Robolectric.flushForegroundThreadScheduler();
324
325        verify(mFeatureFactory.searchFeatureProvider).hideFeedbackButton();
326    }
327
328    @Test
329    public void onLoadFinished_ShowsFeedback() {
330        when(mFeatureFactory.searchFeatureProvider
331                .getDatabaseSearchLoader(any(Context.class), anyString()))
332                .thenReturn(new MockDBLoader(RuntimeEnvironment.application));
333        when(mFeatureFactory.searchFeatureProvider
334                .getInstalledAppSearchLoader(any(Context.class), anyString()))
335                .thenReturn(new MockAppLoader(RuntimeEnvironment.application));
336        when(mFeatureFactory.searchFeatureProvider.getSavedQueryLoader(any(Context.class)))
337                .thenReturn(mSavedQueryLoader);
338        ActivityController<SearchActivity> activityController =
339                Robolectric.buildActivity(SearchActivity.class);
340        activityController.setup();
341        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
342                .findFragmentById(R.id.main_content);
343        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
344                .thenReturn(true);
345
346        fragment.onQueryTextChange("non-empty");
347        Robolectric.flushForegroundThreadScheduler();
348
349        verify(mFeatureFactory.searchFeatureProvider).showFeedbackButton(any(SearchFragment.class),
350                any(View.class));
351    }
352
353    @Test
354    public void preIndexingFinished_isIndexingFinishedFlag_isFalse() {
355        ActivityController<SearchActivity> activityController =
356                Robolectric.buildActivity(SearchActivity.class);
357        activityController.setup();
358        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
359                .findFragmentById(R.id.main_content);
360
361        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
362                .thenReturn(false);
363    }
364
365    @Test
366    public void onIndexingFinished_notShowingSavedQuery_initLoaders() {
367        ActivityController<SearchActivity> activityController =
368                Robolectric.buildActivity(SearchActivity.class);
369        activityController.setup();
370        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
371                .findFragmentById(R.id.main_content));
372        final LoaderManager loaderManager = mock(LoaderManager.class);
373        when(fragment.getLoaderManager()).thenReturn(loaderManager);
374        fragment.mShowingSavedQuery = false;
375        fragment.mQuery = null;
376
377        fragment.onIndexingFinished();
378
379        verify(loaderManager).initLoader(eq(SearchFragment.LOADER_ID_DATABASE),
380                eq(null), any(LoaderManager.LoaderCallbacks.class));
381        verify(loaderManager).initLoader(eq(SearchFragment.LOADER_ID_INSTALLED_APPS),
382                eq(null), any(LoaderManager.LoaderCallbacks.class));
383    }
384
385    @Test
386    public void onIndexingFinished_showingSavedQuery_loadsSavedQueries() {
387        ActivityController<SearchActivity> activityController =
388                Robolectric.buildActivity(SearchActivity.class);
389        activityController.setup();
390        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
391                .findFragmentById(R.id.main_content));
392        fragment.mShowingSavedQuery = true;
393        ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
394
395        fragment.onIndexingFinished();
396
397        verify(fragment.mSavedQueryController).loadSavedQueries();
398    }
399
400    @Test
401    public void onIndexingFinished_noActivity_shouldNotCrash() {
402        ActivityController<SearchActivity> activityController =
403                Robolectric.buildActivity(SearchActivity.class);
404        activityController.setup();
405        SearchFragment fragment = (SearchFragment) spy(activityController.get().getFragmentManager()
406                .findFragmentById(R.id.main_content));
407        when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
408                .thenReturn(true);
409        fragment.mQuery = "bright";
410        ReflectionHelpers.setField(fragment, "mLoaderManager", null);
411        ReflectionHelpers.setField(fragment, "mHost", null);
412
413        fragment.onIndexingFinished();
414        // no crash
415    }
416
417    @Test
418    public void onSearchResultClicked_shouldLogResultMeta() {
419        SearchFragment fragment = new SearchFragment();
420        ReflectionHelpers.setField(fragment, "mMetricsFeatureProvider",
421                mFeatureFactory.metricsFeatureProvider);
422        ReflectionHelpers.setField(fragment, "mSearchFeatureProvider",
423                mFeatureFactory.searchFeatureProvider);
424        ReflectionHelpers.setField(fragment, "mSearchAdapter", mock(SearchResultsAdapter.class));
425        fragment.mSavedQueryController = mock(SavedQueryController.class);
426
427        // Should log result name, result count, clicked rank, etc.
428        final SearchViewHolder resultViewHolder = mock(SearchViewHolder.class);
429        when(resultViewHolder.getClickActionMetricName())
430                .thenReturn(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT);
431        ResultPayload payLoad = new ResultPayload(
432                (new Intent()).putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "test_setting"));
433        SearchResult searchResult = new SearchResult.Builder()
434                .setStableId(payLoad.hashCode())
435                .setPayload(payLoad)
436                .setTitle("setting_title")
437                .build();
438        fragment.onSearchResultClicked(resultViewHolder, searchResult);
439
440        verify(mFeatureFactory.metricsFeatureProvider).action(
441                nullable(Context.class),
442                eq(MetricsProto.MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT),
443                eq("test_setting"),
444                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_COUNT)),
445                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_RESULT_RANK)),
446                argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SEARCH_QUERY_LENGTH)));
447
448        verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
449                nullable(String.class), eq(searchResult));
450    }
451
452    private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag) {
453        return pair -> pair.first == tag;
454    }
455}
456