chromium/chrome/test/data/webui/side_panel/read_anything/prefs_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 {flush} from
// '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserProxy, ToolbarEvent} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import type {AppElement} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import {assertArrayEquals, assertEquals, assertFalse, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';

import {createAndSetVoices, createSpeechSynthesisVoice, emitEvent, setVoices, suppressInnocuousErrors} from './common.js';
import {FakeReadingMode} from './fake_reading_mode.js';
import {FakeSpeechSynthesis} from './fake_speech_synthesis.js';
import {TestColorUpdaterBrowserProxy} from './test_color_updater_browser_proxy.js';

// TODO: b/40927698 - Add more tests.
suite('PrefsTest', () => {
  let app: AppElement;
  let testBrowserProxy: TestColorUpdaterBrowserProxy;
  let speechSynthesis: FakeSpeechSynthesis;

  setup(() => {
    suppressInnocuousErrors();
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    testBrowserProxy = new TestColorUpdaterBrowserProxy();
    BrowserProxy.setInstance(testBrowserProxy);
    const readingMode = new FakeReadingMode();
    chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;
    chrome.readingMode.isReadAloudEnabled = true;
    app = document.createElement('read-anything-app');
    document.body.appendChild(app);
    speechSynthesis = new FakeSpeechSynthesis();
    app.synth = speechSynthesis;
  });


  suite('on restore settings from prefs', () => {
    setup(() => {
      // We are not testing the toolbar for this suite.
      app.$.toolbar.restoreSettingsFromPrefs = () => {};
    });

    suite('with no initial voices', () => {
      setup(() => {
        chrome.readingMode.isAutoVoiceSwitchingEnabled = false;
        chrome.readingMode.baseLanguageForSpeech = 'en';

        // Set synthesis to have no available voices
        setVoices(app, speechSynthesis, []);
        app.resetVoiceForTesting();
      });

      test('with no settings, voice selected in onVoicesChanged', () => {
        chrome.readingMode.getStoredVoice = () => '';

        // When there's no voices available, there shouldn't be a speech
        // synthesis voice selected.
        app.restoreSettingsFromPrefs();
        assertFalse(!!app.getSpeechSynthesisVoice());

        // Update the speech synthesis engine with voices.
        setVoices(
            app, speechSynthesis,
            [createSpeechSynthesisVoice({lang: 'en', name: 'Yu'})]);
        app.onVoicesChanged();

        // Once voices are available, settings should be restored.
        assertTrue(!!app.getSpeechSynthesisVoice());
      });

      test(
          'with no settings, dfferent language voice selected in onVoicesChanged',
          () => {
            chrome.readingMode.getStoredVoice = () => '';

            // When there's no voices available, there shouldn't be a speech
            // synthesis voice selected.
            app.restoreSettingsFromPrefs();
            assertFalse(!!app.getSpeechSynthesisVoice());

            // Update the speech synthesis engine with voices.
            setVoices(
                app, speechSynthesis,
                [createSpeechSynthesisVoice({lang: 'es', name: 'Kristi'})]);
            app.onVoicesChanged();

            // Once voices are available, settings should be restored.
            assertTrue(!!app.getSpeechSynthesisVoice());
          });

      test(
          'with no initial voices and previously selected voice, correct voice selected after onVoicesChanged',
          () => {
            chrome.readingMode.getStoredVoice = () => 'Kristi';

            // When there's no voices available, there shouldn't be a speech
            // synthesis voice selected.
            app.restoreSettingsFromPrefs();
            assertFalse(!!app.getSpeechSynthesisVoice());

            // Update the speech synthesis engine with voices.
            setVoices(app, speechSynthesis, [
              createSpeechSynthesisVoice({lang: 'en', name: 'Lauren'}),
              createSpeechSynthesisVoice({lang: 'en', name: 'Eitan'}),
              createSpeechSynthesisVoice({lang: 'en-uk', name: 'Kristi'}),
            ]);
            app.onVoicesChanged();

            // Once voices are available, settings should be restored.
            const selectedVoice = app.getSpeechSynthesisVoice();
            assertTrue(!!selectedVoice);
            assertEquals('Kristi', selectedVoice.name);
          });

      test(
          'onVoicesChanged after settings restored, settings aren\'t updated',
          () => {
            chrome.readingMode.getStoredVoice = () => 'Shari';

            // When there's no voices available, there shouldn't be a speech
            // synthesis voice selected.
            app.restoreSettingsFromPrefs();
            assertFalse(!!app.getSpeechSynthesisVoice());
            assertTrue(app.shouldAttemptLanguageSettingsRestore);

            const futureSelectedVoice =
                createSpeechSynthesisVoice({lang: 'en', name: 'Kristi'});

            // Update the speech synthesis engine with voices.
            setVoices(app, speechSynthesis, [
              createSpeechSynthesisVoice({lang: 'en', name: 'Lauren'}),
              createSpeechSynthesisVoice({lang: 'en', name: 'Shari'}),
              futureSelectedVoice,
            ]);
            app.onVoicesChanged();
            assertFalse(app.shouldAttemptLanguageSettingsRestore);

            // Once voices are available, settings should be restored.
            let selectedVoice = app.getSpeechSynthesisVoice();
            assertTrue(!!selectedVoice);
            assertEquals('Shari', selectedVoice.name);

            emitEvent(
                app, ToolbarEvent.VOICE,
                {detail: {selectedVoice: futureSelectedVoice}});
            selectedVoice = app.getSpeechSynthesisVoice();
            assertTrue(!!selectedVoice);
            assertEquals('Kristi', selectedVoice.name);

            // We have to update the stored voice so onVoicesChanged recognizes
            // a user chosen voice.
            chrome.readingMode.getStoredVoice = () => 'Kristi';

            app.onVoicesChanged();
            assertFalse(app.shouldAttemptLanguageSettingsRestore);

            // After onVoicesChanged, the most recently selected voice should
            // be used.
            selectedVoice = app.getSpeechSynthesisVoice();
            assertTrue(!!selectedVoice);
            assertEquals('Kristi', selectedVoice.name);
          });
    });

    suite('populates enabled languages', () => {
      const langs = ['si', 'km', 'th'];

      setup(() => {
        createAndSetVoices(app, speechSynthesis, [
          {lang: langs[0]},
          {lang: langs[1]},
          {lang: langs[2]},
        ]);
      });

      test('with langs stored in prefs', () => {
        chrome.readingMode.getLanguagesEnabledInPref = () => langs;

        app.restoreSettingsFromPrefs();

        assertArrayEquals(app.enabledLangs, langs);
      });

      test('with browser lang', () => {
        chrome.readingMode.baseLanguageForSpeech = langs[1]!;

        app.restoreSettingsFromPrefs();

        assertArrayEquals(app.enabledLangs, [langs[1]!]);
      });
    });

    suite('initializes voice', () => {
      const langForDefaultVoice = 'en';
      const lang1 = 'zh';
      const lang2 = 'tr';
      const langWithNoVoices = 'elvish';

      const defaultVoice = createSpeechSynthesisVoice({
        lang: langForDefaultVoice,
        name: 'Kristi',
        default: true,
      });
      const firstVoiceWithLang1 =
          createSpeechSynthesisVoice({lang: lang1, name: 'Lauren'});
      const defaultVoiceWithLang1 = createSpeechSynthesisVoice({
        lang: lang1,
        name: 'Eitan',
        default: true,
      });
      const firstVoiceWithLang2 =
          createSpeechSynthesisVoice({lang: lang2, name: 'Yu'});
      const secondVoiceWithLang2 =
          createSpeechSynthesisVoice({lang: lang2, name: 'Xiang'});
      const otherVoice =
          createSpeechSynthesisVoice({lang: 'it', name: 'Shari'});
      const voices = [
        defaultVoice,
        firstVoiceWithLang1,
        defaultVoiceWithLang1,
        otherVoice,
        firstVoiceWithLang2,
        secondVoiceWithLang2,
      ];

      setup(() => {
        setVoices(app, speechSynthesis, voices);
      });

      test('to the stored voice for this language if there is one', () => {
        chrome.readingMode.getStoredVoice = () => otherVoice.name;
        app.restoreSettingsFromPrefs();
        assertEquals(otherVoice, app.getSpeechSynthesisVoice());
      });

      test('to a default voice if the stored voice is invalid', () => {
        chrome.readingMode.getStoredVoice = () => 'Matt';
        app.enabledLangs = [langForDefaultVoice];
        app.restoreSettingsFromPrefs();
        assertEquals(defaultVoice, app.getSpeechSynthesisVoice());
      });

      suite('when there is no stored voice for this language', () => {
        setup(() => {
          chrome.readingMode.getStoredVoice = () => '';
        });

        test('to the default voice for this language', () => {
          app.enabledLangs = [lang1];
          app.speechSynthesisLanguage = lang1;
          app.restoreSettingsFromPrefs();
          assertEquals(defaultVoiceWithLang1, app.getSpeechSynthesisVoice());
        });

        test('uses current voice if there\'s none for this language', () => {
          app.speechSynthesisLanguage = langWithNoVoices;
          emitEvent(
              app, ToolbarEvent.VOICE, {detail: {selectedVoice: otherVoice}});
          app.enabledLangs = [otherVoice.lang];
          app.restoreSettingsFromPrefs();
          assertEquals(otherVoice, app.getSpeechSynthesisVoice());
        });

        test('uses the device default if there\'s no current voice', () => {
          app.speechSynthesisLanguage = langWithNoVoices;
          app.enabledLangs = [langForDefaultVoice, otherVoice.lang];
          app.restoreSettingsFromPrefs();
          assertEquals(defaultVoice, app.getSpeechSynthesisVoice());
        });

        test(
            'to the first listed voice for this language if there\'s no default',
            () => {
              app.enabledLangs = [lang2];
              app.speechSynthesisLanguage = lang2;
              app.restoreSettingsFromPrefs();
              const currentSelectedVoice = app.getSpeechSynthesisVoice();
              assertTrue(!!currentSelectedVoice);
              assertEquals(firstVoiceWithLang2.name, currentSelectedVoice.name);
              assertEquals(firstVoiceWithLang2.lang, currentSelectedVoice.lang);
            });
      });
    });
  });
});