SearchFragmentTest.java revision f050ab93d1e73085af82b5b71bc0f27f951d6d0a
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).updateIndexAsync(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