// 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-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import {convertLangOrLocaleForVoicePackManager, convertLangOrLocaleToExactVoicePackLocale, convertLangToAnAvailableLangIfPresent, createInitialListOfEnabledLanguages, getFilteredVoiceList, mojoVoicePackStatusToVoicePackStatusEnum, VoicePackServerStatusErrorCode, VoicePackServerStatusSuccessCode} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js';
import {assertDeepEquals, assertEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
import {createSpeechSynthesisVoice} from './common.js';
suite('voice and language utils', () => {
test('mojoVoicePackStatusToVoicePackStatusEnum', () => {
// Success codes
assertEquals(
VoicePackServerStatusSuccessCode.NOT_INSTALLED,
mojoVoicePackStatusToVoicePackStatusEnum('kNotInstalled').code);
assertEquals(
VoicePackServerStatusSuccessCode.INSTALLED,
mojoVoicePackStatusToVoicePackStatusEnum('kInstalled').code);
assertEquals(
VoicePackServerStatusSuccessCode.INSTALLING,
mojoVoicePackStatusToVoicePackStatusEnum('kInstalling').code);
// Error codes
assertEquals(
VoicePackServerStatusErrorCode.OTHER,
mojoVoicePackStatusToVoicePackStatusEnum('kUnknown').code);
assertEquals(
VoicePackServerStatusErrorCode.OTHER,
mojoVoicePackStatusToVoicePackStatusEnum('kOther').code);
assertEquals(
VoicePackServerStatusErrorCode.WRONG_ID,
mojoVoicePackStatusToVoicePackStatusEnum('kWrongId').code);
assertEquals(
VoicePackServerStatusErrorCode.NEED_REBOOT,
mojoVoicePackStatusToVoicePackStatusEnum('kNeedReboot').code);
assertEquals(
VoicePackServerStatusErrorCode.ALLOCATION,
mojoVoicePackStatusToVoicePackStatusEnum('kAllocation').code);
assertEquals(
VoicePackServerStatusErrorCode.UNSUPPORTED_PLATFORM,
mojoVoicePackStatusToVoicePackStatusEnum('kUnsupportedPlatform').code);
});
test('convertLangOrLocaleForVoicePackManager', () => {
// Converts to locale when necessary
assertEquals('en-us', convertLangOrLocaleForVoicePackManager('en'));
assertEquals('es-es', convertLangOrLocaleForVoicePackManager('es'));
assertEquals('pt-br', convertLangOrLocaleForVoicePackManager('pt'));
// Converts to base language when necessary
assertEquals('fr', convertLangOrLocaleForVoicePackManager('fr-FR'));
assertEquals('fr', convertLangOrLocaleForVoicePackManager('fr-Bf'));
// Converts from unsupported locale to supported locale when necessary
assertEquals('en-us', convertLangOrLocaleForVoicePackManager('en-UK'));
assertEquals('es-es', convertLangOrLocaleForVoicePackManager('es-MX'));
// Keeps base language when necessary
assertEquals('nl', convertLangOrLocaleForVoicePackManager('nl'));
assertEquals('yue', convertLangOrLocaleForVoicePackManager('yue'));
// Keeps locale when necesseary
assertEquals('en-gb', convertLangOrLocaleForVoicePackManager('en-GB'));
assertEquals('es-es', convertLangOrLocaleForVoicePackManager('es-es'));
assertEquals('pt-br', convertLangOrLocaleForVoicePackManager('pt-Br'));
// Unsupported languages are undefined
assertEquals(undefined, convertLangOrLocaleForVoicePackManager('cn'));
// Uses enabled langs when available
const enabledLangs = ['en-au', 'es-us', 'pt-pt'];
assertEquals(
'en-au', convertLangOrLocaleForVoicePackManager('en', enabledLangs));
assertEquals(
'es-us', convertLangOrLocaleForVoicePackManager('es', enabledLangs));
assertEquals(
'pt-pt', convertLangOrLocaleForVoicePackManager('pt', enabledLangs));
// Uses available langs when available
const availableLangs = ['en-gb', 'es-us', 'pt-pt'];
assertEquals(
'en-gb',
convertLangOrLocaleForVoicePackManager('en', [], availableLangs));
assertEquals(
'es-us',
convertLangOrLocaleForVoicePackManager('es', [], availableLangs));
assertEquals(
'pt-pt',
convertLangOrLocaleForVoicePackManager('pt', [], availableLangs));
});
test('convertLangOrLocaleToExactVoicePackLocale', () => {
// Converts to voice pack locale when multiple supported
assertEquals('en-us', convertLangOrLocaleToExactVoicePackLocale('en'));
assertEquals('es-es', convertLangOrLocaleToExactVoicePackLocale('es'));
assertEquals('pt-br', convertLangOrLocaleToExactVoicePackLocale('pt'));
// Converts to voice pack locale when only one locale supported
assertEquals('fr-fr', convertLangOrLocaleToExactVoicePackLocale('fr'));
assertEquals('it-it', convertLangOrLocaleToExactVoicePackLocale('it'));
assertEquals('bn-bd', convertLangOrLocaleToExactVoicePackLocale('bn'));
// Converts from unsupported locale to supported locale when necessary
assertEquals('en-us', convertLangOrLocaleToExactVoicePackLocale('en-UK'));
assertEquals('es-es', convertLangOrLocaleToExactVoicePackLocale('es-MX'));
// Keeps locale when necesseary
assertEquals('en-gb', convertLangOrLocaleToExactVoicePackLocale('en-GB'));
assertEquals('es-es', convertLangOrLocaleToExactVoicePackLocale('es-es'));
assertEquals('pt-br', convertLangOrLocaleToExactVoicePackLocale('pt-Br'));
// Unsupported languages are undefined
assertEquals(undefined, convertLangOrLocaleToExactVoicePackLocale('cn'));
assertEquals(undefined, convertLangOrLocaleToExactVoicePackLocale('ar'));
});
test('convertLangToAnAvailableLangIfPresent', () => {
// Returns direct matches
assertEquals(
'en-us',
convertLangToAnAvailableLangIfPresent('en-us', ['en', 'fr', 'en-us']));
assertEquals(
'en',
convertLangToAnAvailableLangIfPresent('en', ['en', 'fr', 'en-us']));
// Finds matching locale for base lang input
assertEquals(
'en-us', convertLangToAnAvailableLangIfPresent('en', ['en-US', 'fr']));
// Finds locale with same base language as input locale, without a direct
// locale match
assertEquals(
'en-us',
convertLangToAnAvailableLangIfPresent('en-nz', ['en-US', 'fr']));
// If there's no direct locale match, but our base lang matches both a
// base lang and a locale with the same base, default to the base lang
assertEquals(
'en',
convertLangToAnAvailableLangIfPresent('en-nz', ['en-US', 'en', 'fr']));
// Uses browser language fallback.
assertEquals(
chrome.readingMode.defaultLanguageForSpeech,
convertLangToAnAvailableLangIfPresent('es', ['en-US', 'en', 'fr']));
// No match
assertEquals(
undefined,
convertLangToAnAvailableLangIfPresent('es', ['zh', 'jp', 'fr']));
});
test('createInitialListOfEnabledLanguages', () => {
assertDeepEquals(
['en'],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'fr',
/* storedLanguagesPref= */[], /* availableLangs= */['en'],
/* langOfDefaultVoice= */ 'en'));
assertDeepEquals(
['fr-fr'],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'fr',
/* storedLanguagesPref= */[], /* availableLangs= */['en', 'fr-FR'],
/* langOfDefaultVoice= */ 'en'));
assertDeepEquals(
['en-us', 'fr-fr'],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'fr',
/* storedLanguagesPref= */['en'],
/* availableLangs= */['en-us', 'fr-FR'],
/* langOfDefaultVoice= */ 'en')
.sort());
assertDeepEquals(
['en-uk', 'fr-fr'],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'fr',
/* storedLanguagesPref= */['en-us'],
/* availableLangs= */['en-uk', 'fr-FR'],
/* langOfDefaultVoice= */ undefined)
.sort());
assertDeepEquals(
['en-uk'],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'en',
/* storedLanguagesPref= */['en-uk'],
/* availableLangs= */['en-uk', 'en-us'],
/* langOfDefaultVoice= */ undefined)
.sort());
assertDeepEquals(
['en-us'],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'en',
/* storedLanguagesPref= */['en-us'],
/* availableLangs= */['en-uk', 'en-us'],
/* langOfDefaultVoice= */ undefined)
.sort());
assertDeepEquals(
[],
createInitialListOfEnabledLanguages(
/* browserOrPageBaseLang= */ 'fr',
/* storedLanguagesPref= */[],
/* availableLangs= */[],
/* langOfDefaultVoice= */ undefined));
});
test('getFilteredVoiceList filters remote voices', () => {
const voice1 = {
default: true,
name: 'Eitan',
lang: 'en-us',
localService: false,
voiceURI: '',
};
const voice2 = {
default: false,
name: 'Lauren',
lang: 'en-us',
localService: true,
voiceURI: '',
};
let possibleVoices: SpeechSynthesisVoice[] = [voice1];
// Remote voices not filtered out with just one voice.
assertDeepEquals([voice1], getFilteredVoiceList(possibleVoices));
possibleVoices = [voice1, voice2];
// Remote voices filtered out when a local voice exists
assertDeepEquals([voice2], getFilteredVoiceList(possibleVoices));
});
test('getFilteredVoiceList filters Android voices', () => {
const voice1 = {
default: false,
name: 'Xiang',
lang: 'en-us',
localService: true,
voiceURI: '',
};
const voice2 = {
default: true,
name: 'Kristi (Android)',
lang: 'en-us',
localService: true,
voiceURI: '',
};
// Android voices should be filtered out only on ChromeOS Ash.
const possibleVoices: SpeechSynthesisVoice[] = [voice1, voice2];
if (chrome.readingMode.isChromeOsAsh) {
assertDeepEquals([voice1], getFilteredVoiceList(possibleVoices));
} else {
assertDeepEquals([voice1, voice2], getFilteredVoiceList(possibleVoices));
}
});
test('getFilteredVoiceList filters eSpeak voices', () => {
const voice1 = createSpeechSynthesisVoice(
{default: true, name: 'eSpeak Yu', localService: true, lang: 'en-us'});
const voice2 = createSpeechSynthesisVoice(
{default: true, name: 'eSpeak Kristi', localService: true, lang: 'cy'});
const voice3 = createSpeechSynthesisVoice({
default: true,
name: 'eSpeak Lauren',
localService: true,
lang: 'en-cb',
});
const possibleVoices: SpeechSynthesisVoice[] = [voice1, voice2, voice3];
if (chrome.readingMode.isChromeOsAsh) {
// Welsh isn't a Google TTS locale, so the Welsh eSpeak voice should be
// kept, but the English eSpeak voices should be filtered out because
// Google TTS voices in English (even if in a different locale) exist.
assertDeepEquals([voice2], getFilteredVoiceList(possibleVoices));
} else {
// eSpeak voices should be filtered out only on ChromeOS Ash.
assertDeepEquals(
[voice1, voice2, voice3], getFilteredVoiceList(possibleVoices));
}
});
});