chromium/chrome/test/data/webui/chromeos/shortcut_customization/text_accelerator_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.

import 'chrome://shortcut-customization/js/text_accelerator.js';
import 'chrome://webui-test/chromeos/mojo_webui_test_support.js';

import {ShortcutInputKeyElement} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input_key.js';
import {KeyInputState} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_utils.js';
import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {mojoString16ToString, stringToMojoString16} from 'chrome://resources/js/mojo_type_util.js';
import {IronIconElement} from 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {AcceleratorLookupManager} from 'chrome://shortcut-customization/js/accelerator_lookup_manager.js';
import {fakeAcceleratorConfig, fakeLayoutInfo} from 'chrome://shortcut-customization/js/fake_data.js';
import {AcceleratorSource, LayoutStyle, TextAcceleratorPart, TextAcceleratorPartType} from 'chrome://shortcut-customization/js/shortcut_types.js';
import {TextAcceleratorElement} from 'chrome://shortcut-customization/js/text_accelerator.js';
import {assertEquals} 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';


function createTextAcceleratorPart(
    text: string, type: TextAcceleratorPartType): TextAcceleratorPart {
  return {text: stringToMojoString16(text), type};
}

suite('textAcceleratorTest', function() {
  let textAccelElement: TextAcceleratorElement|null = null;
  let manager: AcceleratorLookupManager|null = null;

  setup(() => {
    // Set up manager.
    manager = AcceleratorLookupManager.getInstance();
    manager.setAcceleratorLookup(fakeAcceleratorConfig);
    manager.setAcceleratorLayoutLookup(fakeLayoutInfo);
  });

  teardown(() => {
    if (manager) {
      manager.reset();
    }
  });

  function getTextPartsContainer(): HTMLElement {
    return textAccelElement!.shadowRoot!.querySelector('.parts-container') as
        HTMLElement;
  }

  function getAllInputKeys(): NodeListOf<ShortcutInputKeyElement> {
    return getTextPartsContainer().querySelectorAll('shortcut-input-key');
  }

  function getAllPlainTextParts(): NodeListOf<HTMLSpanElement> {
    return getTextPartsContainer().querySelectorAll('span');
  }

  function getAllDelimiterParts(): NodeListOf<IronIconElement> {
    return getTextPartsContainer().querySelectorAll('#delimiter-icon');
  }

  function getLockIcon(): HTMLDivElement {
    return strictQuery(
        '.lock-icon-container', textAccelElement!.shadowRoot, HTMLDivElement);
  }

  setup(() => {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
  });

  teardown(() => {
    if (textAccelElement) {
      textAccelElement.remove();
    }
    textAccelElement = null;
  });

  function initTextAcceleratorElement(
      parts: TextAcceleratorPart[] = [], source: AcceleratorSource,
      action: number, displayLockIcon: boolean): Promise<void> {
    textAccelElement = document.createElement('text-accelerator');
    textAccelElement.parts = parts;
    textAccelElement.source = source;
    textAccelElement.action = action;
    textAccelElement.displayLockIcon = displayLockIcon;
    document.body.appendChild(textAccelElement);
    return flushTasks();
  }


  test('TextAcceleratorPartsSingleModifier', async () => {
    const ctrlKey =
        createTextAcceleratorPart('ctrl', TextAcceleratorPartType.kModifier);
    await initTextAcceleratorElement(
        [ctrlKey], AcceleratorSource.kAmbient, 0, false);
    assertEquals(1, getTextPartsContainer().children.length);
    assertEquals(1, textAccelElement!.parts.length);
    const inputKey = getAllInputKeys()[0];
    assertEquals(inputKey!.key, mojoString16ToString(ctrlKey.text));
    assertEquals(inputKey!.keyState, KeyInputState.MODIFIER_SELECTED);
  });

  test('TextAcceleratorPartsSingleKey', async () => {
    const bKey = createTextAcceleratorPart('b', TextAcceleratorPartType.kKey);
    await initTextAcceleratorElement(
        [bKey], AcceleratorSource.kAmbient, 0, false);
    assertEquals(1, getTextPartsContainer().children.length);
    assertEquals(1, textAccelElement!.parts.length);
    const inputKey = getAllInputKeys()[0];
    assertEquals(inputKey!.key, mojoString16ToString(bKey.text));
    assertEquals(inputKey!.keyState, KeyInputState.ALPHANUMERIC_SELECTED);
  });

  test('TextAcceleratorPartsPlainText', async () => {
    const plainText = createTextAcceleratorPart(
        'Some text', TextAcceleratorPartType.kPlainText);
    await initTextAcceleratorElement(
        [plainText], AcceleratorSource.kAmbient, 0, false);
    assertEquals(1, getTextPartsContainer().children.length);
    const part = getAllPlainTextParts()[0];
    assertEquals(1, textAccelElement!.parts.length);
    assertEquals(part!.innerText, mojoString16ToString(plainText.text));
  });

  test('TextAcceleratorPartsDelimiter', async () => {
    const delimiter =
        createTextAcceleratorPart('+', TextAcceleratorPartType.kDelimiter);
    await initTextAcceleratorElement(
        [delimiter], AcceleratorSource.kAmbient, 0, false);
    assertEquals(1, getTextPartsContainer().children.length);
    const delimiterPart = getAllDelimiterParts()[0];
    assertEquals(1, textAccelElement!.parts.length);
    assertEquals(delimiterPart!.icon, 'shortcut-customization-keys:plus');
  });

  test('TextAcceleratorPartsAll', async () => {
    const ctrlKey =
        createTextAcceleratorPart('ctrl', TextAcceleratorPartType.kModifier);
    const bKey = createTextAcceleratorPart('b', TextAcceleratorPartType.kKey);
    const plainText = createTextAcceleratorPart(
        'Some text', TextAcceleratorPartType.kPlainText);
    const delimiter =
        createTextAcceleratorPart('+', TextAcceleratorPartType.kDelimiter);
    await initTextAcceleratorElement(
        [ctrlKey, bKey, plainText, delimiter], AcceleratorSource.kAmbient, 0,
        false);
    assertEquals(4, getTextPartsContainer().children.length);
    assertEquals(4, textAccelElement!.parts.length);

    const [ctrlInputKey, bInputKey] = getAllInputKeys();
    assertEquals(ctrlInputKey!.key, mojoString16ToString(ctrlKey.text));
    assertEquals(ctrlInputKey!.keyState, KeyInputState.MODIFIER_SELECTED);

    assertEquals(bInputKey!.key, mojoString16ToString(bKey.text));
    assertEquals(bInputKey!.keyState, KeyInputState.ALPHANUMERIC_SELECTED);
    const part = getAllPlainTextParts()[0];
    assertEquals(part!.innerText, mojoString16ToString(plainText.text));

    const delimiterPart = getAllDelimiterParts()[0];
    assertEquals(delimiterPart!.icon, 'shortcut-customization-keys:plus');
  });

  test('LockIconVisibilityBasedOnProperties', async () => {
    const scenarios = [
      {customizationEnabled: true, displayLockIcon: true},
      {customizationEnabled: true, displayLockIcon: false},
      {customizationEnabled: false, displayLockIcon: false},
    ];
    // Prepare all test cases by looping the fakeLayoutInfo.
    const testCases = [];
    for (const layoutInfo of fakeLayoutInfo) {
      // If it's not text-accelerator, break the loop early.
      if (layoutInfo.style === LayoutStyle.kDefault) {
        continue;
      }
      for (const scenario of scenarios) {
        // replicate getSubcategory() logic.
        const subcategory = manager!.getAcceleratorSubcategory(
            layoutInfo.source, layoutInfo.action);
        const subcategoryIsUnlocked =
            !manager!.isSubcategoryLocked(subcategory);
        // replicate shouldShowLockIcon() logic.
        const expectLockIconVisible = scenario.customizationEnabled &&
            !scenario.displayLockIcon && subcategoryIsUnlocked;
        testCases.push({
          ...scenario,
          layoutInfo: layoutInfo,
          expectLockIconVisible: expectLockIconVisible,
        });
      }
    }
    // Verify lock icon show/hide based on properties.
    for (const testCase of testCases) {
      loadTimeData.overrideValues(
          {isCustomizationAllowed: testCase.customizationEnabled});
      const ctrlKey =
          createTextAcceleratorPart('ctrl', TextAcceleratorPartType.kModifier);
      await initTextAcceleratorElement(
          [ctrlKey], testCase.layoutInfo.source, testCase.layoutInfo.action,
          testCase.displayLockIcon);
      await flush();
      assertEquals(testCase.expectLockIconVisible, isVisible(getLockIcon()));
    }
  });
});