chromium/chrome/test/data/webui/chromeos/settings/app_management/fake_page_handler.ts

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

import {AppManagementStore} from 'chrome://os-settings/os_settings.js';
import {App, AppType, ExtensionAppPermissionMessage, PageHandlerInterface, PageHandlerReceiver, PageHandlerRemote, PageRemote, Permission, PermissionType, RunOnOsLoginMode, TriState, WindowMode} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
import {InstallReason, InstallSource} from 'chrome://resources/cr_components/app_management/constants.js';
import {createBoolPermission, createTriStatePermission, getTriStatePermissionValue} from 'chrome://resources/cr_components/app_management/permission_util.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';

type AppConfig = Partial<App>;
type PermissionMap = Partial<Record<PermissionType, Permission>>;

export class FakePageHandler implements PageHandlerInterface {
  static createWebPermissions(
      options?: Partial<Record<PermissionType, Permission>>): PermissionMap {
    const permissionTypes = [
      PermissionType.kLocation,
      PermissionType.kNotifications,
      PermissionType.kMicrophone,
      PermissionType.kCamera,
    ];

    const permissions: PermissionMap = {};

    for (const permissionType of permissionTypes) {
      let permissionValue = TriState.kAllow;
      let isManaged = false;

      if (options && options[permissionType]) {
        const opts = options[permissionType]!;
        permissionValue = opts.value ? getTriStatePermissionValue(opts.value) :
                                       permissionValue;
        isManaged = opts.isManaged || isManaged;
      }
      permissions[permissionType] =
          createTriStatePermission(permissionType, permissionValue, isManaged);
    }

    return permissions;
  }

  static createArcPermissions(optIds?: PermissionType[]): PermissionMap {
    const permissionTypes = optIds || [
      PermissionType.kCamera,
      PermissionType.kLocation,
      PermissionType.kMicrophone,
      PermissionType.kNotifications,
      PermissionType.kContacts,
      PermissionType.kStorage,
    ];

    const permissions: PermissionMap = {};

    for (const permissionType of permissionTypes) {
      permissions[permissionType] =
          createBoolPermission(permissionType, true, /*is_managed=*/ false);
    }

    return permissions;
  }

  static createPermissions(appType: AppType): PermissionMap {
    switch (appType) {
      case (AppType.kWeb):
        return FakePageHandler.createWebPermissions();
      case (AppType.kArc):
        return FakePageHandler.createArcPermissions();
      default:
        return {};
    }
  }

  static createApp(id: string, optConfig?: AppConfig): App {
    const app: App = {
      id: id,
      type: AppType.kWeb,
      title: 'App Title',
      description: '',
      version: '5.1',
      size: '9.0MB',
      isPinned: false,
      isPolicyPinned: false,
      installReason: InstallReason.kUser,
      permissions: {},
      hideMoreSettings: false,
      hidePinToShelf: false,
      isPreferredApp: false,
      windowMode: WindowMode.kWindow,
      hideWindowMode: false,
      resizeLocked: false,
      hideResizeLocked: true,
      supportedLinks: [],
      runOnOsLogin: null,
      fileHandlingState: null,
      installSource: InstallSource.kUnknown,
      appSize: '',
      dataSize: '',
      publisherId: '',
      formattedOrigin: '',
      scopeExtensions: [],
      supportedLocales: [],
      selectedLocale: null,
      showSystemNotificationsSettingsLink: false,
      allowUninstall: true,
    };

    if (optConfig) {
      Object.assign(app, optConfig);
    }

    // Only create default permissions if none were provided in the config.
    if (!optConfig || optConfig.permissions === undefined) {
      app.permissions = FakePageHandler.createPermissions(app.type);
    }

    return app;
  }

  guid: number;
  overlappingAppIds: string[];
  page: PageRemote;
  private apps_: App[];
  private receiver_: PageHandlerReceiver;
  private resolverMap_: Map<string, PromiseResolver<void>>;
  private callCountMap_: Map<string, number>;

  constructor(page: PageRemote) {
    this.receiver_ = new PageHandlerReceiver(this);

    this.guid = 0;
    this.overlappingAppIds = [];
    this.page = page;

    this.apps_ = [];
    this.resolverMap_ = new Map();
    this.callCountMap_ = new Map();
    this.resolverMap_.set('setPreferredApp', new PromiseResolver());
    this.resolverMap_.set('setPermission', new PromiseResolver());
    this.resolverMap_.set('getOverlappingPreferredApps', new PromiseResolver());
    this.resolverMap_.set('setAppLocale', new PromiseResolver());
    this.resolverMap_.set('uninstall', new PromiseResolver());
  }

  private getResolver_(methodName: string): PromiseResolver<any> {
    const method = this.resolverMap_.get(methodName);
    assert(method, `Method '${methodName}' not found.`);
    return method;
  }

  getCallCount(methodName: string): number {
    const count = this.callCountMap_.get(methodName);
    return count ? count : 0;
  }

  methodCalled(methodName: string, returnValue?: any): void {
    const count = this.callCountMap_.get(methodName);
    if (count) {
      this.callCountMap_.set(methodName, count + 1);
    } else {
      this.callCountMap_.set(methodName, 1);
    }

    this.getResolver_(methodName).resolve(returnValue);
  }

  async whenCalled(methodName: string): Promise<any> {
    const promise = await this.getResolver_(methodName).promise;
    // Support sequential calls to whenCalled by replacing the promise.
    this.resolverMap_.set(methodName, new PromiseResolver());
    return promise;
  }

  getRemote(): PageHandlerRemote {
    return this.receiver_.$.bindNewPipeAndPassRemote();
  }

  async flushPipesForTesting(): Promise<void> {
    await this.page.$.flushForTesting();
  }

  async getApps(): Promise<{apps: App[]}> {
    return {apps: this.apps_};
  }

  async getApp(_appId: string): Promise<{app: App}> {
    assertNotReached();
  }

  async getSubAppToParentMap():
      Promise<{subAppToParentMap: {[key: string]: string}}> {
    return {subAppToParentMap: {}};
  }

  async getExtensionAppPermissionMessages(_appId: string):
      Promise<{messages: ExtensionAppPermissionMessage[]}> {
    return {messages: []};
  }

  setApps(appList: App[]): void {
    this.apps_ = appList;
  }

  setPinned(appId: string, isPinned: boolean): void {
    const app = AppManagementStore.getInstance().data.apps[appId];
    assert(app);
    const newApp = {...app, isPinned};
    this.page.onAppChanged(newApp);
  }

  setPermission(appId: string, permission: Permission): void {
    const app = AppManagementStore.getInstance().data.apps[appId];
    assert(app);

    // Check that the app had a previous value for the given permission
    assert(app.permissions[permission.permissionType]);

    const newPermissions = {...app.permissions};
    newPermissions[permission.permissionType] = permission;
    const newApp = {...app, permissions: newPermissions};
    this.page.onAppChanged(newApp);
    this.methodCalled('setPermission', [appId, permission]);
  }

  setResizeLocked(appId: string, resizeLocked: boolean): void {
    const app = AppManagementStore.getInstance().data.apps[appId];
    assert(app);

    const newApp = {...app, resizeLocked};
    this.page.onAppChanged(newApp);
  }

  setHideResizeLocked(appId: string, hideResizeLocked: boolean): void {
    const app = AppManagementStore.getInstance().data.apps[appId];
    assert(app);

    const newApp = {...app, hideResizeLocked};
    this.page.onAppChanged(newApp);
  }

  uninstall(appId: string): void {
    this.methodCalled('uninstall', appId);
    this.page.onAppRemoved(appId);
  }

  setPreferredApp(appId: string, isPreferredApp: boolean): void {
    const app = AppManagementStore.getInstance().data.apps[appId];
    assert(app);

    const newApp = {...app, isPreferredApp};
    this.page.onAppChanged(newApp);
    this.methodCalled('setPreferredApp');
  }

  openNativeSettings(_appId: string): void {}

  updateAppSize(_appId: string): void {}

  setWindowMode(_appId: string, _windowMode: WindowMode): void {
    assertNotReached();
  }

  setAppLocale(appId: string, localeTag: string): void {
    const app = AppManagementStore.getInstance().data.apps[appId];
    assert(app);

    const newApp = {
      ...app,
      selectedLocale: {localeTag, displayName: '', nativeDisplayName: ''},
    };
    this.page.onAppChanged(newApp);
    this.methodCalled('setAppLocale');
  }

  setRunOnOsLoginMode(_appId: string, _runOnOsLoginMode: RunOnOsLoginMode):
      void {
    assertNotReached();
  }

  setFileHandlingEnabled(_appId: string, _fileHandlingEnabled: boolean): void {
    assertNotReached();
  }

  showDefaultAppAssociationsUi(): void {
    assertNotReached();
  }

  async getOverlappingPreferredApps(_appId: string):
      Promise<{appIds: string[]}> {
    this.methodCalled('getOverlappingPreferredApps');
    if (!this.overlappingAppIds) {
      return {appIds: []};
    }
    return {appIds: this.overlappingAppIds};
  }

  openStorePage(_appId: string): void {}

  openSystemNotificationSettings(_appId: string): void {}

  async addApp(optId?: string, optConfig?: AppConfig): Promise<App> {
    optId = optId || String(this.guid++);
    const app = FakePageHandler.createApp(optId, optConfig);
    this.page.onAppAdded(app);
    await this.flushPipesForTesting();
    return app;
  }

  /**
   * Takes an app id and an object mapping app fields to the values they
   * should be changed to, and dispatches an action to carry out these
   * changes.
   */
  async changeApp(id: string, changes: AppConfig): Promise<void> {
    this.page.onAppChanged(FakePageHandler.createApp(id, changes));
    await this.flushPipesForTesting();
  }
}