chromium/ui/file_manager/integration_tests/file_manager/toolbar.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 type {ElementObject} from '../prod/file_manager/shared_types.js';
import {addEntries, ENTRIES, getCaller, pending, repeatUntil, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';

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

/**
 * Tests that the Delete menu item is disabled if no entry is selected.
 */
export async function toolbarDeleteWithMenuItemNoEntrySelected() {
  const contextMenu = '#file-context-menu:not([hidden])';

  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Right click the list without selecting an entry.
  chrome.test.assertTrue(
      !!await remoteCall.callRemoteTestUtil(
          'fakeMouseRightClick', appId, ['list.list']),
      'fakeMouseRightClick failed');

  // Wait until the context menu is shown.
  await remoteCall.waitForElement(appId, contextMenu);

  // Assert the menu delete command is disabled.
  const deleteDisabled = '[command="#delete"][disabled="disabled"]';
  await remoteCall.waitForElement(appId, contextMenu + ' ' + deleteDisabled);
}

/**
 * Tests that the toolbar Delete button opens the delete confirm dialog and
 * that the dialog cancel button has the focus by default.
 */
export async function toolbarDeleteButtonOpensDeleteConfirmDialog() {
  // Open Files app.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.desktop]);

  // Select My Desktop Background.png
  await remoteCall.waitUntilSelected(appId, ENTRIES.desktop.nameText);

  // Click the toolbar Delete button.
  await remoteCall.simulateUiClick(appId, '#delete-button');

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

  // Check: the dialog 'Cancel' button should be focused by default.
  const defaultDialogButton =
      await remoteCall.waitForElement(appId, '.cr-dialog-cancel:focus');
  chrome.test.assertEq('Cancel', defaultDialogButton.text);
}

/**
 * Tests that the toolbar Delete button keeps focus after the delete confirm
 * dialog is closed.
 */
export async function toolbarDeleteButtonKeepFocus() {
  // Open Files app.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.hello], []);

  // USB delete never uses trash and always shows the delete dialog.

  // Mount a USB volume.
  await sendTestMessage({name: 'mountFakeUsb'});

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

  // Check: the USB files should appear in the file list.
  const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
  await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});

  // Select hello.txt
  await remoteCall.waitUntilSelected(appId, ENTRIES.hello.nameText);

  // Click the toolbar Delete button.
  await remoteCall.simulateUiClick(appId, '#delete-button');

  // Check: the Delete button should lose focus.
  await remoteCall.waitForElementLost(appId, '#delete-button:focus');

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

  // Check: the dialog 'Cancel' button should be focused by default.
  const defaultDialogButton =
      await remoteCall.waitForElement(appId, '.cr-dialog-cancel:focus');
  chrome.test.assertEq('Cancel', defaultDialogButton.text);

  // Click the dialog 'Cancel' button.
  await remoteCall.waitAndClickElement(appId, '.cr-dialog-cancel');

  // Check: the toolbar Delete button should be focused.
  await remoteCall.waitForElement(appId, '#delete-button:focus');
}

/**
 * Tests deleting an entry using the toolbar.
 */
export async function toolbarDeleteEntry() {
  const beforeDeletion = TestEntryInfo.getExpectedRows([
    ENTRIES.photos,
    ENTRIES.hello,
    ENTRIES.world,
    ENTRIES.desktop,
    ENTRIES.beautiful,
  ]);

  const afterDeletion = TestEntryInfo.getExpectedRows([
    ENTRIES.photos,
    ENTRIES.hello,
    ENTRIES.world,
    ENTRIES.beautiful,
  ]);

  // Open Files app.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Confirm entries in the directory before the deletion.
  await remoteCall.waitForFiles(
      appId, beforeDeletion, {ignoreLastModifiedTime: true});

  // Select My Desktop Background.png
  await remoteCall.waitUntilSelected(appId, 'My Desktop Background.png');

  // Click move to trash button in the toolbar.
  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
      'fakeMouseClick', appId, ['#move-to-trash-button']));

  // Confirm the file is removed.
  await remoteCall.waitForFiles(
      appId, afterDeletion, {ignoreLastModifiedTime: true});
}

/**
 * Tests that refresh button hides in selection mode.
 *
 * Non-watchable volumes (other than Recent views) display the refresh
 * button so users can refresh the file list content. However this
 * button should be hidden when entering the selection mode.
 * crbug.com/978383
 */
export async function toolbarRefreshButtonWithSelection() {
  // Open files app.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Add files to the DocumentsProvider volume (which is non-watchable)
  await addEntries(['documents_provider'], BASIC_LOCAL_ENTRY_SET);

  // Wait for the DocumentsProvider volume to mount.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('documents_provider');
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      appId, '/DocumentsProvider');

  // Check that refresh button is visible.
  await remoteCall.waitForElement(appId, '#refresh-button:not([hidden])');

  // Ctrl+A to enter selection mode.
  const ctrlA = ['#file-list', 'a', true, false, false] as const;
  await remoteCall.fakeKeyDown(appId, ...ctrlA);

  // Check that the button should be hidden.
  await remoteCall.waitForElement(appId, '#refresh-button[hidden]');
}

/**
 * Tests that refresh button is not shown when the Recent view is selected.
 */
export async function toolbarRefreshButtonHiddenInRecents() {
  // Open files app.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Navigate to Recent.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByLabel('Recent');
  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/Recent');

  // Check that the button should be hidden.
  await remoteCall.waitForElement(appId, '#refresh-button[hidden]');
}

/**
 * Tests that refresh button is shown for non-watchable volumes.
 */
export async function toolbarRefreshButtonShownForNonWatchableVolume() {
  // Open files app.
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

  // Add files to the DocumentsProvider volume (which is non-watchable)
  await addEntries(['documents_provider'], BASIC_LOCAL_ENTRY_SET);

  // Wait for the DocumentsProvider volume to mount.
  const directoryTree = await DirectoryTreePageObject.create(appId);
  await directoryTree.selectItemByType('documents_provider');
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      appId, '/DocumentsProvider');

  // Check that refresh button is visible.
  await remoteCall.waitForElement(appId, '#refresh-button:not([hidden])');
}

/**
 * Tests that refresh button is hidden for watchable volumes.
 */
export async function toolbarRefreshButtonHiddenForWatchableVolume() {
  // Open Files app on local Downloads.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // It should start in Downloads.
  await remoteCall.waitUntilCurrentDirectoryIsChanged(
      appId, '/My files/Downloads');

  // Check that the button should be hidden.
  await remoteCall.waitForElement(appId, '#refresh-button[hidden]');
}

/**
 * Tests that command Alt+A focus the toolbar.
 */
export async function toolbarAltACommand() {
  // Open files app.
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DOWNLOADS, [ENTRIES.beautiful], []);

  // Press Alt+A in the File List.
  const altA = ['#file-list', 'a', false, false, true] as const;
  await remoteCall.fakeKeyDown(appId, ...altA);

  // Check that a menu-button should be focused.
  const focusedElement =
      await remoteCall.callRemoteTestUtil<ElementObject|null>(
          'getActiveElement', appId, []);
  const cssClasses = focusedElement?.attributes['class'] || '';
  chrome.test.assertTrue(cssClasses.includes('menu-button'));
}

/**
 * Tests that the menu drop down follows the button if the button moves. This
 * happens when the search box is expanded and then collapsed.
 */
export async function toolbarMultiMenuFollowsButton() {
  const entry = ENTRIES.hello;

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

  // Override the tasks so the "Open" button becomes a dropdown button.
  await remoteCall.callRemoteTestUtil(
      'overrideTasks', appId, [DOWNLOADS_FAKE_TASKS]);

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

  // Click the toolbar search button.
  await remoteCall.waitAndClickElement(appId, '#search-button');

  // Wait for the search box to expand.
  await remoteCall.waitForElementLost(appId, '#search-wrapper[collapsed]');

  // Click the toolbar "Open" dropdown button.
  await remoteCall.simulateUiClick(appId, '#tasks');

  // Wait for the search box to collapse.
  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');

  // Check that the dropdown menu and "Open" button are aligned.
  const caller = getCaller();
  await repeatUntil(async () => {
    const openButton =
        await remoteCall.waitForElementStyles(appId, '#tasks', ['width']);
    const menu =
        await remoteCall.waitForElementStyles(appId, '#tasks-menu', ['width']);

    if (openButton.renderedLeft === menu.renderedLeft) {
      return;
    }

    return pending(
        caller,
        `Waiting for the menu and button to be aligned: ` +
            `${openButton.renderedLeft} !== ${menu.renderedLeft}`);
  });
}

/**
 * Tests that the sharesheet button is enabled and executable.
 */
export async function toolbarSharesheetButtonWithSelection() {
  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

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

  // Fake chrome.fileManagerPrivate.invokeSharesheet.
  fakeData = {
    'chrome.fileManagerPrivate.invokeSharesheet': ['static_promise_fake', []],
  } as any;
  await remoteCall.callRemoteTestUtil('foregroundFake', appId, [fakeData]);

  const entry = ENTRIES.hello;

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

  await remoteCall.waitAndClickElement(
      appId, '#sharesheet-button:not([hidden])');

  // Check invoke sharesheet is called.
  chrome.test.assertEq(
      1, await remoteCall.callRemoteTestUtil('staticFakeCounter', appId, [
        'chrome.fileManagerPrivate.invokeSharesheet',
      ]));

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

/**
 * Tests that the sharesheet command in context menu is enabled and executable.
 */
export async function toolbarSharesheetContextMenuWithSelection() {
  const contextMenu = '#file-context-menu:not([hidden])';

  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

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

  // Fake chrome.fileManagerPrivate.invokeSharesheet.
  fakeData = {
    'chrome.fileManagerPrivate.invokeSharesheet': ['static_promise_fake', []],
  } as any;
  await remoteCall.callRemoteTestUtil('foregroundFake', appId, [fakeData]);

  const entry = ENTRIES.hello;

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

  chrome.test.assertTrue(!!await remoteCall.waitAndRightClick(
      appId, '#file-list .table-row[selected]'));

  // Wait until the context menu is shown.
  await remoteCall.waitForElement(appId, contextMenu);

  // Assert the menu sharesheet command is not hidden.
  const sharesheetEnabled =
      '[command="#invoke-sharesheet"]:not([hidden]):not([disabled])';

  await remoteCall.waitAndClickElement(
      appId, contextMenu + ' ' + sharesheetEnabled);

  // Check invoke sharesheet is called.
  chrome.test.assertEq(
      1, await remoteCall.callRemoteTestUtil('staticFakeCounter', appId, [
        'chrome.fileManagerPrivate.invokeSharesheet',
      ]));

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

/**
 * Tests that the sharesheet item is hidden if no entry is selected.
 */
export async function toolbarSharesheetNoEntrySelected() {
  const contextMenu = '#file-context-menu:not([hidden])';

  const appId = await remoteCall.setupAndWaitUntilReady(RootPath.DOWNLOADS);

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

  // Right click the list without selecting an entry.
  chrome.test.assertTrue(
      !!await remoteCall.waitAndRightClick(appId, 'list.list'));

  // Wait until the context menu is shown.
  await remoteCall.waitForElement(appId, contextMenu);

  // Assert the menu sharesheet command is disabled.
  const sharesheetDisabled =
      '[command="#invoke-sharesheet"][hidden][disabled="disabled"]';
  await remoteCall.waitForElement(
      appId, contextMenu + ' ' + sharesheetDisabled);

  await remoteCall.waitForElement(appId, '#sharesheet-button[hidden]');

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

/**
 * Tests that the cloud icon does not appear if bulk pinning is disabled.
 */
export async function toolbarCloudIconShouldNotShowWhenBulkPinningDisabled() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
}

/**
 * Tests that the cloud icon does not appear if the bulk pinning preference is
 * disabled and the supplied Stage does not have a UI state in the progress
 * panel.
 */
export async function
toolbarCloudIconShouldNotShowIfPreferenceDisabledAndNoUIStateAvailable() {
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: false});

  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
}

/**
 * Tests that the cloud icon should only show when the bulk pinning is in
 * progress.
 */
export async function toolbarCloudIconShouldShowForInProgress() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Mock the free space returned by spaced to be 4 GB, the test files
  // initialized on the Drive root are 92 KB so well below the 1GB space
  // requirement.
  await remoteCall.setSpacedFreeSpace(4n << 30n);

  // Enable the bulk pinning preference and assert the cloud button is no longer
  // hidden.
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});
  await remoteCall.waitForElementLost(appId, '#cloud-button[hidden]');
  await remoteCall.waitForElement(
      appId, '#cloud-button > xf-icon[type="cloud_sync"]');
}

/**
 * Tests that the cloud icon should show when there is not enough disk space
 * available to pin.
 */
export async function toolbarCloudIconShowsWhenNotEnoughDiskSpaceIsReturned() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Mock the free space available as 100 MB, this will trigger the
  // `NotEnoughSpace` stage for bulk pinning.
  await remoteCall.setSpacedFreeSpace(100n << 20n);

  // Enable the bulk pinning preference and even though the end state is an
  // error, there is a UI state to show so the toolbar should still be visible.
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});
  await remoteCall.waitForElementLost(appId, '#cloud-button[hidden]');
  await remoteCall.waitForElement(
      appId, '#cloud-button > xf-icon[type="cloud_error"]');
}


/**
 * Tests that the cloud icon should not show if an error state has been
 * returned (in this case `CannotGetFreeSpace`).
 */
export async function toolbarCloudIconShouldNotShowWhenCannotGetFreeSpace() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Mock the free space returned by spaced to be 4 GB.
  await remoteCall.setSpacedFreeSpace(4n << 30n);

  // Enable the bulk pinning preference and assert the cloud button is shown.
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});
  await remoteCall.waitForElementLost(appId, '#cloud-button[hidden]');

  // Mock the free space available as -1 which indicates an error returned
  // during the free space retrieval.
  await remoteCall.setSpacedFreeSpace(-1n);

  // Force the bulk pinning manager to check for free space again (this
  // currently is done on a 60s poll).
  await sendTestMessage({name: 'forcePinningManagerSpaceCheck'});
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
}

/**
 * Tests that when the cloud icon is pressed the xf-cloud-panel moves into space
 * and resizes correctly.
 */
export async function toolbarCloudIconWhenPressedShouldOpenCloudPanel() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Mock the free space returned by spaced to be 4 GB.
  await remoteCall.setSpacedFreeSpace(4n << 30n);

  // Enable the bulk pinning preference and assert the cloud button is no longer
  // hidden.
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});
  await remoteCall.waitForElementLost(appId, '#cloud-button[hidden]');

  // Ensure at first the cloud panel is not shown.
  const styles = await remoteCall.waitForElementStyles(
      appId, ['xf-cloud-panel', 'cr-action-menu', 'dialog'], ['left']);
  chrome.test.assertEq(styles.renderedHeight, 0);
  chrome.test.assertEq(styles.renderedWidth, 0);
  chrome.test.assertEq(styles.renderedTop, 0);
  chrome.test.assertEq(styles.renderedLeft, 0);

  // Click the cloud icon and wait for the dialog to move into space.
  await remoteCall.waitAndClickElement(appId, '#cloud-button:not([hidden])');
  await remoteCall.waitForCloudPanelVisible(appId);
}

/**
 * Tests that the cloud icon should not show if bulk pinning is paused (which
 * represents an offline state) and the user preference is disabled.
 */
export async function toolbarCloudIconShouldNotShowWhenPrefDisabled() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], BASIC_DRIVE_ENTRY_SET);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Force the bulk pinning preference off.
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: false});

  // Mock the free space returned by spaced to be 4 GB.
  await remoteCall.setSpacedFreeSpace(4n << 30n);

  // Set the bulk pinning manager to enter offline mode. This will surface a
  // `PAUSED` state which has a UI representation iff the pref is enabled.
  // This is done to ensure the bulk pinning doesn't finish before our
  // assertions are able to run (small amount of test files make this finish
  // super quick).
  await sendTestMessage({name: 'setBulkPinningOnline', enabled: false});

  // Force the bulk pinning to calculate required space which will kick it
  // into a `PAUSED` state from a `STOPPED` state.
  await sendTestMessage({name: 'forceBulkPinningCalculateRequiredSpace'});

  // Assert the stage is `PAUSED` and the cloud button is still hidden.
  await remoteCall.waitForBulkPinningStage('PausedOffline');
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');
}

/**
 * Tests that the cloud icon should show if bulk pinning is paused (which
 * represents an offline state) and the user preference is enabled.
 */
export async function toolbarCloudIconShouldShowWhenPausedState() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], BASIC_DRIVE_ENTRY_SET);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Force the bulk pinning preference on.
  await remoteCall.setSpacedFreeSpace(4n << 30n);
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});

  // Set the bulk pinning manager to enter offline mode. This will surface a
  // `PAUSED_OFFLINE` state which has a UI representation iff the pref is
  // enabled.
  await sendTestMessage({name: 'setBulkPinningOnline', enabled: false});

  // Assert the stage is `PAUSED_OFFLINE`, the cloud button is visible and the
  // icon is the offline icon.
  await remoteCall.waitForBulkPinningStage('PausedOffline');
  await remoteCall.waitForElement(appId, '#cloud-button:not([hidden])');
  await remoteCall.waitForElement(
      appId, '#cloud-button > xf-icon[type="bulk_pinning_offline"]');
}

/**
 * Tests that the cloud icon should show when a Files app window has started.
 * This mainly tests that on startup the bulk pin progress is fetched and
 * doesn't require an async event to show.
 */
export async function toolbarCloudIconShouldShowOnStartupEvenIfSyncing() {
  await addEntries(['drive'], [ENTRIES.hello]);

  // Mock the free space returned by spaced to be 4 GB.
  await remoteCall.setSpacedFreeSpace(4n << 30n);

  // Enable the bulk pinning preference.
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});

  // Wait until the pin manager enters the syncing stage otherwise the next
  // syncing event will get ignored.
  await remoteCall.waitForBulkPinningStage('Syncing');

  // Mock the Drive pinning event completing downloading all the data.
  await sendTestMessage({
    name: 'setDrivePinSyncingEvent',
    path: `/root/${ENTRIES.hello.targetPath}`,
    bytesTransferred: 100,
    bytesToTransfer: 100,
  });

  // Wait until the bulk pinning required space reaches 0 (no bytes left to
  // pin). This indicates the bulk pinning manager has finished. When Files app
  // starts after this, no event will go via onBulkPinProgress.
  await remoteCall.waitForBulkPinningRequiredSpace(0);

  // Open a new window to the Drive root and ensure the cloud button is not
  // hidden. The cloud button will show on startup as it relies on the bulk
  // pinning preference to be set.
  const appId =
      await remoteCall.openNewWindow(RootPath.DRIVE, /*appState=*/ {});
  await remoteCall.waitForElement(appId, '#detail-table');
  await remoteCall.waitForElement(appId, '#cloud-button:not([hidden])');

  // The underlying pin manager has a 60s timer to get free disk space. When
  // this happens it emits a progress event and updates the UI. However, once
  // the cloud button is visible the `<xf-cloud-panel>` should have it's data
  // set. To bypass async issues, only wait 10s to check the data is available
  // to ensure its done prior to the 60s free disk space check.
  await remoteCall.waitForCloudPanelState(
      appId, /*items=*/ 1, /*percentage=*/ 100);
}

/**
 * Tests that the cloud icon should show if bulk pinning is paused due to being
 * on a metered network.
 */
export async function toolbarCloudIconShouldShowWhenOnMeteredNetwork() {
  const appId = await remoteCall.setupAndWaitUntilReady(
      RootPath.DRIVE, [], [ENTRIES.hello]);
  await remoteCall.waitForElement(appId, '#cloud-button[hidden]');

  // Force the bulk pinning preference on.
  await remoteCall.setSpacedFreeSpace(4n << 30n);
  await sendTestMessage({name: 'setBulkPinningEnabledPref', enabled: true});

  // Update the drive connection status to return metered and then disable the
  // sync on metered property.
  await sendTestMessage({name: 'setSyncOnMeteredNetwork', enabled: false});
  await sendTestMessage({name: 'setDriveConnectionStatus', status: 'metered'});

  // Assert the cloud icon should have the paused subicon.
  await remoteCall.waitForElement(appId, '#cloud-button:not([hidden])');
  await remoteCall.waitForElement(
      appId, '#cloud-button > xf-icon[type="cloud_paused"]');
}