chromium/chrome/test/data/webui/chromeos/shortcut_customization/shortcut_utils_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://webui-test/chromeos/mojo_webui_test_support.js';

import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {stringToMojoString16} from 'chrome://resources/js/mojo_type_util.js';
import {CycleTabsTextSearchResult, SnapWindowLeftSearchResult, TakeScreenshotSearchResult} from 'chrome://shortcut-customization/js/fake_data.js';
import {Accelerator, AcceleratorCategory, AcceleratorKeyState, Modifier, StandardAcceleratorInfo, TextAcceleratorPart, TextAcceleratorPartType} from 'chrome://shortcut-customization/js/shortcut_types.js';
import {areAcceleratorsEqual, compareAcceleratorInfos, getAccelerator, getAcceleratorId, getModifiersForAcceleratorInfo, getModifierString, getNumpadKeyDisplay, getSortedModifiers, getSourceAndActionFromAcceleratorId, getUnidentifiedKeyDisplay, getURLForSearchResult, isCustomizationAllowed, isStandardAcceleratorInfo, isTextAcceleratorInfo, SHORTCUTS_APP_URL} from 'chrome://shortcut-customization/js/shortcut_utils.js';
import {assertArrayEquals, assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';

import {createStandardAcceleratorInfo, createTextAcceleratorInfo} from './shortcut_customization_test_util.js';

function areModifiersEqual(
    modifiersA: string[], modifiersB: string[]): boolean {
  return modifiersA.length === modifiersB.length &&
      modifiersA.every((val, index) => val === modifiersB[index]);
}

function areStandardAcceleratorInfosEqual(
    first: StandardAcceleratorInfo[], second: StandardAcceleratorInfo[]) {
  assertEquals(first.length, second.length);
  for (let i = 0; i < first.length; ++i) {
    const firstAccelerator = first[i]!.layoutProperties.standardAccelerator;
    const secondAccelerator = second[i]!.layoutProperties.standardAccelerator;
    assertEquals(
        firstAccelerator.accelerator.modifiers,
        secondAccelerator.accelerator.modifiers);
    assertEquals(
        firstAccelerator.accelerator.keyCode,
        secondAccelerator.accelerator.keyCode);
    assertEquals(firstAccelerator.keyDisplay, secondAccelerator.keyDisplay);
  }
}

suite('shortcutUtilsTest', function() {
  test('CustomizationAllowed', async () => {
    loadTimeData.overrideValues({isCustomizationAllowed: true});
    assertTrue(isCustomizationAllowed());
  });

  test('CustomizationDisallowed', async () => {
    loadTimeData.overrideValues({isCustomizationAllowed: false});
    assertFalse(isCustomizationAllowed());
  });

  test('GetAcceleratorId', async () => {
    assertEquals(`${0}-${80}`, getAcceleratorId(0, 80));
    assertEquals(`${0}-${80}`, getAcceleratorId('0', '80'));
  });

  test('isTextAcceleratorInfo', async () => {
    const textAcceleratorParts: TextAcceleratorPart[] =
        [{text: stringToMojoString16('a'), type: TextAcceleratorPartType.kKey}];
    const textAccelerator = createTextAcceleratorInfo(textAcceleratorParts);
    assertTrue(isTextAcceleratorInfo(textAccelerator));
    assertFalse(isStandardAcceleratorInfo(textAccelerator));
  });

  test('isStandardAcceleratorInfo', async () => {
    const standardAccelerator = createStandardAcceleratorInfo(
        Modifier.ALT,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');
    assertTrue(isStandardAcceleratorInfo(standardAccelerator));
    assertFalse(isTextAcceleratorInfo(standardAccelerator));
  });

  test('GetAccelerator', async () => {
    const acceleratorInfo = createStandardAcceleratorInfo(
        Modifier.ALT,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');
    const expectedAccelerator: Accelerator = {
      modifiers: Modifier.ALT,
      keyCode: 221,  // c
      keyState: AcceleratorKeyState.PRESSED,
    };
    const actualAccelerator = getAccelerator(acceleratorInfo);
    assertDeepEquals(expectedAccelerator, actualAccelerator);
  });

  test('GetSortedModifiers', () => {
    // Empty modifiers
    const actualEmpty = getSortedModifiers([]);
    const expectedEmpty: string[] = [];
    assertTrue(areModifiersEqual(actualEmpty, expectedEmpty));

    // Single modifiers
    const actualSingle = getSortedModifiers(['alt']);
    const expectedSingle: string[] = ['alt'];
    assertTrue(areModifiersEqual(actualSingle, expectedSingle));

    // Multiple modifiers
    const actualMultiple = getSortedModifiers(['ctrl', 'shift', 'meta', 'alt']);
    const expectedMultiple: string[] = ['meta', 'ctrl', 'alt', 'shift'];
    assertTrue(areModifiersEqual(actualMultiple, expectedMultiple));

    const actualMultiple2 = getSortedModifiers(['ctrl', 'shift', 'meta']);
    const expectedMultiple2: string[] = ['meta', 'ctrl', 'shift'];
    assertTrue(areModifiersEqual(actualMultiple2, expectedMultiple2));

    const actualMultiple3 =
        getSortedModifiers(['shift', 'meta', 'ctrl', 'alt']);
    const expectedMultiple3: string[] = ['meta', 'ctrl', 'alt', 'shift'];
    assertTrue(areModifiersEqual(actualMultiple3, expectedMultiple3));
  });

  test('getModifierString', () => {
    assertEquals('shift', getModifierString(Modifier.SHIFT));
    assertEquals('ctrl', getModifierString(Modifier.CONTROL));
    assertEquals('alt', getModifierString(Modifier.ALT));
    assertEquals('meta', getModifierString(Modifier.COMMAND));
  });

  test('getModifiersForAcceleratorInfo', () => {
    assertArrayEquals(
        [],
        getModifiersForAcceleratorInfo(createStandardAcceleratorInfo(
            0, /*keyCode=*/ 221,
            /*keyDisplay=*/ ']')));
    assertArrayEquals(
        ['alt'],
        getModifiersForAcceleratorInfo(createStandardAcceleratorInfo(
            Modifier.ALT, /*keyCode=*/ 221, /*keyDisplay=*/ ']')));
    assertArrayEquals(
        ['ctrl', 'alt'],
        getModifiersForAcceleratorInfo(createStandardAcceleratorInfo(
            Modifier.ALT | Modifier.CONTROL, /*keyCode=*/ 221,
            /*keyDisplay=*/ ']')));
    assertArrayEquals(
        ['meta', 'ctrl', 'alt', 'shift'],
        getModifiersForAcceleratorInfo(createStandardAcceleratorInfo(
            Modifier.ALT | Modifier.CONTROL | Modifier.COMMAND | Modifier.SHIFT,
            /*keyCode=*/ 221,
            /*keyDisplay=*/ ']')));
  });

  test('getURLForSearchResult', () => {
    assertEquals(
        `${SHORTCUTS_APP_URL}/?action=1&category=${
            AcceleratorCategory.kGeneral}`,
        getURLForSearchResult(CycleTabsTextSearchResult).href);
    assertEquals(
        `${SHORTCUTS_APP_URL}/?action=0&category=${
            AcceleratorCategory.kWindowsAndDesks}`,
        getURLForSearchResult(SnapWindowLeftSearchResult).href);
    assertEquals(
        `${SHORTCUTS_APP_URL}/?action=2&category=${
            AcceleratorCategory.kWindowsAndDesks}`,
        getURLForSearchResult(TakeScreenshotSearchResult).href);
  });

  test('sortStandardAcceleratorInfo', () => {
    // Low modifiers, relatively high priority.
    const standardAcceleratorInfo1 = createStandardAcceleratorInfo(
        Modifier.ALT,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');

    // Meta only key, highest priority.
    const standardAcceleratorInfo2 = createStandardAcceleratorInfo(
        Modifier.NONE,
        /*keyCode=*/ 91,
        /*keyDisplay=*/ 'Meta');

    // Lots of modifiers, low priority.
    const standardAcceleratorInfo3 = createStandardAcceleratorInfo(
        Modifier.ALT | Modifier.SHIFT | Modifier.COMMAND,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');

    // Medium amount of modifiers, middle priority.
    const standardAcceleratorInfo4 = createStandardAcceleratorInfo(
        Modifier.ALT | Modifier.SHIFT,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');

    // No modifier, high priority.
    const standardAcceleratorInfo5 = createStandardAcceleratorInfo(
        Modifier.NONE,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');


    const initialOrder = [
      standardAcceleratorInfo1,
      standardAcceleratorInfo2,
      standardAcceleratorInfo3,
      standardAcceleratorInfo4,
      standardAcceleratorInfo5,
    ];
    const expectedOrder = [
      standardAcceleratorInfo2,
      standardAcceleratorInfo5,
      standardAcceleratorInfo1,
      standardAcceleratorInfo4,
      standardAcceleratorInfo3,
    ];
    initialOrder.sort(compareAcceleratorInfos);
    areStandardAcceleratorInfosEqual(expectedOrder, initialOrder);
  });

  test('sortStandardAcceleratorInfoStableOrder', async () => {
    const standardAcceleratorInfo1 = createStandardAcceleratorInfo(
        Modifier.ALT,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');

    // No modifier, this should get the highest priority.
    const standardAcceleratorInfo2 = createStandardAcceleratorInfo(
        Modifier.COMMAND,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');

    const standardAcceleratorInfo3 = createStandardAcceleratorInfo(
        Modifier.CONTROL,
        /*keyCode=*/ 221,
        /*keyDisplay=*/ ']');

    const initialOrder = [
      standardAcceleratorInfo1,
      standardAcceleratorInfo2,
      standardAcceleratorInfo3,
    ];
    const expectedOrder = [
      standardAcceleratorInfo1,
      standardAcceleratorInfo2,
      standardAcceleratorInfo3,
    ];
    initialOrder.sort(compareAcceleratorInfos);
    areStandardAcceleratorInfosEqual(expectedOrder, initialOrder);
  });

  test('getSourceAndActionFromAcceleratorId', async () => {
    const result1 = getSourceAndActionFromAcceleratorId('3-45');
    assertDeepEquals(result1, {source: 3, action: 45});

    const result2 = getSourceAndActionFromAcceleratorId('0-33');
    assertDeepEquals(result2, {source: 0, action: 33});
  });

  test('getUnidentifiedKeyDisplay', async () => {
    // If unidentified keys in unidentifiedKeyCodeToKey map, return the mapped
    // value.
    const key_event_1 = new KeyboardEvent('keydown', {
      key: 'Unidentified',
      keyCode: 239,
      code: '',
    });
    assertEquals('ViewAllApps', getUnidentifiedKeyDisplay(key_event_1));

    // For other unidentified keys, keydisplay is "Key {digit}".
    const key_event_2 = new KeyboardEvent('keydown', {
      key: 'Unidentified',
      keyCode: 10,
      code: 'Unidentified',
    });
    assertEquals('Key 10', getUnidentifiedKeyDisplay(key_event_2));
  });

  test('areAcceleratorsEqual', async () => {
    const accelerator1: Accelerator = {
      keyCode: 65,  // A
      modifiers: Modifier.ALT,
      keyState: AcceleratorKeyState.PRESSED,
    };

    const accelerator2: Accelerator = {
      keyCode: 65,  // A
      modifiers: Modifier.ALT,
      keyState: AcceleratorKeyState.PRESSED,
    };

    const accelerator3: Accelerator = {
      keyCode: 66,  // B
      modifiers: Modifier.ALT,
      keyState: AcceleratorKeyState.PRESSED,
    };

    assertTrue(areAcceleratorsEqual(accelerator1, accelerator2));
    assertFalse(areAcceleratorsEqual(accelerator1, accelerator3));
  });

  test('getNumpadKeyDisplay', async () => {
    assertEquals('numpad 0', getNumpadKeyDisplay('Numpad0'));
    assertEquals('numpad 9', getNumpadKeyDisplay('Numpad9'));
    assertEquals('numpad +', getNumpadKeyDisplay('NumpadAdd'));
    assertEquals('numpad /', getNumpadKeyDisplay('NumpadDivide'));
    assertEquals('numpad .', getNumpadKeyDisplay('NumpadDecimal'));
    assertEquals('enter', getNumpadKeyDisplay('NumpadEnter'));
  });
});