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

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

const tooltipQuery = 'files-tooltip';
const tooltipQueryHidden = 'files-tooltip:not([visible])';
const tooltipQueryVisible = 'files-tooltip[visible=true]';
const searchButton = '#search-button[has-tooltip]';
const viewButton = '#view-button[has-tooltip]';
const readonlyIndicator =
    '#read-only-indicator[has-tooltip][show-card-tooltip]';
const fileList = '#file-list';
const cancelButton = '#cancel-selection-button[has-tooltip]';
const deleteButton = '#delete-button[has-tooltip]';

const tooltipShowTimeout = 500;  // ms

/**
 * Waits until the element by |id| is the document.activeElement.
 *
 * @param appId The Files app windowId.
 * @param id The element id.
 */
function getActiveElementById(appId: string, id: string): Promise<void> {
  const caller = getCaller();
  return repeatUntil(async () => {
    const element = await remoteCall.callRemoteTestUtil<ElementObject|null>(
        'getActiveElement', appId, []);
    if (!element || element.attributes['id'] !== id) {
      return pending(caller, 'Waiting for active element by id #%s.', id);
    }
    return undefined;
  });
}

/**
 * Tests that tooltip is displayed when focusing an element with tooltip.
 */
export async function filesTooltipFocus() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Focus a button with a tooltip: the search button.
  await remoteCall.focus(appId, [searchButton]);
  await getActiveElementById(appId, 'search-button');

  // Check: the search button tooltip should be visible.
  let label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Search', label.text);

  // Focus an element that has no tooltip: the file-list.
  await remoteCall.focus(appId, [fileList]);
  await getActiveElementById(appId, 'file-list');

  // Check: the tooltip should hide.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Select all the files to enable the cancel selection button.
  const ctrlA = ['#file-list', 'a', true, false, false];
  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlA);

  // Focus the cancel selection button.
  await remoteCall.focus(appId, [cancelButton]);
  await getActiveElementById(appId, 'cancel-selection-button');

  // Check: the cancel selection button tooltip should be visible.
  label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Cancel selection', label.text);
}

/**
 * Tests that tooltip is displayed when focusing an element with tooltip.
 */
export async function filesTooltipLabelChange() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Focus a button with a tooltip: the view button.
  await remoteCall.focus(appId, [viewButton]);
  await getActiveElementById(appId, 'view-button');

  // Check: the view button tooltip should be visible.
  let label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Switch to thumbnail view', label.text);

  // Click the view button to update its label.
  await remoteCall.waitAndClickElement(appId, [viewButton]);

  // Check: the view button should still be focused.
  await getActiveElementById(appId, 'view-button');

  // Check: the tooltip text should be updated.
  label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Switch to list view', label.text);
}

/**
 * Tests that tooltips display when hovering an element that has a tooltip.
 */
export async function filesTooltipMouseOver() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Mouse hover over a button that has a tooltip: the search button.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [searchButton]));

  // Check: the search button tooltip should be visible.
  const firstElement =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Search', firstElement.text);

  // Move the mouse away from the search button.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOut', appId, [searchButton]));

  // Check: the tooltip should hide.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Move the mouse over the search button again.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [searchButton]));

  // Check: the search button tooltip should be visible.
  const lastElement =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Search', lastElement.text);
}

/**
 * Tests that tooltips stay open when hovering over the tooltip.
 */
export async function filesTooltipMouseOverStaysOpen() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Mouse hover over a button that has a tooltip: the search button.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [searchButton]));

  // Check: the search button tooltip should be visible.
  const firstElement =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Search', firstElement.text);

  // Move the mouse away from the search button, but on the tooltip.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOut', appId, [searchButton]));
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [tooltipQuery]));

  // Check: the tooltip should still be visible.
  await remoteCall.waitForElement(appId, tooltipQueryVisible);

  // Move the mouse away from the tooltip.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOut', appId, [tooltipQuery]));

  // Check: the tooltip should hide.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);
}

/**
 * Tests that tooltip is hidden when clicking on body (or anything else).
 */
export async function filesTooltipClickHides() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Hover over a button that has a tooltip: the search button.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [searchButton]));

  // Check: the search button tooltip should be visible.
  const label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Search', label.text);

  // Click the body element.
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, ['body']));

  // Check: the tooltip should hide.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);
}

/**
 * Tests that card tooltip is hidden when clicking on body (or anything else).
 */
export async function filesCardTooltipClickHides() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Click the 'Android files' volume tab in the directory tree.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('android_files');

  // Wait for the read-only bubble to appear in the files app tool bar.
  const readonlyBubbleShown = '#read-only-indicator:not([hidden])';
  await remoteCall.waitForElement(appId, readonlyBubbleShown);

  // Check: initially, no tooltip should be visible.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Hover the mouse over the read-only bubble.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [readonlyIndicator]));

  // Check: the read-only bubble card tooltip should be visible.
  const expectedLabelText = 'The contents of this folder are read-only. ' +
      'Some activities are not supported.';
  const tooltip = await remoteCall.waitForElement(appId, tooltipQueryVisible);
  const label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq(expectedLabelText, label.text);
  chrome.test.assertEq('card-tooltip', tooltip.attributes['class']);
  chrome.test.assertEq('card-label', label.attributes['class']);

  // Click the body element.
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, ['body']));

  // Check: the tooltip should hide.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);
}

/**
 * Tests that the tooltip should hide when the window resizes.
 */
export async function filesTooltipHidesOnWindowResize() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Focus a button with tooltip: the search button.
  await remoteCall.focus(appId, [searchButton]);
  await getActiveElementById(appId, 'search-button');

  // Check: the search button tooltip should be visible.
  const label =
      await remoteCall.waitForElement(appId, [tooltipQueryVisible, '#label']);
  chrome.test.assertEq('Search', label.text);

  // Resize the window.
  chrome.test.assertTrue(
      await remoteCall.callRemoteTestUtil('resizeWindow', appId, [1200, 1200]));

  // Check: the tooltip should hide.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);
}

/**
 * Tests that the tooltip is hidden after the delete confirm dialog closes.
 */
export async function filesTooltipHidesOnDeleteDialogClosed() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.beautiful, ENTRIES.photos]);

  const fileListItemQuery = '#file-list li[file-name="Beautiful Song.ogg"]';

  // Check: initially the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Select file.
  await remoteCall.waitAndClickElement(appId, fileListItemQuery);

  // Mouse over the delete button and leave time for tooltip to show.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [deleteButton]));
  await wait(tooltipShowTimeout);

  // Click the toolbar delete button.
  await remoteCall.waitAndClickElement(appId, deleteButton);

  // Check: the delete confirm dialog should appear.
  await remoteCall.waitForElement(appId, '.cr-dialog-container.shown');

  // Click the delete confirm dialog 'Cancel' button.
  const dialogCancelButton =
      await remoteCall.waitAndClickElement(appId, '.cr-dialog-cancel:focus');
  chrome.test.assertEq('Cancel', dialogCancelButton.text);

  // Leave time for tooltip to show.
  await wait(tooltipShowTimeout);

  // Check: the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);

  // Select file.
  await remoteCall.waitAndClickElement(appId, fileListItemQuery);

  // Mouse over the delete button and leave time for tooltip to show.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseOver', appId, [deleteButton]));
  await wait(tooltipShowTimeout);

  // Click the toolbar delete button.
  await remoteCall.waitAndClickElement(appId, deleteButton);

  // Check: the delete confirm dialog should appear.
  await remoteCall.waitForElement(appId, '.cr-dialog-container.shown');

  // Click the delete confirm dialog 'Delete' button.
  const dialogDeleteButton =
      await remoteCall.waitAndClickElement(appId, '.cr-dialog-ok');
  chrome.test.assertEq('Delete', dialogDeleteButton.text);

  // Check: the delete confirm dialog should close.
  await remoteCall.waitForElementLost(appId, '.cr-dialog-container.shown');

  // Leave time for tooltip to show.
  await wait(tooltipShowTimeout);

  // Check: the tooltip should be hidden.
  await remoteCall.waitForElement(appId, tooltipQueryHidden);
}