chromium/chrome/browser/resources/chromeos/accessibility/chromevox/common/locale_output_helper_test.js

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Include test fixture.
GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js']);

/**
 * Test fixture for LocaleOutputHelper.
 */
ChromeVoxLocaleOutputHelperTest = class extends ChromeVoxE2ETest {
  /** @override */
  testGenCppIncludes() {
    super.testGenCppIncludes();
    GEN(`
  #include "base/command_line.h"
  #include "ui/accessibility/accessibility_switches.h"
  #include "ui/base/ui_base_switches.h"
      `);
  }

  /** @override */
  testGenPreamble() {
    GEN(`
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
      ::switches::kEnableExperimentalAccessibilityLanguageDetection);
    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(::switches::kLang, "en-US");
      `);
    super.testGenPreamble();
  }

  /** @override */
  async setUpDeferred() {
    await super.setUpDeferred();

    // Mock this api to return a predefined set of voices.
    chrome.tts.getVoices = function(callback) {
      callback([
        // All properties of TtsVoice object are optional.
        // https://developer.chrome.com/apps/tts#type-TtsVoice.
        {},
        {voiceName: 'Android'},
        {'lang': 'en-US'},
        {'lang': 'fr-CA'},
        {'lang': 'es-ES'},
        {'lang': 'it-IT'},
        {'lang': 'ja-JP'},
        {'lang': 'ko-KR'},
        {'lang': 'zh-TW'},
        {'lang': 'ast'},
        {'lang': 'pt'},
      ]);
    };
  }

  /**
   * Calls mock version of chrome.tts.getVoices() to populate
   * LocaleOutputHelper's available voice list with a specific set of voices.
   */
  setAvailableVoices() {
    chrome.tts.getVoices(function(voices) {
      LocaleOutputHelper.instance.availableVoices_ = voices;
    });
  }

  get asturianAndJapaneseDoc() {
    return `
      <meta charset="utf-8">
      <p lang="ja">ど</p>
      <p lang="ast">
        Pretend that this text is Asturian. Testing three-letter language code logic.
      </p>
    `;
  }

  get buttonAndLinkDoc() {
    return `
      <body lang="es">
        <p>This is a paragraph, written in English.</p>
        <button type="submit">This is a button, written in English.</button>
        <a href="https://www.google.com">Este es un enlace.</a>
      </body>
    `;
  }

  get englishAndFrenchUnlabeledDoc() {
    return `
      <p>
        This entire object should be read in English, even the following French passage:
        salut mon ami! Ca va? Bien, et toi? It's hard to differentiate between latin-based languages.
      </p>
    `;
  }

  get englishAndKoreanUnlabeledDoc() {
    return `
      <meta charset="utf-8">
      <p>This text is written in English. 차에 한하여 중임할 수. This text is also written in English.</p>
    `;
  }

  get japaneseAndChineseUnlabeledDoc() {
    return `
      <meta charset="utf-8">
      <p id="text">
        天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追
      </p>
    `;
  }

  get japaneseAndEnglishUnlabeledDoc() {
    return `
      <meta charset="utf-8">
      <p>Hello, my name is 太田あきひろ. It's a pleasure to meet you. どうぞよろしくお願いします.</p>
    `;
  }

  get japaneseAndKoreanUnlabeledDoc() {
    return `
      <meta charset="utf-8">
      <p lang="ko">
        私は. 법률이 정하는 바에 의하여 대법관이 아닌 법관을 둘 수 있다
      </p>
    `;
  }

  get japaneseCharacterUnlabeledDoc() {
    return `
      <meta charset="utf-8">
      <p>ど</p>
    `;
  }

  get multipleLanguagesLabeledDoc() {
    return `
      <p lang="es">Hola.</p>
      <p lang="en">Hello.</p>
      <p lang="fr">Salut.</p>
      <span lang="it">Ciao amico.</span>
    `;
  }

  get japaneseAndInvalidLanguagesLabeledDoc() {
    return `
      <meta charset="utf-8">
      <p lang="ja">どうぞよろしくお願いします</p>
      <p lang="invalid-code">Test</p>
      <p lang="hello">Yikes</p>
    `;
  }

  get nestedLanguagesLabeledDoc() {
    return `
      <p id="breakfast" lang="en">In the morning, I sometimes eat breakfast.</p>
      <p id="lunch" lang="fr">Dans l'apres-midi, je dejeune.</p>
      <p id="greeting" lang="en">
        Hello it's a pleasure to meet you.
        <span lang="fr">Comment ca va?</span>Switching back to English.
        <span lang="es">Hola.</span>Goodbye.
      </p>
    `;
  }

  get vietnameseAndUrduLabeledDoc() {
    return `
      <p lang="vi">Vietnamese text.</p>
      <p lang="ur">Urdu text.</p>
    `;
  }

  get chineseDoc() {
    return `
      <p lang="en-us">United States</p>
      <p lang="zh-hans">Simplified Chinese</p>
      <p lang="zh-hant">Traditional Chinese</p>
    `;
  }

  get portugueseDoc() {
    return `
      <p lang="en-us">United States</p>
      <p lang="pt-br">Brazil</p>
      <p lang="pt-pt">Portugal</p>
    `;
  }
};

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'MultipleLanguagesLabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(this.multipleLanguagesLabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('es', 'español: Hola.');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('en', 'English: Hello.');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('fr', 'français: Salut.');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('it', 'italiano: Ciao amico.');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'NestedLanguagesLabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(this.nestedLanguagesLabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en', 'In the morning, I sometimes eat breakfast.');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale(
              'fr', 'français: Dans l\'apres-midi, je dejeune.');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale(
              'en', 'English: Hello it\'s a pleasure to meet you. ');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('fr', 'français: Comment ca va?');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('en', 'English: Switching back to English. ');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('es', 'español: Hola.');
      mockFeedback.call(doCmd('nextLine'))
          .expectSpeechWithLocale('en', 'English: Goodbye.');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'ButtonAndLinkDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.buttonAndLinkDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback
          .call(doCmd('jumpToTop'))
          // Use the author-provided language of 'es'.
          .expectSpeechWithLocale(
              'es', 'español: This is a paragraph, written in English.')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('es', 'This is a button, written in English.')
          .expectSpeechWithLocale(
              undefined, 'Button', 'Press Search+Space to activate')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('es', 'Este es un enlace.')
          .expectSpeechWithLocale(undefined, 'Link');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndEnglishUnlabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.japaneseAndEnglishUnlabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback
          .call(doCmd('jumpToTop'))
          // Expect the node's contents to be read in one language
          // (English).
          // Language detection does not run on small runs of text, like
          // the one in this test, we are falling back on the UI language
          // of the browser, which is en-US. Please see testGenPreamble
          // for more details.
          .expectSpeechWithLocale(
              'en-us',
              'Hello, my name is 太田あきひろ. It\'s a pleasure to meet' +
                  ' you. どうぞよろしくお願いします.');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'EnglishAndKoreanUnlabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.englishAndKoreanUnlabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en-us',
              'This text is written in English. 차에 한하여 중임할 수.' +
                  ' This text is also written in English.');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'EnglishAndFrenchUnlabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.englishAndFrenchUnlabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en',
              'This entire object should be read in English, even' +
                  ' the following French passage: ' +
                  'salut mon ami! Ca va? Bien, et toi? It\'s hard to' +
                  ' differentiate between latin-based languages.');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'JapaneseCharacterUnlabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.japaneseCharacterUnlabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('en-us', 'ど');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndChineseUnlabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.japaneseAndChineseUnlabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en-us',
              '天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndChineseLabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      // Only difference between doc used in this test and
      // this.japaneseAndChineseUnlabeledDoc is the lang="zh" attribute.
      const root = await this.runWithLoadedTree(`
        <meta charset="utf-8">
        <p lang="zh">
          天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追
        </p>
    `);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'zh',
              '中文: 天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'JapaneseAndKoreanUnlabeledDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.japaneseAndKoreanUnlabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      // Language detection runs and assigns language of 'ko' to the node.
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'ko',
              '한국어: 私は. 법률이 정하는 바에 의하여 대법관이 아닌 법관을 둘 수' +
                  ' 있다');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'AsturianAndJapaneseDocTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.asturianAndJapaneseDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('ja', '日本語: ど')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale(
              'ast',
              'asturianu: Pretend that this text is Asturian. Testing' +
                  ' three-letter language code logic.');
      await mockFeedback.replay();
    });


AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'LanguageSwitchingOffTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.multipleLanguagesLabeledDoc);
      SettingsManager.set('languageSwitching', false);
      this.setAvailableVoices();
      // Locale should not be set if the language switching feature is off.
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(undefined, 'Hola.')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale(undefined, 'Hello.')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale(undefined, 'Salut.')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale(undefined, 'Ciao amico.');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'DefaultToUILocaleTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(
          this.japaneseAndInvalidLanguagesLabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('ja', '日本語: どうぞよろしくお願いします')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('en-us', 'English (United States): Test')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('en-us', 'Yikes');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'NoAvailableVoicesTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root =
          await this.runWithLoadedTree(this.vietnameseAndUrduLabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en-us', 'No voice available for language: Vietnamese')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale(
              'en-us', 'No voice available for language: Urdu');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'WordNavigationTest', async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(this.nestedLanguagesLabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en', 'In the morning, I sometimes eat breakfast.')
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale(
              'fr', 'français: Dans l\'apres-midi, je dejeune.')
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `l'apres`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `-`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `midi`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `,`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `je`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `dejeune`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `.`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `English: Hello`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `it's`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `a`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `pleasure`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `to`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `meet`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `you`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('en', `.`)
          .call(doCmd('nextWord'))
          .expectSpeechWithLocale('fr', `français: Comment`)
          .call(doCmd('previousWord'))
          .expectSpeechWithLocale('en', `English: .`)
          .call(doCmd('previousWord'))
          .expectSpeechWithLocale('en', `you`);
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'CharacterNavigationTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(this.nestedLanguagesLabeledDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale(
              'en', 'In the morning, I sometimes eat breakfast.')
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale(
              'fr', 'français: Dans l\'apres-midi, je dejeune.')
          .call(doCmd('nextCharacter'))
          .expectSpeechWithLocale('fr', `a`)
          .call(doCmd('nextCharacter'))
          .expectSpeechWithLocale('fr', `n`)
          .call(doCmd('nextCharacter'))
          .expectSpeechWithLocale('fr', `s`)
          .call(doCmd('nextCharacter'))
          .expectSpeechWithLocale('fr', ` `)
          .call(doCmd('nextCharacter'))
          .expectSpeechWithLocale('fr', `l`)
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale(
              'en', `English: Hello it's a pleasure to meet you. `)
          .call(doCmd('nextCharacter'))
          .expectSpeechWithLocale('en', `e`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('en', `H`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('fr', `français: .`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('fr', `e`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('fr', `n`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('fr', `u`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('fr', `e`)
          .call(doCmd('previousCharacter'))
          .expectSpeechWithLocale('fr', `j`);
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'SwitchBetweenChineseDialectsTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(this.chineseDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('en-us', 'United States')
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale('zh-hans', '中文(简体): Simplified Chinese')
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale(
              'zh-hant', '中文(繁體): Traditional Chinese');
      await mockFeedback.replay();
    });

AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'SwitchBetweenPortugueseDialectsTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(this.portugueseDoc);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('en-us', 'United States')
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale('pt-br', 'português (Brasil): Brazil')
          .call(doCmd('nextLine'))
          .expectSpeechWithLocale('pt-pt', 'português (Portugal): Portugal');
      await mockFeedback.replay();
    });

// Tests logic in shouldAnnounceLocale_(). We only announce the locale once when
// transitioning to more specific locales, e.g. 'en' -> 'en-us'. Transitions to
// less specific locales, e.g. 'en-us' -> 'en' should not be announced. Finally,
// subsequent transitions to the same locale, e.g. 'en' -> 'en-us' should not be
// announced.
AX_TEST_F(
    'ChromeVoxLocaleOutputHelperTest', 'MaybeAnnounceLocale', async function() {
      const mockFeedback = this.createMockFeedback();
      await this.runWithLoadedTree(`
  <p lang="en">Start</p>
  <p lang="en-ca">Middle</p>
  <p lang="en">Penultimate</p>
  <p lang="en-ca">End</p>
  `);
      SettingsManager.set('languageSwitching', true);
      this.setAvailableVoices();
      mockFeedback.call(doCmd('jumpToTop'))
          .expectSpeechWithLocale('en', 'Start')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('en-ca', 'English (Canada): Middle')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('en', 'Penultimate')
          .call(doCmd('nextObject'))
          .expectSpeechWithLocale('en-ca', 'End');
      await mockFeedback.replay();
    });