chromium/chrome/test/data/webui/chromeos/settings/crostini_page/crostini_subpage_test.ts

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

import 'chrome://os-settings/lazy_load.js';

import {CrostiniBrowserProxyImpl, CrostiniPortSetting, GuestOsBrowserProxyImpl, SettingsCrostiniDiskResizeDialogElement, SettingsCrostiniSubpageElement} from 'chrome://os-settings/lazy_load.js';
import {CrSliderElement, Router, routes, settingMojom, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertGE, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

import {TestGuestOsBrowserProxy} from '../guest_os/test_guest_os_browser_proxy.js';
import {clearBody} from '../utils.js';

import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';

interface PrefParams {
  sharedPaths?: {[key: string]: string[]};
  forwardedPorts?: CrostiniPortSetting[];
  micAllowed?: boolean;
  arcEnabled?: boolean;
  bruschettaInstalled?: boolean;
}

suite('<settings-crostini-subpage>', () => {
  let subpage: SettingsCrostiniSubpageElement;
  let guestOsBrowserProxy: TestGuestOsBrowserProxy;
  let crostiniBrowserProxy: TestCrostiniBrowserProxy;

  const MIC_ALLOWED_PREF_PATH = 'prefs.crostini.mic_allowed.value';

  function setCrostiniPrefs(enabled: boolean, {
    sharedPaths = {},
    forwardedPorts = [],
    micAllowed = false,
    arcEnabled = false,
    bruschettaInstalled = false,
  }: PrefParams = {}): void {
    subpage.prefs = {
      arc: {
        enabled: {value: arcEnabled},
      },
      bruschetta: {
        installed: {
          value: bruschettaInstalled,
        },
      },
      crostini: {
        enabled: {value: enabled},
        mic_allowed: {value: micAllowed},
        port_forwarding: {ports: {value: forwardedPorts}},
      },
      guest_os: {
        paths_shared_to_vms: {value: sharedPaths},
      },
    };
    flush();
  }

  setup(async () => {
    loadTimeData.overrideValues({
      isCrostiniAllowed: true,
      isCrostiniSupported: true,
      showCrostiniExportImport: true,
      showCrostiniContainerUpgrade: true,
      showCrostiniPortForwarding: true,
      showCrostiniDiskResize: true,
      arcAdbSideloadingSupported: true,
      showCrostiniExtraContainers: true,
    });

    crostiniBrowserProxy = new TestCrostiniBrowserProxy();
    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
    guestOsBrowserProxy = new TestGuestOsBrowserProxy();
    GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);

    Router.getInstance().navigateTo(routes.CROSTINI_DETAILS);

    clearBody();
    subpage = document.createElement('settings-crostini-subpage');
    document.body.appendChild(subpage);
    setCrostiniPrefs(true, {arcEnabled: true});
    await flushTasks();
  });

  teardown(() => {
    Router.getInstance().resetRouteForTesting();
  });

  suite('Subpage default', () => {
    test('Basic', () => {
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniSharedPathsRow')));
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniSharedUsbDevicesRow')));
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniExportImportRow')));
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniEnableArcAdbRow')));
      assertTrue(isVisible(subpage.shadowRoot!.querySelector('#remove')));
      assertTrue(
          isVisible(subpage.shadowRoot!.querySelector('#container-upgrade')));
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniPortForwardingRow')));
      assertTrue(isVisible(subpage.shadowRoot!.querySelector(
          '#crostini-mic-permission-toggle')));
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniDiskResizeRow')));
      assertTrue(isVisible(
          subpage.shadowRoot!.querySelector('#crostiniExtraContainersRow')));
    });

    test('Shared paths', async () => {
      const button = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
          '#crostiniSharedPathsRow');
      assertTrue(!!button);
      button.click();
      flush();

      assertEquals(
          routes.CROSTINI_SHARED_PATHS, Router.getInstance().currentRoute);
    });

    test('Container upgrade', () => {
      const crButton = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
          '#container-upgrade cr-button');
      assertTrue(!!crButton);
      crButton.click();
      assertEquals(
          1,
          crostiniBrowserProxy.getCallCount(
              'requestCrostiniContainerUpgradeView'));
    });

    test('Container upgrade button disabled on upgrade dialog', async () => {
      const button = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
          '#container-upgrade cr-button');
      assertTrue(!!button);

      await flushTasks();
      assertFalse(button.disabled);
      webUIListenerCallback('crostini-upgrader-status-changed', true);

      await flushTasks();
      assertTrue(button.disabled);
      webUIListenerCallback('crostini-upgrader-status-changed', false);

      await flushTasks();
      assertFalse(button.disabled);
    });

    test('Container upgrade button disabled on install', async () => {
      const button = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
          '#container-upgrade cr-button');
      assertTrue(!!button);

      await flushTasks();
      assertFalse(button.disabled);
      webUIListenerCallback('crostini-installer-status-changed', true);

      await flushTasks();
      assertTrue(button.disabled);
      webUIListenerCallback('crostini-installer-status-changed', false);

      await flushTasks();
      assertFalse(button.disabled);
    });

    test('Installer status queried on attach', () => {
      // We navigated the page during setup, so this request should've been
      // triggered by here.
      assertGE(
          crostiniBrowserProxy.getCallCount('requestCrostiniInstallerStatus'),
          1);
    });

    test('Toggle crostini mic permission cancel', async () => {
      // Crostini is assumed to be running when the page is loaded.
      let toggle =
          subpage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
              '#crostini-mic-permission-toggle');
      assertTrue(!!toggle);
      let dialog =
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog');
      assertNull(dialog);

      setCrostiniPrefs(true, {micAllowed: true});
      assertTrue(toggle.checked);

      toggle.click();
      await flushTasks();

      dialog =
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog');
      assertTrue(!!dialog);
      const dialogClosedPromise = eventToPromise('close', dialog);
      const cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('.cancel-button');
      assertTrue(!!cancelBtn);
      cancelBtn.click();
      await Promise.all([dialogClosedPromise, flushTasks()]);

      // Because the dialog was cancelled, the toggle should not have changed.
      assertNull(
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog'));

      toggle = subpage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
          '#crostini-mic-permission-toggle');
      assertTrue(!!toggle);
      assertTrue(toggle.checked);
      assertTrue(subpage.get(MIC_ALLOWED_PREF_PATH));
    });

    test('Toggle crostini mic permission shutdown', async () => {
      // Crostini is assumed to be running when the page is loaded.
      let toggle =
          subpage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
              '#crostini-mic-permission-toggle');
      assertTrue(!!toggle);
      let dialog =
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog');
      assertNull(dialog);

      setCrostiniPrefs(true, {micAllowed: false});

      assertFalse(toggle.checked);

      toggle.click();
      await flushTasks();
      dialog =
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog');
      assertTrue(!!dialog);
      const dialogClosedPromise = eventToPromise('close', dialog);
      const actionBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('.action-button');
      assertTrue(!!actionBtn);
      actionBtn.click();
      await Promise.all([dialogClosedPromise, flushTasks()]);
      assertEquals(1, crostiniBrowserProxy.getCallCount('shutdownCrostini'));
      assertNull(
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog'));
      toggle = subpage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
          '#crostini-mic-permission-toggle');
      assertTrue(!!toggle);
      assertTrue(toggle.checked);
      assertTrue(subpage.get(MIC_ALLOWED_PREF_PATH));

      // Crostini is now shutdown, this means that it doesn't need to be
      // restarted in order for changes to take effect, therefore no dialog is
      // needed and the mic sharing settings can be changed immediately.
      toggle.click();
      await flushTasks();
      assertNull(
          subpage.shadowRoot!.querySelector('#crostini-mic-permission-dialog'));
      assertFalse(toggle.checked);
      assertFalse(subpage.get(MIC_ALLOWED_PREF_PATH));
    });

    // TODO(b/313456787) Re-enable test once fixed.
    /* test('Remove', async () => {
      const button = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
          '#remove cr-button');
      assertTrue(!!button);
      button.click();

      assertEquals(
          1, crostiniBrowserProxy.getCallCount('requestRemoveCrostini'));
      setCrostiniPrefs(false);

      await flushTasks();
      assertEquals(routes.CROSTINI, Router.getInstance().currentRoute);

      const crostiniSettingsCard =
          crostiniPage.shadowRoot!.querySelector('crostini-settings-card');
      assertTrue(!!crostiniSettingsCard);
      assertTrue(!!crostiniSettingsCard.shadowRoot!.querySelector(
          '#enableCrostiniButton'));
    }); */

    test('Remove hidden', async () => {
      // Elements are not destroyed when a dom-if stops being shown, but we
      // can check if their rendered width is non-zero. This should be
      // resilient against most formatting changes, since we're not relying on
      // them having any exact size, or on Polymer using any particular means
      // of hiding elements.
      let removeElement = subpage.shadowRoot!.querySelector('#remove');
      assertTrue(isVisible(removeElement));
      webUIListenerCallback('crostini-installer-status-changed', true);

      await flushTasks();
      removeElement = subpage.shadowRoot!.querySelector('#remove');
      assertTrue(!!removeElement);
      assertEquals(0, removeElement.clientWidth);
      webUIListenerCallback('crostini-installer-status-changed', false);

      await flushTasks();
      removeElement = subpage.shadowRoot!.querySelector('#remove');
      assertTrue(isVisible(removeElement));
    });

    test('Disabling crostini returns to previous route', async () => {
      assertEquals(routes.CROSTINI_DETAILS, Router.getInstance().currentRoute);
      const popstateEventPromise = eventToPromise('popstate', window);
      setCrostiniPrefs(false);
      await popstateEventPromise;
    });

    test('Disk resize opens when clicked', async () => {
      const showDiskResizeButton =
          subpage.shadowRoot!.querySelector<HTMLButtonElement>(
              '#showDiskResizeButton');
      assertTrue(!!showDiskResizeButton);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo',
          {succeeded: true, canResize: true, isUserChosenSize: true});
      showDiskResizeButton.click();

      await flushTasks();
      const dialog = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-dialog');
      assertTrue(!!dialog);
    });

    test('Deep link to resize disk', async () => {
      assertTrue(!!subpage.shadowRoot!.querySelector('#showDiskResizeButton'));
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo',
          {succeeded: true, canResize: true, isUserChosenSize: true});

      const CROSTINI_DISK_RESIZE_SETTING =
          settingMojom.Setting.kCrostiniDiskResize.toString();
      const params = new URLSearchParams();
      params.append('settingId', CROSTINI_DISK_RESIZE_SETTING);
      Router.getInstance().navigateTo(routes.CROSTINI_DETAILS, params);

      const deepLinkElement =
          subpage.shadowRoot!.querySelector<HTMLButtonElement>(
              '#showDiskResizeButton');
      assertTrue(!!deepLinkElement);
      await waitAfterNextRender(deepLinkElement);
      assertEquals(
          deepLinkElement, subpage.shadowRoot!.activeElement,
          `Resize disk button should be focused for settingId=${
              CROSTINI_DISK_RESIZE_SETTING}.`);
    });
  });

  suite('Disk resize', () => {
    let dialog: SettingsCrostiniDiskResizeDialogElement;
    /**
     * Helper function to assert that the expected block is visible and the
     * others are not.
     */
    function assertVisibleBlockIs(selector: string): void {
      const selectors = ['#unsupported', '#resize-block', '#error', '#loading'];

      assertTrue(isVisible(dialog.shadowRoot!.querySelector(selector)));
      selectors.filter(s => s !== selector).forEach(s => {
        assertFalse(isVisible(dialog.shadowRoot!.querySelector(s)));
      });
    }

    const ticks = [
      {label: 'label 0', value: 0, ariaLabel: 'label 0'},
      {label: 'label 10', value: 10, ariaLabel: 'label 10'},
      {label: 'label 100', value: 100, ariaLabel: 'label 100'},
    ];

    const resizeableData = {
      succeeded: true,
      canResize: true,
      isUserChosenSize: true,
      isLowSpaceAvailable: false,
      defaultIndex: 2,
      ticks,
    };

    const sparseDiskData = {
      succeeded: true,
      canResize: true,
      isUserChosenSize: false,
      isLowSpaceAvailable: false,
      defaultIndex: 2,
      ticks,
    };

    async function clickShowDiskResize(userChosen: boolean): Promise<void> {
      await crostiniBrowserProxy.resolvePromises('getCrostiniDiskInfo', {
        succeeded: true,
        canResize: true,
        isUserChosenSize: userChosen,
        ticks,
        defaultIndex: 2,
      });

      const button = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
          '#showDiskResizeButton');
      assertTrue(!!button);
      button.click();
      await flushTasks();

      const dialogElement = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-dialog');

      if (userChosen) {
        // We should be on the loading page but unable to kick off a resize
        // yet.
        assertTrue(!!dialogElement);
        dialog = dialogElement;
        assertTrue(!!dialog.shadowRoot!.querySelector('#loading'));
        const resizeBtn =
            dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
        assertTrue(!!resizeBtn);
        assertTrue(resizeBtn.disabled);
      }
    }

    test('Resize unsupported', async () => {
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', {succeeded: true, canResize: false});
      assertFalse(isVisible(
          subpage.shadowRoot!.querySelector('#showDiskResizeButton')));
      const subtext = subpage.shadowRoot!.querySelector<HTMLElement>(
          '#diskSizeDescription');
      assertTrue(!!subtext);
      assertEquals(
          loadTimeData.getString('crostiniDiskResizeNotSupportedSubtext'),
          subtext.innerText);
    });

    test('Resize button and subtext correctly set', async () => {
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', resizeableData);
      const button = subpage.shadowRoot!.querySelector<HTMLElement>(
          '#showDiskResizeButton');
      const subtext = subpage.shadowRoot!.querySelector<HTMLElement>(
          '#diskSizeDescription');
      assertTrue(!!button);
      assertTrue(!!subtext);

      assertEquals(
          loadTimeData.getString('crostiniDiskResizeShowButton'),
          button.innerText);
      assertEquals('label 100', subtext.innerText);
    });

    test('Reserve size button and subtext correctly set', async () => {
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', sparseDiskData);
      const button = subpage.shadowRoot!.querySelector<HTMLElement>(
          '#showDiskResizeButton');
      const subtext = subpage.shadowRoot!.querySelector<HTMLElement>(
          '#diskSizeDescription');
      assertTrue(!!button);
      assertTrue(!!subtext);

      assertEquals(
          loadTimeData.getString('crostiniDiskReserveSizeButton'),
          button.innerText);
      assertEquals(
          loadTimeData.getString(
              'crostiniDiskResizeDynamicallyAllocatedSubtext'),
          subtext.innerText);
    });

    test('Resize recommendation shown correctly', async () => {
      await clickShowDiskResize(true);
      const diskInfo = resizeableData;
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', diskInfo);

      assertTrue(
          isVisible(dialog.shadowRoot!.querySelector('#recommended-size')));
      assertFalse(isVisible(
          dialog.shadowRoot!.querySelector('#recommended-size-warning')));
    });

    test('Resize recommendation warning shown correctly', async () => {
      await clickShowDiskResize(true);
      const diskInfo = resizeableData;
      diskInfo.isLowSpaceAvailable = true;
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', diskInfo);

      assertFalse(
          isVisible(dialog.shadowRoot!.querySelector('#recommended-size')));
      assertTrue(isVisible(
          dialog.shadowRoot!.querySelector('#recommended-size-warning')));
    });

    test('Message shown if error and can retry', async () => {
      await clickShowDiskResize(true);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', {succeeded: false, isUserChosenSize: true});

      // We failed, should have a retry button.
      let button =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#retry');
      assertVisibleBlockIs('#error');
      assertTrue(!!button);

      let resizeBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!resizeBtn);
      assertTrue(resizeBtn.disabled);

      let cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertFalse(cancelBtn.disabled);

      // Back to the loading screen.
      button.click();
      await flushTasks();
      assertVisibleBlockIs('#loading');

      resizeBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!resizeBtn);
      assertTrue(resizeBtn.disabled);

      cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertFalse(cancelBtn.disabled);

      // And failure page again.
      await crostiniBrowserProxy.rejectPromises('getCrostiniDiskInfo');
      button = dialog.shadowRoot!.querySelector('#retry');
      assertTrue(isVisible(button));
      assertVisibleBlockIs('#error');

      resizeBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!resizeBtn);
      assertTrue(resizeBtn.disabled);

      cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertFalse(cancelBtn.disabled);
    });

    test('Message shown if cannot resize', async () => {
      await clickShowDiskResize(true);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo',
          {succeeded: true, canResize: false, isUserChosenSize: true});
      assertVisibleBlockIs('#unsupported');

      const resizeBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!resizeBtn);
      assertTrue(resizeBtn.disabled);

      const cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertFalse(cancelBtn.disabled);
    });

    test('Resize page shown if can resize', async () => {
      await clickShowDiskResize(true);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', resizeableData);
      assertVisibleBlockIs('#resize-block');

      const labelBegin =
          dialog.shadowRoot!.querySelector<HTMLElement>('#label-begin');
      assertTrue(!!labelBegin);
      assertEquals(ticks[0]!.label, labelBegin.innerText);

      const labelEnd =
          dialog.shadowRoot!.querySelector<HTMLElement>('#label-end');
      assertTrue(!!labelEnd);
      assertEquals(ticks[2]!.label, labelEnd.innerText);

      const diskSlider =
          dialog.shadowRoot!.querySelector<CrSliderElement>('#diskSlider');
      assertTrue(!!diskSlider);
      assertEquals(2, diskSlider.value);

      const resizeBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!resizeBtn);
      assertFalse(resizeBtn.disabled);

      const cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertFalse(cancelBtn.disabled);
    });

    test('In progress resizing', async () => {
      await clickShowDiskResize(true);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', resizeableData);
      const resizeBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!resizeBtn);
      resizeBtn.click();
      await flushTasks();
      assertTrue(resizeBtn.disabled);
      assertFalse(isVisible(dialog.shadowRoot!.querySelector('#done')));
      assertTrue(isVisible(dialog.shadowRoot!.querySelector('#resizing')));
      assertFalse(isVisible(dialog.shadowRoot!.querySelector('#resize-error')));
      const cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertTrue(cancelBtn.disabled);
    });

    test('Error resizing', async () => {
      await clickShowDiskResize(true);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', resizeableData);
      const button =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!button);
      button.click();
      await crostiniBrowserProxy.resolvePromises('resizeCrostiniDisk', false);
      assertFalse(button.disabled);

      assertFalse(isVisible(dialog.shadowRoot!.querySelector('#done')));
      assertFalse(isVisible(dialog.shadowRoot!.querySelector('#resizing')));
      assertTrue(isVisible(dialog.shadowRoot!.querySelector('#resize-error')));

      const cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      assertFalse(cancelBtn.disabled);
    });

    test('Success resizing', async () => {
      await clickShowDiskResize(true);
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', resizeableData);
      const button =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#resize');
      assertTrue(!!button);
      button.click();
      await crostiniBrowserProxy.resolvePromises('resizeCrostiniDisk', true);
      // Dialog should close itself.
      await eventToPromise('close', dialog);
    });

    test('Disk resize confirmation dialog shown and accepted', async () => {
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', sparseDiskData);
      await clickShowDiskResize(false);
      // Dismiss confirmation.
      let confirmationDialog = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-confirmation-dialog');
      assertTrue(!!confirmationDialog);
      assertTrue(
          isVisible(confirmationDialog.shadowRoot!.querySelector('#cancel')));
      let continueBtn =
          confirmationDialog.shadowRoot!.querySelector<HTMLButtonElement>(
              '#continue');
      assertTrue(!!continueBtn);
      continueBtn.click();
      await eventToPromise('close', confirmationDialog);
      assertFalse(isVisible(confirmationDialog));

      let dialogElement = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-dialog');
      assertTrue(!!dialogElement);
      dialog = dialogElement;
      assertTrue(isVisible(dialog.shadowRoot!.querySelector('#resize')));

      // Cancel main resize dialog.
      const cancelBtn =
          dialog.shadowRoot!.querySelector<HTMLButtonElement>('#cancel');
      assertTrue(!!cancelBtn);
      cancelBtn.click();
      await eventToPromise('close', dialog);
      assertFalse(isVisible(dialog));

      // On another click, confirmation dialog should be shown again.
      await clickShowDiskResize(false);
      confirmationDialog = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-confirmation-dialog');
      assertTrue(!!confirmationDialog);
      continueBtn =
          confirmationDialog.shadowRoot!.querySelector<HTMLButtonElement>(
              '#continue');
      assertTrue(!!continueBtn);
      continueBtn.click();
      await eventToPromise('close', confirmationDialog);

      // Main dialog should show again.
      dialogElement = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-dialog');
      assertTrue(!!dialogElement);
      dialog = dialogElement;
      assertTrue(isVisible(dialog.shadowRoot!.querySelector('#resize')));
      assertTrue(isVisible(dialog.shadowRoot!.querySelector('#cancel')));
    });

    test('Disk resize confirmation dialog shown and canceled', async () => {
      await crostiniBrowserProxy.resolvePromises(
          'getCrostiniDiskInfo', sparseDiskData);
      await clickShowDiskResize(false);

      const confirmationDialog = subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-confirmation-dialog');
      assertTrue(!!confirmationDialog);
      assertTrue(
          isVisible(confirmationDialog.shadowRoot!.querySelector('#continue')));

      const cancelBtn =
          confirmationDialog.shadowRoot!.querySelector<HTMLButtonElement>(
              '#cancel');
      assertTrue(!!cancelBtn);
      cancelBtn.click();
      await eventToPromise('close', confirmationDialog);

      assertNull(subpage.shadowRoot!.querySelector(
          'settings-crostini-disk-resize-dialog'));
    });
  });
});