chromium/ui/file_manager/integration_tests/file_manager/providers.ts

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

import {getHistogramCount, RootPath, sendTestMessage} from '../test_util.js';

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

/**
 * Returns provider name of the given testing provider manifest viz., the
 * the value of the name field in the |manifest| file.
 * @param manifest Testing provider manifest file name.
 * @return Testing provider name.
 */
function getProviderNameForTest(manifest: string): string {
  if (manifest === 'manifest.json') {
    return 'Files Testing Provider test extension';
  }
  if (manifest === 'manifest_multiple_mounts.json') {
    return 'Files Testing Provider multiple mounts test extension';
  }
  if (manifest === 'manifest_source_device.json') {
    return 'Files Testing Provider device test extension';
  }
  if (manifest === 'manifest_source_file.json') {
    return 'Files Testing Provider file test extension';
  }

  throw new Error('unknown mainfest: '.concat(manifest));
}

/**
 * Initializes the provider extension.
 * @param manifest The manifest name of testing provider extension to launch for
 *     the test case.
 */
async function setUpProvider(manifest: string) {
  await sendTestMessage({name: 'launchProviderExtension', manifest: manifest});
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);
  return appId;
}

/**
 * Clicks on the "Services" menu button.
 */
async function showProvidersMenu(appId: string) {
  const providersMenuItem = '#gear-menu-providers:not([hidden])';

  // Open the gear menu by clicking the gear button.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, ['#gear-button']),
      'fakeMouseClick failed');

  // Wait for providers menu item to appear.
  await remoteCall.waitForElement(appId, providersMenuItem);

  // Click the menu item.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, [providersMenuItem]),
      'fakeMouseClick failed');
}

/**
 * Confirms that a provided volume is mounted.
 */
async function confirmVolume(appId: string, ejectExpected: boolean) {
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('provided');

  await directoryTree.waitForFocusedItemByType('provided');
  if (ejectExpected) {
    await directoryTree.waitForItemEjectButtonByType('provided');
  } else {
    await directoryTree.waitForItemEjectButtonLostByType('provided');
  }
}

/**
 * Tests that a provided extension with |manifest| is mountable via the menu
 * button.
 *
 * @param multipleMounts Whether multiple mounts are supported by the providing
 *     extension.
 * @param manifest Name of the manifest file for the providing extension.
 */
async function requestMountInternal(multipleMounts: boolean, manifest: string) {
  const providerName = getProviderNameForTest(manifest);
  const appId = await setUpProvider(manifest);
  await showProvidersMenu(appId);

  // Wait for providers menu to appear.
  let result = await remoteCall.waitForElement(
      appId, '#providers-menu:not([hidden]) cr-menu-item:first-child span');

  // Click to install test provider.
  chrome.test.assertEq(providerName, result.text);
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId,
          ['#providers-menu cr-menu-item:first-child span']),
      'fakeMouseClick failed');

  await confirmVolume(appId, false /* ejectExpected */);

  // If multipleMounts we display the providers menu and check the provider is
  // still listed.
  if (multipleMounts) {
    await showProvidersMenu(appId);
    const selector = '#providers-menu:not([hidden]) cr-menu-item:first-child ' +
        'span';
    result = await remoteCall.waitForElement(appId, selector);
    chrome.test.assertEq(providerName, result.text);
    return;
  }

  const isSmbEnabled = await sendTestMessage({name: 'isSmbEnabled'}) === 'true';
  if (!isSmbEnabled) {
    return;
  }

  // If !multipleMounts but isSmbEnabled, we display the provider menu and
  // check the provider is not listed.
  await showProvidersMenu(appId);
  const selector = '#providers-menu:not([hidden]) cr-menu-item:first-child ' +
      'span';
  result = await remoteCall.waitForElement(appId, selector);
  chrome.test.assertFalse(providerName === result.text);
}

/**
 * Tests that a provided extension with |manifest| is not available in the
 * providers menu, but it's mounted automatically.
 *
 * @param manifest Name of the manifest file for the providing extension.
 */
async function requestMountNotInMenuInternal(manifest: string) {
  const appId = await setUpProvider(manifest);
  await confirmVolume(appId, true /* ejectExpected */);

  const isSmbEnabled = await sendTestMessage({name: 'isSmbEnabled'}) === 'true';

  // Open the gear menu by clicking the gear button.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, ['#gear-button']),
      'fakeMouseClick failed');

  // The providers menu item should be hidden if Smb is disabled, since there
  // are no providers to show.
  const providersMenuItem = isSmbEnabled ?
      '#gear-menu-providers:not([hidden])' :
      '#gear-menu-providers[hidden]';
  const element = await remoteCall.waitForElement(appId, providersMenuItem);

  if (!isSmbEnabled) {
    return;
  }

  // Since a provider is installed (here isSmbEnabled), we need to test that
  // 'providers-menu' sub-menu does not contain the |manifest| provider.
  chrome.test.assertTrue(isSmbEnabled);
  chrome.test.assertEq(element.text, 'Services');
  chrome.test.assertEq(
      '#show-providers-submenu', element.attributes['command']);
  chrome.test.assertEq('#providers-menu', element.attributes['sub-menu']);

  // Open the providers submenu by hovering over the menu item.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseOver', appId, ['#gear-menu-providers']),
      'fakeMouseOver failed');

  // Extract 'providers-menu' sub-menu items.
  const selector = ['#providers-menu:not([hidden]) cr-menu-item'];
  const submenu = await remoteCall.waitForElement(appId, selector);

  // Check the sub-menu do not contain the |manifest| provider.
  chrome.test.assertEq('SMB file share', submenu.innerText);
}

/**
 * Tests mounting a single mount point in the button menu.
 */
export async function requestMount() {
  const multipleMounts = false;
  return requestMountInternal(multipleMounts, 'manifest.json');
}

/**
 * Tests mounting multiple mount points in the button menu.
 */
export async function requestMountMultipleMounts() {
  const multipleMounts = true;
  return requestMountInternal(multipleMounts, 'manifest_multiple_mounts.json');
}

/**
 * Tests mounting a device not present in the button menu.
 */
export async function requestMountSourceDevice() {
  return requestMountNotInMenuInternal('manifest_source_device.json');
}

/**
 * Tests mounting a file not present in the button menu.
 */
export async function requestMountSourceFile() {
  return requestMountNotInMenuInternal('manifest_source_file.json');
}

/**
 * Tests that pressing the eject button on a FSP adds a message to screen
 * reader.
 */
export async function providerEject() {
  const manifest = 'manifest_source_file.json';
  const appId = await setUpProvider(manifest);

  // Click to eject Test (1) provider/volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.ejectItemByType('provided');

  // Wait a11y-msg to have some text.
  await remoteCall.waitForElement(appId, '#a11y-msg:not(:empty)');

  // Fetch A11y messages.
  const a11yMessages =
      await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []);

  // Check that opening the file was announced to screen reader.
  chrome.test.assertTrue(a11yMessages instanceof Array);
  chrome.test.assertEq(1, a11yMessages.length);
  chrome.test.assertEq('Test (1) has been ejected.', a11yMessages[0]);
}

/**
 * Tests mounting a file system provider emits only a single UMA when running
 * from either the SWA or Chrome app.
 */
export async function deduplicatedUmaMetricForFileSystemProviders() {
  const umaMetricName = 'FileBrowser.FileSystemProviderMounted';
  const testProviderMetricEnumValue = 0;  // UNKNOWN = 0.

  let mountedVolumeCount = 0;
  chrome.test.assertEq(
      0, mountedVolumeCount, 'Unexpected value in UMA metric for mounted FSPs');

  // Setup Files app before loading the File System Provider. When testing the
  // SWA records only 1 mount event, if the FSP is loaded prior to the window
  // starting, it will always miss the load event leading to a false positive
  // test result.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Setup the FSP and wait for the volume to appear in the directory tree.
  await sendTestMessage({
    name: 'launchProviderExtension',
    manifest: 'manifest_source_device.json',
  });
  await confirmVolume(appId, true /* ejectExpected */);

  // Assert the histogram for an unknown FSP is incremented by 1.
  mountedVolumeCount =
      await getHistogramCount(umaMetricName, testProviderMetricEnumValue);
  chrome.test.assertEq(
      1, mountedVolumeCount, 'Unexpected value in UMA metric for mounted FSPs');
}