chromium/ui/file_manager/integration_tests/file_manager/tab_index.ts

// Copyright 2014 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 type {TestEntryInfo} from '../test_util.js';
import {addEntries, RootPath} from '../test_util.js';

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

/** The id attribute of the dismiss button in the educational banner. */
async function getDismissButtonId(appId: string) {
  return await remoteCall.isCrosComponents(appId) ? 'dismiss-button' :
                                                    'dismiss-button-old';
}

/**
 * Tests the focus behavior of the search box.
 */
export async function tabindexSearchBoxFocus() {
  // Open Files app on Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE);

  // Check that the file list has the focus on launch.
  await remoteCall.waitForElement(appId, ['#file-list:focus']);

  // Check that the search UI is in the collapsed state (hidden from the user).
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Press the Ctrl-F key.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeKeyDown', appId, ['body', 'f', true, false, false]));

  // Wait for the search box to fully open. Only once the search wrapper
  // is fully expanded the collapsed attribute is removed.
  await remoteCall.waitForElementLost(appId, '#search-wrapper[collapsed]');

  // Check that the search box has the focus.
  await remoteCall.waitForElement(appId, ['#search-box cr-input:focus-within']);

  // Press the Esc key.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeKeyDown', appId,
      ['#search-box cr-input', 'Escape', false, false, false]));

  // Check that the focus moves to the next button: #view-button.
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'view-button'));
}

/**
 * Tests the tab focus behavior of the Files app when no file is selected.
 */
export async function tabindexFocus() {
  // Open Files app on Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE);

  await remoteCall.isolateBannerForTesting(appId, 'drive-welcome-banner');
  const driveWelcomeLinkQuery = '#banners > drive-welcome-banner:not([hidden])';

  // Check that the file list has the focus on launch.
  await Promise.all([
    remoteCall.waitForElement(appId, ['#file-list:focus']),
    remoteCall.waitForElement(appId, [driveWelcomeLinkQuery]),
  ]);
  const element = await remoteCall.callRemoteTestUtil<ElementObject>(
      'getActiveElement', appId, []);
  chrome.test.assertEq('list', element.attributes['class']);

  // Send Tab key events to cycle through the tabbable elements.
  chrome.test.assertTrue(
      // format: directory-tree#<tree item label>
      await remoteCall.checkNextTabFocus(appId, 'directory-tree#My Drive'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'search-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'view-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sort-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'gear-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'drive-learn-more-button'));
  chrome.test.assertTrue(await remoteCall.checkNextTabFocus(
      appId, await getDismissButtonId(appId)));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sort-direction-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'file-list'));
}

/**
 * Tests the tab focus behavior of the Files app when no file is selected in
 * Downloads directory.
 */
export async function tabindexFocusDownloads() {
  // Open Files app on Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  await remoteCall.isolateBannerForTesting(
      appId, 'holding-space-welcome-banner');

  // Check that the file list has the focus on launch.
  await remoteCall.waitForElement(appId, ['#file-list:focus']);
  const element = await remoteCall.callRemoteTestUtil<ElementObject>(
      'getActiveElement', appId, []);
  chrome.test.assertEq('list', element.attributes['class']);

  // Send Tab key events to cycle through the tabbable elements.
  chrome.test.assertTrue(
      // format: directory-tree#<tree item label>
      await remoteCall.checkNextTabFocus(appId, 'directory-tree#Downloads'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'breadcrumbs'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'search-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'view-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sort-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'gear-button'));
  chrome.test.assertTrue(await remoteCall.checkNextTabFocus(
      appId, await getDismissButtonId(appId)));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sort-direction-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'file-list'));
}

/**
 * Tests the tab focus behavior of the Files app when a directory is selected.
 */
export async function tabindexFocusDirectorySelected() {
  // Open Files app on Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE);

  await remoteCall.isolateBannerForTesting(appId, 'drive-welcome-banner');
  const driveWelcomeLinkQuery = '#banners > drive-welcome-banner:not([hidden])';

  // Check that the file list has the focus on launch.
  await Promise.all([
    remoteCall.waitForElement(appId, ['#file-list:focus']),
    remoteCall.waitForElement(appId, [driveWelcomeLinkQuery]),
  ]);
  const element = await remoteCall.callRemoteTestUtil<ElementObject>(
      'getActiveElement', appId, []);
  chrome.test.assertEq('list', element.attributes['class']);

  // Fake chrome.fileManagerPrivate.sharesheetHasTargets to return true.
  const fakeData = {
    'chrome.fileManagerPrivate.sharesheetHasTargets':
        ['static_promise_fake', [true]],
  };
  await remoteCall.callRemoteTestUtil('foregroundFake', appId, [fakeData]);

  // Select the directory named 'photos'.
  await remoteCall.waitUntilSelected(appId, 'photos');

  await Promise.all([

    // Wait for share button to to be visible and enabled.
    remoteCall.waitForElement(
        appId, ['#sharesheet-button:not([hidden]):not([disabled])']),

    // Wait for delete button to to be visible and enabled.
    remoteCall.waitForElement(
        appId, ['#delete-button:not([hidden]):not([disabled])']),
  ]);

  const pinnedToggleId = await remoteCall.isCrosComponents(appId) ?
      'pinned-toggle-jelly' :
      'pinned-toggle';

  // Send Tab key events to cycle through the tabable elements.
  chrome.test.assertTrue(
      // format: directory-tree#<tree item label>
      await remoteCall.checkNextTabFocus(appId, 'directory-tree#My Drive'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, pinnedToggleId));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sharesheet-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'delete-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'search-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'view-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sort-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'gear-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'drive-learn-more-button'));
  chrome.test.assertTrue(await remoteCall.checkNextTabFocus(
      appId, await getDismissButtonId(appId)));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'sort-direction-button'));
  chrome.test.assertTrue(
      await remoteCall.checkNextTabFocus(appId, 'file-list'));

  // Remove fakes.
  const removedCount = await remoteCall.callRemoteTestUtil(
      'removeAllForegroundFakes', appId, []);
  chrome.test.assertEq(1, removedCount);
}

/**
 * Tests the tab focus in the dialog and closes the dialog.
 *
 * @param dialogParams Dialog parameters to be passed to
 *     chrome.fileSystem.chooseEntry.
 * @param volumeType Volume icon type passed to the
 *     remoteCall.openAndWaitForClosingDialog function.
 * @param expectedSet Expected set of the entries.
 * @param initialize Initialization before test runs. The window ID is passed as
 *     an argument. If null, do nothing as initialization.
 * @param initialElements Selectors of the elements which shows the Files app is
 *     ready. After all the elements show up, the tabfocus tests starts. Array
 *     with the IDs of the element with the corresponding order of expected
 *     tab-indexes.
 */
async function tabIndexFocus(
    dialogParams: chrome.fileSystem.ChooseEntryOptions, volumeType: string,
    expectedSet: TestEntryInfo[],
    initialize: null|((appId: string) => Promise<void>),
    initialElements: string[],
    getExpectedTabOrder: (arg: string) => Promise<string[]>) {
  await Promise.all([
    addEntries(['local'], BASIC_LOCAL_ENTRY_SET),
    addEntries(['drive'], BASIC_DRIVE_ENTRY_SET),
  ]);

  const selectAndCheckAndClose = async (appId: string) => {
    const directoryTree = await DirectoryTreePageObject.create(appId);

    if (dialogParams.type === 'saveFile') {
      await remoteCall.waitForElement(
          appId, ['#filename-input-textbox:focus-within']);
    } else {
      await directoryTree.waitForFocusedItemByType(volumeType);
    }

    // Wait for Files app to finish loading.
    await remoteCall.waitFor('isFileManagerLoaded', appId, true);

    if (initialize) {
      await initialize(appId);
    }

    // Wait for the initial element.
    await Promise.all(initialElements.map((selector) => {
      return remoteCall.waitForElement(appId, [selector]);
    }));

    // Checks tabfocus.
    for (const className of await getExpectedTabOrder(appId)) {
      chrome.test.assertTrue(
          await remoteCall.checkNextTabFocus(appId, className), className);
    }
    // Closes the window by pressing Escape.
    chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
        'fakeKeyDown', appId, ['#file-list', 'Escape', false, false, false]));
  };

  await remoteCall.openAndWaitForClosingDialog(
      dialogParams, volumeType, expectedSet, selectAndCheckAndClose);
}

/**
 * Tests the tab focus behavior of Open Dialog (Downloads).
 */
export async function tabindexOpenDialogDownloads() {
  return tabIndexFocus(
      {type: 'openFile'}, 'downloads', BASIC_LOCAL_ENTRY_SET,
      async (appId) => {
        await remoteCall.waitUntilSelected(appId, 'hello.txt');
        await remoteCall.isolateBannerForTesting(
            appId, 'holding-space-welcome-banner');
      },
      ['#ok-button:not([disabled])'],
      async (appId) =>
          ['cancel-button',
           'ok-button',
           // format: directory-tree#<tree item label>
           'directory-tree#Downloads',
           /* first breadcrumb */ 'first',
           'search-button',
           'view-button',
           'sort-button',
           'gear-button',
           await getDismissButtonId(appId),
           'sort-direction-button',
           'file-list',
  ]);
}


/**
 * Tests the tab focus behavior of Open Dialog (Drive).
 */
export async function tabindexOpenDialogDrive() {
  return tabIndexFocus(
      {type: 'openFile'}, 'drive', BASIC_DRIVE_ENTRY_SET,
      async (appId) => {
        await remoteCall.waitUntilSelected(appId, 'hello.txt');
        await remoteCall.isolateBannerForTesting(appId, 'drive-welcome-banner');
      },
      ['#ok-button:not([disabled])'],
      async (appId) =>
          ['cancel-button',
           'ok-button',
           'search-button',
           'view-button',
           'sort-button',
           'gear-button',
           'drive-learn-more-button',
           await getDismissButtonId(appId),
           // format: directory-tree#<tree item label>
           'directory-tree#My Drive',
           'file-list',
  ]);
}

/**
 * Tests the tab focus behavior of Save File Dialog (Downloads).
 */
export async function tabindexSaveFileDialogDownloads() {
  return tabIndexFocus(
      {
        type: 'saveFile',
        suggestedName: 'hoge.txt',  // Prevent showing a override prompt
      },
      'downloads', BASIC_LOCAL_ENTRY_SET, null, ['#ok-button:not([disabled])'],
      async () =>
          ['cancel-button',
           'ok-button',
           // format: directory-tree#<tree item label>
           'directory-tree#Downloads',
           /* first breadcrumb */ 'first',
           'search-button',
           'view-button',
           'sort-button',
           'gear-button',
           'file-list',
           'new-folder-button',
           'filename-input-textbox',
  ]);
}


/**
 * Tests the tab focus behavior of Save File Dialog (Drive).
 */
export async function tabindexSaveFileDialogDrive() {
  return tabIndexFocus(
      {
        type: 'saveFile',
        suggestedName: 'hoge.txt',  // Prevent showing a override prompt
      },
      'drive', BASIC_DRIVE_ENTRY_SET, null, ['#ok-button:not([disabled])'],
      async () =>
          ['cancel-button',
           'ok-button',
           // format: directory-tree#<tree item label>
           'directory-tree#My Drive',
           'search-button',
           'view-button',
           'sort-button',
           'gear-button',
           'file-list',
           'new-folder-button',
           'filename-input-textbox',
  ]);
}