chromium/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.ts

// Copyright 2021 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/accelerator_view.js';
import 'chrome://webui-test/chromeos/mojo_webui_test_support.js';

import {IronIconElement} from '//resources/polymer/v3_0/iron-icon/iron-icon.js';
import {VKey} from 'chrome://resources/ash/common/shortcut_input_ui/accelerator_keys.mojom-webui.js';
import {FakeShortcutInputProvider} from 'chrome://resources/ash/common/shortcut_input_ui/fake_shortcut_input_provider.js';
import {KeyEvent} from 'chrome://resources/ash/common/shortcut_input_ui/input_device_settings.mojom-webui.js';
import {ShortcutInputElement} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input.js';
import {ShortcutInputKeyElement} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input_key.js';
import {KeyInputState, Modifier as ModifierEnum} 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 {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {AcceleratorLookupManager} from 'chrome://shortcut-customization/js/accelerator_lookup_manager.js';
import {AcceleratorViewElement, ViewState} from 'chrome://shortcut-customization/js/accelerator_view.js';
import {fakeAcceleratorConfig, fakeDefaultAccelerators, fakeLayoutInfo} from 'chrome://shortcut-customization/js/fake_data.js';
import {FakeShortcutProvider} from 'chrome://shortcut-customization/js/fake_shortcut_provider.js';
import {setShortcutProviderForTesting} from 'chrome://shortcut-customization/js/mojo_interface_provider.js';
import {setShortcutInputProviderForTesting} from 'chrome://shortcut-customization/js/shortcut_input_mojo_interface_provider.js';
import {AcceleratorConfigResult, AcceleratorSource, LayoutStyle, Modifier} from 'chrome://shortcut-customization/js/shortcut_types.js';
import {AcceleratorResultData} from 'chrome://shortcut-customization/mojom-webui/shortcut_customization.mojom-webui.js';
import {assertEquals, assertFalse, 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 {createStandardAcceleratorInfo, createUserAcceleratorInfo} from './shortcut_customization_test_util.js';

export function initAcceleratorViewElement(): AcceleratorViewElement {
  const element = document.createElement('accelerator-view');
  // Set default acceleratorInfo and viewState
  element.acceleratorInfo = createUserAcceleratorInfo(
      Modifier.CONTROL | Modifier.SHIFT,
      /*key=*/ 71,
      /*keyDisplay=*/ 'g');
  element.viewState = ViewState.VIEW;
  document.body.appendChild(element);
  flush();
  return element;
}

suite('acceleratorViewTest', function() {
  let viewElement: AcceleratorViewElement|null = null;

  let manager: AcceleratorLookupManager|null = null;
  let provider: FakeShortcutProvider;
  const shortcutInputProvider: FakeShortcutInputProvider =
      new FakeShortcutInputProvider();

  setup(() => {
    provider = new FakeShortcutProvider();
    provider.setFakeGetDefaultAcceleratorsForId(fakeDefaultAccelerators);
    setShortcutProviderForTesting(provider);
    setShortcutInputProviderForTesting(shortcutInputProvider);

    manager = AcceleratorLookupManager.getInstance();
    manager.setAcceleratorLookup(fakeAcceleratorConfig);
    manager.setAcceleratorLayoutLookup(fakeLayoutInfo);
  });

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

    if (viewElement) {
      viewElement.remove();
    }
    viewElement = null;
  });

  function getPendingKeyElement(shortcutInputElement: ShortcutInputElement):
      ShortcutInputKeyElement {
    return strictQuery(
        '#pendingKey', shortcutInputElement!.shadowRoot,
        ShortcutInputKeyElement);
  }

  function getCtrlElement(shortcutInputElement: ShortcutInputElement):
      ShortcutInputKeyElement {
    return strictQuery(
        '#ctrlKey', shortcutInputElement!.shadowRoot, ShortcutInputKeyElement);
  }

  function getShiftElement(shortcutInputElement: ShortcutInputElement):
      ShortcutInputKeyElement {
    return strictQuery(
        '#shiftKey', shortcutInputElement!.shadowRoot, ShortcutInputKeyElement);
  }

  function getAltElement(shortcutInputElement: ShortcutInputElement):
      ShortcutInputKeyElement {
    return strictQuery(
        '#altKey', shortcutInputElement!.shadowRoot, ShortcutInputKeyElement);
  }

  function getSearchElement(shortcutInputElement: ShortcutInputElement):
      ShortcutInputKeyElement {
    return strictQuery(
        '#searchKey', shortcutInputElement!.shadowRoot,
        ShortcutInputKeyElement);
  }

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

  function getEditIcon(): HTMLDivElement {
    return strictQuery(
        '.edit-icon-container', viewElement!.shadowRoot, HTMLDivElement);
  }

  test('LoadsBasicAccelerator', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();

    const keys = viewElement.shadowRoot!.querySelectorAll('shortcut-input-key');
    // Three keys: shift, control, g
    assertEquals(3, keys.length);

    assertEquals(
        'ctrl',
        keys[0]!.shadowRoot!.querySelector('#key')!.textContent!.trim());
    assertEquals(
        'shift',
        keys[1]!.shadowRoot!.querySelector('#key')!.textContent!.trim());
    assertEquals(
        'g', keys[2]!.shadowRoot!.querySelector('#key')!.textContent!.trim());
  });

  test('EditableAccelerator', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();

    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    await flush();
    // Enable the edit view.
    viewElement.viewState = ViewState.EDIT;

    await flush();

    const shortcutInput = strictQuery(
        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);

    let ctrlKey = getCtrlElement(shortcutInput);
    let altKey = getAltElement(shortcutInput);
    let metaKey = getSearchElement(shortcutInput);
    let shiftKey = getShiftElement(shortcutInput);
    let pendingKey = getPendingKeyElement(shortcutInput);

    // By default, no keys should be registered.
    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, pendingKey.keyState);
    assertEquals('key', pendingKey.key);

    const fakeResult: AcceleratorResultData = {
      result: AcceleratorConfigResult.kConflict,
      shortcutName: {data: [1]},
    };

    provider.setFakeReplaceAcceleratorResult(fakeResult);

    // Simulate Ctrl.
    const keyEvent: KeyEvent = {
      vkey: VKey.kControl,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.CONTROL,
      keyDisplay: 'ctrl',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent, keyEvent);

    await flush();

    assertEquals(KeyInputState.MODIFIER_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, pendingKey.keyState);

    // Release Ctrl, expect it to not be selected.
    const keyEventReleased: KeyEvent = {
      vkey: VKey.kControl,
      domCode: 0,
      domKey: 0,
      modifiers: 0,
      keyDisplay: 'ctrl',
    };
    shortcutInputProvider.sendKeyReleaseEvent(
        keyEventReleased, keyEventReleased);

    await flush();

    ctrlKey = getCtrlElement(shortcutInput);
    altKey = getAltElement(shortcutInput);
    metaKey = getSearchElement(shortcutInput);
    shiftKey = getShiftElement(shortcutInput);
    pendingKey = getPendingKeyElement(shortcutInput);

    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, pendingKey.keyState);

    const keyEvent2: KeyEvent = {
      vkey: VKey.kKeyE,
      domCode: 0,
      domKey: 0,
      modifiers: 0,
      keyDisplay: 'e',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent2, keyEvent2);

    await flushTasks();
    pendingKey = getPendingKeyElement(shortcutInput);

    assertEquals(KeyInputState.ALPHANUMERIC_SELECTED, pendingKey.keyState);
    assertEquals('e', pendingKey.key);

    // Release `e`, expect it to not be selected.
    const keyEvent2Released: KeyEvent = {
      vkey: VKey.kKeyE,
      domCode: 0,
      domKey: 0,
      modifiers: 0,
      keyDisplay: '',
    };
    shortcutInputProvider.sendKeyReleaseEvent(
        keyEvent2Released, keyEvent2Released);
    await flushTasks();

    ctrlKey = getCtrlElement(shortcutInput);
    altKey = getAltElement(shortcutInput);
    metaKey = getSearchElement(shortcutInput);
    shiftKey = getShiftElement(shortcutInput);
    pendingKey = getPendingKeyElement(shortcutInput);

    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals('key', pendingKey.key);
  });

  test('EditWithFunctionKeyAsOnlyKey', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();

    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    await flushTasks();
    // Enable the edit view.
    viewElement.viewState = ViewState.EDIT;

    await flushTasks();

    const shortcutInput = strictQuery(
        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);

    const ctrlKey = getCtrlElement(shortcutInput);
    const altKey = getAltElement(shortcutInput);
    const metaKey = getSearchElement(shortcutInput);
    const shiftKey = getShiftElement(shortcutInput);
    const pendingKey = getPendingKeyElement(shortcutInput);

    // By default, no keys should be registered.
    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, pendingKey.keyState);
    assertEquals('key', pendingKey.key);

    const fakeResult: AcceleratorResultData = {
      result: AcceleratorConfigResult.kConflict,
      shortcutName: {data: [1]},
    };

    provider.setFakeReplaceAcceleratorResult(fakeResult);

    // Simulate F3.
    const keyEvent: KeyEvent = {
      vkey: VKey.kF3,
      domCode: 0,
      domKey: 0,
      modifiers: 0,
      keyDisplay: 'f3',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent, keyEvent);

    await flush();

    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.ALPHANUMERIC_SELECTED, pendingKey.keyState);
    assertEquals('f3', pendingKey.key);
  });

  test('LockIconVisibilityBasedOnProperties', async () => {
    viewElement = initAcceleratorViewElement();
    const scenarios = [
      {customizationEnabled: true, locked: true, sourceIsLocked: true},
      {customizationEnabled: true, locked: true, sourceIsLocked: false},
      {customizationEnabled: true, locked: false, sourceIsLocked: true},
      {customizationEnabled: true, locked: false, sourceIsLocked: false},
      {customizationEnabled: false, locked: true, sourceIsLocked: true},
      {customizationEnabled: false, locked: true, sourceIsLocked: false},
      {customizationEnabled: false, locked: false, sourceIsLocked: true},
      {customizationEnabled: false, locked: false, sourceIsLocked: false},
    ];

    // Prepare all test cases by looping the fakeLayoutInfo.
    const testCases = [];
    for (const layoutInfo of fakeLayoutInfo) {
      // If it's text accelerator, break the loop early.
      if (layoutInfo.style !== LayoutStyle.kDefault) {
        continue;
      }
      for (const scenario of scenarios) {
        // replicate getCategory() logic.
        const subcategory = manager!.getAcceleratorSubcategory(
            layoutInfo.source, layoutInfo.action);
        const subcategoryIsLocked = manager!.isSubcategoryLocked(subcategory);
        // replicate shouldShowLockIcon() logic.
        const expectLockIconVisible = scenario.customizationEnabled &&
            !subcategoryIsLocked &&
            (scenario.locked || scenario.sourceIsLocked);
        testCases.push({
          ...scenario,
          layoutInfo: layoutInfo,
          subcategoryIsLocked: subcategoryIsLocked,
          expectLockIconVisible: expectLockIconVisible,
        });
      }
    }
    // Verify lock icon show/hide based on properties.
    for (const testCase of testCases) {
      loadTimeData.overrideValues(
          {isCustomizationAllowed: testCase.customizationEnabled});
      viewElement.source = testCase.layoutInfo.source;
      viewElement.action = testCase.layoutInfo.action;
      viewElement.subcategoryIsLocked = testCase.subcategoryIsLocked;
      const acceleratorInfo = createStandardAcceleratorInfo(
          Modifier.CONTROL | Modifier.SHIFT,
          /*key=*/ 71,
          /*keyDisplay=*/ 'g');
      viewElement.acceleratorInfo = acceleratorInfo;
      viewElement.set('acceleratorInfo.locked', testCase.locked);
      viewElement.sourceIsLocked = testCase.sourceIsLocked;

      await flush();
      assertEquals(testCase.expectLockIconVisible, isVisible(getLockIcon()));
    }
  });

  test('EditIconVisibilityBasedOnProperties', async () => {
    viewElement = initAcceleratorViewElement();
    // Mainly test on customizationEnabled and accelerator is not locked.
    const scenarios = [
      {
        customizationEnabled: true,
        locked: false,
        sourceIsLocked: false,
        isAcceleratorRow: false,
        isFirstAccelerator: true,
      },
      {
        customizationEnabled: true,
        locked: false,
        sourceIsLocked: false,
        isAcceleratorRow: true,
        isFirstAccelerator: true,
      },
      {
        customizationEnabled: true,
        locked: true,
        sourceIsLocked: false,
        isAcceleratorRow: false,
        isFirstAccelerator: true,
      },
      {
        customizationEnabled: true,
        locked: false,
        sourceIsLocked: true,
        isAcceleratorRow: true,
        isFirstAccelerator: false,
      },
      {
        customizationEnabled: false,
        locked: false,
        sourceIsLocked: false,
        isAcceleratorRow: false,
        isFirstAccelerator: true,
      },
    ];

    // Prepare all test cases by looping the fakeLayoutInfo.
    const testCases = [];
    for (const layoutInfo of fakeLayoutInfo) {
      // If it's 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 subcategoryIsLocked = manager!.isSubcategoryLocked(subcategory);
        // replicate shouldShowLockIcon() logic.
        const expectEditIconVisible = scenario.customizationEnabled &&
            scenario.isAcceleratorRow && !subcategoryIsLocked &&
            !scenario.locked && !scenario.sourceIsLocked &&
            scenario.isFirstAccelerator;
        testCases.push({
          ...scenario,
          layoutInfo: layoutInfo,
          subcategoryIsLocked: subcategoryIsLocked,
          expectEditIconVisible: expectEditIconVisible,
        });
      }
    }
    for (const testCase of testCases) {
      loadTimeData.overrideValues(
          {isCustomizationAllowed: testCase.customizationEnabled});
      viewElement.source = testCase.layoutInfo.source;
      viewElement.action = testCase.layoutInfo.action;
      viewElement.subcategoryIsLocked = testCase.subcategoryIsLocked;
      viewElement.showEditIcon = testCase.isAcceleratorRow;
      viewElement.isFirstAccelerator = testCase.isFirstAccelerator;
      const acceleratorInfo = createStandardAcceleratorInfo(
          Modifier.CONTROL | Modifier.SHIFT,
          /*key=*/ 71,
          /*keyDisplay=*/ 'g');
      viewElement.acceleratorInfo = acceleratorInfo;
      viewElement.set('acceleratorInfo.locked', testCase.locked);
      viewElement.sourceIsLocked = testCase.sourceIsLocked;

      await flush();
      assertEquals(
          testCase.expectEditIconVisible,
          !getEditIcon().hasAttribute('hidden'));
    }
  });

  test('KeyDisplayAndIconDuringEdit', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();
    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    await flush();

    // Enable the edit view.
    viewElement.viewState = ViewState.EDIT;
    await flush();

    const shortcutInput = strictQuery(
        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);
    const pendingKey = getPendingKeyElement(shortcutInput);

    const fakeResult: AcceleratorResultData = {
      result: AcceleratorConfigResult.kConflict,
      shortcutName: {data: [1]},
    };
    provider.setFakeReplaceAcceleratorResult(fakeResult);

    // Simulate SHIFT + SPACE, expect the key display to be 'space'.
    const keyEvent: KeyEvent = {
      vkey: VKey.kSpace,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.SHIFT,
      keyDisplay: 'space',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent, keyEvent);

    await flush();
    assertEquals('space', pendingKey.key);

    // Simulate SHIFT + OVERVIEW, expect the key display to be
    // 'LaunchApplication1' and the icon to be 'overview'.
    const keyEvent2: KeyEvent = {
      vkey: VKey.kF4,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.SHIFT,
      keyDisplay: 'LaunchApplication1',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent2, keyEvent2);

    await flush();

    assertEquals('LaunchApplication1', pendingKey.key);
    const keyIconElement =
        pendingKey.shadowRoot!.querySelector('#key-icon') as IronIconElement;
    assertEquals('shortcut-input-keys:overview', keyIconElement.icon);

    // Simulate SHIFT + BRIGHTNESS_UP, expect the key display to be
    // 'BrightnessUp' and the icon to be 'display-brightness-up'.
    const keyEvent3: KeyEvent = {
      vkey: VKey.kBrightnessUp,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.SHIFT,
      keyDisplay: 'BrightnessUp',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent3, keyEvent3);

    await flush();

    assertEquals('BrightnessUp', pendingKey.key);
    const keyIconElement2 =
        pendingKey.shadowRoot!.querySelector('#key-icon') as IronIconElement;
    assertEquals(
        'shortcut-input-keys:display-brightness-up', keyIconElement2.icon);

    // Simulate SHIFT + MUTE_MICROPHONE.
    const keyEvent4: KeyEvent = {
      vkey: VKey.kMicrophoneMuteToggle,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.SHIFT,
      keyDisplay: 'MicrophoneMuteToggle',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent4, keyEvent4);

    await flush();

    assertEquals('MicrophoneMuteToggle', pendingKey.key);
    const keyIconElement3 =
        pendingKey.shadowRoot!.querySelector('#key-icon') as IronIconElement;
    assertEquals('shortcut-input-keys:microphone-mute', keyIconElement3.icon);

    // Simulate CONTROL + BACKQUOTE.
    const keyEvent5: KeyEvent = {
      vkey: VKey.kOem3,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.SHIFT,
      keyDisplay: '`',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent5, keyEvent5);
    await flush();

    assertEquals('`', pendingKey.key);
  });

  test('GetAriaLabels', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();

    const acceleratorInfo = createStandardAcceleratorInfo(
        Modifier.SHIFT | Modifier.ALT,
        /*key=*/ 221,
        /*keyDisplay=*/ 's');
    viewElement.acceleratorInfo = acceleratorInfo;
    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    viewElement.viewState = ViewState.VIEW;
    await flush();

    let viewContainer =
        strictQuery('#container', viewElement.shadowRoot, HTMLDivElement);
    assertEquals('alt shift s', viewContainer.ariaLabel);

    // Aria label is empty during editing process.
    viewElement.viewState = ViewState.EDIT;
    await flush();

    viewContainer =
        strictQuery('#container', viewElement.shadowRoot, HTMLDivElement);
    assertEquals('', viewContainer.ariaLabel);
  });

  test('GetAriaLabelsWithIcon', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();

    const acceleratorInfo = createStandardAcceleratorInfo(
        Modifier.SHIFT | Modifier.ALT | Modifier.COMMAND,
        /*key=*/ 220,
        /*keyDisplay=*/ 'LaunchApplication1');
    viewElement.acceleratorInfo = acceleratorInfo;
    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    await flush();

    const viewContainer =
        viewElement.shadowRoot!.querySelector('#container') as HTMLDivElement;
    // The icon label is 'show windows'.
    const regex = /^(search|launcher) alt shift show windows$/;
    assertTrue(!!viewContainer.ariaLabel);
    assertTrue(regex.test(viewContainer.ariaLabel));
  });

  test('GetAriaLabelsWithLwinKey', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();
    // Open/close launcher -> Lwin key.
    const acceleratorInfo = createStandardAcceleratorInfo(
        Modifier.NONE,
        /*key=*/ 224,
        /*keyDisplay=*/ 'Meta');
    viewElement.acceleratorInfo = acceleratorInfo;
    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    await flush();

    const viewContainer =
        viewElement.shadowRoot!.querySelector('#container') as HTMLDivElement;
    const regex = /^(search|launcher)$/;
    assertTrue(!!viewContainer.ariaLabel);
    assertTrue(regex.test(viewContainer.ariaLabel));
  });

  test('CancelInputWithShortcut', async () => {
    viewElement = initAcceleratorViewElement();
    await flushTasks();

    viewElement.source = AcceleratorSource.kAsh;
    viewElement.action = 1;
    // Enable the edit view.
    viewElement.viewState = ViewState.EDIT;

    await flush();

    // Assert that this is in the EDIT state.
    assertEquals(ViewState.EDIT, viewElement.viewState);

    const shortcutInput = strictQuery(
        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);

    let ctrlKey = getCtrlElement(shortcutInput);
    let altKey = getAltElement(shortcutInput);
    let metaKey = getSearchElement(shortcutInput);
    let shiftKey = getShiftElement(shortcutInput);
    let pendingKey = getPendingKeyElement(shortcutInput);

    // By default, no keys should be registered.
    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, pendingKey.keyState);
    assertEquals('key', pendingKey.key);

    // Simulate Alt.
    const keyEvent: KeyEvent = {
      vkey: VKey.kMenu,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.ALT,
      keyDisplay: 'alt',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent, keyEvent);

    await flush();

    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.MODIFIER_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, pendingKey.keyState);

    // Now press Escape.
    const keyEvent2: KeyEvent = {
      vkey: VKey.kEscape,
      domCode: 0,
      domKey: 0,
      modifiers: ModifierEnum.ALT,
      keyDisplay: 'escape',
    };
    shortcutInputProvider.sendKeyPressEvent(keyEvent2, keyEvent2);

    await flush();

    ctrlKey = getCtrlElement(shortcutInput);
    altKey = getAltElement(shortcutInput);
    metaKey = getSearchElement(shortcutInput);
    shiftKey = getShiftElement(shortcutInput);
    pendingKey = getPendingKeyElement(shortcutInput);

    assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
    assertEquals(KeyInputState.MODIFIER_SELECTED, altKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, shiftKey.keyState);
    assertEquals(KeyInputState.NOT_SELECTED, metaKey.keyState);

    // Expect that press Alt + Esc will cancel the edit state.
    assertEquals(ViewState.VIEW, viewElement.viewState);
    assertFalse(viewElement.hasError);
    assertEquals('', viewElement.statusMessage);
  });
});