chromium/chromeos/components/quick_answers/understanding/intent_generator_unittest.cc

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

#include "chromeos/components/quick_answers/understanding/intent_generator.h"

#include <memory>
#include <string>

#include "base/functional/bind.h"
#include "base/test/task_environment.h"
#include "chromeos/components/quick_answers/public/cpp/quick_answers_state.h"
#include "chromeos/components/quick_answers/quick_answers_model.h"
#include "chromeos/components/quick_answers/test/quick_answers_test_base.h"
#include "chromeos/components/quick_answers/utils/quick_answers_utils.h"
#include "chromeos/components/quick_answers/utils/spell_checker.h"
#include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
#include "chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
#include "chromeos/services/machine_learning/public/mojom/text_classifier.mojom.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace quick_answers {
namespace {

using ::chromeos::machine_learning::FakeServiceConnectionImpl;
using ::chromeos::machine_learning::mojom::TextAnnotation;
using ::chromeos::machine_learning::mojom::TextAnnotationPtr;
using ::chromeos::machine_learning::mojom::TextEntity;
using ::chromeos::machine_learning::mojom::TextEntityData;
using ::chromeos::machine_learning::mojom::TextEntityPtr;
using ::chromeos::machine_learning::mojom::TextLanguage;
using ::chromeos::machine_learning::mojom::TextLanguagePtr;

TextLanguagePtr DefaultLanguage() {
  return TextLanguage::New("en", /* confidence */ 1);
}

std::vector<TextLanguagePtr> DefaultLanguages() {
  std::vector<TextLanguagePtr> languages;
  languages.push_back(DefaultLanguage());
  return languages;
}

class FakeSpellChecker : public SpellChecker {
 public:
  FakeSpellChecker(
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
      : SpellChecker(url_loader_factory) {}
  ~FakeSpellChecker() override = default;

  // SpellChecker:
  void CheckSpelling(const std::string& word,
                     CheckSpellingCallback callback) override {
    std::move(callback).Run(dictionary_.find(word) != dictionary_.end(),
                            dictionary_[word]);
  }

  void AddWordToDictionary(const std::string& word,
                           const std::string& language = "en") {
    dictionary_.insert({word, language});
  }

 private:
  std::map<std::string, std::string> dictionary_;
};

}  // namespace

class IntentGeneratorTest : public QuickAnswersTestBase {
 public:
  IntentGeneratorTest() = default;

  IntentGeneratorTest(const IntentGeneratorTest&) = delete;
  IntentGeneratorTest& operator=(const IntentGeneratorTest&) = delete;

  void SetUp() override {
    QuickAnswersTestBase::SetUp();

    test_shared_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_);
    spell_checker_ =
        std::make_unique<FakeSpellChecker>(test_shared_loader_factory_);
    intent_generator_ = std::make_unique<IntentGenerator>(
        spell_checker_->GetWeakPtr(),
        base::BindOnce(&IntentGeneratorTest::IntentGeneratorTestCallback,
                       base::Unretained(this)));

    fake_quick_answers_state()->set_use_text_annotator_for_testing();
    fake_quick_answers_state()->SetApplicationLocale("en");
    fake_quick_answers_state()->SetPreferredLanguages("en");
  }

  void TearDown() override {
    intent_generator_.reset();
    spell_checker_.reset();
    QuickAnswersTestBase::TearDown();
  }

  void IntentGeneratorTestCallback(const IntentInfo& intent_info) {
    intent_info_ = intent_info;
  }

  // Flush all relevant Mojo pipes.
  void FlushForTesting() {
    intent_generator_->FlushForTesting();
    fake_service_connection_.FlushForTesting();
  }

 protected:
  void UseFakeServiceConnection(
      const std::vector<TextAnnotationPtr>& annotations =
          std::vector<TextAnnotationPtr>(),
      const std::vector<TextLanguagePtr>& languages = DefaultLanguages()) {
    chromeos::machine_learning::ServiceConnection::
        UseFakeServiceConnectionForTesting(&fake_service_connection_);
    chromeos::machine_learning::ServiceConnection::GetInstance()->Initialize();

    fake_service_connection_.SetOutputAnnotation(annotations);
    fake_service_connection_.SetOutputLanguages(languages);
  }

  FakeSpellChecker* spell_checker() { return spell_checker_.get(); }

  base::test::TaskEnvironment task_environment_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
  std::unique_ptr<FakeSpellChecker> spell_checker_;
  std::unique_ptr<IntentGenerator> intent_generator_;
  IntentInfo intent_info_;
  FakeServiceConnectionImpl fake_service_connection_;
};

TEST_F(IntentGeneratorTest, TranslationIntent) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("es");
  fake_quick_answers_state()->SetPreferredLanguages("es");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should generate translation intent.
  EXPECT_EQ(IntentType::kTranslation, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
  EXPECT_EQ("es", intent_info_.device_language);
  EXPECT_EQ("en", intent_info_.source_language);
}

TEST_F(IntentGeneratorTest, TranslationIntentWithSubtag) {
  std::vector<TextLanguagePtr> languages;
  languages.push_back(TextLanguage::New("en-US", /* confidence */ 1));
  UseFakeServiceConnection({}, languages);

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("es");
  fake_quick_answers_state()->SetPreferredLanguages("es");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should generate translation intent.
  EXPECT_EQ(IntentType::kTranslation, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
  EXPECT_EQ("es", intent_info_.device_language);
  // Should drop substag for source language.
  EXPECT_EQ("en", intent_info_.source_language);
}

TEST_F(IntentGeneratorTest, TranslationIntentSameLanguage) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("en");
  fake_quick_answers_state()->SetPreferredLanguages("en");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the detected language is the
  // same as system language.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentPreferredLocale) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("es");
  fake_quick_answers_state()->SetPreferredLanguages("es,en,zh");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the detected language is in
  // the preferred languages list.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentPreferredLanguage) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("es");
  fake_quick_answers_state()->SetPreferredLanguages("es-MX,en-US,zh-CN");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the detected language is in
  // the preferred languages list.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentTextLengthAboveThreshold) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text =
      "Search the world's information, including webpages, images, videos and "
      "more. Google has many special features to help you find exactly what "
      "you're looking ...";
  fake_quick_answers_state()->SetApplicationLocale("es");
  fake_quick_answers_state()->SetPreferredLanguages("es");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the length of the selected
  // text is above the threshold.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ(
      "Search the world's information, including webpages, images, videos and "
      "more. Google has many special features to help you find exactly what "
      "you're looking ...",
      intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentWithAnnotation) {
  QuickAnswersRequest request;
  request.selected_text = "prueba";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "dictionary",                            // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,  // Start offset.
                                                   6,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());
  std::vector<TextLanguagePtr> languages;
  languages.push_back(TextLanguage::New("es", /* confidence */ 1));
  UseFakeServiceConnection(annotations, languages);

  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should generate dictionary intent which is prioritized against
  // translation.
  EXPECT_EQ(IntentType::kDictionary, intent_info_.intent_type);
  EXPECT_EQ("prueba", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentDeviceLanguageNotSet) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the device language is not
  // set.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentUnsupportedDeviceLanguage) {
  UseFakeServiceConnection();

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("unk");
  fake_quick_answers_state()->SetPreferredLanguages("unk");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the device language is
  // not in the supported languages list.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TranslationIntentUnsupportedSourceLanguage) {
  std::vector<TextLanguagePtr> languages;
  languages.push_back(TextLanguage::New("unk", /* confidence */ 1));
  UseFakeServiceConnection({}, languages);

  QuickAnswersRequest request;
  request.selected_text = "quick answers";
  fake_quick_answers_state()->SetApplicationLocale("en");
  fake_quick_answers_state()->SetPreferredLanguages("en");
  intent_generator_->GenerateIntent(request);

  FlushForTesting();

  // Should not generate translation intent since the detected source
  // language is not in the supported languages list.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("quick answers", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationDefinitionIntent) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "unfathomable";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "dictionary",                            // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,   // Start offset.
                                                   12,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());

  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should generate dictionary intent.
  EXPECT_EQ(IntentType::kDictionary, intent_info_.intent_type);
  EXPECT_EQ("unfathomable", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest,
       TextAnnotationDefinitionIntentExtraCharsBelowThreshold) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "“unfathomable”";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "dictionary",                            // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(1,   // Start offset.
                                                   13,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());

  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should generate dictionary intent since the extra characters is below the
  // threshold.
  EXPECT_EQ(IntentType::kDictionary, intent_info_.intent_type);
  EXPECT_EQ("unfathomable", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest,
       TextAnnotationDefinitionIntentExtraCharsAboveThreshold) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "the unfathomable";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "dictionary",                            // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(4,   // Start offset.
                                                   16,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());

  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should not generate dictionary intent since the extra characters is above
  // the threshold.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("unfathomable", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationUnitIntentExtraChars) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "23 cm to";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "unit",                                  // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,  // Start offset.
                                                   5,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());

  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should generate unit conversion intent.
  EXPECT_EQ(IntentType::kUnit, intent_info_.intent_type);
  EXPECT_EQ("23 cm", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationUnitIntentUtf16Char) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "350°F";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "unit",                                  // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,  // Start offset.
                                                   5,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());

  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should generate unit conversion intent.
  EXPECT_EQ(IntentType::kUnit, intent_info_.intent_type);
  EXPECT_EQ("350°F", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationUnitIntentExtraCharsAboveThreshold) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "23 cm is equal to 9.06 inches";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "unit",                                  // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,  // Start offset.
                                                   5,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());

  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should not generate unit conversion intent since the extra characters is
  // above the threshold.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("23 cm", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationNonEnglishLanguage) {
  fake_quick_answers_state()->SetApplicationLocale("es");
  fake_quick_answers_state()->SetPreferredLanguages("es");

  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "unfathomable";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "dictionary",                            // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,   // Start offset.
                                                   12,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());
  std::vector<TextLanguagePtr> languages;
  languages.push_back(TextLanguage::New("en", /* confidence */ 1));
  UseFakeServiceConnection(annotations, languages);

  intent_generator_->GenerateIntent(*quick_answers_request);

  FlushForTesting();

  // Should not generate dictionary intent since English is not device language
  // or preferred language. Should fallback to translation intent.
  EXPECT_EQ(IntentType::kTranslation, intent_info_.intent_type);
  EXPECT_EQ("unfathomable", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationIntentNoAnnotation) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "the unfathomable reaches of space";

  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);
  FlushForTesting();

  // Should generate unknown intent since no annotation found.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("the unfathomable reaches of space", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationIntentNoEntity) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "the unfathomable reaches of space";

  std::vector<TextEntityPtr> entities;
  auto dictionary_annotation = TextAnnotation::New(4,   // Start offset.
                                                   16,  // End offset.
                                                   std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());
  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);
  FlushForTesting();

  // Should generate unknown intent since no entity found.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("the unfathomable reaches of space", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, TextAnnotationIntentUnSupportedEntity) {
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "the unfathomable reaches of space";

  // Create the test annotations.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "something_else",                        // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto some_annotation = TextAnnotation::New(4,   // Start offset.
                                             16,  // End offset.
                                             std::move(entities));

  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(some_annotation->Clone());
  UseFakeServiceConnection(annotations);

  intent_generator_->GenerateIntent(*quick_answers_request);
  FlushForTesting();

  // Should generate unknown intent unsupported entity is provided.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("the unfathomable reaches of space", intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, ShouldTriggerForSingleWordInDictionary) {
  const std::string kWord = "single";

  // No Annotation provided.
  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  // Add word to the dictionary.
  spell_checker()->AddWordToDictionary(kWord);

  // Word selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kWord;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should generate dictionary intent.
  EXPECT_EQ(IntentType::kDictionary, intent_info_.intent_type);
  EXPECT_EQ(kWord, intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, ShouldTriggerForNonEnglishWordInDictionary) {
  const std::string kWord = "palabra";
  const std::string kLanguage = "es";

  // No Annotation provided.
  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  // Add word to the dictionary.
  spell_checker()->AddWordToDictionary(kWord, kLanguage);

  // Word selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kWord;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should generate dictionary intent.
  EXPECT_EQ(IntentType::kDictionary, intent_info_.intent_type);
  EXPECT_EQ(kWord, intent_info_.intent_text);
  EXPECT_EQ(kLanguage, intent_info_.source_language);
}

TEST_F(IntentGeneratorTest,
       ShouldNotTriggerForSingleWordInDictionaryWithDigits) {
  const std::string kWord = "1st";

  // No Annotation provided.
  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  // Add word to the dictionary.
  spell_checker()->AddWordToDictionary(kWord);

  // Word selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kWord;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should not generate dictionary intent if the word contains digits even if
  // it is in the dictionary.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ(kWord, intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, ShouldNotTriggerForProperNounInDictionary) {
  const std::string kWord = "Amy";

  // No Annotation provided.
  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  // Add word to the dictionary.
  spell_checker()->AddWordToDictionary(kWord);

  // Word selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kWord;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should not generate dictionary intent if the word contains digits even if
  // it is in the dictionary.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ(kWord, intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest,
       ShouldFallbackToAnnotationsForWordNotInDictionaryNoAnnotation) {
  const std::string kWord = "single";

  // No Annotation provided, and not add the word to the dictionary.
  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  // Word selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kWord;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should not generate dictionary intent if the word is not in the dictionary
  // and no annotation provided.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ(kWord, intent_info_.intent_text);
}

TEST_F(
    IntentGeneratorTest,
    ShouldFallbackToAnnotationsForWordNotInDictionaryWithDictionaryAnnotation) {
  const std::string kWord = "unfathomable";

  // Annotation provided, and not add the word to the dictionary.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "dictionary",                            // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,   // Start offset.
                                                   12,  // End offset.
                                                   std::move(entities));
  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());
  UseFakeServiceConnection(annotations);

  // Word selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kWord;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should generate dictionary intent for the word.
  EXPECT_EQ(IntentType::kDictionary, intent_info_.intent_type);
  EXPECT_EQ(kWord, intent_info_.intent_text);
}

TEST_F(
    IntentGeneratorTest,
    ShouldFallbackToAnnotationsForWordNotInDictionaryWithUnitConversionAnnotation) {
  const std::string kText = "50kg";

  // Annotation provided, and not add the text to the dictionary.
  std::vector<TextEntityPtr> entities;
  entities.emplace_back(TextEntity::New(
      "unit",                                  // Entity name.
      1.0,                                     // Confidence score.
      TextEntityData::NewNumericValue(0.0)));  // Data extracted.

  auto dictionary_annotation = TextAnnotation::New(0,  // Start offset.
                                                   4,  // End offset.
                                                   std::move(entities));
  std::vector<TextAnnotationPtr> annotations;
  annotations.push_back(dictionary_annotation->Clone());
  UseFakeServiceConnection(annotations);

  // Text selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = kText;

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should generate unit conversion intent.
  EXPECT_EQ(IntentType::kUnit, intent_info_.intent_type);
  EXPECT_EQ(kText, intent_info_.intent_text);
}

TEST_F(IntentGeneratorTest, ShouldNotTriggerForMultipleWords) {
  // No Annotation provided.
  std::vector<TextAnnotationPtr> annotations;
  UseFakeServiceConnection(annotations);

  // Multiple words selected.
  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
      std::make_unique<QuickAnswersRequest>();
  quick_answers_request->selected_text = "multiple words";

  intent_generator_->GenerateIntent(*quick_answers_request);
  task_environment_.RunUntilIdle();

  // Should fallback to unknown intent.
  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
  EXPECT_EQ("multiple words", intent_info_.intent_text);
}

}  // namespace quick_answers