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

// Copyright 2020 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 {AppManagementPinToShelfItemElement, AppManagementPluginVmDetailViewElement} from 'chrome://os-settings/lazy_load.js';
import {AppManagementStore, AppManagementToggleRowElement, CrButtonElement, CrToggleElement, PluginVmBrowserProxyImpl, updateSelectedAppId} from 'chrome://os-settings/os_settings.js';
import {App, AppType, Permission, PermissionType} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
import {PermissionTypeIndex} from 'chrome://resources/cr_components/app_management/permission_constants.js';
import {createBoolPermission} from 'chrome://resources/cr_components/app_management/permission_util.js';
import {getPermissionValueBool} from 'chrome://resources/cr_components/app_management/util.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';

import {FakePageHandler} from '../../app_management/fake_page_handler.js';
import {getPermissionCrToggleByType, getPermissionToggleByType, replaceBody, replaceStore, setupFakeHandler} from '../../app_management/test_util.js';

import {TestPluginVmBrowserProxy} from './test_plugin_vm_browser_proxy.js';

// TODO(b/270728282) - remove "as" cast once getPermissionCrToggleByType()
// becomes a TS function.
suite('<app-management-plugin-vm-detail-view>', () => {
  let pluginVmDetailView: AppManagementPluginVmDetailViewElement;
  let fakeHandler: FakePageHandler;
  let pluginVmBrowserProxy: TestPluginVmBrowserProxy;
  let appId: string;

  function getPermissionBoolByType(permissionType: PermissionTypeIndex):
      boolean {
    return getPermissionValueBool(
        pluginVmDetailView.get('app_'), permissionType);
  }

  function isCrToggleChecked(permissionType: PermissionTypeIndex): boolean {
    return (getPermissionCrToggleByType(pluginVmDetailView, permissionType) as
            CrToggleElement)
        .checked;
  }

  async function clickToggle(permissionType: PermissionTypeIndex):
      Promise<void> {
    const toggleRow =
        getPermissionToggleByType(pluginVmDetailView, permissionType) as
        AppManagementToggleRowElement;
    toggleRow.click();
    // It appears that we need to await twice so that camera/mic toggles can
    // get back from `isRelaunchNeededForNewPermissions()` and commit the
    // change.
    await fakeHandler.flushPipesForTesting();
    await fakeHandler.flushPipesForTesting();
  }

  function getSelectedAppFromStore(): App {
    const storeData = AppManagementStore.getInstance().data;
    assertTrue(!!storeData);
    const selectedAppId = storeData.selectedAppId;
    assertTrue(!!selectedAppId);
    const selectedApp = storeData.apps[selectedAppId];
    assertTrue(!!selectedApp);
    return selectedApp;
  }

  async function checkAndAcceptDialog(textId: string): Promise<void> {
    const dialogBody = pluginVmDetailView.shadowRoot!.querySelector(
        'cr-dialog div[slot="body"]');
    assertTrue(!!dialogBody);
    assertEquals(dialogBody.textContent, loadTimeData.getString(textId));
    const button =
        pluginVmDetailView.shadowRoot!.querySelector<CrButtonElement>(
            'cr-dialog cr-button.action-button');
    assertTrue(!!button);
    button.click();
    await fakeHandler.flushPipesForTesting();
  }

  async function checkAndCancelDialog(
      textId: string, cancelByEsc: boolean): Promise<void> {
    const dialogBody = pluginVmDetailView.shadowRoot!.querySelector(
        'cr-dialog div[slot="body"]');
    assertTrue(!!dialogBody);
    assertEquals(dialogBody.textContent, loadTimeData.getString(textId));
    if (cancelByEsc) {
      // When <esc> is used to cancel the button, <cr-dialog> will fire a
      // "cancel" event.
      const dialog = pluginVmDetailView.shadowRoot!.querySelector(`cr-dialog`);
      assertTrue(!!dialog);
      dialog.dispatchEvent(new Event('cancel'));
    } else {
      const button =
          pluginVmDetailView.shadowRoot!.querySelector<CrButtonElement>(
              'cr-dialog cr-button.cancel-button');
      assertTrue(!!button);
      button.click();
    }
    await fakeHandler.flushPipesForTesting();
  }

  suiteSetup(() => {
    pluginVmBrowserProxy = new TestPluginVmBrowserProxy();
    PluginVmBrowserProxyImpl.setInstanceForTesting(pluginVmBrowserProxy);
  });

  setup(async () => {
    fakeHandler = setupFakeHandler();
    replaceStore();

    loadTimeData.overrideValues({
      showPluginVmCameraPermissions: true,
      showPluginVmMicrophonePermissions: true,
    });

    const permissions: Partial<Record<PermissionType, Permission>> = {};
    const permissionTypes = [
      PermissionType.kPrinting,
      PermissionType.kCamera,
      PermissionType.kMicrophone,
    ];
    for (const permissionType of permissionTypes) {
      permissions[permissionType] =
          createBoolPermission(permissionType, true, false /*is_managed*/);
    }

    pluginVmBrowserProxy.setPluginVmRunning(false);

    // Add an app, and make it the currently selected app.
    const options = {
      type: AppType.kPluginVm,
      permissions: permissions,
    };
    const app = await fakeHandler.addApp('', options);
    appId = app.id;
    AppManagementStore.getInstance().dispatch(updateSelectedAppId(appId));

    pluginVmDetailView =
        document.createElement('app-management-plugin-vm-detail-view');
    replaceBody(pluginVmDetailView);
    await fakeHandler.flushPipesForTesting();
  });

  teardown(() => {
    pluginVmDetailView.remove();
    pluginVmBrowserProxy.reset();
  });

  test('App is rendered correctly', () => {
    assertEquals(
        AppManagementStore.getInstance().data.selectedAppId,
        pluginVmDetailView.get('app_').id);
  });

  // The testing browser proxy return false by default in
  // `isRelaunchNeededForNewPermissions()`, so camera and microphone toggles
  // will not trigger the dialog.
  ['kCamera', 'kMicrophone', 'kPrinting'].forEach(
      (type) => test(`Toggle ${type} without dialogs`, async () => {
        const permissionType: PermissionTypeIndex = type as PermissionTypeIndex;
        assertTrue(getPermissionBoolByType(permissionType));
        assertTrue(isCrToggleChecked(permissionType));

        // Toggle off.
        await clickToggle(permissionType);
        assertNull(pluginVmDetailView.shadowRoot!.querySelector('cr-dialog'));
        assertFalse(getPermissionBoolByType(permissionType));
        assertFalse(isCrToggleChecked(permissionType));

        // Toggle on.
        await clickToggle(permissionType);
        assertNull(pluginVmDetailView.shadowRoot!.querySelector('cr-dialog'));
        assertTrue(getPermissionBoolByType(permissionType));
        assertTrue(isCrToggleChecked(permissionType));
      }));

  [{type: 'kCamera', dialogTextId: 'pluginVmPermissionDialogCameraLabel'}, {
    type: 'kMicrophone',
    dialogTextId: 'pluginVmPermissionDialogMicrophoneLabel',
  }]
      .forEach(
          ({type, dialogTextId}) => [true, false].forEach(
              (cancelByEsc) => test(
                  `Toggle ${type} with dialogs (${cancelByEsc})`, async () => {
                    const permissionType = type as PermissionTypeIndex;
                    pluginVmBrowserProxy.setPluginVmRunning(true);

                    assertTrue(getPermissionBoolByType(permissionType));
                    assertTrue(isCrToggleChecked(permissionType));

                    // Toggle off and cancel the dialog
                    await clickToggle(permissionType);
                    assertTrue(getPermissionBoolByType(permissionType));
                    await checkAndCancelDialog(dialogTextId, cancelByEsc);
                    // No relaunch, and permission should not be changed.
                    assertEquals(
                        0,
                        pluginVmBrowserProxy.getCallCount('relaunchPluginVm'));
                    assertTrue(getPermissionBoolByType(permissionType));
                    assertTrue(isCrToggleChecked(permissionType));

                    // Toggle off again and accept the dialog
                    await clickToggle(permissionType);
                    assertTrue(getPermissionBoolByType(permissionType));
                    await checkAndAcceptDialog(dialogTextId);
                    // Relaunch, and permission should be changed.
                    assertEquals(
                        1,
                        pluginVmBrowserProxy.getCallCount('relaunchPluginVm'));
                    assertFalse(getPermissionBoolByType(permissionType));
                    assertFalse(isCrToggleChecked(permissionType));

                    // Toggle on and cancel the dialog
                    await clickToggle(permissionType);
                    assertFalse(getPermissionBoolByType(permissionType));
                    await checkAndCancelDialog(dialogTextId, cancelByEsc);
                    // No relaunch, and permission should not be changed.
                    assertEquals(
                        1,
                        pluginVmBrowserProxy.getCallCount('relaunchPluginVm'));
                    assertFalse(getPermissionBoolByType(permissionType));
                    assertFalse(isCrToggleChecked(permissionType));

                    // Toggle on again and accept the dialog
                    await clickToggle(permissionType);
                    assertFalse(getPermissionBoolByType(permissionType));
                    await checkAndAcceptDialog(dialogTextId);
                    // Relaunch, and permission should be changed.
                    assertEquals(
                        2,
                        pluginVmBrowserProxy.getCallCount('relaunchPluginVm'));
                    assertTrue(getPermissionBoolByType(permissionType));
                    assertTrue(isCrToggleChecked(permissionType));
                  })));

  test('Pin to shelf toggle', async () => {
    const pinToShelfItem =
        pluginVmDetailView.shadowRoot!
            .querySelector<AppManagementPinToShelfItemElement>(
                '#pinToShelfSetting');
    assertTrue(!!pinToShelfItem);
    const toggleRow =
        pinToShelfItem.shadowRoot!.querySelector<AppManagementToggleRowElement>(
            '#toggleRow');
    assertTrue(!!toggleRow);
    const toggle = toggleRow.$.toggle;

    assertFalse(toggle.checked);
    assertEquals(toggle.checked, getSelectedAppFromStore().isPinned);
    pinToShelfItem.click();
    await fakeHandler.flushPipesForTesting();
    assertTrue(toggle.checked);
    assertEquals(toggle.checked, getSelectedAppFromStore().isPinned);
    pinToShelfItem.click();
    await fakeHandler.flushPipesForTesting();
    assertFalse(toggle.checked);
    assertEquals(toggle.checked, getSelectedAppFromStore().isPinned);
  });
});