chromium/ui/file_manager/integration_tests/file_manager/zip_files.ts

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

import {addEntries, ENTRIES, expectHistogramTotalCount, getCaller, pending, repeatUntil, RootPath, sendTestMessage} from '../test_util.js';

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

/** The name of the UMA to track the zip creation time. */
const ZipCreationTimeHistogramName = 'FileBrowser.ZipTask.Time';

/** The name of the UMA to track extract archive status. */
const ExtractArchiveStatusHistogramName = 'FileBrowser.ExtractTask.Status';

/**
 * Returns the expected file list row entries after opening (mounting) the
 * ENTRIES.zipArchive file list entry.
 */
function getUnzippedFileListRowEntries() {
  return [
    [
      'SUCCESSFULLY_PERFORMED_FAKE_MOUNT.txt',
      '21 bytes',
      'Plain text',
      'Dec 31, 1980, 12:00 AM',
    ],
  ];
}

/**
 * Tests ZIP mounting from Downloads.
 */
export async function zipFileOpenDownloads() {
  await sendTestMessage({
    name: 'expectFileTask',
    fileNames: [ENTRIES.zipArchive.targetPath],
    openType: 'launch',
  });

  // Open Files app on Downloads containing a zip file.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.zipArchive], []);

  // Select the zip file.
  await remoteCall.waitUntilSelected(appId, ENTRIES.zipArchive.nameText);

  // Press the Enter key.
  const key = ['#file-list', 'Enter', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key),
      'fakeKeyDown failed');

  // Check: the zip file content should be shown (unzip).
  const files = getUnzippedFileListRowEntries();
  await remoteCall.waitForFiles(appId, files, {'ignoreLastModifiedTime': true});
}

/**
 * Tests that Files app's ZIP mounting notifies FileTasks when mounted.
 */
export async function zipNotifyFileTasks() {
  await sendTestMessage({
    name: 'expectFileTask',
    fileNames: [ENTRIES.zipArchive.targetPath],
    openType: 'launch',
  });

  // Open Files app on Downloads containing a zip file.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.zipArchive], []);

  // Open the zip file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('openFile', appId, ['archive.zip']),
      'openFile failed');

  // Wait for the zip archive to mount.
  await remoteCall.waitForElement(appId, `[scan-completed="archive.zip"]`);
}

/**
 * Tests ZIP mounting from Google Drive.
 */
export async function zipFileOpenDrive() {
  await sendTestMessage({
    name: 'expectFileTask',
    fileNames: [ENTRIES.zipArchive.targetPath],
    openType: 'launch',
  });

  // Open Files app on Drive containing a zip file.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.zipArchive]);

  // Select the zip file.
  await remoteCall.waitUntilSelected(appId, ENTRIES.zipArchive.nameText);

  // Press the Enter key.
  const key = ['#file-list', 'Enter', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key),
      'fakeKeyDown failed');

  // Check: the zip file content should be shown (unzip).
  const files = getUnzippedFileListRowEntries();
  await remoteCall.waitForFiles(appId, files, {'ignoreLastModifiedTime': true});
}

/**
 * Tests ZIP mounting from a removable USB volume.
 */
export async function zipFileOpenUsb() {
  await sendTestMessage({
    name: 'expectFileTask',
    fileNames: [ENTRIES.zipArchive.targetPath],
    openType: 'launch',
  });

  // Open Files app on Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.beautiful]);

  // Mount empty USB volume in the Drive window.
  await sendTestMessage({name: 'mountFakeUsbEmpty'});

  // Wait for the USB mount and click to open the USB volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('removable');

  // Add zip file to the USB volume.
  await addEntries(['usb'], [ENTRIES.zipArchive]);

  // Verify the USB file list.
  const archive = [ENTRIES.zipArchive.getExpectedRow()];
  await remoteCall.waitForFiles(appId, archive);

  // Select the zip file.
  await remoteCall.waitUntilSelected(appId, ENTRIES.zipArchive.nameText);

  // Press the Enter key.
  const key = ['#file-list', 'Enter', false, false, false];
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, key),
      'fakeKeyDown failed');

  // Check: the zip file content should be shown (unzip).
  const files = getUnzippedFileListRowEntries();
  await remoteCall.waitForFiles(appId, files, {'ignoreLastModifiedTime': true});
}

/**
 * Returns the expected file list rows after invoking the 'Zip selection' menu
 * command on the ENTRIES.photos file list item.
 */
function getZipSelectionFileListRowEntries() {
  return [
    ['photos', '--', 'Folder', 'Jan 1, 1980, 11:59 PM'],
    ['photos.zip', '134 bytes', 'ZIP archive', 'Oct 21, 1983, 11:55 AM'],
  ];
}

/**
 * Tests creating a ZIP file on Downloads.
 */
export async function zipCreateFileDownloads() {
  // Open Files app on Downloads containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.photos], []);

  // Select the file.
  await remoteCall.waitUntilSelected(appId, ENTRIES.photos.nameText);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Click the 'Zip selection' menu command.
  const zip = '[command="#zip-selection"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [zip]),
      'fakeMouseClick failed');

  // Check: a zip file should be created.
  const files = getZipSelectionFileListRowEntries();
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Check: a zip time histogram value should have been recorded.
  await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
}

/**
 * Tests creating a ZIP file on Drive.
 */
export async function zipCreateFileDrive() {
  // Open Files app on Drive containing ENTRIES.photos.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.photos]);

  // Select the file.
  await remoteCall.waitUntilSelected(appId, ENTRIES.photos.nameText);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Click the 'Zip selection' menu command.
  const zip = '[command="#zip-selection"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [zip]),
      'fakeMouseClick failed');

  // Check: a zip file should be created.
  const files = getZipSelectionFileListRowEntries();
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Check: a zip time histogram value should have been recorded.
  await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
}

/**
 * Tests creating a ZIP file containing an Office file on Drive.
 */
export async function zipCreateFileDriveOffice() {
  // Open Files app on Drive containing ENTRIES.photos and ENTRIES.docxFile.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.photos, ENTRIES.docxFile]);

  // Select the files.
  await remoteCall.waitAndClickElement(
      appId, `#file-list [file-name="${ENTRIES.photos.nameText}"]`);
  await remoteCall.waitAndClickElement(
      appId, `#file-list [file-name="${ENTRIES.docxFile.nameText}"]`,
      {shift: true});

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Click the 'Zip selection' menu command.
  const zip = '[command="#zip-selection"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [zip]),
      'fakeMouseClick failed');

  // Check: a zip file should be created.
  await remoteCall.waitForElement(
      appId, '#file-list [file-name="Archive.zip"]');

  // Check: a zip time histogram value should have been recorded.
  await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
}

/**
 * Tests that creating a ZIP file containing an encrypted file is disabled.
 */
export async function zipDoesntCreateFileEncrypted() {
  // Open Files app on Drive containing a test CSE file.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.testCSEFile]);

  // Select the file.
  await remoteCall.waitAndClickElement(
      appId, `#file-list [file-name="${ENTRIES.testCSEFile.nameText}"]`);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Get the zip menu item.
  const element =
      await remoteCall.waitForElement(appId, '[command="#zip-selection"]');

  chrome.test.assertEq('disabled', element.attributes['disabled']);
}

/**
 * Tests creating a ZIP file on a removable USB volume.
 */
export async function zipCreateFileUsb() {
  // Open Files app on Drive.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.beautiful]);

  // Mount empty USB volume in the Drive window.
  await sendTestMessage({name: 'mountFakeUsbEmpty'});

  // Wait for the USB mount and click to open the USB volume.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('removable');

  // Add ENTRIES.photos to the USB volume.
  await addEntries(['usb'], [ENTRIES.photos]);

  // Verify the USB file list.
  const photos = [ENTRIES.photos.getExpectedRow()];
  await remoteCall.waitForFiles(appId, photos);

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Click the 'Zip selection' menu command.
  const zip = '[command="#zip-selection"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [zip]),
      'fakeMouseClick failed');

  // Check: a zip file should be created.
  const files = getZipSelectionFileListRowEntries();
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Check: a zip time histogram value should have been recorded.
  await expectHistogramTotalCount(ZipCreationTimeHistogramName, 1);
}

/**
 * Tests that extraction of a ZIP archive produces a feedback panel.
 */
export async function zipExtractShowPanel() {
  const entry = ENTRIES.zipArchive;
  const targetDirectoryName = entry.nameText.split('.')[0];

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

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Tell the background page to never finish the file extraction.
  await remoteCall.callRemoteTestUtil(
      'progressCenterNeverNotifyCompleted', appId, []);

  // Resolves when the new app window opens.
  const waitForWindowPromise = remoteCall.waitForWindow();

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: The new window has navigated to the unzipped folder.
  const newAppId = await waitForWindowPromise;
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      newAppId, '/My files/Downloads/' + targetDirectoryName);

  // Check that the error appears in the feedback panel.
  const caller = getCaller();
  await repeatUntil(async () => {
    const element = await remoteCall.waitForElement(
        appId, ['#progress-panel', 'xf-panel-item']);
    const expectedMsg = `Extracting ${entry.nameText} to Downloads`;
    const actualMsg = element.attributes['primary-text'];

    if (actualMsg === expectedMsg) {
      return;
    }

    return pending(
        caller,
        `Expected feedback panel msg: "${expectedMsg}", got "${actualMsg}"`);
  });

  // Check: a extract archive status histogram value should have been recorded.
  await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
}

/**
 * Tests that extraction of a multiple ZIP archives produces the correct
 * feedback panel string.
 */
export async function zipExtractShowMultiPanel() {
  const entries = COMPLEX_ZIP_ENTRY_SET;

  // Open files app.
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);

  // Select two ZIP files.
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="archive.zip"]');
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="sjis.zip"]', {shift: true});

  // Right-click the selection.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Tell the background page to never finish the file extraction.
  await remoteCall.callRemoteTestUtil(
      'progressCenterNeverNotifyCompleted', appId, []);

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // We don't verify that two new Files app windows have opened because there is
  // a race setting appId when opening multiple windows at the same time.

  // Check that the error appears in the feedback panel.
  const caller = getCaller();
  await repeatUntil(async () => {
    const element = await remoteCall.waitForElement(
        appId, ['#progress-panel', 'xf-panel-item']);
    const expectedMsg = `Extracting 2 items…`;
    const actualMsg = element.attributes['primary-text'];

    if (actualMsg === expectedMsg) {
      return;
    }

    return pending(
        caller,
        `Expected feedback panel msg: "${expectedMsg}", got "${actualMsg}"`);
  });

  // Check: a extract archive status histogram value should have been recorded.
  await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
}

/**
 * Tests that various selections enable/hide the correct menu items.
 */
export async function zipExtractSelectionMenus() {
  const entries = BASIC_ZIP_ENTRY_SET;

  // Open files app.
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);

  // Select the first file (ENTRIES.hello).
  await remoteCall.waitUntilSelected(appId, entries[0]!.nameText);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Check: the Zip selection menu item should be visible.
  await remoteCall.waitForElement(
      appId, '[command="#zip-selection"]:not([hidden])');

  // Check: the Extract all menu item should be hidden.
  await remoteCall.waitForElement(appId, '[command="#extract-all"][hidden]');

  // Click the main dialog area to hide the context menu.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, ['.dialog-main']),
      'fakeMouseClick failed');

  // Select the third file (ENTRIES.zipArchive).
  await remoteCall.waitUntilSelected(appId, entries[2]!.nameText);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Check: the Extract all menu item should be visible.
  await remoteCall.waitForElement(
      appId, '[command="#extract-all"]:not([hidden])');

  // Check: the Zip selection menu item should be hidden.
  await remoteCall.waitForElement(appId, '[command="#zip-selection"][hidden]');

  // Click the main dialog area to hide the context menu.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, ['.dialog-main']),
      'fakeMouseClick failed');

  // Select the first and second file (ENTRIES.hello, ENTRIES.world).
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="world.ogv"]');
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="hello.txt"]', {shift: true});

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Check: the Extract all menu item should be hidden.
  await remoteCall.waitForElement(appId, '[command="#extract-all"][hidden]');

  // Check: the Zip selection menu item should be visible.
  await remoteCall.waitForElement(
      appId, '[command="#zip-selection"]:not([hidden])');

  // Click the main dialog area to hide the context menu.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseClick', appId, ['.dialog-main']),
      'fakeMouseClick failed');

  // Select the first and third file (ENTRIES.hello, ENTRIES.zipArchive).
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="archive.zip"]');
  await remoteCall.waitAndClickElement(
      appId, '#file-list [file-name="hello.txt"]', {shift: true});

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Check: the Extract all menu item should be visible.
  await remoteCall.waitForElement(
      appId, '[command="#extract-all"]:not([hidden])');

  // Check: the Zip selection menu item should be visible.
  await remoteCall.waitForElement(
      appId, '[command="#zip-selection"]:not([hidden])');
}

/**
 * Tests that extraction of a ZIP archive generates correct output files.
 */
export async function zipExtractCheckContent() {
  const entry = ENTRIES.zipArchive;
  const targetDirectoryName = entry.nameText.split('.')[0];

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

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Resolves when the new app window opens.
  const waitForWindowPromise = remoteCall.waitForWindow();

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: The new window has navigated to the unzipped folder.
  const newAppId = await waitForWindowPromise;
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      newAppId, '/My files/Downloads/' + targetDirectoryName);

  const directoryQuery = '#file-list [file-name="' + targetDirectoryName + '"]';
  // Check: the extract directory should appear.
  await remoteCall.waitForElement(appId, directoryQuery);

  // Double click the created directory to open it.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseDoubleClick', appId, [directoryQuery]),
      'fakeMouseDoubleClick failed');

  // Check: File content in the ZIP should appear.
  await remoteCall.waitForElement(appId, '#file-list [file-name="folder"]');
  await remoteCall.waitForElement(appId, '#file-list [file-name="text.txt"]');
  await remoteCall.waitForElement(appId, '#file-list [file-name="image.png"]');

  // Check: a extract archive status histogram value should have been recorded.
  await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
}

/**
 * Tests that repeated extraction of a ZIP archive generates extra directories.
 */
export async function zipExtractCheckDuplicates() {
  const entry = ENTRIES.zipArchive;
  const directory = entry.nameText.split('.')[0];

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

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Resolves when the new app window opens.
  const waitForWindowPromise = remoteCall.waitForWindow();

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: The new window has navigated to the unzipped folder.
  const newAppId = await waitForWindowPromise;
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      newAppId, '/My files/Downloads/' + directory);

  let directoryQuery = '#file-list [file-name="' + directory + '"]';
  // Check: the extract directory should appear.
  await remoteCall.waitForElement(appId, directoryQuery);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Resolves when the second new app window opens.
  const waitForWindowPromise2 = remoteCall.waitForWindow();

  // Click the 'Extract all' menu command.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: The second new window has navigated to the second unzipped folder.
  const newAppId2 = await waitForWindowPromise2;
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      newAppId2, '/My files/Downloads/' + directory + ' (1)');

  directoryQuery = '#file-list [file-name="' + directory + ' (1)"]';
  // Check: the duplicate named extract directory should appear.
  await remoteCall.waitForElement(appId, directoryQuery);

  // Double click the duplicate created directory to open it.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseDoubleClick', appId, [directoryQuery]),
      'fakeMouseDoubleClick failed');

  // Check: File content in the ZIP should appear.
  await remoteCall.waitForElement(appId, '#file-list [file-name="folder"]');
  await remoteCall.waitForElement(appId, '#file-list [file-name="text.txt"]');
  await remoteCall.waitForElement(appId, '#file-list [file-name="image.png"]');

  // Check: 2 extract archive status histogram value should have been recorded.
  await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 2);
}

/**
 * Tests extraction of a ZIP archive can detect and unpack filename encodings.
 */
export async function zipExtractCheckEncodings() {
  const entry = ENTRIES.zipSJISArchive;
  const targetDirectoryName = entry.nameText.split('.')[0];

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

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Resolves when the new app window opens.
  const waitForWindowPromise = remoteCall.waitForWindow();

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: The new window has navigated to the unzipped folder.
  const newAppId = await waitForWindowPromise;
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      newAppId, '/My files/Downloads/' + targetDirectoryName);

  const directoryQuery = '#file-list [file-name="' + targetDirectoryName + '"]';
  // Check: the extract directory should appear.
  await remoteCall.waitForElement(appId, directoryQuery);

  // Double click the created directory to open it.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseDoubleClick', appId, [directoryQuery]),
      'fakeMouseDoubleClick failed');

  // Check: File content in the ZIP with decoded name should appear.
  await remoteCall.waitForElement(
      appId, '#file-list [file-name="新しいフォルダ"]');

  // Check: a extract archive status histogram value should have been recorded.
  await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
}

/**
 * Tests extract option menu item has proper a11y labels.
 */
export async function zipExtractA11y() {
  const entry = ENTRIES.zipArchive;

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

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Check: Extract all menu item is properly labelled for ARIA.
  // NB: It's sufficient to check the ARIA role attribute is set correctly.
  await remoteCall.waitForElement(
      appId, '[command="#extract-all"][role="menuitem"]');
}

/**
 * Tests extraction of a ZIP archive fails if there's not enough disk space.
 */
export async function zipExtractNotEnoughSpace() {
  const entry = ENTRIES.zipExtArchive;  // 120TB fake archive.

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

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

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: Error panel appears.
  const caller = getCaller();
  await repeatUntil(async () => {
    const element = await remoteCall.waitForElement(
        appId, ['#progress-panel', 'xf-panel-item']);
    const expectedMsg = 'Extract operation failed. There is not enough space.';
    const actualMsg = element.attributes['primary-text'];

    if (actualMsg === expectedMsg) {
      return;
    }

    return pending(
        caller,
        `Expected feedback panel msg: "${expectedMsg}", got "${actualMsg}"`);
  });

  // Check: a extract archive status histogram value should have been recorded.
  await expectHistogramTotalCount(ExtractArchiveStatusHistogramName, 1);
}

/**
 * Tests that extraction of a ZIP archive from a read only volume succeeds.
 */
export async function zipExtractFromReadOnly() {
  const entry = ENTRIES.readOnlyZipFile;
  const targetDirectoryName = entry.nameText.split('.')[0];

  // Open files app.
  const appId =
      await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE, [], [entry]);

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

  // Wait for the navigation to complete.
  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/Shared with me');

  // Make sure read-only indicator on toolbar is visible.
  await remoteCall.waitForElement(appId, '#read-only-indicator:not([hidden])');

  // Make sure the file we are about to select is present.
  await remoteCall.waitForFiles(appId, [entry.getExpectedRow()]);

  // Select the ZIP file.
  await remoteCall.waitUntilSelected(appId, entry.nameText);

  // Right-click the selected file.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['.table-row[selected]']),
      'fakeMouseRightClick failed');

  // Check: the context menu should appear.
  await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');

  // Resolves when the new app window opens.
  const waitForWindowPromise = remoteCall.waitForWindow();

  // Click the 'Extract all' menu command.
  const extract = '[command="#extract-all"]';
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [extract]),
      'fakeMouseClick failed');

  // Check: The new window has navigated to the unzipped folder.
  const newAppId = await waitForWindowPromise;
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      newAppId, '/My files/' + targetDirectoryName);

  // Navigate to My Files.
  await directoryTree.navigateToPath('/My files');

  const directoryQuery = '#file-list [file-name="' + targetDirectoryName + '"]';
  // Check: the extract directory should appear.
  await remoteCall.waitForElement(appId, directoryQuery);

  // Double click the created directory to open it.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseDoubleClick', appId, [directoryQuery]),
      'fakeMouseDoubleClick failed');

  // Check: File content in the ZIP should appear.
  await remoteCall.waitForElement(appId, '#file-list [file-name="folder"]');
  await remoteCall.waitForElement(appId, '#file-list [file-name="text.txt"]');
  await remoteCall.waitForElement(appId, '#file-list [file-name="image.png"]');
}

/**
 * Tests ZIP mounting can be closed from the "Close" context menu item.
 */
export async function zipCloseFromContextMenu() {
  await sendTestMessage({
    name: 'expectFileTask',
    fileNames: [ENTRIES.zipArchive.targetPath],
    openType: 'launch',
  });

  // Open Files app on Downloads containing a zip file.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.zipArchive], []);

  // Select the zip file.
  await remoteCall.waitUntilSelected(appId, ENTRIES.zipArchive.nameText);

  // Press the Enter key.
  await remoteCall.fakeKeyDown(
      appId, '#file-list', 'Enter', false, false, false);

  // Check: the zip mount should show on the directory tree.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.waitForSelectedItemByLabel(ENTRIES.zipArchive.nameText);

  // Trigger context menu event by long pressing.
  await directoryTree.longPressItemByLabel(ENTRIES.zipArchive.nameText);

  // Wait and click "Close" context menu.
  await remoteCall.waitForElement(appId, '#roots-context-menu:not([hidden])');
  await remoteCall.waitAndClickElement(
      appId, '[command="#unmount"]:not([hidden]):not([disabled])');

  // Check: the zip mount should disappear from the directory tree.
  await directoryTree.waitForItemLostByLabel(ENTRIES.zipArchive.nameText);
}