chromium/ui/file_manager/integration_tests/file_manager/folder_shortcuts.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 {addEntries, ENTRIES, getCaller, pending, repeatUntil, RootPath, sendTestMessage} from '../test_util.js';

import {remoteCall} from './background.js';
import {DirectoryTreePageObject} from './page_objects/directory_tree.js';

/**
 * Directory tree path constants.
 */
const TREEITEM_A = `/My Drive/A`;
const TREEITEM_B = `${TREEITEM_A}/B`;
const TREEITEM_C = `${TREEITEM_B}/C`;

const TREEITEM_D = `/My Drive/D`;
const TREEITEM_E = `${TREEITEM_D}/E`;

/**
 * Entry set used for the folder shortcut tests.
 */
const FOLDER_ENTRY_SET = [
  ENTRIES.directoryA,
  ENTRIES.directoryB,
  ENTRIES.directoryC,
  ENTRIES.directoryD,
  ENTRIES.directoryE,
  ENTRIES.directoryF,
];

interface EntryInfo {
  contents: string[][];
  name: string;
  treeItem: string;
}

/**
 * Constants for each folder.
 */
const DIRECTORY = {
  Drive: {
    contents: [
      ENTRIES.directoryA.getExpectedRow(),
      ENTRIES.directoryD.getExpectedRow(),
    ],
    name: 'My Drive',
    treeItem: 'My Drive',
  },
  A: {
    contents: [ENTRIES.directoryB.getExpectedRow()],
    name: 'A',
    treeItem: TREEITEM_A,
  },
  B: {
    contents: [ENTRIES.directoryC.getExpectedRow()],
    name: 'B',
    treeItem: TREEITEM_B,
  },
  C: {
    contents: [],
    name: 'C',
    treeItem: TREEITEM_C,
  },
  D: {
    contents: [ENTRIES.directoryE.getExpectedRow()],
    name: 'D',
    treeItem: TREEITEM_D,
  },
  E: {
    contents: [ENTRIES.directoryF.getExpectedRow()],
    name: 'E',
    treeItem: TREEITEM_E,
  },
};

/**
 * Expands whole directory tree under DIRECTORY.Drive.
 *
 * @param appId Files app windowId.
 * @return Promise fulfilled on success.
 */
async function expandDirectoryTree(appId: string): Promise<void> {
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.recursiveExpand(DIRECTORY.Drive.treeItem);
  await directoryTree.recursiveExpand(DIRECTORY.A.treeItem);
  await directoryTree.recursiveExpand(DIRECTORY.B.treeItem);
  await directoryTree.recursiveExpand(DIRECTORY.D.treeItem);
}

/**
 * Navigate to |directory| (makes |directory| the current directory).
 *
 * @param appId Files app windowId.
 * @param directory Directory to navigate to.
 * @return Promise fulfilled on success.
 */
async function navigateToDirectory(
    appId: string, directory: EntryInfo): Promise<void> {
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.navigateToPath(directory.treeItem);
  await remoteCall.waitForFiles(appId, directory.contents);
}

/**
 * Removes the folder shortcut to |directory|. Note the current directory must
 * be a parent of the given |directory|.
 *
 * @param appId Files app windowId.
 * @param directory Directory of shortcut to be removed.
 * @return Promise fulfilled on success.
 */
async function removeShortcut(
    appId: string, directory: EntryInfo): Promise<void> {
  const caller = getCaller();
  const removeShortcutMenuItem =
      '[command="#unpin-folder"]:not([hidden]):not([disabled])';
  const directoryTree = await DirectoryTreePageObject.create(appId);

  // Right-click for context menu with retry.
  await repeatUntil(async () => {
    // Right click.
    await directoryTree.showContextMenuForShortcutItemByLabel(directory.name);

    // Wait context menu to show.
    await remoteCall.waitForElement(appId, '#roots-context-menu:not([hidden])');

    // Check menu item is visible and enabled.
    const menuItem = await remoteCall.callRemoteTestUtil<ElementObject[]>(
        'queryAllElements', appId, [removeShortcutMenuItem]);
    if (menuItem.length > 0) {
      return true;
    }

    return pending(
        caller,
        `Waiting "remove shortcut" menu item to be available on ${appId}.`);
  });

  // Click the remove shortcut menu item.
  await remoteCall.waitAndClickElement(
      appId,
      ['#roots-context-menu [command="#unpin-folder"]:' +
       'not([hidden])']);

  await directoryTree.waitForShortcutItemLostByLabel(directory.name);
}

/**
 * Waits until the current directory becomes |currentDir| and current
 * selection becomes the shortcut to |shortcutDir|.
 *
 * @param appId Files app windowId.
 * @param currentDir Directory which should be a current directory.
 * @param shortcutDir Directory whose shortcut should be selected.
 * @return Promise fulfilled on success.
 */
async function expectSelection(
    appId: string, currentDir: EntryInfo,
    shortcutDir: EntryInfo): Promise<void> {
  await remoteCall.waitForFiles(appId, currentDir.contents);
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForFocusedShortcutItemByLabel(shortcutDir.name);
}

/**
 * Clicks folder shortcut to |directory|.
 *
 * @param appId Files app windowId.
 * @param directory Directory whose shortcut will be clicked.
 * @return Promise fulfilled on success.
 */
async function clickShortcut(
    appId: string, directory: EntryInfo): Promise<void> {
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectShortcutItemByLabel(directory.name);
}

/**
 * Creates some shortcuts and traverse them and some other directories.
 */
export async function traverseFolderShortcuts() {
  // Open Files app on Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], FOLDER_ENTRY_SET);

  // Expand the directory tree.
  await expandDirectoryTree(appId);

  // Create a shortcut to directory D.
  await remoteCall.createShortcut(appId, DIRECTORY.D.name);

  // Navigate to directory B.
  await navigateToDirectory(appId, DIRECTORY.B);

  // Create a shortcut to directory C.
  await remoteCall.createShortcut(appId, DIRECTORY.C.name);

  // Click the Drive root (My Drive).
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByLabel('My Drive');

  // Check: current directory and selection should be the Drive root.
  await directoryTree.waitForSelectedItemByLabel('My Drive');

  // Send Ctrl+3 key to file-list to select 3rd volume in the
  // directory tree. This corresponds to the second shortcut (to 'D')
  // as shortcuts are ordered alphabetically. Volumes 1 is the
  // Recent View.
  const key = ['#file-list', '3', true, false, false];
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key));

  // Make sure directory change is finished before focusing on the tree.
  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/My Drive/D');

  // The focus is on file list now after Ctrl+3, in order to use `:focus`
  // selector in the below `expectSelection` we need to focus the tree first.
  await directoryTree.focusTree();

  // Check: current directory and selection should be D.
  await expectSelection(appId, DIRECTORY.D, DIRECTORY.D);

  // Send UpArrow key to directory tree to select the shortcut
  // above D.
  await directoryTree.focusPreviousItem();

  // Check: current directory should be D, with shortcut C selected.
  await expectSelection(appId, DIRECTORY.D, DIRECTORY.C);

  // Send Enter key to the directory tree to change to directory C.
  await directoryTree.selectFocusedItem();

  // Check: current directory and selection should be C.
  await expectSelection(appId, DIRECTORY.C, DIRECTORY.C);
}

/**
 * Adds and removes shortcuts from other window and check if the active
 * directories and selected navigation items are correct.
 */
export async function addRemoveFolderShortcuts() {
  async function openFilesAppOnDrive() {
    const appId = await remoteCall.openNewWindow(RootPath.DRIVE);
    await remoteCall.waitForElement(appId, '#file-list');
    await remoteCall.waitForFiles(appId, DIRECTORY.Drive.contents);
    return appId;
  }

  // Add entries to Drive.
  await addEntries(['drive'], FOLDER_ENTRY_SET);

  // Open one Files app window on Drive.
  const appId1 = await openFilesAppOnDrive();

  // Open another Files app window on Drive.
  const appId2 = await openFilesAppOnDrive();

  // appId2 window is focused now because that's the last opened window. We are
  // asserting on the appId1 window below, in order to use ":focus" selector in
  // `expectSelection`, we need to make sure the appId1 window is focused first.
  await sendTestMessage({appId: appId1, name: 'focusWindow'});

  // Create a shortcut to D.
  await remoteCall.createShortcut(appId1, DIRECTORY.D.name);

  // Click the shortcut to D.
  await clickShortcut(appId1, DIRECTORY.D);

  // Check: current directory and selection should be D.
  await expectSelection(appId1, DIRECTORY.D, DIRECTORY.D);

  // Create a shortcut to A from the other window.
  await remoteCall.createShortcut(appId2, DIRECTORY.A.name);

  // Check: current directory and selection should still be D.
  await expectSelection(appId1, DIRECTORY.D, DIRECTORY.D);

  // Remove shortcut to D from the other window.
  await removeShortcut(appId2, DIRECTORY.D);

  // Check: directory D in the directory tree should be focused.
  const directoryTree = await DirectoryTreePageObject.create(appId1);
  await directoryTree.waitForFocusedItemByLabel('D');
}