chromium/chrome/test/data/webui/chromeos/settings/os_apps_page/app_management_page/permission_item_test.ts

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

/** @fileoverview Test suite for app-manageemnt-permission-item. */
import 'chrome://os-settings/lazy_load.js';

import {AppManagementPermissionItemElement, MediaDevicesProxy} from 'chrome://os-settings/lazy_load.js';
import {AppManagementStore, CrButtonElement, GeolocationAccessLevel, LocalizedLinkElement, updateSelectedAppId} from 'chrome://os-settings/os_settings.js';
import {App, Permission, PermissionType, TriState} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
import {AppManagementUserAction} from 'chrome://resources/cr_components/app_management/constants.js';
import {PermissionTypeIndex} from 'chrome://resources/cr_components/app_management/permission_constants.js';
import {createTriStatePermission} from 'chrome://resources/cr_components/app_management/permission_util.js';
import {getPermissionValueBool} from 'chrome://resources/cr_components/app_management/util.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, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

import {FakePageHandler} from '../../app_management/fake_page_handler.js';
import {addFakeSensor, fakeComponentBrowserProxy, replaceStore, setupFakeHandler} from '../../app_management/test_util.js';
import {FakeMediaDevices} from '../../fake_media_devices.js';
import {clearBody} from '../../utils.js';

type PermissionMap = Partial<Record<PermissionType, Permission>>;
suite('AppManagementPermissionItemTest', function() {
  let permissionItem: AppManagementPermissionItemElement;
  let fakeHandler: FakePageHandler;
  const app_id: string = 'app_id';
  let mediaDevices: FakeMediaDevices;

  function createPermissionItem(
      permissionType: PermissionTypeIndex = 'kLocation'): void {
    clearBody();
    permissionItem = document.createElement('app-management-permission-item');
    permissionItem.app = getApp();
    permissionItem.permissionType = permissionType;
    permissionItem.prefs = {
      'ash': {
        'user': {
          'camera_allowed': {
            value: true,
          },
          'microphone_allowed': {
            value: true,
          },
          'geolocation_access_level': {
            value: GeolocationAccessLevel.ALLOWED,
          },
        },
      },
    };
    document.body.appendChild(permissionItem);
    flush();
  }

  setup(async function() {
    loadTimeData.overrideValues({'privacyHubAppPermissionsV2Enabled': false});

    const permissions: PermissionMap = {};
    const permissionTypes: PermissionTypeIndex[] =
        ['kCamera', 'kMicrophone', 'kLocation'];
    for (const permissionType of permissionTypes) {
      permissions[PermissionType[permissionType]] = createTriStatePermission(
          PermissionType[permissionType], TriState.kAsk, false);
    }
    fakeHandler = setupFakeHandler();
    replaceStore();
    await fakeHandler.addApp(app_id, {permissions: permissions});
    AppManagementStore.getInstance().dispatch(updateSelectedAppId(app_id));

    createPermissionItem();
  });

  // Fetches the app state from the `AppManagementStore`.
  function getApp(): App {
    const app = AppManagementStore.getInstance().data.apps[app_id];
    assertTrue(!!app);
    return app;
  }

  test('Toggle permission', async function() {
    assertFalse(getPermissionValueBool(
        permissionItem.app, permissionItem.permissionType));

    permissionItem.click();

    const data = await fakeHandler.whenCalled('setPermission');
    assertEquals(data[1].value.tristateValue, TriState.kAllow);

    assertTrue(!!fakeComponentBrowserProxy);
    const metricData =
        await fakeComponentBrowserProxy.whenCalled('recordEnumerationValue');
    assertEquals(metricData[1], AppManagementUserAction.LOCATION_TURNED_ON);
  });

  test('Permission item has no description', async function() {
    assertNull(permissionItem.shadowRoot!.querySelector<HTMLElement>(
        '#permissionDescription'));
  });

  suite('Permission item with description', () => {
    setup(() => {
      mediaDevices = new FakeMediaDevices();
      MediaDevicesProxy.setMediaDevicesForTesting(mediaDevices);

      loadTimeData.overrideValues({
        'privacyHubAppPermissionsV2Enabled': true,
        'privacyHubLocationAccessControlEnabled': true,
      });

      createPermissionItem();
    });

    teardown(() => {
      loadTimeData.overrideValues({
        'privacyHubAppPermissionsV2Enabled': false,
        'privacyHubLocationAccessControlEnabled': false,
      });
    });

    function getPermissionDescriptionString(): string {
      return permissionItem.shadowRoot!
          .querySelector<LocalizedLinkElement>(
              '#permissionDescription')!.localizedString.toString();
    }

    async function togglePermission(): Promise<void> {
      permissionItem.click();
      await flushTasks();
      permissionItem.set('app', getApp());
    }

    test('Toggle permission', async () => {
      assertEquals(
          loadTimeData.getString('appManagementPermissionAsk'),
          getPermissionDescriptionString());

      await togglePermission();

      assertEquals(
          loadTimeData.getString('appManagementPermissionAllowed'),
          getPermissionDescriptionString());

      await togglePermission();

      assertEquals(
          loadTimeData.getString('appManagementPermissionDenied'),
          getPermissionDescriptionString());
    });

    test('Turn on sensor system access button displayed', async () => {
      assertEquals(
          loadTimeData.getString('appManagementPermissionAsk'),
          getPermissionDescriptionString());

      await togglePermission();

      assertEquals(
          loadTimeData.getString('appManagementPermissionAllowed'),
          getPermissionDescriptionString());

      permissionItem.set(
          'prefs.ash.user.geolocation_access_level.value',
          GeolocationAccessLevel.DISALLOWED);

      assertEquals(
          loadTimeData.getString(
              'permissionAllowedTextWithTurnOnLocationAccessButton'),
          getPermissionDescriptionString());

      permissionItem.set(
          'prefs.ash.user.geolocation_access_level.value',
          GeolocationAccessLevel.ALLOWED);

      assertEquals(
          loadTimeData.getString('appManagementPermissionAllowed'),
          getPermissionDescriptionString());
    });

    function getDialogElement(): HTMLElement|null {
      return permissionItem.shadowRoot!.querySelector<HTMLElement>('#dialog');
    }

    async function openDialog(): Promise<void> {
      const permissionDescription =
          permissionItem.shadowRoot!.querySelector<LocalizedLinkElement>(
              '#permissionDescription');
      assertTrue(!!permissionDescription);
      const link = permissionDescription.shadowRoot!.querySelector('a');
      assertTrue(!!link);
      link.click();
      await waitAfterNextRender(permissionItem);
    }

    test('Open dialog and close using cancel button', async () => {
      await togglePermission();

      permissionItem.set(
          'prefs.ash.user.geolocation_access_level.value',
          GeolocationAccessLevel.DISALLOWED);

      assertEquals(
          loadTimeData.getString(
              'permissionAllowedTextWithTurnOnLocationAccessButton'),
          getPermissionDescriptionString());

      // Dialog not visible initially.
      assertNull(getDialogElement());

      await openDialog();

      // Dialog is visible.
      assertTrue(!!getDialogElement());

      // Close dialog.
      const cancelButton =
          getDialogElement()!.shadowRoot!.querySelector<CrButtonElement>(
              '#cancelButton');
      assertTrue(!!cancelButton);
      cancelButton.click();
      await waitAfterNextRender(permissionItem);

      // Dialog not visible anymore.
      assertNull(getDialogElement());
    });

    test('Open dialog and turn on sensor access', async () => {
      await togglePermission();

      permissionItem.set(
          'prefs.ash.user.geolocation_access_level.value',
          GeolocationAccessLevel.DISALLOWED);

      assertEquals(
          loadTimeData.getString(
              'permissionAllowedTextWithTurnOnLocationAccessButton'),
          getPermissionDescriptionString());

      // Dialog not visible initially.
      assertNull(getDialogElement());

      await openDialog();

      // Dialog is visible.
      assertTrue(!!getDialogElement());

      // Turn on system sensor access.
      const confirmButton =
          getDialogElement()!.shadowRoot!.querySelector<CrButtonElement>(
              '#confirmButton');
      assertTrue(!!confirmButton);
      confirmButton.click();
      await waitAfterNextRender(permissionItem);

      // Dialog not visible anymore.
      assertNull(getDialogElement());
      // Sensor access is turned ON.
      assertEquals(
          GeolocationAccessLevel.ALLOWED,
          permissionItem.prefs.ash.user.geolocation_access_level.value);
    });

    test(
        'Permission description updated when no sensor connected', async () => {
          const checkPermissionDescription = async (
              permissionType: PermissionTypeIndex,
              expectedDescription: string) => {
            createPermissionItem(permissionType);

            // Permission state is kAsk at the beginning of the test.
            assertEquals(
                loadTimeData.getString('appManagementPermissionAsk'),
                getPermissionDescriptionString());

            await togglePermission();

            assertEquals(expectedDescription, getPermissionDescriptionString());

            await addFakeSensor(mediaDevices, permissionType);

            assertEquals(
                loadTimeData.getString('appManagementPermissionAllowed'),
                getPermissionDescriptionString());

            mediaDevices.popDevice();
            await flushTasks();

            assertEquals(expectedDescription, getPermissionDescriptionString());
          };

          await checkPermissionDescription(
              'kCamera',
              loadTimeData.getString(
                  'permissionAllowedButNoCameraConnectedText'));
          await checkPermissionDescription(
              'kMicrophone',
              loadTimeData.getString(
                  'permissionAllowedButNoMicrophoneConnectedText'));
        });

    test(
        'Permission description updated when microphone hw switch ON',
        async () => {
          createPermissionItem('kMicrophone');

          // Permission state is kAsk at the beginning of the test.
          assertEquals(
              loadTimeData.getString('appManagementPermissionAsk'),
              getPermissionDescriptionString());

          await togglePermission();

          assertEquals(
              loadTimeData.getString(
                  'permissionAllowedButNoMicrophoneConnectedText'),
              getPermissionDescriptionString());

          await addFakeSensor(mediaDevices, 'kMicrophone');

          assertEquals(
              loadTimeData.getString('appManagementPermissionAllowed'),
              getPermissionDescriptionString());

          webUIListenerCallback('microphone-hardware-toggle-changed', true);
          await waitAfterNextRender(permissionItem);

          assertEquals(
              loadTimeData.getString(
                  'permissionAllowedButMicrophoneHwSwitchActiveText'),
              getPermissionDescriptionString());

          webUIListenerCallback('microphone-hardware-toggle-changed', false);
          await waitAfterNextRender(permissionItem);

          assertEquals(
              loadTimeData.getString('appManagementPermissionAllowed'),
              getPermissionDescriptionString());
        });

    // Camera toggle button is force-disabled in CRD session.
    test(
        'Allow camera access button hidden if camera toggle is force disabled',
        async () => {
          createPermissionItem('kCamera');

          // Permission state is kAsk at the beginning of the test.
          assertEquals(
              loadTimeData.getString('appManagementPermissionAsk'),
              getPermissionDescriptionString());

          await togglePermission();
          permissionItem.set('prefs.ash.user.camera_allowed.value', false);
          await addFakeSensor(mediaDevices, 'kCamera');

          assertEquals(
              loadTimeData.getString(
                  'permissionAllowedTextWithTurnOnCameraAccessButton'),
              getPermissionDescriptionString());

          webUIListenerCallback('force-disable-camera-switch', true);
          await waitAfterNextRender(permissionItem);

          assertEquals(
              loadTimeData.getString('appManagementPermissionAllowed'),
              getPermissionDescriptionString());

          webUIListenerCallback('force-disable-camera-switch', false);
          await waitAfterNextRender(permissionItem);

          assertEquals(
              loadTimeData.getString(
                  'permissionAllowedTextWithTurnOnCameraAccessButton'),
              getPermissionDescriptionString());
        });

    test(
        'Allow mic access button hidden if mic is muted by security curtain',
        async () => {
          createPermissionItem('kMicrophone');

          // Permission state is kAsk at the beginning of the test.
          assertEquals(
              loadTimeData.getString('appManagementPermissionAsk'),
              getPermissionDescriptionString());

          await togglePermission();
          permissionItem.set('prefs.ash.user.microphone_allowed.value', false);
          await addFakeSensor(mediaDevices, 'kMicrophone');

          assertEquals(
              loadTimeData.getString(
                  'permissionAllowedTextWithTurnOnMicrophoneAccessButton'),
              getPermissionDescriptionString());

          webUIListenerCallback(
              'microphone-muted-by-security-curtain-changed', true);
          await waitAfterNextRender(permissionItem);

          assertEquals(
              loadTimeData.getString('appManagementPermissionAllowed'),
              getPermissionDescriptionString());

          webUIListenerCallback(
              'microphone-muted-by-security-curtain-changed', false);
          await waitAfterNextRender(permissionItem);

          assertEquals(
              loadTimeData.getString(
                  'permissionAllowedTextWithTurnOnMicrophoneAccessButton'),
              getPermissionDescriptionString());
        });
  });
});