chromium/chrome/test/data/webui/chromeos/settings/controls/dropdown_menu_test.ts

// Copyright 2024 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 {SettingsDropdownMenuElement} from 'chrome://os-settings/os_settings.js';
import {assertEquals, assertFalse, assertNotReached, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {clearBody} from '../utils.js';

/** @fileoverview Suite of tests for settings-dropdown-menu. */
suite('SettingsDropdownMenu', function() {
  let dropdown: SettingsDropdownMenuElement;

  // The <select> used internally by the dropdown menu.
  let selectElement: HTMLSelectElement;

  // The "Custom" option in the <select> menu.
  let customOption: HTMLOptionElement;

  function waitUntilDropdownUpdated(): Promise<void> {
    return waitAfterNextRender(dropdown);
  }

  function simulateChangeEvent(value: string): Promise<void> {
    selectElement.value = value;
    selectElement.dispatchEvent(new CustomEvent('change'));
    return waitUntilDropdownUpdated();
  }

  setup(function() {
    clearBody();
    dropdown = document.createElement('settings-dropdown-menu');
    document.body.appendChild(dropdown);
    selectElement = dropdown.shadowRoot!.querySelector('select')!;
    assertTrue(!!selectElement);
    const options = selectElement.options;
    customOption = options[options.length - 1]!;
    assertTrue(!!customOption);
  });

  test('with number options', async function() {
    dropdown.pref = {
      key: 'test.number',
      type: chrome.settingsPrivate.PrefType.NUMBER,
      value: 100,
    };
    dropdown.menuOptions = [
      {value: 100, name: 'Option 100'},
      {value: 200, name: 'Option 200'},
      {value: 300, name: 'Option 300'},
      {value: 400, name: 'Option 400'},
    ];
    await waitUntilDropdownUpdated();

    // Initially selected item.
    assertEquals(
        'Option 100', selectElement.selectedOptions[0]!.textContent!.trim());

    // Selecting an item updates the pref.
    await simulateChangeEvent('200');
    assertEquals(200, dropdown.pref!.value);

    // Updating the pref selects an item.
    dropdown.set('pref.value', 400);
    await waitUntilDropdownUpdated();
    assertEquals('400', selectElement.value);
  });

  test('with string options', async function() {
    dropdown.pref = {
      key: 'test.string',
      type: chrome.settingsPrivate.PrefType.STRING,
      value: 'c',
    };
    dropdown.menuOptions = [
      {value: 'a', name: 'AAA'},
      {value: 'b', name: 'BBB'},
      {value: 'c', name: 'CCC'},
      {value: 'd', name: 'DDD'},
    ];
    await waitUntilDropdownUpdated();

    // Initially selected item.
    assertEquals('CCC', selectElement.selectedOptions[0]!.textContent!.trim());

    // Selecting an item updates the pref.
    await simulateChangeEvent('a');
    assertEquals('a', dropdown.pref!.value);

    // Item remains selected after updating menu items.
    const newMenuOptions = dropdown.menuOptions.slice().reverse();
    dropdown.menuOptions = newMenuOptions;
    await waitUntilDropdownUpdated();
    assertEquals('AAA', selectElement.selectedOptions[0]!.textContent!.trim());
  });

  test('with custom value', async function() {
    dropdown.pref = {
      key: 'test.string',
      type: chrome.settingsPrivate.PrefType.STRING,
      value: 'f',
    };
    dropdown.menuOptions = [
      {value: 'a', name: 'AAA'},
      {value: 'b', name: 'BBB'},
      {value: 'c', name: 'CCC'},
      {value: 'd', name: 'DDD'},
    ];
    dropdown.addEventListener('settings-control-change', () => {
      // Failure, custom value shouldn't ever call this.
      assertNotReached(
          'settings-control-change should not be triggered for custom value');
    });

    await waitUntilDropdownUpdated();
    // "Custom" initially selected.
    assertEquals(dropdown.notFoundValue, selectElement.value);
    assertEquals('block', getComputedStyle(customOption).display);
    assertFalse(customOption.disabled);

    // Pref should not have changed.
    assertEquals('f', dropdown.pref!.value);
  });

  test('with hidden options', async function() {
    dropdown.pref = {
      key: 'test.string',
      type: chrome.settingsPrivate.PrefType.STRING,
      value: 'f',
    };
    dropdown.menuOptions = [
      {value: 'a', name: 'AAA', hidden: true},
      {value: 'b', name: 'BBB', hidden: false},
      {value: 'c', name: 'CCC'},
      {value: 'd', name: 'DDD'},
    ];
    await waitUntilDropdownUpdated();

    // `options` contains the options above plus 'Custom'.
    assertEquals(5, selectElement.options.length);
    assertTrue(selectElement.options[0]!.hidden);
    assertFalse(selectElement.options[1]!.hidden);
    assertFalse(selectElement.options[2]!.hidden);
    assertFalse(selectElement.options[3]!.hidden);
  });

  function waitForTimeout(timeMs: number): Promise<void> {
    return new Promise<void>(function(resolve) {
      setTimeout(resolve, timeMs);
    });
  }

  test('delay setting options', async function() {
    dropdown.pref = {
      key: 'test.number2',
      type: chrome.settingsPrivate.PrefType.NUMBER,
      value: 200,
    };

    await waitForTimeout(100);
    await waitUntilDropdownUpdated();
    assertTrue(selectElement.disabled);
    assertEquals('SETTINGS_DROPDOWN_NOT_FOUND_ITEM', selectElement.value);

    dropdown.menuOptions = [
      {value: 100, name: 'Option 100'},
      {value: 200, name: 'Option 200'},
      {value: 300, name: 'Option 300'},
      {value: 400, name: 'Option 400'},
    ];
    await waitUntilDropdownUpdated();
    // Dropdown menu enables itself and selects the new menu option
    // corresponding to the pref value.
    assertFalse(selectElement.disabled);
    assertEquals('200', selectElement.value);

    // The "Custom" option should not show up in the dropdown list or be
    // reachable via type-ahead.
    assertEquals('none', getComputedStyle(customOption).display);
    assertTrue(customOption.disabled);
  });

  test('works with dictionary pref', async function() {
    let settingsControlChangeCount = 0;
    dropdown.pref = {
      key: 'test.dictionary',
      type: chrome.settingsPrivate.PrefType.DICTIONARY,
      value: {
        'key1': 'value1',
        'key2': 'value2',
      },
    };
    dropdown.prefKey = 'key2';
    dropdown.menuOptions = [
      {value: 'value2', name: 'Option 2'},
      {value: 'value3', name: 'Option 3'},
      {value: 'value4', name: 'Option 4'},
    ];
    dropdown.addEventListener('settings-control-change', () => {
      ++settingsControlChangeCount;
    });

    await waitUntilDropdownUpdated();
    // Initially selected item.
    assertEquals(
        'Option 2', selectElement.selectedOptions[0]!.textContent!.trim());

    // Setup does not call the settings-control-change event.
    assertEquals(0, settingsControlChangeCount);

    // Selecting an item updates the pref.
    await simulateChangeEvent('value3');
    assertEquals('value3', dropdown.pref!.value['key2']);

    // The settings-control-change callback should have been triggered
    // exactly once.
    assertEquals(1, settingsControlChangeCount);

    // Updating the pref selects an item.
    dropdown.set('pref.value.key2', 'value4');
    await waitUntilDropdownUpdated();
    assertEquals('value4', selectElement.value);

    // The settings-control-change callback should have been triggered
    // exactly once still -- manually updating the pref is not a user
    // action so the count should not be incremented.
    assertEquals(1, settingsControlChangeCount);
  });
});