chromium/chrome/test/data/webui/chromeos/settings/device_page/per_device_keyboard_subsection_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/os_settings.js';
import 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';

import {CrLinkRowElement, FakeInputDeviceSettingsProvider, fakeKeyboards, Keyboard, MetaKey, PolicyStatus, Router, routes, setInputDeviceSettingsProviderForTesting, SettingsPerDeviceKeyboardSubsectionElement, SettingsSliderElement, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.js';
import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
import {assert} from 'chrome://resources/js/assert.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome://webui-test/test_util.js';

const KEYBOARD_FUNCTION_KEYS_SETTING_ID = 411;
const KEYBOARD_SWITCH_TOP_ROW_KEYS_SETTING_ID = 441;

suite('<settings-per-device-keyboard-subsection>', () => {
  let subsection: SettingsPerDeviceKeyboardSubsectionElement;
  let provider: FakeInputDeviceSettingsProvider;

  setup(async () => {
    await initializePerDeviceKeyboardSubsection(
        fakeKeyboards, /*rgbKeyboardSupported=*/ true,
        /*hasKeyboardBacklight=*/ true,
        /*hasAmbientLightSensor=*/ true);
  });

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

  function initializePerDeviceKeyboardSubsection(
      fakeKeyboards: Keyboard[], rgbKeyboardSupported: boolean,
      hasKeyboardBacklight: boolean,
      hasAmbientLightSensor: boolean): Promise<void> {
    provider = new FakeInputDeviceSettingsProvider();
    provider.setFakeKeyboards(fakeKeyboards);
    provider.setFakeIsRgbKeyboardSupported(rgbKeyboardSupported);
    provider.setFakeHasKeyboardBacklight(hasKeyboardBacklight);
    provider.setFakeHasAmbientLightSensor(hasAmbientLightSensor);
    setInputDeviceSettingsProviderForTesting(provider);

    subsection =
        document.createElement('settings-per-device-keyboard-subsection');
    subsection.set('keyboard', {...fakeKeyboards[0]});
    document.body.appendChild(subsection);
    return flushTasks();
  }

  function getElement(selector: string): Element|null {
    return subsection.shadowRoot!.querySelector(selector);
  }

  /**
   * Override enableKeyboardBacklightControlInSettings feature flag.
   * @param {!boolean} isEnabled
   */
  function setKeyboardBacklightControlEnabled(isEnabled: boolean): void {
    loadTimeData.overrideValues({
      enableKeyboardBacklightControlInSettings: isEnabled,
    });
  }

  /**
   * Changes the external state of the keyboard.
   */
  function changeIsExternalState(isExternal: boolean): Promise<void> {
    const keyboard = {
      ...subsection.get('keyboard'),
      isExternal: isExternal,
      metaKey: isExternal ? MetaKey.kExternalMeta : MetaKey.kSearch,
    };
    subsection.set('keyboard', keyboard);
    return flushTasks();
  }

  /**
   * Test that API are updated when keyboard settings change.
   */
  test('Update API when keyboard settings change', async () => {
    const externalTopRowAreFunctionKeysButton =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#externalTopRowAreFunctionKeysButton');
    assert(externalTopRowAreFunctionKeysButton);
    externalTopRowAreFunctionKeysButton!.click();
    await flushTasks();
    let updatedKeyboards = await provider.getConnectedKeyboardSettings();
    assertEquals(
        updatedKeyboards[0]!.settings.topRowAreFkeys,
        externalTopRowAreFunctionKeysButton.pref!.value);

    const blockMetaFunctionKeyRewritesButton =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#blockMetaFunctionKeyRewritesButton');
    assert(blockMetaFunctionKeyRewritesButton);
    blockMetaFunctionKeyRewritesButton.click();
    await flushTasks();

    updatedKeyboards = await provider.getConnectedKeyboardSettings();

    assertEquals(
        updatedKeyboards[0]!.settings.suppressMetaFkeyRewrites,
        blockMetaFunctionKeyRewritesButton.pref!.value);
  });

  /**Test that keyboard settings data are from the keyboard provider.*/
  test('Verify keyboard settings data', async () => {
    let externalTopRowAreFunctionKeysButton =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#externalTopRowAreFunctionKeysButton');
    assert(externalTopRowAreFunctionKeysButton);
    assertTrue(isVisible(externalTopRowAreFunctionKeysButton));
    assertEquals(
        fakeKeyboards[0]!.settings.topRowAreFkeys,
        externalTopRowAreFunctionKeysButton.pref!.value);
    let blockMetaFunctionKeyRewritesButton =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#blockMetaFunctionKeyRewritesButton');
    assert(blockMetaFunctionKeyRewritesButton);
    assertTrue(isVisible(blockMetaFunctionKeyRewritesButton));
    assertEquals(
        fakeKeyboards[0]!.settings.suppressMetaFkeyRewrites,
        blockMetaFunctionKeyRewritesButton.pref!.value);
    let internalTopRowAreFunctionKeysButton =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#internalTopRowAreFunctionKeysButton');
    assertFalse(isVisible(internalTopRowAreFunctionKeysButton));

    subsection.set('keyboard', fakeKeyboards[1]);
    await flushTasks();

    externalTopRowAreFunctionKeysButton = subsection.shadowRoot!.querySelector(
        '#externalTopRowAreFunctionKeysButton');
    assertFalse(isVisible(externalTopRowAreFunctionKeysButton));
    blockMetaFunctionKeyRewritesButton = subsection.shadowRoot!.querySelector(
        '#blockMetaFunctionKeyRewritesButton');
    assertFalse(isVisible(blockMetaFunctionKeyRewritesButton));
    internalTopRowAreFunctionKeysButton = subsection.shadowRoot!.querySelector(
        '#internalTopRowAreFunctionKeysButton');
    assertTrue(isVisible(internalTopRowAreFunctionKeysButton));
    assertEquals(
        fakeKeyboards[1]!.settings.topRowAreFkeys,
        internalTopRowAreFunctionKeysButton!.pref!.value);
  });

  /**
   * Test that keyboard settings are correctly show or hidden based on internal
   * vs external.
   */
  test('Verify keyboard settings visibility', async () => {
    // Change the isExternal state to true.
    await changeIsExternalState(true);
    // Verify external top-row are function keys toggle button is visible in the
    // page.
    let externalTopRowAreFunctionKeysButton =
        subsection.shadowRoot!.querySelector(
            '#externalTopRowAreFunctionKeysButton');
    assertTrue(isVisible(externalTopRowAreFunctionKeysButton));

    // Verify block meta function key rewrites toggle button is visible in the
    // page.
    let blockMetaFunctionKeyRewritesButton =
        subsection.shadowRoot!.querySelector(
            '#blockMetaFunctionKeyRewritesButton');
    assertTrue(isVisible(blockMetaFunctionKeyRewritesButton));

    // Verify internal top-row are function keys toggle button is not visible in
    // the page.
    let internalTopRowAreFunctionKeysButton =
        subsection.shadowRoot!.querySelector(
            '#internalTopRowAreFunctionKeysButton');
    assertFalse(isVisible(internalTopRowAreFunctionKeysButton));

    // Change the isExternal state to false.
    await changeIsExternalState(false);
    // Verify external top-row are function keys toggle button is not visible in
    // the page.
    externalTopRowAreFunctionKeysButton = subsection.shadowRoot!.querySelector(
        '#externalTopRowAreFunctionKeysButton');
    assertFalse(isVisible(externalTopRowAreFunctionKeysButton));

    // Verify block meta function key rewrites toggle button is not visible in
    // the page.
    blockMetaFunctionKeyRewritesButton = subsection.shadowRoot!.querySelector(
        '#blockMetaFunctionKeyRewritesButton');
    assertFalse(isVisible(blockMetaFunctionKeyRewritesButton));

    // Verify internal top-row are function keys toggle button is visible in the
    // page.
    internalTopRowAreFunctionKeysButton = subsection.shadowRoot!.querySelector(
        '#internalTopRowAreFunctionKeysButton');
    assertTrue(isVisible(internalTopRowAreFunctionKeysButton));
  });

  /**
   * Test that expected html elements are in the page after loaded.
   */
  test('per-device keyboard subsection loaded', () => {
    // Verify the external top-row are function keys toggle button is in the
    // page.
    const externalTopRowAreFunctionKeysButton =
        subsection.shadowRoot!.querySelector(
            '#externalTopRowAreFunctionKeysButton');
    assert(externalTopRowAreFunctionKeysButton);
  });

  /**
   * Verify the Keyboard remap keys row label is loaded, and sub-label is
   * correctly displayed when the keyboard has 2, 1 or 0 remapped keys.
   */
  test('remap keys sub-label displayed correctly', async () => {
    const remapKeysRow =
        subsection.shadowRoot!.querySelector('#remapKeyboardKeys');
    assert(remapKeysRow);
    assertEquals(
        'Customize keyboard keys',
        remapKeysRow.shadowRoot!.querySelector('#label')!.textContent!.trim());

    const remapKeysSubLabel =
        remapKeysRow.shadowRoot!.querySelector('#subLabel');
    assert(remapKeysSubLabel);
    assertEquals(
        2,
        Object.keys(subsection.get('keyboard.settings.modifierRemappings'))
            .length);
    assertEquals('2 customized keys', remapKeysSubLabel.textContent!.trim());

    subsection.set('keyboard', fakeKeyboards[2]);
    await flushTasks();
    assertEquals(
        1,
        Object.keys(subsection.get('keyboard.settings.modifierRemappings'))
            .length);
    assertEquals('1 customized key', remapKeysSubLabel.textContent!.trim());

    subsection.set('keyboard', fakeKeyboards[1]);
    await flushTasks();
    assertEquals(
        0,
        Object.keys(subsection.get('keyboard.settings.modifierRemappings'))
            .length);
    assertEquals('No keys customized', remapKeysSubLabel.textContent!.trim());
    loadTimeData.overrideValues({
      enableAltClickAndSixPackCustomization: true,
    });
    subsection.set('keyboard', fakeKeyboards[3]);
    await flushTasks();
    // Expect 3 remapped six pack key shortcuts and 2 remapped modifier keys.
    assertEquals('5 customized keys', remapKeysSubLabel.textContent!.trim());
  });

  /**
   * Verify clicking the Keyboard remap keys button will be redirecting to the
   * remapped keys subpage.
   */
  test('click remap keys button redirect to new subpage', async () => {
    const remapKeysRow = subsection.shadowRoot!.querySelector<CrLinkRowElement>(
        '#remapKeyboardKeys');
    assert(remapKeysRow);
    remapKeysRow.click();

    await flushTasks();
    assertEquals(
        routes.PER_DEVICE_KEYBOARD_REMAP_KEYS,
        Router.getInstance().currentRoute);

    const urlSearchQuery =
        Router.getInstance().getQueryParameters().get('keyboardId');
    assert(urlSearchQuery);
    const keyboardId = Number(urlSearchQuery);
    assertFalse(isNaN(keyboardId));
    const expectedKeyboardId = subsection.get('keyboard.id');
    assertEquals(expectedKeyboardId, keyboardId);
  });

  /**
   * Verify entering the page with search tags matched will auto focus the
   * searched element.
   */
  test('deep linking mixin focus on the first searched element', async () => {
    const topRowAreFunctionKeysToggle =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#externalTopRowAreFunctionKeysButton');
    assert(topRowAreFunctionKeysToggle);
    subsection.set('keyboardIndex', 0);
    // Enter the page from auto repeat search tag.
    const searchAutoRepeatUrl = new URLSearchParams(
        'search=keyboard&settingId=' +
        encodeURIComponent(KEYBOARD_FUNCTION_KEYS_SETTING_ID));

    await Router.getInstance().navigateTo(
        routes.PER_DEVICE_KEYBOARD,
        /* dynamicParams= */ searchAutoRepeatUrl, /* removeSearch= */ true);

    await waitAfterNextRender(topRowAreFunctionKeysToggle);
    assertEquals(
        subsection.shadowRoot!.activeElement, topRowAreFunctionKeysToggle);

    const switchTopRowKeysButton =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#blockMetaFunctionKeyRewritesButton');
    assert(switchTopRowKeysButton);
    const searchSwitchTopRowKeysUrl = new URLSearchParams(
        'search=switch+top&settingId=' +
        encodeURIComponent(KEYBOARD_SWITCH_TOP_ROW_KEYS_SETTING_ID));

    await Router.getInstance().navigateTo(
        routes.PER_DEVICE_KEYBOARD,
        /* dynamicParams= */ searchSwitchTopRowKeysUrl,
        /* removeSearch= */ true);

    await waitAfterNextRender(switchTopRowKeysButton);
    assert(switchTopRowKeysButton);
    assertEquals(subsection.shadowRoot!.activeElement, switchTopRowKeysButton);
  });

  /**
   * Verify entering the page with search tags matched wll not auto focus the
   * searched element if it's not the first keyboard displayed.
   */
  test('deep linkng mixin does not focus on second element', async () => {
    const topRowAreFunctionKeysToggle = subsection.shadowRoot!.querySelector(
        '#externalTopRowAreFunctionKeysButton');
    assert(topRowAreFunctionKeysToggle);
    subsection.set('keyboardIndex', 1);
    // Enter the page from auto repeat search tag.
    const url = new URLSearchParams(
        'search=keyboard&settingId=' +
        encodeURIComponent(KEYBOARD_FUNCTION_KEYS_SETTING_ID));

    await Router.getInstance().navigateTo(
        routes.PER_DEVICE_KEYBOARD,
        /* dynamicParams= */ url, /* removeSearch= */ true);
    await flushTasks();

    assertEquals(null, subsection.shadowRoot!.activeElement);

    const switchTopRowKeysButton = subsection.shadowRoot!.querySelector(
        '#blockMetaFunctionKeyRewritesButton');
    assert(switchTopRowKeysButton);

    const searchSwitchTopRowKeysUrl = new URLSearchParams(
        'search=switch+top&settingId=' +
        encodeURIComponent(KEYBOARD_SWITCH_TOP_ROW_KEYS_SETTING_ID));

    await Router.getInstance().navigateTo(
        routes.PER_DEVICE_KEYBOARD,
        /* dynamicParams= */ searchSwitchTopRowKeysUrl,
        /* removeSearch= */ true);

    await flushTasks();
    assertEquals(null, subsection.shadowRoot!.activeElement);
  });

  /**
   * Verifies that the indicator policy is properly reflected in the UI.
   */
  test('top row are fkeys policy reflected in UI', async () => {
    subsection.set('keyboardPolicies', {
      topRowAreFkeysPolicy:
          {policy_status: PolicyStatus.kManaged, value: false},
    });
    await flushTasks();
    const topRowAreFunctionKeysToggle = subsection.shadowRoot!.querySelector(
        '#externalTopRowAreFunctionKeysButton');
    assert(topRowAreFunctionKeysToggle);
  });

  test(
      'Built-in Keyboard customize keys rows has additional class name',
      async () => {
        await changeIsExternalState(false);
        assertTrue(subsection.shadowRoot!.querySelector('#remapKeyboardKeys')!
                       .classList.contains('remap-keyboard-keys-row-internal'));
      });

  test(
      'Verify keyboard backlight control elements visibility with flag',
      async () => {
        setKeyboardBacklightControlEnabled(true);
        await changeIsExternalState(false);

        // Initially, elements should be visible.
        assertTrue(isVisible(getElement('#rgbKeyboardControlLink')));
        assertTrue(isVisible(getElement('#keyboardAutoBrightnessToggle')));
        assertTrue(isVisible(getElement('#keyboardBrightnessSlider')));

        // Disable keyboard backlight control flag and reinitialize.
        setKeyboardBacklightControlEnabled(false);
        await initializePerDeviceKeyboardSubsection(
            fakeKeyboards, /*rgbKeyboardSupported=*/ true,
            /*hasKeyboardBacklight=*/ true,
            /*hasAmbientLightSensor=*/ true);
        await changeIsExternalState(false);

        // Elements should be hidden after flag is disabled.
        assertFalse(isVisible(getElement('#rgbKeyboardControlLink')));
        assertFalse(isVisible(getElement('#keyboardAutoBrightnessToggle')));
        assertFalse(isVisible(getElement('#keyboardBrightnessSlider')));
      });

  test(
      'Verify elements visibility with keyboard backlight status', async () => {
        setKeyboardBacklightControlEnabled(true);
        await initializePerDeviceKeyboardSubsection(
            fakeKeyboards, /*rgbKeyboardSupported=*/ true,
            /*hasKeyboardBacklight=*/ true,
            /*hasAmbientLightSensor=*/ true);
        await changeIsExternalState(false);
        assertTrue(isVisible(getElement('#keyboardAutoBrightnessToggle')));
        assertTrue(isVisible(getElement('#keyboardBrightnessSlider')));

        // Disable keyboard backlight, then reinitialize.
        await initializePerDeviceKeyboardSubsection(
            fakeKeyboards, /*rgbKeyboardSupported=*/ true,
            /*hasKeyboardBacklight=*/ false,
            /*hasAmbientLightSensor=*/ true);
        await changeIsExternalState(false);
        assertFalse(isVisible(getElement('#keyboardAutoBrightnessToggle')));
        assertFalse(isVisible(getElement('#keyboardBrightnessSlider')));
      });

  test('Verify keyboard auto brightness toggle visibility', async () => {
    setKeyboardBacklightControlEnabled(true);
    await initializePerDeviceKeyboardSubsection(
        fakeKeyboards, /*rgbKeyboardSupported=*/ true,
        /*hasKeyboardBacklight=*/ true,
        /*hasAmbientLightSensor=*/ true);
    await changeIsExternalState(false);
    assertTrue(isVisible(getElement('#keyboardAutoBrightnessToggle')));

    // Disable ambient light sensor, then reinitialize.
    await initializePerDeviceKeyboardSubsection(
        fakeKeyboards, /*rgbKeyboardSupported=*/ false,
        /*hasKeyboardBacklight=*/ true,
        /*hasAmbientLightSensor=*/ false);
    await changeIsExternalState(false);
    assertFalse(isVisible(getElement('#keyboardAutoBrightnessToggle')));
  });

  test('Verify rgb keyboard control link visiblity', async () => {
    setKeyboardBacklightControlEnabled(true);
    await initializePerDeviceKeyboardSubsection(
        fakeKeyboards, /*rgbKeyboardSupported=*/ true,
        /*hasKeyboardBacklight=*/ true, /*hasAmbientLightSensor=*/ true);
    await changeIsExternalState(false);
    assertTrue(isVisible(getElement('#rgbKeyboardControlLink')));

    // Disable RGB keyboard support, then reinitialize.
    await initializePerDeviceKeyboardSubsection(
        fakeKeyboards, /*rgbKeyboardSupported=*/ false,
        /*hasKeyboardBacklight=*/ true,
        /*hasAmbientLightSensor=*/ true);
    await changeIsExternalState(false);
    assertFalse(isVisible(getElement('#rgbKeyboardControlLink')));
  });

  test('observe keyboard brightness change', async () => {
    await changeIsExternalState(false);

    const slider = subsection.shadowRoot!.querySelector<SettingsSliderElement>(
        '#keyboardBrightnessSlider');
    assertTrue(!!slider);

    // Define initial and test values for keyboard brightness to improve
    // readability.
    const initialBrightness = 40.0;
    const firstAdjustedBrightness = 60.5;
    const secondAdjustedBrightness = 20.5;

    // Verify initial brightness is set correctly when observer is registered.
    assertEquals(initialBrightness, slider.pref!.value);

    // Simulate a keyboard brightness change and verify the slider updates
    // accordingly.
    provider.sendKeyboardBrightnessChange(firstAdjustedBrightness);
    await flushTasks();
    assertEquals(firstAdjustedBrightness, slider.pref!.value);

    // Simulate another keyboard brightness change.
    provider.sendKeyboardBrightnessChange(secondAdjustedBrightness);
    await flushTasks();
    assertEquals(secondAdjustedBrightness, slider.pref!.value);
  });

  test('observe keyboard ambient light sensor enabled change', async () => {
    await changeIsExternalState(false);
    const toggle =
        subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#keyboardAutoBrightnessToggle');
    assertTrue(!!toggle);

    // Simulate a keyboard ambient light sensor enabled change and verify the
    // toggle updates accordingly.
    provider.sendKeyboardAmbientLightSensorEnabledChange(true);
    await flushTasks();
    assertTrue(toggle.pref!.value);

    // Simulate another keyboard ambient light sensor enabled change.
    provider.sendKeyboardAmbientLightSensorEnabledChange(false);
    await flushTasks();
    assertFalse(toggle.pref!.value);
  });

  test('Set keyboard brightness via slider', async () => {
    await changeIsExternalState(false);
    const slider = subsection.shadowRoot!.querySelector<SettingsSliderElement>(
        '#keyboardBrightnessSlider');
    assertTrue(!!slider);

    const initialBrightness = 40;
    const firstAdjustedBrightness = 60.5;
    const secondAdjustedBrightness = 20.5;

    // Default brightness is 40.
    assertEquals(initialBrightness, slider.pref.value);
    assertEquals(initialBrightness, provider.getKeyboardBrightness());

    // Set keyboard brightness via slider.
    slider.pref.value = firstAdjustedBrightness;
    slider.dispatchEvent(new CustomEvent('cr-slider-value-changed'));
    await flushTasks();
    assertEquals(firstAdjustedBrightness, provider.getKeyboardBrightness());

    // Set keyboard brightness via slider again.
    slider.pref.value = secondAdjustedBrightness;
    slider.dispatchEvent(new CustomEvent('cr-slider-value-changed'));
    await flushTasks();
    assertEquals(secondAdjustedBrightness, provider.getKeyboardBrightness());
  });

  test(
      'Set keyboard ambient light sensor enable via toggle element',
      async () => {
        await changeIsExternalState(false);
        const toggle =
            subsection.shadowRoot!.querySelector<SettingsToggleButtonElement>(
                '#keyboardAutoBrightnessToggle');
        assertTrue(!!toggle);

        // Verify initial state of the toggle and fake provider is 'false'.
        assertFalse(toggle.checked);
        assertFalse(provider.getKeyboardAmbientLightSensorEnabled());

        // Toggle to enable the keyboard ambient light sensor.
        toggle.click();
        await flushTasks();
        assertTrue(toggle.checked);
        assertTrue(provider.getKeyboardAmbientLightSensorEnabled());

        // Toggle again to disable the sensor.
        toggle.click();
        await flushTasks();
        assertFalse(toggle.checked);
        assertFalse(provider.getKeyboardAmbientLightSensorEnabled());
      });

  test('Record keyboard color link clicked', async () => {
    await changeIsExternalState(false);
    assertEquals(0, provider.getKeyboardColorLinkClicks());
    const rgbKeyboardControlLink =
        subsection.shadowRoot!.querySelector<CrLinkRowElement>(
            '#rgbKeyboardControlLink');
    assertTrue(!!rgbKeyboardControlLink);

    rgbKeyboardControlLink.click();
    await flushTasks();
    assertEquals(1, provider.getKeyboardColorLinkClicks());
  });

  test('Record brightness change from slider', async () => {
    await changeIsExternalState(false);
    const slider = subsection.shadowRoot!.querySelector<SettingsSliderElement>(
        '#keyboardBrightnessSlider');
    assertTrue(!!slider);
    assertEquals(
        0, provider.getRecordKeyboardBrightnessChangeFromSliderCallCount());

    // Ensure no metric is recorded on pointerdown.
    slider.dispatchEvent(new PointerEvent('pointerdown'));
    assertEquals(
        0, provider.getRecordKeyboardBrightnessChangeFromSliderCallCount());

    // Verify metric is recorded once on pointerup.
    slider.dispatchEvent(new PointerEvent('pointerup'));
    assertEquals(
        1, provider.getRecordKeyboardBrightnessChangeFromSliderCallCount());

    // Keydown events shouldn't trigger metric recording.
    slider.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
    assertEquals(
        1, provider.getRecordKeyboardBrightnessChangeFromSliderCallCount());

    // Verify metric records on keyup with arrow keys.
    slider.dispatchEvent(new KeyboardEvent('keyup', {key: 'ArrowLeft'}));
    assertEquals(
        2, provider.getRecordKeyboardBrightnessChangeFromSliderCallCount());

    // Non-arrow keyup events shouldn't record metrics.
    slider.dispatchEvent(new KeyboardEvent('keyup', {key: 'Enter'}));
    assertEquals(
        2, provider.getRecordKeyboardBrightnessChangeFromSliderCallCount());
  });

  test('Observe lid state change for internal keyboard', async () => {
    // Set keyboard to internal state.
    await changeIsExternalState(false);
    let subsectionHeader =
        subsection.shadowRoot!.querySelector<HTMLElement>('#subsectionHeader');
    let subsectionBody =
        subsection.shadowRoot!.querySelector<HTMLElement>('.subsection');

    // Subsection header and body should be present.
    assertTrue(!!subsectionHeader);
    assertTrue(!!subsectionBody);

    // Simulate lid close.
    provider.setLidStateClosed();
    await flushTasks();

    // Subsection header and body should be hidden.
    subsectionHeader =
        subsection.shadowRoot!.querySelector<HTMLElement>('#subsectionHeader');
    subsectionBody =
        subsection.shadowRoot!.querySelector<HTMLElement>('.subsection');
    assertFalse(!!subsectionHeader);
    assertFalse(!!subsectionBody);

    // Simulate lid open.
    provider.setLidStateOpen();
    await flushTasks();

    // Subsection header and body should be visible again.
    subsectionHeader =
        subsection.shadowRoot!.querySelector<HTMLElement>('#subsectionHeader');
    subsectionBody =
        subsection.shadowRoot!.querySelector<HTMLElement>('.subsection');
    assertTrue(!!subsectionHeader);
    assertTrue(!!subsectionBody);
  });

  test('Observe lid state change for external keyboard', async () => {
    // Set keyboard to external state.
    await changeIsExternalState(true);

    // Subsection header and body should be present.
    let subsectionHeader =
        subsection.shadowRoot!.querySelector<HTMLElement>('#subsectionHeader');
    let subsectionBody =
        subsection.shadowRoot!.querySelector<HTMLElement>('.subsection');
    assertTrue(!!subsectionHeader);
    assertTrue(!!subsectionBody);

    // Simulate lid close.
    provider.setLidStateClosed();
    await flushTasks();

    // Subsection header and body should still be present.
    subsectionHeader =
        subsection.shadowRoot!.querySelector<HTMLElement>('#subsectionHeader');
    subsectionBody =
        subsection.shadowRoot!.querySelector<HTMLElement>('.subsection');
    assertTrue(!!subsectionHeader);
    assertTrue(!!subsectionBody);
  });
});