chromium/ui/file_manager/integration_tests/file_manager/search.ts

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import type {ElementObject} from '../prod/file_manager/shared_types.js';
import {addEntries, ENTRIES, EntryType, getCaller, getDateWithDayDiff, pending, repeatUntil, RootPath, sendTestMessage, SharedOption, TestEntryInfo} from '../test_util.js';

import {remoteCall} from './background.js';
import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
import {BASIC_ANDROID_ENTRY_SET, BASIC_DRIVE_ENTRY_SET, BASIC_LOCAL_ENTRY_SET, COMPLEX_DOCUMENTS_PROVIDER_ENTRY_SET, COMPUTERS_ENTRY_SET, NESTED_ENTRY_SET} from './test_data.js';

/**
 * @param appId The ID that identifies the files app.
 * @param type The search option type (location, recency, type).
 * @return The text of the element with 'selected-option' ID.
 */
async function getSelectedOptionText(
    appId: string, type: string): Promise<string> {
  // Force refresh of the element by showing the dropdown menu.
  await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [
    [
      'xf-search-options',
      `xf-select#${type}-selector`,
    ],
  ]);
  // Fetch the current selected item.
  const option = await remoteCall.waitForElement(appId, [
    'xf-search-options',
    `xf-select#${type}-selector`,
    '#selected-option',
  ]);
  return option.text ?? '';
}

/**
 * Tests searching inside Downloads with results.
 */
export async function searchDownloadsWithResults() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');

  // Wait file list to display the search result.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));

  // Check that a11y message for results has been issued.
  const a11yMessages: string[] =
      await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
  chrome.test.assertEq(1, a11yMessages.length, 'Missing a11y message');
  chrome.test.assertEq('Showing results for hello.', a11yMessages[0]);

  return appId;
}

/**
 * Tests searching inside Downloads without results.
 */
export async function searchDownloadsWithNoResults() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Search for name not present among basic entry set.
  await remoteCall.typeSearchText(appId, 'INVALID TERM');

  // Wait file list to display no results.
  await remoteCall.waitForFiles(appId, []);

  // Check that a11y message for no results has been issued.
  const a11yMessages: string[] =
      await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
  chrome.test.assertEq(1, a11yMessages.length, 'Missing a11y message');
  chrome.test.assertEq(
      'There are no results for INVALID TERM.', a11yMessages[0]);
}

/**
 * Tests that clearing the search box announces the A11y.
 */
export async function searchDownloadsClearSearch() {
  // Perform a normal search, to be able to clear the search box.
  const appId = await searchDownloadsWithResults();

  // Click on the clear search button.
  await remoteCall.waitAndClickElement(appId, '#search-box .clear');

  // Wait for the search box to fully collapse.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Wait for file list to display all files.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows(BASIC_LOCAL_ENTRY_SET));

  // Check that a11y message for clearing the search term has been issued.
  const a11yMessages: string[] =
      await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);
  chrome.test.assertEq(
      2, a11yMessages.length,
      `Want 2 messages got ${JSON.stringify(a11yMessages)}`);
  chrome.test.assertEq(
      'Search text cleared, showing all files and folders.', a11yMessages[1]);
}

/**
 * Tests that clearing the search box with keydown crbug.com/910068.
 */
export async function searchDownloadsClearSearchKeyDown() {
  // Perform a normal search, to be able to clear the search box.
  const appId = await searchDownloadsWithResults();

  const clearButton = '#search-box .clear';
  // Wait for clear button.
  await remoteCall.waitForElement(appId, clearButton);

  // Send a enter key to the clear button.
  const enterKey = [clearButton, 'Enter', false, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...enterKey);

  // Check: Search input field is empty.
  const searchInput =
      await remoteCall.waitForElement(appId, '#search-box [type="search"]');
  chrome.test.assertEq('', searchInput.value);

  // Wait until the search button get the focus.
  // Use repeatUntil() here because the focus won't shift to search button
  // until the CSS animation is finished.
  const caller = getCaller();
  await repeatUntil(async () => {
    const activeElement =
        await remoteCall.callRemoteTestUtil<ElementObject|null>(
            'getActiveElement', appId, []);
    if (activeElement && activeElement.attributes['id'] !== 'search-button') {
      return pending(
          caller, 'Expected active element should be search-button, got %s',
          activeElement.attributes['id']);
    }
    return;
  });
}

/**
 * Tests that the search text entry box stays expanded until the end of user
 * interaction.
 */
export async function searchHidingTextEntryField() {
  const entry = ENTRIES.hello;

  // Open Files app on Downloads.
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, [entry], []);

  // Select an entry in the file list.
  await remoteCall.waitUntilSelected(appId, entry.nameText);

  // Click the toolbar search button.
  await remoteCall.waitAndClickElement(appId, '#search-button');

  // Verify the toolbar search text entry box is enabled.
  let textInputElement =
      await remoteCall.waitForElement(appId, ['#search-box cr-input', 'input']);
  chrome.test.assertEq(undefined, textInputElement.attributes['disabled']);

  // Send a 'mousedown' to the toolbar 'delete' button.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeEvent', appId, ['#delete-button', 'mousedown']));

  // Verify the toolbar search text entry is still enabled.
  textInputElement =
      await remoteCall.waitForElement(appId, ['#search-box cr-input', 'input']);
  chrome.test.assertEq(undefined, textInputElement.attributes['disabled']);

  // Send a 'mouseup' to the toolbar 'delete' button.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeEvent', appId, ['#delete-button', 'mouseup']));

  // Verify the toolbar search text entry is still enabled.
  textInputElement =
      await remoteCall.waitForElement(appId, ['#search-box cr-input', 'input']);
  chrome.test.assertEq(undefined, textInputElement.attributes['disabled']);
}

/**
 * Tests that the search box collapses when empty and Tab out of the box.
 */
export async function searchHidingViaTab() {
  const entry = ENTRIES.hello;

  // Open Files app on Downloads.
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, [entry], []);

  // Search box should start collapsed.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Click the toolbar search button.
  await remoteCall.waitAndClickElement(appId, '#search-button');

  // Wait for the search box to expand.
  await remoteCall.waitForElementLost(appId, '#search-wrapper[collapsed]');

  // Verify the search input has focus.
  const input = await remoteCall.callRemoteTestUtil<ElementObject>(
      'deepGetActiveElement', appId, []);
  chrome.test.assertEq(input.attributes['id'], 'input');
  chrome.test.assertEq(input.attributes['aria-label'], 'Search');

  // Send Tab key to focus the next element.
  const result = await sendTestMessage({name: 'dispatchTabKey'});
  chrome.test.assertEq(result, 'tabKeyDispatched', 'Tab key dispatch failure');

  // Check: the search box should collapse.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');
}

/**
 * Tests that clicking the search button expands and collapses the search box.
 */
export async function searchButtonToggles() {
  const entry = ENTRIES.hello;

  // Open Files app on Downloads.
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, [entry], []);

  // Search box should start collapsed.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Measure the width of the search box when it's collapsed.
  const collapsedSearchBox = await remoteCall.waitForElementStyles(
      appId, '#search-wrapper', ['width']);

  // Click the toolbar search button.
  await remoteCall.waitAndClickElement(appId, '#search-button');

  // Wait for the search box to expand.
  await remoteCall.waitForElementLost(appId, '#search-wrapper[collapsed]');

  // Check: The search box width should have increased.
  const caller = getCaller();
  await repeatUntil(async () => {
    const element = await remoteCall.waitForElementStyles(
        appId, '#search-wrapper', ['width']);
    if (collapsedSearchBox.renderedWidth! >= element.renderedWidth!) {
      return pending(caller, 'Waiting search box to expand');
    }
    return;
  });

  // Click the toolbar search button again.
  await remoteCall.waitAndClickElement(appId, '#search-button');

  // Check: the search box should collapse.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Check: the search box width should decrease.
  await repeatUntil(async () => {
    const element = await remoteCall.waitForElementStyles(
        appId, '#search-wrapper', ['width']);
    if (collapsedSearchBox.renderedWidth! < element.renderedWidth!) {
      return pending(caller, 'Waiting search box to collapse');
    }
    return;
  });
}

/**
 * Tests that Files app performs a search at app start up when
 * LaunchParam.searchQuery is specified.
 */
export async function searchQueryLaunchParam() {
  // Open Files app with LaunchParam.searchQuery='gdoc'.
  const query = 'gdoc';
  const appState = {searchQuery: query};
  const appId = await remoteCall.setupAndWaitUntilReady(
      null, BASIC_LOCAL_ENTRY_SET, BASIC_DRIVE_ENTRY_SET, appState);

  // Check: search box should be filled with the query.
  const caller = getCaller();
  await repeatUntil(async () => {
    const searchBoxInput =
        await remoteCall.waitForElement(appId, '#search-box cr-input');
    if (searchBoxInput.value !== query) {
      return pending(caller, 'Waiting search box to be filled with the query.');
    }
    return;
  });

  // Check: "My Drive" directory should be selected because it is the sole
  //        directory that contains query-matched files (*.gdoc).
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForSelectedItemByLabel('My Drive');
  await directoryTree.waitForFocusableItemByLabel('My Drive');

  // Check: Query-matched files should be shown in the files list.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.testDocument,
    ENTRIES.testSharedDocument,
  ]));
}

/**
 * Checks that changing location options correctly filters search results.
 */
export async function searchWithLocationOptions() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Modify the basic entry set by adding nested directories and
  // a copy of the hello entry.
  const nestedHello = ENTRIES.hello.cloneWith({
    targetPath: 'A/hello.txt',
  });
  addEntries(['local'], [ENTRIES.directoryA, nestedHello]);

  // Start in the nested directory, as the default search location
  // is THIS_FOLDER. Expect to find one hello file. Then search on
  // THIS_CHROMEBOOK and expect to find two.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files/Downloads/A');

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');

  // Verify that the search options are visible.
  await remoteCall.waitForElement(appId, 'xf-search-options:not([hidden])');

  // Expect only the nested hello to be found.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    nestedHello,
  ]));

  // Click the second button, which is My files.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 2),
      'Failed to click "My files" location selector');

  // Expect all hello files to be found.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
    nestedHello,
  ]));
}

/**
 * Checks that changing recency options correctly filters search results.
 */
export async function searchWithRecencyOptions() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Modify the basic entry set by adding another hello file with
  // a recent date. We cannot make it today's date as those dates
  // are rendered with 'Today' string rather than actual date string.
  const recentHello = ENTRIES.hello.cloneWith({
    nameText: 'hello-recent.txt',
    lastModifiedTime: new Date().toDateString(),
    targetPath: 'hello-recent.txt',
  });
  await addEntries(['local'], [recentHello]);
  // Unfortunately, today's files use custom date string. Make it so.
  const todayHello = recentHello.cloneWith({
    lastModifiedTime: 'Today 12:00 AM',
  });

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');

  // Expect two files, with no recency restrictions.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
    todayHello,
  ]));

  // Click the fourth button, which is "Last week" option.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'recency', 4),
      'Failed to click "Last week" recency selector');

  // Expect only the recent hello file to be found.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    todayHello,
  ]));
}

/**
 * Checks that when searching Google Drive we correctly match on name, not on
 * contents.
 */
export async function matchDriveFilesByName() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, BASIC_LOCAL_ENTRY_SET, [
        ENTRIES.image2,
      ]);
  await remoteCall.typeSearchText(appId, 'image2');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.image2]));
}

/**
 * Checks that changing recency options correctly filters search results on
 * drive.
 */
export async function searchDriveWithRecencyOptions() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Modify the basic entry set by adding another hello file with
  // a recent date. We cannot make it today's date as those dates
  // so it must be accessed with ['hello'].
  const recentHello = ENTRIES.hello.cloneWith({
    nameText: 'hello-recent.txt',
    lastModifiedTime: getDateWithDayDiff(3),
    targetPath: 'hello-recent.txt',
  });
  await addEntries(['drive'], [recentHello]);

  // Navigate to Google Drive. We are searching "local" directory, which limits
  // search results to Drive.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My Drive');

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');

  // Expect two files, with no recency restrictions.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
    recentHello,
  ]));

  // Click the fourth button, which is "Last week" option.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'recency', 4),
      'Failed to click "Last week" recency selector');

  // Expect only the recent hello file to be found.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    recentHello,
  ]));
}

/**
 * Checks that changing file types options correctly filters local
 * search results.
 */
export async function searchLocalWithTypeOptions() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'o');

  // Expect all basic files, with no type restrictions.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows(BASIC_LOCAL_ENTRY_SET));

  // Click the fifth button, which is "Video" option.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'type', 5),
      'Failed to click "Videos" type selector');

  // Expect only world, which is a video file.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.world,
  ]));
}

/**
 * Checks that changing file types options correctly filters
 * Drive search results.
 */
export async function searchDriveWithTypeOptions() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Navigate to Google Drive; make sure we have the desired files.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My Drive');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows(BASIC_DRIVE_ENTRY_SET));

  // Search the Drive for all files with "b" in their name.
  await remoteCall.typeSearchText(appId, 'b');

  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.desktop,
    ENTRIES.beautiful,
  ]));

  // Click the second button, which is "Audio" option.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'type', 2),
      'Failed to click "Audio" type selector');

  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.beautiful,
  ]));
}

/**
 * @param withPartitions Whether or not USB has partitions.
 * @return The label that can be used to query for elements.
 */
function getUsbVolumeQuery(withPartitions: boolean): string {
  return withPartitions ? 'Drive Label' : 'fake-usb';
}

/**
 * @param appId The ID of the files app under test.
 * @param withPartitions Whether or not USB has partitions.
 */
async function mountUsb(appId: string, withPartitions: boolean) {
  const nameSuffix = withPartitions ? 'UsbWithPartitions' : 'FakeUsb';
  await sendTestMessage({name: `mount${nameSuffix}`});
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemByLabel(getUsbVolumeQuery(withPartitions));
}

/**
 * Checks that the new search correctly finds files on a USB drive.
 */
export async function searchRemovableDevice() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  // Mount a USB with no partitions.
  await mountUsb(appId, false);

  // Navigate to the root of the USB.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByLabel(getUsbVolumeQuery(false));

  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([
        ENTRIES.hello,
      ]),
      {ignoreLastModifiedTime: true});
}

/**
 * Checks that the new search correctly finds files on a USB drive with multiple
 * partitions.
 */
export async function searchPartitionedRemovableDevice() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  await mountUsb(appId, /* withPartitions= */ true);

  // Wait for removable partition-1 to appear in the directory tree.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.expandTreeItemByLabel(
      getUsbVolumeQuery(/* withPartitions= */ true));
  const partitionOne = await directoryTree.waitForItemByLabel('partition-1');
  chrome.test.assertEq(
      'removable', directoryTree.getItemVolumeType(partitionOne));

  // Wait for removable partition-2 to appear in the directory tree.
  const partitionTwo = await directoryTree.waitForItemByLabel('partition-2');
  chrome.test.assertEq(
      'removable', directoryTree.getItemVolumeType(partitionTwo));

  // Navigate to the root of the USB.
  await directoryTree.selectItemByLabel(getUsbVolumeQuery(true));

  // Search for the 'hello' and expect two files; ignore the modified time
  // as these were copied when mounting the USB.
  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([
        ENTRIES.hello,
        ENTRIES.hello,
      ]),
      {ignoreLastModifiedTime: true});
}
/**
 * Checks that the search options are reset to default on folder change.
 */
export async function resetSearchOptionsOnFolderChange() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Type something into the search query to see search options.
  await remoteCall.typeSearchText(appId, 'b');

  // Check the defaults.
  chrome.test.assertEq(
      'Downloads', await getSelectedOptionText(appId, 'location'));
  chrome.test.assertEq(
      'Any time', await getSelectedOptionText(appId, 'recency'));
  chrome.test.assertEq('All types', await getSelectedOptionText(appId, 'type'));

  // Change options.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'type', 2),
      'Failed to change to "Audio" type selector');
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'recency', 4),
      'Failed to change to "Last week" recency selector');

  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files/Downloads/photos');

  // Start search again.
  await remoteCall.typeSearchText(appId, 'b');

  // Check that we are back to defaults.
  chrome.test.assertEq(
      'photos', await getSelectedOptionText(appId, 'location'));
  chrome.test.assertEq(
      'Any time', await getSelectedOptionText(appId, 'recency'));
  chrome.test.assertEq('All types', await getSelectedOptionText(appId, 'type'));
}

/**
 * Checks that we are showing the correct message in breadcrumbs when search is
 * active.
 */
export async function showSearchResultMessageWhenSearching() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Check that we start with My Files
  const beforeSearchPath =
      await remoteCall.callRemoteTestUtil('getBreadcrumbPath', appId, []);
  chrome.test.assertEq('/My files/Downloads', beforeSearchPath);

  // Type something into the search query to start search.
  await remoteCall.typeSearchText(appId, 'b');

  // Wait for the search to fully expand.
  await remoteCall.waitForElementLost(appId, '#search-wrapper[collapsed]');

  // Check that the breadcumb shows that we are searching.
  const duringSearchPath =
      await remoteCall.callRemoteTestUtil('getBreadcrumbPath', appId, []);
  chrome.test.assertEq('/Search results', duringSearchPath);

  // Clear and close search.
  await remoteCall.waitAndClickElement(appId, '#search-box .clear');

  // Wait for the search to fully close.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Expect the path to return to the original path.
  const afterSearchPath =
      await remoteCall.callRemoteTestUtil('getBreadcrumbPath', appId, []);
  chrome.test.assertEq(beforeSearchPath, afterSearchPath);
}

/**
 * Checks that search works correctly when starting in My Files.
 */
export async function searchFromMyFiles() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files');
  const beforeSearchPath =
      await remoteCall.callRemoteTestUtil('getBreadcrumbPath', appId, []);
  chrome.test.assertEq('/My files', beforeSearchPath);

  // Make sure the search returns a matching file even when originating in My
  // Files rather than My Files/Downloads directory.
  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
  ]));

  // Close the search before adding Linux files.
  await remoteCall.waitAndClickElement(appId, '#search-box .clear');
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Add Linux files.
  await remoteCall.mountCrostini(appId);
  // Add some Linux specific files.
  await addEntries(['crostini'], [ENTRIES.debPackage]);
  // Navigate back to /My files
  await directoryTree.navigateToPath('/My files');

  // Search for files containing ack (should include debPackage.
  await remoteCall.typeSearchText(appId, 'ack');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.desktop,
    ENTRIES.desktop,
    ENTRIES.debPackage,
  ]));
}

/**
 * Checks that the selection path correctly reflects paths of elements found by
 * search.
 */
export async function selectionPath() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, NESTED_ENTRY_SET.concat([
        ENTRIES.hello,
        ENTRIES.desktop,
        ENTRIES.deeplyBuriedSmallJpeg,
      ]));
  // Search for files containing 'e'; should be three of those.
  await remoteCall.typeSearchText(appId, 'e');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
    ENTRIES.desktop,
    ENTRIES.deeplyBuriedSmallJpeg,
  ]));
  await remoteCall.waitUntilSelected(appId, ENTRIES.hello.nameText);
  const breadcrumbSingleSelection = await remoteCall.waitForElement(appId, [
    '#search-breadcrumb',
  ]);
  chrome.test.assertFalse(breadcrumbSingleSelection.hidden);
  chrome.test.assertEq(
      'My files/Downloads/' + ENTRIES.hello.nameText,
      breadcrumbSingleSelection.attributes['path']);
  // Select now the desktop entry, too. Two or more selected files,
  // regardless of the directory in which they sit, result in no path.
  await remoteCall.waitAndClickElement(
      appId, `#file-list [file-name="${ENTRIES.desktop.nameText}"]`,
      {ctrl: true});
  const breadcrumbDoubleSelection = await remoteCall.waitForElement(appId, [
    '#search-breadcrumb',
  ]);
  chrome.test.assertTrue(breadcrumbDoubleSelection.hidden);
  chrome.test.assertEq('', breadcrumbDoubleSelection.attributes['path']);
  await remoteCall.waitAndClickElement(
      appId,
      `#file-list [file-name="${ENTRIES.deeplyBuriedSmallJpeg.nameText}"]`,
      {ctrl: true});
  const breadcrumbTripleSelection = await remoteCall.waitForElement(appId, [
    '#search-breadcrumb',
  ]);
  chrome.test.assertTrue(breadcrumbTripleSelection.hidden);
  chrome.test.assertEq('', breadcrumbTripleSelection.attributes['path']);

  // Close search. Select any file. Confirm that the path display is not shown,
  // now that the search is inactive.
  await remoteCall.waitAndClickElement(appId, '#search-box .clear');
  await remoteCall.waitUntilSelected(appId, ENTRIES.hello.nameText);
  const pathDisplayWhileNotSearching = await remoteCall.waitForElement(appId, [
    '#search-breadcrumb',
  ]);
  chrome.test.assertTrue(pathDisplayWhileNotSearching.hidden);
}

/**
 * Checks that we correctly traverse search hierarchy. If you start searching in
 * a local folder, the search should search it and its subfolders only. If we
 * change the location to be the root directory, it should correctly search any
 * folders (including Linux and Playfiles, if necessary) that are visually
 * under the root folder. Finally, search everywhere should search everything
 * we can search (Google Doc, removable drives, local file syste, * etc.).
 */
export async function searchHierarchy() {
  // hello file stored in My files/Downloads/photos.
  const photosHello = ENTRIES.hello.cloneWith({
    targetPath: 'photos/photos-hello.txt',
    nameText: 'photos-hello.txt',
  });
  // hello file stored in My files
  const myFilesHello = ENTRIES.hello.cloneWith({
    targetPath: 'my-files-hello.txt',
    nameText: 'my-files-hello.txt',
  });
  // hello file stored on Linux.
  const linuxHello = ENTRIES.hello.cloneWith({
    targetPath: 'linux-hello.txt',
    nameText: 'linux-hello.txt',
  });
  // hello file stored on a removable drive.
  const usbHello = ENTRIES.hello.cloneWith({
    targetPath: 'usb-hello.txt',
    nameText: 'usb-hello.txt',
  });
  // hello file stored on Google Drive.
  const driveHello = ENTRIES.hello.cloneWith({
    targetPath: 'drive-hello.txt',
    nameText: 'drive-hello.txt',
  });
  // hello file stored in playfiles.
  const playfilesHello = ENTRIES.hello.cloneWith({
    targetPath: 'Documents/playfile-hello.txt',
    nameText: 'playfile-hello.txt',
  });

  // Set up the app. This creates entries in My files and Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [myFilesHello, ENTRIES.photos, photosHello],
      [driveHello]);

  // Mount USBs.
  await mountUsb(appId, false);

  // Add Linux files.
  await remoteCall.mountCrostini(appId);

  // Add custom hello files to Linux, USB, and PlayFiles.
  await addEntries(['android_files'], BASIC_ANDROID_ENTRY_SET.concat([
    ENTRIES.directoryDocuments,
    playfilesHello,
  ]));
  await addEntries(['usb'], [usbHello]);
  await addEntries(['crostini'], [linuxHello]);

  // Move to a nested directory under My files.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files/Downloads/photos');

  // Expect photosHello, as the only result when searching in photos.
  await remoteCall.typeSearchText(appId, '-hello.txt');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([photosHello]),
      {ignoreLastModifiedTime: true});

  // Select the second button, which is root directory (My Fles in our case).
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 2),
      'Failed to click "My files" location selector');

  // Expect files from My files, Play files and Linux files.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([
        myFilesHello,
        photosHello,
        linuxHello,
        playfilesHello,
      ]),
      {ignoreLastModifiedTime: true});

  // Select the first button, which is Everywhere.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 1),
      'Failed to click "Everywhere" location selector');

  // Expect files from My files, Play files, Linux files, USB, and Drive.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([
        myFilesHello,
        photosHello,
        linuxHello,
        playfilesHello,
        driveHello,
        usbHello,
      ]),
      {ignoreLastModifiedTime: true});
}

/**
 * Checks that search is not visible when in the Trash volume.
 */
export async function hideSearchInTrash() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  // Make sure that the search button is visible in Downloads.
  await remoteCall.waitForElement(appId, '#search-button');
  let searchButton = await remoteCall.waitForElementStyles(
      appId, ['#search-button'], ['display']);
  chrome.test.assertTrue(searchButton.styles!['display'] !== 'none');

  // Navigate to Trash and confirm that the search button is now hidden.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/Trash');
  searchButton = await remoteCall.waitForElementStyles(
      appId, ['#search-button'], ['visibility']);
  chrome.test.assertTrue(searchButton.styles!['visibility'] === 'hidden');

  // Try to use keyboard shortcuts Ctrl+F to launch search anyway.
  const ctrlF = ['#file-list', 'f', true, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...ctrlF);

  const searchWrapper =
      await remoteCall.waitForElement(appId, ['#search-wrapper']);
  // Confirm that search wrapper is still in collapsed state.
  chrome.test.assertEq(searchWrapper.attributes['collapsed'], '');

  // Go back to Downloads and confirm that the search button is visible again.
  await directoryTree.navigateToPath('/My files/Downloads');
  searchButton = await remoteCall.waitForElementStyles(
      appId, ['#search-button'], ['visibility']);
  chrome.test.assertTrue(searchButton.styles!['visibility'] !== 'hidden');

  // Make sure that search still works.
  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));
}

/**
 * Checks that files in trash do not appear in the search results when trash
 * is enabled, and appear when it is disabled.
 */
export async function searchTrashedFiles() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');
  // Confirm that we can find hello.txt.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));

  // Clear and close search.
  await remoteCall.waitAndClickElement(appId, '#search-box .clear');

  // Select hello.txt.
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="hello.txt"]');
  // Delete hello.txt and wait for it to be moved to trash.
  await remoteCall.clickTrashButton(appId);

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');
  // Confirm that we cannot find it.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([]));

  // Disable trash.
  await sendTestMessage({name: 'setTrashEnabled', enabled: false});

  // Search for all files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');
  // Confirm that we cannot find it because these files are under .Trash which
  // won't be shown unless "Show hidden files" is on.
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([]));
  // Turn on "Show hidden files" in the menu.
  await remoteCall.showHiddenFiles(appId);
  // Confirm that we can find it. We also expect trashinfo file to appear.
  const helloTrashinfo = ENTRIES.hello.cloneWith({
    nameText: 'hello.txt.trashinfo',
    typeText: 'TRASHINFO file',
  });
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello, helloTrashinfo]),
      {ignoreLastModifiedTime: true, ignoreFileSize: true});
}

/**
 * Checcks that finding files directly in Shared with me, or in folders nested
 * in Shared with me, works.
 */
export async function searchSharedWithMe() {
  // Create a shared file for nested directory. It must have SHARED_WITH_ME
  // attribute on it, as NESTED_SHARED_WITH_ME does not have shared metadata
  // set on it.
  const nestedTestSharedFile = ENTRIES.sharedWithMeDirectoryFile.cloneWith({
    sharedOption: SharedOption.SHARED_WITH_ME,
    targetPath: 'Shared Directory/nested.txt',
    nameText: 'nested.txt',
  });
  // Open Files app on Drive containing "Shared with me" file entries.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE, [], [
    ENTRIES.testSharedFile,
    ENTRIES.sharedWithMeDirectory,
    nestedTestSharedFile,
  ]);

  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/Shared with me');

  // Find the specific file, test.txt
  await remoteCall.typeSearchText(appId, 'test.txt');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.testSharedFile]));

  // Search for the file nested in the shared directory.
  await remoteCall.typeSearchText(appId, 'nested.txt');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([nestedTestSharedFile]));
}

/**
 * Checks that the simple search from the root of a documents provider directory
 * works. No file category or modified time filters are used.
 */
export async function searchDocumentsProvider() {
  await addEntries(
      ['documents_provider'], COMPLEX_DOCUMENTS_PROVIDER_ENTRY_SET);
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Wait for DocumentsProvider to mount and Verify that the files are visible.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemToHaveChildrenByType(
      'documents_provider', /* hasChildren= */ true);

  // Search for all files with "nam" in their name.
  await directoryTree.navigateToPath('/DocumentsProvider');
  await remoteCall.typeSearchText(appId, 'nam');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.renamableFile]),
      {ignoreLastModifiedTime: true});
}

/**
 * Checks that changing file types options correctly filters
 * files exposed via Documents Provider.
 */
export async function searchDocumentsProviderWithTypeOptions() {
  await addEntries(
      ['documents_provider'], COMPLEX_DOCUMENTS_PROVIDER_ENTRY_SET);
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Wait for DocumentsProvider to mount and Verify that the files are visible.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemToHaveChildrenByType(
      'documents_provider', /* hasChildren= */ true);
  await directoryTree.navigateToPath('/DocumentsProvider');

  // Search the DocumentsProvider folder for files with "File" in their name.
  await remoteCall.typeSearchText(appId, 'File');

  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.readOnlyFile,
    ENTRIES.deletableFile,
    ENTRIES.renamableFile,
  ]));

  // Click the second button, which is "Images" option.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'type', 4),
      'Failed to click "Images" type selector');

  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.readOnlyFile,
  ]));
}

/**
 * Checks that changing file types options correctly filters
 * files exposed via Documents Provider.
 */
export async function searchDocumentsProviderWithRecencyOptions() {
  const recentHellos = [];
  for (let i = 0; i < 10; ++i) {
    // The lastModifiedTime is set so that we cannot hit days close to current
    // date. These often are rephrased as Today, Yesterday. We use 6, so that at
    // most we get something that is 4 days old. This way, we avoid phrases
    // such as Today, Yesterday, or Two Days Ago (which exist in Japanese,
    // Polish and other languages).
    recentHellos.push(ENTRIES.hello.cloneWith({
      nameText: `hello-recent-${i}.txt`,
      lastModifiedTime: getDateWithDayDiff(6 - (i % 3)),
      targetPath: `hello-recent-${i}.txt`,
    }));
  }
  await addEntries(
      ['documents_provider'],
      COMPLEX_DOCUMENTS_PROVIDER_ENTRY_SET.concat(recentHellos));

  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Wait for DocumentsProvider to mount and Verify that the files are visible.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemToHaveChildrenByType(
      'documents_provider', /* hasChildren= */ true);
  await directoryTree.navigateToPath('/DocumentsProvider');

  // Search the DocumentsProvider for files with "hello" in their name.
  await remoteCall.typeSearchText(appId, 'hello');

  // Expect the original hello and recent hello files to be present.
  await remoteCall.waitForFiles(
      appId,
      TestEntryInfo.getExpectedRows(recentHellos.concat([ENTRIES.hello])));

  // Click the fourth button, which is "Last week" option.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'recency', 4),
      'Failed to click "Last week" recency selector');

  // Expect all rececent hello files to be present.
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows(recentHellos));
}

/**
 * Checks that search works on volumes mounted via fileSystemProvider.
 */
export async function searchFileSystemProvider() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  await sendTestMessage({
    name: 'launchProviderExtension',
    manifest: 'manifest_source_device.json',
  });
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForItemByType('provided');
  await directoryTree.navigateToPath('/Test (1)');
  await directoryTree.waitForFocusedItemByType('provided');
  await remoteCall.typeSearchText(appId, 'folder');
  const expectedFolder = new TestEntryInfo({
    type: EntryType.DIRECTORY,
    targetPath: 'folder',
    lastModifiedTime: 'Jan 1, 2000, 1:00 PM',
    nameText: 'folder',
    sizeText: '--',
    typeText: 'Folder',
  });
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([expectedFolder]),
      {ignoreLastModifiedTime: true});
}

/**
 * Test searching images by content. There are two modes supported: search by
 * text contained in the image and search by keywords associtated with
 * objects detected in the image. The first search is known as optical character
 * recorgnition (OCR), the second as image content annotation (ICA). However,
 * from the Files app point of view there is no difference and all it knows is
 * that there are "terms" associated with images processed by the local image
 * search service. So that's all we test: that we can find images by terms
 * associated with them.
 */
export async function searchImageByContent() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello, ENTRIES.desktop, ENTRIES.image3]);

  // Pretend that the desktop image was processed by the local search service
  // and was assigned the keywords 'marsupial' and 'duck'.
  await sendTestMessage({
    name: 'setupImageTerms',
    path: ENTRIES.desktop.targetPath,
    terms: 'marsupial,duck',
  });
  // The second image, image2, had 'ghost' assigned to it.
  await sendTestMessage({
    name: 'setupImageTerms',
    path: ENTRIES.image3.targetPath,
    terms: 'ghost',
  });

  await remoteCall.typeSearchText(appId, 'marsupial');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.desktop]),
      {ignoreLastModifiedTime: true});

  // Search again, using the second term 'duck'.
  await remoteCall.typeSearchText(appId, 'duck');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.desktop]),
      {ignoreLastModifiedTime: true});

  // Search, using the term 'ghost', assigned to image3.
  await remoteCall.typeSearchText(appId, 'ghost');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.image3]),
      {ignoreLastModifiedTime: true});
}

/**
 * Checks that any search, regardless if it has results or not, is closed if we
 * navigate to another directory.
 */
export async function changingDirectoryClosesSearch() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My files/Downloads/photos');
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');
}

/**
 * Check that if we are either at the top directory of Google Drive or in one of
 * the nested directories, we show the correct location. As we always search the
 * entire Google Drive, we should always show My Drive as the selected location.
 */
export async function verifyDriveLocationOption() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE, [], [
    ENTRIES.hello,
    ENTRIES.sharedDirectory,
    ENTRIES.sharedDirectoryFile,
  ]);

  // Navigate to Google Drive; make sure we have the desired files.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath('/My Drive');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.sharedDirectory,
    ENTRIES.hello,
  ]));

  // Search the Drive for all files with "b" in their name.
  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
  ]));

  // Check that the location shows My Drive.
  chrome.test.assertEq(
      'Google Drive', await getSelectedOptionText(appId, 'location'));

  await directoryTree.navigateToPath('/My Drive/Shared');
  await remoteCall.typeSearchText(appId, 'file');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.sharedDirectoryFile,
  ]));
  chrome.test.assertEq(
      'Google Drive', await getSelectedOptionText(appId, 'location'));
}

/*
 * Checks that search with non-current folder in Downloads should unselect the
 * current directory item in the tree.
 */
export async function unselectCurrentDirectoryInTreeOnSearchInDownloads() {
  // Setup default file set within Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForSelectedItemByLabel('Downloads');

  // Search "hello".
  await remoteCall.typeSearchText(appId, 'hello');
  await remoteCall.waitForFiles(
      appId, TestEntryInfo.getExpectedRows([ENTRIES.hello]));
  chrome.test.assertEq(
      'Downloads', await getSelectedOptionText(appId, 'location'));
  // Expect the Downloads folder is still selected.
  await directoryTree.waitForSelectedItemByLabel('Downloads');

  // Change location to "My files".
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 2),
      'Failed to click "My files" location selector');
  // Expect the Downloads folder to be unselected.
  await directoryTree.waitForSelectedItemLostByLabel('Downloads');

  // Change location back to "Downloads".
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 3),
      'Failed to click "Downloads" location selector');
  // Expect the Downloads folder to be selected again.
  await directoryTree.waitForSelectedItemByLabel('Downloads');

  // Change location to "My files" and click the Downloads.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 2),
      'Failed to click "My files" location selector');
  await directoryTree.waitForSelectedItemLostByLabel('Downloads');
  await directoryTree.selectItemByLabel('Downloads');
  // Expect the search will be cleared.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');
}

/**
 * Checks that search with non-root folder in Drive should unselect the current
 * directory item in the tree.
 */
export async function unselectCurrentDirectoryInTreeOnSearchInDrive() {
  // Setup Drive with Computers files.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], COMPUTERS_ENTRY_SET);
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForSelectedItemByLabel('My Drive');

  // Search "txt", both My Drive and Computers folder will be searched.
  await remoteCall.typeSearchText(appId, 'txt');
  await remoteCall.waitForFiles(appId, TestEntryInfo.getExpectedRows([
    ENTRIES.hello,
    ENTRIES.computerAFile,
  ]));
  chrome.test.assertEq(
      'Google Drive', await getSelectedOptionText(appId, 'location'));
  // Expect the My Drive folder is still selected.
  await directoryTree.waitForSelectedItemByLabel('My Drive');

  // Change location to "Everywhere".
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 1),
      'Failed to click "Everywhere" location selector');
  // Expect the My Drive folder to be unselected.
  await directoryTree.waitForSelectedItemLostByLabel('My Drive');

  // Change location back to "Google Drive".
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 2),
      'Failed to click "Google Drive" location selector');
  // Expect the My Drive folder to be selected again.
  await directoryTree.waitForSelectedItemByLabel('My Drive');

  // Change location to "Everywhere" and click the My Drive.
  chrome.test.assertTrue(
      !!await remoteCall.selectSearchOption(appId, 'location', 1),
      'Failed to click "Everywhere" location selector');
  await directoryTree.waitForSelectedItemLostByLabel('My Drive');
  await directoryTree.selectItemByLabel('My Drive');

  // Expect the search will be cleared.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');
}