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

import {CustomizeButtonsSubsectionElement, KeyCombinationInputDialogElement} from 'chrome://os-settings/lazy_load.js';
import {fakeGraphicsTabletButtonActions, fakeGraphicsTablets} from 'chrome://os-settings/os_settings.js';
import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {CrIconButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import {CrInputElement} from 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import {assert} from 'chrome://resources/js/assert.js';
import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';

suite('<customize-buttons-subsection>', () => {
  let customizeButtonsSubsection: CustomizeButtonsSubsectionElement;
  let buttonRemappingChangedEventCount: number = 0;
  let reorderButtonEventCount: number = 0;
  setup(() => {
    assert(window.trustedTypes);
    document.body.innerHTML = window.trustedTypes.emptyHTML;
  });

  teardown(async () => {
    if (!customizeButtonsSubsection) {
      return;
    }
    buttonRemappingChangedEventCount = 0;
    reorderButtonEventCount = 0;
    customizeButtonsSubsection.remove();
    await flushTasks();
  });

  async function initializeCustomizeButtonsSubsection() {
    customizeButtonsSubsection =
        document.createElement(CustomizeButtonsSubsectionElement.is);
    customizeButtonsSubsection.addEventListener(
        'button-remapping-changed', function() {
          buttonRemappingChangedEventCount++;
        });
    customizeButtonsSubsection.addEventListener('reorder-button', function() {
      reorderButtonEventCount++;
    });
    customizeButtonsSubsection.set(
        'actionList', [...fakeGraphicsTabletButtonActions]);
    customizeButtonsSubsection.set(
        'buttonRemappingList',
        [...fakeGraphicsTablets[0]!.settings!.penButtonRemappings]);
    document.body.appendChild(customizeButtonsSubsection);
    return await flushTasks();
  }

  test(
      'Drag move icon via keyboard triggers reorder button event', async () => {
        await initializeCustomizeButtonsSubsection();
        assertTrue(!!customizeButtonsSubsection);
        assertTrue(!!customizeButtonsSubsection.get('buttonRemappingList'));

        // Drag move icon via keyboard triggers reorder button event.
        const moveIcon =
            customizeButtonsSubsection.shadowRoot!.querySelector('#row-0')!
                .shadowRoot!.querySelector<CrIconButtonElement>(
                    '#reorderButton');
        assertTrue(!!moveIcon);
        assertEquals(reorderButtonEventCount, 0);
        moveIcon.dispatchEvent(new KeyboardEvent('keydown', {
          ctrlKey: true,
          key: 'ArrowDown',
        }));

        await flushTasks();
        assertEquals(reorderButtonEventCount, 1);
      });

  test('Initialize customize buttons subsection', async () => {
    await initializeCustomizeButtonsSubsection();
    assertTrue(!!customizeButtonsSubsection);
    assertTrue(!!customizeButtonsSubsection.get('buttonRemappingList'));

    // Verify that renaming dialog will pop out when click the edit button.
    const editButton =
        customizeButtonsSubsection.shadowRoot!.querySelector('#row-0')!
            .shadowRoot!.querySelector<CrIconButtonElement>('#renameButton');
    assertTrue(!!editButton);
    const dialog =
        customizeButtonsSubsection.shadowRoot!.querySelector<CrDialogElement>(
            '#renamingDialog');
    assertTrue(!!dialog);

    editButton.click();
    await flushTasks();
    assertTrue(dialog.open);

    // Verify that the renaming dialog update the button name after clicking
    // 'save' button.
    const buttonLabelInput: CrInputElement|null =
        customizeButtonsSubsection.shadowRoot!.querySelector(
            '#renamingDialogInput');
    const saveButton: CrButtonElement|null =
        customizeButtonsSubsection.shadowRoot!.querySelector('#saveButton');
    assertTrue(!!buttonLabelInput);
    assertEquals(buttonRemappingChangedEventCount, 0);
    assertTrue(!!saveButton);
    await flushTasks();

    // Verify that if the input name is empty, the save button is disabled.
    buttonLabelInput.value = '';
    await flushTasks();
    assertTrue(saveButton.disabled);

    // Verify that if the new button is too long, it will cause invalid and be
    // truncated.
    buttonLabelInput.value =
        'Button name which exceeds 32 character is invalid.';
    await flushTasks();

    assertFalse(saveButton.disabled);
    assertTrue(customizeButtonsSubsection.get('buttonNameInvalid_'));
    assertEquals(buttonLabelInput.value.length, 32);
    const inputCountText: HTMLDivElement|null =
        customizeButtonsSubsection.shadowRoot!.querySelector('#inputCount');
    assertEquals(inputCountText!.textContent!.trim(), '32/32');

    // Verify that if the button name is duplicate with other buttons, the
    // save button is blocked.
    buttonLabelInput.value = 'Redo';
    saveButton.click();
    await flushTasks();
    assertTrue(buttonLabelInput.invalid);
    assertEquals(buttonRemappingChangedEventCount, 0);

    buttonLabelInput.value = 'New Button Name';
    assertEquals(inputCountText!.textContent!.trim(), '15/32');
    assertFalse(customizeButtonsSubsection.get('buttonNameInvalid_'));
    saveButton.click();
    await flushTasks();
    assertEquals(buttonRemappingChangedEventCount, 1);
    assertFalse(dialog.open);
  });

  test('open key combination dialog', async () => {
    await initializeCustomizeButtonsSubsection();
    const keyCombinationDialog: KeyCombinationInputDialogElement|null =
        customizeButtonsSubsection.shadowRoot!.querySelector(
            '#keyCombinationInputDialog');
    assertTrue(!!keyCombinationDialog);
    assertFalse(keyCombinationDialog.isOpen);
    customizeButtonsSubsection.dispatchEvent(
        new CustomEvent('show-key-combination-dialog', {
          bubbles: true,
          composed: true,
          detail: {
            buttonIndex: 0,
          },
        }));
    await flushTasks();
    assertTrue(keyCombinationDialog.isOpen);
  });

  test('Drop event should trigger remapping', async () => {
    await initializeCustomizeButtonsSubsection();
    assertTrue(!!customizeButtonsSubsection);
    assertTrue(!!customizeButtonsSubsection.get('buttonRemappingList'));

    const buttonRemappingListBefore =
        structuredClone(customizeButtonsSubsection.buttonRemappingList);

    // Call the callback directly since the event listening functionality
    // is handled by DragAndDropManager (which is unit tested separately).
    // @ts-expect-error (we're invoking a private method for the test).
    customizeButtonsSubsection.onDrop_(1, 0);
    await flushTasks();

    assertNotEquals(
        buttonRemappingListBefore[0],
        customizeButtonsSubsection.buttonRemappingList[0]);
    assertNotEquals(
        buttonRemappingListBefore[1],
        customizeButtonsSubsection.buttonRemappingList[1]);

    const expectedRemappingList = buttonRemappingListBefore.slice();
    const secondItem = buttonRemappingListBefore[1]!;
    // Remove second item.
    expectedRemappingList.splice(1, 1);
    // Add second item at beginning.
    expectedRemappingList.splice(0, 0, secondItem);

    assertDeepEquals(
        expectedRemappingList, customizeButtonsSubsection.buttonRemappingList);

    assertEquals(1, buttonRemappingChangedEventCount);

    // When onDrop_ is called with invalid indices, the button remapping list
    // should not change.
    // @ts-expect-error (we're invoking a private method for the test).
    customizeButtonsSubsection.onDrop_(100, -10);
    await flushTasks();

    assertEquals(1, buttonRemappingChangedEventCount);
  });
});