chromium/chrome/test/data/webui/chromeos/settings/device_page/storage_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 {SettingsStorageElement} from 'chrome://os-settings/lazy_load.js';
import {DevicePageBrowserProxyImpl, Router, routes, setDisplayApiForTesting, StorageSpaceState} 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, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome://webui-test/test_util.js';

import {FakeSystemDisplay} from '../fake_system_display.js';
import {clearBody} from '../utils.js';

import {getFakePrefs} from './device_page_test_util.js';
import {TestDevicePageBrowserProxy} from './test_device_page_browser_proxy.js';

suite('<settings-storage> for device page', () => {
  const isRevampWayfindingEnabled =
      loadTimeData.getBoolean('isRevampWayfindingEnabled');
  let storageSubpage: SettingsStorageElement;
  let fakeSystemDisplay: FakeSystemDisplay;
  let browserProxy: TestDevicePageBrowserProxy;

  setup(async () => {
    // Default is persistent user. If any test needs it, they can override.
    loadTimeData.overrideValues({
      isCryptohomeDataEphemeral: false,
    });

    fakeSystemDisplay = new FakeSystemDisplay();
    setDisplayApiForTesting(fakeSystemDisplay);

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

    browserProxy = new TestDevicePageBrowserProxy();
    DevicePageBrowserProxyImpl.setInstanceForTesting(browserProxy);
  });

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

  async function createStorageSubpage(): Promise<void> {
    clearBody();
    storageSubpage = document.createElement('settings-storage');
    storageSubpage.prefs = getFakePrefs();
    document.body.appendChild(storageSubpage);
    await flushTasks();

    storageSubpage['stopPeriodicUpdate_']();
  }

  async function assertDriveOfflineSizeVisibility(
      driveEnabled: boolean, visible: boolean): Promise<void> {
    await createStorageSubpage();
    storageSubpage.set('prefs.gdata.disabled.value', !driveEnabled);
    await flushTasks();

    const expectedState = visible ? 'be visible' : 'not be visible';
    assertEquals(
        visible,
        isVisible(
            storageSubpage.shadowRoot!.querySelector('#driveOfflineSize')),
        `Expected #driveOfflineSize to ${expectedState} with driveEnabled: ${
            driveEnabled}. isVisible: ${visible}`);
  }

  /**
   * Simulate storage size stat callback.
   */
  function sendStorageSizeStat(
      usedSize: string, availableSize: string, usedRatio: number,
      spaceState: number): void {
    webUIListenerCallback('storage-size-stat-changed', {
      usedSize: usedSize,
      availableSize: availableSize,
      usedRatio: usedRatio,
      spaceState: spaceState,
    });
    flush();
  }

  function getStorageItemLabelFromId(id: string): string {
    const rowItem = storageSubpage.shadowRoot!.querySelector('#' + id);
    assertTrue(!!rowItem);
    const label = rowItem.shadowRoot!.querySelector<HTMLElement>('#label');
    assertTrue(!!label);
    return label.innerText;
  }

  function getStorageItemSubLabelFromId(id: string): string {
    const rowItem = storageSubpage.shadowRoot!.querySelector('#' + id);
    assertTrue(!!rowItem);
    const subLabel =
        rowItem.shadowRoot!.querySelector<HTMLElement>('#subLabel');
    assertTrue(!!subLabel);
    return subLabel.innerText;
  }

  /**
   * Asserts the visibility state of the passed MyFiles element.
   * @param id ID of the MyFiles HTML element to check. One of `myFilesSizeLink`
   *     and `myFilesSizeDiv`.
   * @param visible Whether the checked element should be visible.
   */
  function checkMyFilesElement(id: string, visible: boolean): void {
    const myFilesSizeLabel =
        storageSubpage.shadowRoot!.querySelector<HTMLElement>('#' + id);
    const expectedState = visible ? 'be visible' : 'not be visible';
    if (!myFilesSizeLabel) {
      // Element can't be found at all.
      assertEquals(false, visible, `Expected ${id} to be ${expectedState}`);
      return;
    }

    // Element exists: check the value of `display` which is changed by the
    // `dom-if` condition.
    const expectedDisplay = visible ? '' : 'none';
    assertEquals(
        myFilesSizeLabel.style.display, expectedDisplay,
        `Expected ${id} to be ${expectedState}`);
  }

  /**
   * Sets the `filebrowser.local_user_files_allowed` pref value.
   * @param value Whether local files are allowed.
   */
  async function setLocalUserFilesAllowed(value: boolean): Promise<void> {
    const newPrefs = getFakePrefs();
    newPrefs.filebrowser.local_user_files_allowed.value = value;
    storageSubpage.prefs = newPrefs;
    await flushTasks();
  }

  test('storage stats size', async () => {
    await createStorageSubpage();

    // Low available storage space.
    sendStorageSizeStat('9.1 GB', '0.9 GB', 0.91, StorageSpaceState.LOW);
    assertEquals('91%', storageSubpage.$.inUseLabelArea.style.width);
    assertEquals('9%', storageSubpage.$.availableLabelArea.style.width);
    assertTrue(
        isVisible(storageSubpage.shadowRoot!.querySelector('#lowMessage')));
    assertFalse(isVisible(
        storageSubpage.shadowRoot!.querySelector('#criticallyLowMessage')));
    assertTrue(!!storageSubpage.shadowRoot!.querySelector('#bar.space-low'));
    assertNull(
        storageSubpage.shadowRoot!.querySelector('#bar.space-critically-low'));

    let inUseArea = storageSubpage.$.inUseLabelArea.querySelector<HTMLElement>(
        '.storage-size');
    assertTrue(!!inUseArea);
    assertEquals('9.1 GB', inUseArea.innerText);

    let availableArea =
        storageSubpage.$.availableLabelArea.querySelector<HTMLElement>(
            '.storage-size');
    assertTrue(!!availableArea);
    assertEquals('0.9 GB', availableArea.innerText);

    // Critically low available storage space.
    sendStorageSizeStat(
        '9.7 GB', '0.3 GB', 0.97, StorageSpaceState.CRITICALLY_LOW);
    assertEquals('97%', storageSubpage.$.inUseLabelArea.style.width);
    assertEquals('3%', storageSubpage.$.availableLabelArea.style.width);
    assertFalse(
        isVisible(storageSubpage.shadowRoot!.querySelector('#lowMessage')));
    assertTrue(isVisible(
        storageSubpage.shadowRoot!.querySelector('#criticallyLowMessage')));
    assertNull(storageSubpage.shadowRoot!.querySelector('#bar.space-low'));
    assertTrue(!!storageSubpage.shadowRoot!.querySelector(
        '#bar.space-critically-low'));

    inUseArea = storageSubpage.$.inUseLabelArea.querySelector<HTMLElement>(
        '.storage-size');
    assertTrue(!!inUseArea);
    assertEquals('9.7 GB', inUseArea.innerText);

    availableArea =
        storageSubpage.$.availableLabelArea.querySelector<HTMLElement>(
            '.storage-size');
    assertTrue(!!availableArea);
    assertEquals('0.3 GB', availableArea.innerText);

    // Normal storage usage.
    sendStorageSizeStat('2.5 GB', '7.5 GB', 0.25, StorageSpaceState.NORMAL);
    assertEquals('25%', storageSubpage.$.inUseLabelArea.style.width);
    assertEquals('75%', storageSubpage.$.availableLabelArea.style.width);
    assertFalse(
        isVisible(storageSubpage.shadowRoot!.querySelector('#lowMessage')));
    assertFalse(isVisible(
        storageSubpage.shadowRoot!.querySelector('#criticallyLowMessage')));
    assertNull(storageSubpage.shadowRoot!.querySelector('#bar.space-low'));
    assertNull(
        storageSubpage.shadowRoot!.querySelector('#bar.space-critically-low'));

    inUseArea = storageSubpage.$.inUseLabelArea.querySelector<HTMLElement>(
        '.storage-size');
    assertTrue(!!inUseArea);
    assertEquals('2.5 GB', inUseArea.innerText);

    availableArea =
        storageSubpage.$.availableLabelArea.querySelector<HTMLElement>(
            '.storage-size');
    assertTrue(!!availableArea);
    assertEquals('7.5 GB', availableArea.innerText);
  });

  test('system size default', async () => {
    await createStorageSubpage();

    const systemSizeLabel =
        storageSubpage.shadowRoot!.querySelector<HTMLElement>(
            '#systemSizeLabel');
    assertTrue(!!systemSizeLabel);
    assertEquals('System', systemSizeLabel.innerText);

    let systemSizeSubLabel =
        storageSubpage.shadowRoot!.querySelector<HTMLElement>(
            '#systemSizeSubLabel');
    assertTrue(!!systemSizeSubLabel);
    assertEquals('Calculating…', systemSizeSubLabel.innerText);

    // Send system size callback.
    webUIListenerCallback('storage-system-size-changed', '8.4 GB');
    flush();
    systemSizeSubLabel = storageSubpage.shadowRoot!.querySelector<HTMLElement>(
        '#systemSizeSubLabel');
    assertTrue(!!systemSizeSubLabel);
    assertEquals('8.4 GB', systemSizeSubLabel.innerText);
  });

  test('system size guest mode', async () => {
    // In guest mode, the system row should be hidden.
    loadTimeData.overrideValues({
      isCryptohomeDataEphemeral: true,
    });
    await createStorageSubpage();

    flush();
    assertFalse(
        isVisible(storageSubpage.shadowRoot!.querySelector('#systemSize')));
  });

  test('apps extensions size default', async () => {
    await createStorageSubpage();

    const expectedLabel =
        isRevampWayfindingEnabled ? 'Apps' : 'Apps and extensions';
    assertEquals(expectedLabel, getStorageItemLabelFromId('appsSize'));
    assertEquals('Calculating…', getStorageItemSubLabelFromId('appsSize'));

    // Send apps size callback.
    webUIListenerCallback('storage-apps-size-changed', '59.5 KB');
    flush();
    assertEquals('59.5 KB', getStorageItemSubLabelFromId('appsSize'));
  });

  test('other users size default', async () => {
    await createStorageSubpage();

    // The other users row is visible by default, displaying
    // "calculating...".
    assertEquals('Other users', getStorageItemLabelFromId('otherUsersSize'));
    assertEquals(
        'Calculating…', getStorageItemSubLabelFromId('otherUsersSize'));

    // Simulate absence of other users.
    webUIListenerCallback('storage-other-users-size-changed', '0 B', true);
    flush();
    assertFalse(
        isVisible(storageSubpage.shadowRoot!.querySelector('#otherUsersSize')));

    // Send other users callback with a size that is not null.
    webUIListenerCallback('storage-other-users-size-changed', '322 MB', false);
    flush();
    assertTrue(
        isVisible(storageSubpage.shadowRoot!.querySelector('#otherUsersSize')));
    assertEquals('322 MB', getStorageItemSubLabelFromId('otherUsersSize'));

    // If the user is in Guest mode, the row is not visible.
    storageSubpage.set('isEphemeralUser_', true);
    webUIListenerCallback('storage-other-users-size-changed', '322 MB', false);
    flush();
    assertFalse(
        isVisible(storageSubpage.shadowRoot!.querySelector('#otherUsersSize')));
  });

  test('drive offline size drive disabled', async () => {
    await assertDriveOfflineSizeVisibility(
        false, /* isDriveEnabled */
        false, /* isVisible */
    );

    await assertDriveOfflineSizeVisibility(
        true, /* isDriveEnabled */
        true, /* isVisible */
    );
  });

  test('my files skyvault disabled', async () => {
    loadTimeData.overrideValues({
      enableSkyVault: false,
    });
    await createStorageSubpage();

    checkMyFilesElement('myFilesSizeLink', true);
    checkMyFilesElement('myFilesSizeDiv', false);

    await setLocalUserFilesAllowed(false);
    // Since SkyVault flag is disabled, nothing should change.
    checkMyFilesElement('myFilesSizeLink', true);
    checkMyFilesElement('myFilesSizeDiv', false);
  });

  test('my files skyvault enabled', async () => {
    loadTimeData.overrideValues({
      enableSkyVault: true,
    });
    await createStorageSubpage();

    checkMyFilesElement('myFilesSizeLink', true);
    checkMyFilesElement('myFilesSizeDiv', false);

    await setLocalUserFilesAllowed(false);
    // With SkyVault enabled, the "no link" version should appear.
    checkMyFilesElement('myFilesSizeLink', false);
    checkMyFilesElement('myFilesSizeDiv', true);

    await setLocalUserFilesAllowed(true);
    checkMyFilesElement('myFilesSizeLink', true);
    checkMyFilesElement('myFilesSizeDiv', false);
  });

  test('system encryption', async () => {
    await createStorageSubpage();
    const systemEncryptionLabel =
        storageSubpage.shadowRoot!.querySelector<HTMLElement>(
            '#systemEncryptionLabel');
    assertTrue(!!systemEncryptionLabel);
    assertEquals('User data encryption', systemEncryptionLabel.innerText);

    let systemEncryptionSubLabel =
        storageSubpage.shadowRoot!.querySelector<HTMLElement>(
            '#systemEncryptionSubLabel');
    assertTrue(!!systemEncryptionSubLabel);

    systemEncryptionSubLabel =
        storageSubpage.shadowRoot!.querySelector<HTMLElement>(
            '#systemEncryptionSubLabel');
    assertTrue(!!systemEncryptionSubLabel);
    assertEquals('AES-256', systemEncryptionSubLabel.innerText);
  });

  test('system encryption with guest user', async () => {
    loadTimeData.overrideValues({
      isCryptohomeDataEphemeral: true,
    });
    await createStorageSubpage();

    flush();
    assertFalse(isVisible(
        storageSubpage.shadowRoot!.querySelector('#systemEncryption')));
  });
});