chromium/chrome/test/data/webui/chromeos/settings/os_a11y_page/select_to_speak_subpage_test.ts

// Copyright 2022 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 {HandlerVoice, SettingsSelectToSpeakSubpageElement} from 'chrome://os-settings/lazy_load.js';
import {CrSettingsPrefs, SelectToSpeakSubpageBrowserProxyImpl, SettingsPrefsElement, SettingsToggleButtonElement} from 'chrome://os-settings/os_settings.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

import {TestSelectToSpeakSubpageBrowserProxy} from './test_select_to_speak_subpage_browser_proxy.js';

/**
 * Extension ID of the enhanced network TTS voices extension.
 */
const ENHANCED_TTS_EXTENSION_ID = 'jacnkoglebceckolkoapelihnglgaicd';

suite('<settings-select-to-speak-subpage>', () => {
  let page: SettingsSelectToSpeakSubpageElement;
  let browserProxy: TestSelectToSpeakSubpageBrowserProxy;
  let prefElement: SettingsPrefsElement;

  setup(async () => {
    browserProxy = new TestSelectToSpeakSubpageBrowserProxy();
    SelectToSpeakSubpageBrowserProxyImpl.setInstanceForTesting(browserProxy);

    prefElement = document.createElement('settings-prefs');
    document.body.appendChild(prefElement);

    await CrSettingsPrefs.initialized;
    page = document.createElement('settings-select-to-speak-subpage');
    page.prefs = prefElement.prefs;
    document.body.appendChild(page);
    flush();
  });

  teardown(() => {
    page.remove();
    prefElement.remove();
    browserProxy.reset();
  });

  // TODO(crbug.com/1354821): Add tests that the language filter works for
  // enhanced and device voices.

  test('voice pref and dropdown synced', async () => {
    // Make sure voice dropdown is system voice, matching default pref state.
    const voiceDropdown =
        page.shadowRoot!.querySelector<HTMLElement>('#voiceDropdown');
    assertTrue(!!voiceDropdown);
    await waitAfterNextRender(voiceDropdown);
    const voiceSelectElement =
        voiceDropdown.shadowRoot!.querySelector('select');
    assertTrue(!!voiceSelectElement);
    assertEquals('select_to_speak_system_voice', voiceSelectElement.value);

    // Change voice to Chrome OS US English, and verify pref is also changed.
    voiceSelectElement.value = 'Chrome OS US English';
    voiceSelectElement.dispatchEvent(new CustomEvent('change'));
    flush();
    const voicePref = page.getPref('settings.a11y.select_to_speak_voice_name');
    assertEquals('Chrome OS US English', voicePref.value);
  });

  test('voice preview text field and button sends sample message', async () => {
    // Make sure preview input exists, and write a sample message into it.
    const voicePreviewInput =
        page.shadowRoot!.querySelector<HTMLInputElement>('#voicePreviewInput');
    assertTrue(!!voicePreviewInput);
    voicePreviewInput.value = 'The quick brown fox jumped over the lazy dog.';

    // Click preview button, expect sample message to be sent.
    const voicePreviewButton =
        page.shadowRoot!.querySelector<HTMLButtonElement>(
            '#voicePreviewButton');
    assertTrue(!!voicePreviewButton);
    voicePreviewButton.click();
    const [previewText, previewVoice] =
        await browserProxy.whenCalled('previewTtsVoice');
    assertEquals('The quick brown fox jumped over the lazy dog.', previewText);
    assertEquals('{"name":"","extension":""}', previewVoice);
  });

  test('voice switching pref and toggle synced', () => {
    // Make sure voice switching toggle is off, matching default pref state.
    const voiceSwitchingToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#voiceSwitchingToggle');
    assertTrue(!!voiceSwitchingToggle);
    assertFalse(voiceSwitchingToggle.checked);

    // Toggle voice switching on, and verify voice_switching pref is enabled.
    voiceSwitchingToggle.click();
    const voiceSwitchingPref =
        page.getPref('settings.a11y.select_to_speak_voice_switching');
    assertTrue(voiceSwitchingPref.value);
  });

  test('enhanced network voices pref and toggle synced', () => {
    // Make sure enhanced network voices toggle is off, matching default pref
    // state.
    const enhancedNetworkVoicesToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#enhancedNetworkVoicesToggle');
    assertTrue(!!enhancedNetworkVoicesToggle);
    assertFalse(enhancedNetworkVoicesToggle.checked);

    // Toggle enhanced network voices on, and verify voice_switching pref is
    // enabled.
    enhancedNetworkVoicesToggle.click();
    const enhancedNetworkVoicesPref =
        page.getPref('settings.a11y.select_to_speak_enhanced_network_voices');
    assertTrue(enhancedNetworkVoicesPref.value);
  });

  test('enhanced network voices toggle respects enterprise policy', () => {
    // Make sure enhanced network voices toggle is togglable and off, matching
    // default pref state, and verify enterprise managed icon + controls are not
    // present.
    const enhancedNetworkVoicesToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#enhancedNetworkVoicesToggle');
    assertTrue(!!enhancedNetworkVoicesToggle);
    assertFalse(
        enhancedNetworkVoicesToggle.controlDisabled(),
        'enhanced voices toggle should be togglable');
    assertFalse(
        enhancedNetworkVoicesToggle.checked,
        'enhanced voices toggle should be off');
    const getManagedIcon = () =>
        enhancedNetworkVoicesToggle.shadowRoot!.querySelector(
            'cr-policy-pref-indicator');
    const managedIconVisible = () => getManagedIcon()!.style.display !== 'none';
    const getEnhancedVoiceControls = () =>
        page.shadowRoot!.querySelector<HTMLElement>(
            '#enhancedNetworkVoiceControls');
    const enhancedVoiceControlsVisible = () =>
        getEnhancedVoiceControls()!.style.display !== 'none';
    assertEquals(null, getManagedIcon(), 'managed icon should not be present');
    assertEquals(
        null, getEnhancedVoiceControls(),
        'enhanced voice controls should not be present');

    // Toggle enhanced network voices on, and verify voice_switching pref is
    // enabled, toggle is on, enterprise managed icon is not present, and
    // controls are visible.
    enhancedNetworkVoicesToggle.click();
    flush();
    const enhancedNetworkVoicesPref =
        page.getPref('settings.a11y.select_to_speak_enhanced_network_voices');
    assertTrue(
        enhancedNetworkVoicesPref.value,
        'enhanced voices pref should be enabled');
    assertTrue(
        enhancedNetworkVoicesToggle.checked,
        'enhanced voices toggle should be on');
    assertEquals(
        null, getManagedIcon(), 'managed icon should still not be present');
    assertTrue(
        enhancedVoiceControlsVisible(),
        'enhanced voice controls should be visible');

    // Disallow enhanced voices via enterprise policy.
    page.setPrefValue(
        'settings.a11y.enhanced_network_voices_in_select_to_speak_allowed',
        false);
    flush();

    // Verify voice switching toggle is immediately disabled and off, enterprise
    // managed icon is visible, and controls are not visible.
    assertTrue(
        enhancedNetworkVoicesToggle.controlDisabled(),
        'enhanced voices toggle should not be togglable');
    assertFalse(
        enhancedNetworkVoicesToggle.checked,
        'enhanced voices toggle should be off again');
    assertTrue(managedIconVisible(), 'managed icon should be visible');
    assertFalse(
        enhancedVoiceControlsVisible(),
        'enhanced voice controls should not be visible');

    // Assert pref is still enabled (we don't disable the user's pref just
    // because the enterprise policy pref is disabled).
    assertTrue(
        enhancedNetworkVoicesPref.value,
        'enhanced voices pref should still be enabled');

    // Reallow enhanced voices via enterprise policy.
    page.setPrefValue(
        'settings.a11y.enhanced_network_voices_in_select_to_speak_allowed',
        true);
    flush();

    // Verify voice switching toggle is togglable and turned back on again,
    // enterprise managed icon is not visible, and controls are visible.
    assertFalse(
        enhancedNetworkVoicesToggle.controlDisabled(),
        'enhanced voices toggle should be togglable again');
    assertTrue(
        enhancedNetworkVoicesToggle.checked,
        'enhanced voices toggle should be on again');
    assertFalse(managedIconVisible(), 'managed icon should not be visible');
    assertTrue(
        enhancedVoiceControlsVisible(),
        'enhanced voice controls should be visible again');
  });

  test('enhanced network voice pref and dropdown synced', async () => {
    // Turn on enhanced network voices.
    const enhancedNetworkVoicesToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#enhancedNetworkVoicesToggle');
    assertTrue(!!enhancedNetworkVoicesToggle);
    enhancedNetworkVoicesToggle.click();
    flush();

    // Make sure enhanced network voice dropdown is default voice, matching
    // default pref state.
    const enhancedNetworkVoiceDropdown =
        page.shadowRoot!.querySelector<HTMLElement>(
            '#enhancedNetworkVoiceDropdown');
    assertTrue(!!enhancedNetworkVoiceDropdown);
    await waitAfterNextRender(enhancedNetworkVoiceDropdown);
    const enhancedNetworkVoiceSelectElement =
        enhancedNetworkVoiceDropdown.shadowRoot!
            .querySelector<HTMLInputElement>('select');
    assertTrue(!!enhancedNetworkVoiceSelectElement);
    assertEquals('default-wavenet', enhancedNetworkVoiceSelectElement.value);

    // Change voice to Bangla (India) 1, and verify pref is also changed.
    enhancedNetworkVoiceSelectElement.value = 'bnm';
    enhancedNetworkVoiceSelectElement.dispatchEvent(new CustomEvent('change'));
    const enhancedNetworkVoicePref =
        page.getPref('settings.a11y.select_to_speak_enhanced_voice_name');
    assertEquals('bnm', enhancedNetworkVoicePref.value);
  });

  test('enhanced network voices not in primary voice dropdown', async () => {
    // Turn on enhanced network voices.
    const enhancedNetworkVoicesToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#enhancedNetworkVoicesToggle');
    assertTrue(!!enhancedNetworkVoicesToggle);
    enhancedNetworkVoicesToggle.click();
    flush();

    // Get all of the voices from the primary voice dropdown.
    const voiceDropdown =
        page.shadowRoot!.querySelector<HTMLElement>('#voiceDropdown');
    assertTrue(!!voiceDropdown);
    await waitAfterNextRender(voiceDropdown);
    const voiceSelectElement =
        voiceDropdown.shadowRoot!.querySelector('select');
    assertTrue(!!voiceSelectElement);
    const voices = [...voiceSelectElement.options].map(({value}) => value);

    // Make sure none of the voices are enhanced network voices.
    page.get('voices_')
        .filter(
            (pageVoice: HandlerVoice) =>
                voices.find(voice => voice === pageVoice.voiceName))
        .forEach(
            ({extensionId}: HandlerVoice) =>
                assertNotEquals(ENHANCED_TTS_EXTENSION_ID, extensionId));
  });

  test('enhanced network voice preview sends sample message', async () => {
    // Turn on enhanced network voices.
    const enhancedNetworkVoicesToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#enhancedNetworkVoicesToggle');
    assertTrue(!!enhancedNetworkVoicesToggle);
    enhancedNetworkVoicesToggle.click();
    flush();

    // Make sure enhanced network preview input exists, and write a sample
    // message into it.
    const enhancedNetworkVoicePreviewInput =
        page.shadowRoot!.querySelector<HTMLInputElement>(
            '#enhancedNetworkVoicePreviewInput');
    assertTrue(!!enhancedNetworkVoicePreviewInput);
    enhancedNetworkVoicePreviewInput.value =
        'The quick brown fox jumped over the lazy dog.';

    // Click preview button, expect sample message to be sent.
    const enhancedNetworkVoicePreviewButton =
        page.shadowRoot!.querySelector<HTMLButtonElement>(
            '#enhancedNetworkVoicePreviewButton');
    assertTrue(!!enhancedNetworkVoicePreviewButton);
    enhancedNetworkVoicePreviewButton.click();
    const [previewText, previewVoice] =
        await browserProxy.whenCalled('previewTtsVoice');
    assertEquals(previewText, 'The quick brown fox jumped over the lazy dog.');
    assertEquals(
        '{"name":"default-wavenet","extension":"jacnkoglebceckolkoapelihnglgaicd"}',
        previewVoice);
  });

  test(
      'voice preview buttons and inputs enabled when not speaking and disabled when speaking',
      () => {
        // Turn on enhanced network voices.
        const enhancedNetworkVoicesToggle =
            page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
                '#enhancedNetworkVoicesToggle');
        assertTrue(!!enhancedNetworkVoicesToggle);
        enhancedNetworkVoicesToggle.click();
        flush();

        // Get all voice preview buttons and inputs.
        const voicePreviewElements:
            Array<HTMLInputElement|HTMLButtonElement|null> = [
              page.shadowRoot!.querySelector('#voicePreviewButton'),
              page.shadowRoot!.querySelector('#voicePreviewInput'),
              page.shadowRoot!.querySelector(
                  '#enhancedNetworkVoicePreviewButton'),
              page.shadowRoot!.querySelector(
                  '#enhancedNetworkVoicePreviewInput'),
            ];

        // Make sure voice preview buttons and inputs are not disabled.
        voicePreviewElements.forEach(button => {
          assertTrue(!!button);
          assertFalse(button.disabled);
        });

        // Simulate TTS voice speaking.
        webUIListenerCallback('tts-preview-state-changed', true);

        // Make sure voice preview buttons and inputs are disabled.
        voicePreviewElements.forEach(button => {
          assertTrue(!!button);
          assertTrue(button.disabled);
        });
      });

  test(
      'voice preview buttons and inputs enabled when not empty and disabled when empty',
      () => {
        // Turn on enhanced network voices.
        const enhancedNetworkVoicesToggle =
            page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
                '#enhancedNetworkVoicesToggle');
        assertTrue(!!enhancedNetworkVoicesToggle);
        enhancedNetworkVoicesToggle.click();
        flush();

        // Get voice preview buttons and inputs.
        const voicePreviewButton =
            page.shadowRoot!.querySelector<HTMLButtonElement>(
                '#voicePreviewButton');
        const voicePreviewInput =
            page.shadowRoot!.querySelector<HTMLInputElement>(
                '#voicePreviewInput');
        const enhancedNetworkVoicePreviewButton =
            page.shadowRoot!.querySelector<HTMLButtonElement>(
                '#enhancedNetworkVoicePreviewButton');
        const enhancedNetworkVoicePreviewInput =
            page.shadowRoot!.querySelector<HTMLInputElement>(
                '#enhancedNetworkVoicePreviewInput');

        assertTrue(!!voicePreviewButton);
        assertTrue(!!voicePreviewInput);
        assertTrue(!!enhancedNetworkVoicePreviewButton);
        assertTrue(!!enhancedNetworkVoicePreviewInput);

        // Make sure voice preview buttons and inputs are not disabled.
        assertFalse(voicePreviewButton.disabled);
        assertFalse(voicePreviewInput.disabled);
        assertFalse(enhancedNetworkVoicePreviewButton.disabled);
        assertFalse(enhancedNetworkVoicePreviewInput.disabled);

        // Clear primary voice preview input. Make sure only primary voice
        // preview button is disabled.
        voicePreviewInput.value = '';
        assertTrue(voicePreviewButton.disabled);
        assertFalse(voicePreviewInput.disabled);
        assertFalse(enhancedNetworkVoicePreviewButton.disabled);
        assertFalse(enhancedNetworkVoicePreviewInput.disabled);

        // Clear enhanced network voice preview input. Make sure both voice
        // preview buttons are disabled.
        enhancedNetworkVoicePreviewInput.value = '';
        assertTrue(voicePreviewButton.disabled);
        assertFalse(voicePreviewInput.disabled);
        assertTrue(enhancedNetworkVoicePreviewButton.disabled);
        assertFalse(enhancedNetworkVoicePreviewInput.disabled);

        // Add text back to the primary voice preview input. Make sure only
        // enhanced network voice preview button is disabled.
        voicePreviewInput.value = 'Testing';
        assertFalse(voicePreviewButton.disabled);
        assertFalse(voicePreviewInput.disabled);
        assertTrue(enhancedNetworkVoicePreviewButton.disabled);
        assertFalse(enhancedNetworkVoicePreviewInput.disabled);

        // Add text back to the enhanced network voice preview input. Make sure
        // all elements are enabled.
        enhancedNetworkVoicePreviewInput.value =
            'Enhanced Network Voice Testing';
        assertFalse(voicePreviewButton.disabled);
        assertFalse(voicePreviewInput.disabled);
        assertFalse(enhancedNetworkVoicePreviewButton.disabled);
        assertFalse(enhancedNetworkVoicePreviewInput.disabled);
      });

  test('word highlight pref and toggle synced', () => {
    // Make sure word highlight toggle is on, matching default pref state.
    const wordHighlightToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#wordHighlightToggle');
    assertTrue(!!wordHighlightToggle);
    assertTrue(wordHighlightToggle.checked);

    // Toggle word highlighting off, and verify word_highlight pref is enabled.
    wordHighlightToggle.click();
    const wordHighlightPref =
        page.getPref('settings.a11y.select_to_speak_word_highlight');
    assertFalse(wordHighlightPref.value);
  });

  test('background shading pref and toggle synced', () => {
    // Make sure background shading toggle is off, matching default pref state.
    const backgroundShadingToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#backgroundShadingToggle');
    assertTrue(!!backgroundShadingToggle);
    assertFalse(backgroundShadingToggle.checked);

    // Toggle background shading on, and verify pref is enabled.
    backgroundShadingToggle.click();
    const backgroundShadingPref =
        page.getPref('settings.a11y.select_to_speak_background_shading');
    assertTrue(backgroundShadingPref.value);
  });

  test('navigation controls pref and toggle synced', () => {
    // Make sure navigation controls toggle is on, matching default pref state.
    const navigationControlsToggle =
        page.shadowRoot!.querySelector<SettingsToggleButtonElement>(
            '#navigationControlsToggle');
    assertTrue(!!navigationControlsToggle);
    assertTrue(navigationControlsToggle.checked);

    // Toggle navigation controls off, and verify pref is enabled.
    navigationControlsToggle.click();
    const navigationControlsPref =
        page.getPref('settings.a11y.select_to_speak_navigation_controls');
    assertFalse(navigationControlsPref.value);
  });

  test('highlight color pref and dropdown synced', async () => {
    // Make sure highlight color dropdown is blue, matching default pref state.
    const highlightColorDropdown =
        page.shadowRoot!.querySelector<HTMLElement>('#highlightColorDropdown');
    assertTrue(!!highlightColorDropdown);
    await waitAfterNextRender(highlightColorDropdown);
    const highlightColorSelectElement =
        highlightColorDropdown.shadowRoot!.querySelector('select');
    assertTrue(!!highlightColorSelectElement);
    assertEquals('#5e9bff', highlightColorSelectElement.value);

    // Turn highlight color to orange, and verify pref is also orange.
    highlightColorSelectElement.value = '#ffa13d';
    highlightColorSelectElement.dispatchEvent(new CustomEvent('change'));
    const highlightColorPref =
        page.getPref('settings.a11y.select_to_speak_highlight_color');
    assertEquals('#ffa13d', highlightColorPref.value);
  });
});