chromium/chrome/test/data/webui/chromeos/settings/device_page/keyboard_test.ts

// Copyright 2023 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 {SettingsKeyboardElement} from 'chrome://os-settings/lazy_load.js';
import {DevicePageBrowserProxyImpl, Route, Router, routes, settingMojom, SettingsSliderElement} from 'chrome://os-settings/os_settings.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {getDeepActiveElement} from 'chrome://resources/js/util.js';
import {pressAndReleaseKeyOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

import {clearBody} from '../utils.js';

import {getFakePrefs} from './device_page_test_util.js';
import {TestDevicePageBrowserProxy} from './test_device_page_browser_proxy.js';

suite('<settings-keyboard>', () => {
  let keyboardPage: SettingsKeyboardElement;
  let browserProxy: TestDevicePageBrowserProxy;

  setup(async () => {
    browserProxy = new TestDevicePageBrowserProxy();
    DevicePageBrowserProxyImpl.setInstanceForTesting(browserProxy);

    Router.getInstance().navigateTo(routes.KEYBOARD);

    clearBody();
    keyboardPage = document.createElement('settings-keyboard');
    keyboardPage.prefs = getFakePrefs();
    document.body.appendChild(keyboardPage);
    await flushTasks();
  });

  teardown(() => {
    Router.getInstance().resetRouteForTesting();
  });

  /**
   * Checks that the deep link to a setting focuses the correct element.
   * @param deepLinkElement The element that should be focused by
   *                                   the deep link
   * @param elementDesc A human-readable description of the element,
   *                              for assertion messages
   */
  async function checkDeepLink(
      route: Route, settingId: string, deepLinkElement: HTMLElement,
      elementDesc: string): Promise<void> {
    const params = new URLSearchParams();
    params.append('settingId', settingId);
    Router.getInstance().navigateTo(route, params);

    await waitAfterNextRender(deepLinkElement);
    assertEquals(
        deepLinkElement, getDeepActiveElement(),
        `${elementDesc} should be focused for settingId=${settingId}.`);
  }

  const name = (k: string) => `prefs.settings.language.${k}.value`;
  const get = (k: string) => keyboardPage.get(name(k));
  const set = (k: string, v: number|boolean) => keyboardPage.set(name(k), v);

  test('keyboard', async () => {
    // Initially, the optional keys are hidden.
    assertNull(keyboardPage.shadowRoot!.querySelector('#capsLockKey'));

    // Pretend no internal keyboard is available.
    const keyboardParams = {
      'showCapsLock': false,
      'showExternalMetaKey': false,
      'showAppleCommandKey': false,
      'hasLauncherKey': false,
      'hasAssistantKey': false,
    };
    webUIListenerCallback('show-keys-changed', keyboardParams);
    flush();
    assertNull(keyboardPage.shadowRoot!.querySelector('#launcherKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#capsLockKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#externalMetaKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#externalCommandKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#assistantKey'));

    // Pretend a Caps Lock key is now available.
    keyboardParams['showCapsLock'] = true;
    webUIListenerCallback('show-keys-changed', keyboardParams);
    flush();
    assertNull(keyboardPage.shadowRoot!.querySelector('#launcherKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#capsLockKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#externalMetaKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#externalCommandKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#assistantKey'));

    // Add a non-Apple external keyboard.
    keyboardParams['showExternalMetaKey'] = true;
    webUIListenerCallback('show-keys-changed', keyboardParams);
    flush();
    assertNull(keyboardPage.shadowRoot!.querySelector('#launcherKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#capsLockKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalMetaKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#externalCommandKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#assistantKey'));

    // Add an Apple keyboard.
    keyboardParams['showAppleCommandKey'] = true;
    webUIListenerCallback('show-keys-changed', keyboardParams);
    flush();
    assertNull(keyboardPage.shadowRoot!.querySelector('#launcherKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#capsLockKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalMetaKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalCommandKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#assistantKey'));

    // Add an internal keyboard.
    keyboardParams['hasLauncherKey'] = true;
    webUIListenerCallback('show-keys-changed', keyboardParams);
    flush();
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#launcherKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#capsLockKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalMetaKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalCommandKey'));
    assertNull(keyboardPage.shadowRoot!.querySelector('#assistantKey'));

    // Pretend an Assistant key is now available.
    keyboardParams['hasAssistantKey'] = true;
    webUIListenerCallback('show-keys-changed', keyboardParams);
    flush();
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#launcherKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#capsLockKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalMetaKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#externalCommandKey'));
    assertTrue(!!keyboardPage.shadowRoot!.querySelector('#assistantKey'));

    const collapse = keyboardPage.shadowRoot!.querySelector('iron-collapse');
    assertTrue(!!collapse);
    assertTrue(collapse.opened);

    let delaySlider =
        keyboardPage.shadowRoot!.querySelector<SettingsSliderElement>(
            '#delaySlider');
    assertTrue(!!delaySlider);
    assertEquals(500, delaySlider.pref.value);

    let repeatRateSlider =
        keyboardPage.shadowRoot!.querySelector<SettingsSliderElement>(
            '#repeatRateSlider');
    assertTrue(!!repeatRateSlider);
    assertEquals(500, repeatRateSlider.pref.value);

    const delayCrSlider = delaySlider.shadowRoot!.querySelector('cr-slider');
    assertTrue(!!delayCrSlider);

    const repeatCrSlider =
        repeatRateSlider.shadowRoot!.querySelector('cr-slider');
    assertTrue(!!repeatCrSlider);

    // Test interaction with the settings-slider's underlying cr-slider.
    pressAndReleaseKeyOn(delayCrSlider, 37 /* left */, [], 'ArrowLeft');
    pressAndReleaseKeyOn(repeatCrSlider, 39, [], 'ArrowRight');
    await flushTasks();
    assertEquals(1000, get('xkb_auto_repeat_delay_r2'));
    assertEquals(300, get('xkb_auto_repeat_interval_r2'));

    // Test sliders change when prefs change.
    set('xkb_auto_repeat_delay_r2', 1500);
    await flushTasks();

    delaySlider = keyboardPage.shadowRoot!.querySelector<SettingsSliderElement>(
        '#delaySlider');
    assertTrue(!!delaySlider);
    assertEquals(1500, delaySlider.pref.value);
    set('xkb_auto_repeat_interval_r2', 2000);
    await flushTasks();

    repeatRateSlider =
        keyboardPage.shadowRoot!.querySelector<SettingsSliderElement>(
            '#repeatRateSlider');
    assertTrue(!!repeatRateSlider);
    assertEquals(2000, repeatRateSlider.pref.value);

    // Test sliders round to nearest value when prefs change.
    set('xkb_auto_repeat_delay_r2', 600);
    await flushTasks();

    delaySlider = keyboardPage.shadowRoot!.querySelector<SettingsSliderElement>(
        '#delaySlider');
    assertTrue(!!delaySlider);
    assertEquals(500, delaySlider.pref.value);
    set('xkb_auto_repeat_interval_r2', 45);
    await flushTasks();

    repeatRateSlider =
        keyboardPage.shadowRoot!.querySelector<SettingsSliderElement>(
            '#repeatRateSlider');
    assertTrue(!!repeatRateSlider);
    assertEquals(50, repeatRateSlider.pref.value);

    set('xkb_auto_repeat_enabled_r2', false);
    assertFalse(collapse.opened);

    // Test keyboard shortcut viewer button.
    const button = keyboardPage.shadowRoot!.querySelector<HTMLButtonElement>(
        '#shortcutCustomizationApp');
    assertTrue(!!button);
    button.click();
    assertEquals(1, browserProxy.getCallCount('showShortcutCustomizationApp'));
  });

  test('Deep link to keyboard shortcuts', async () => {
    const element =
        keyboardPage.shadowRoot!.querySelector('#shortcutCustomizationApp');
    assertTrue(!!element);
    const button = element.shadowRoot!.querySelector('cr-icon-button');
    assertTrue(!!button);
    await checkDeepLink(
        routes.KEYBOARD, settingMojom.Setting.kKeyboardShortcuts.toString(),
        button, 'Keyboard shortcuts button');
  });
});