chromium/chrome/browser/extensions/api/language_settings_private/language_settings_private_api_unittest.cc

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

#include "chrome/browser/extensions/api/language_settings_private/language_settings_private_api.h"

#include <optional>
#include <string>
#include <vector>

#include "base/check_deref.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate.h"
#include "chrome/browser/extensions/api/language_settings_private/language_settings_private_delegate_factory.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/browser/spellchecker/spellcheck_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_member.h"
#include "components/spellcheck/common/spellcheck_features.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_prefs.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "ui/base/ime/ash/component_extension_ime_manager.h"
#include "ui/base/ime/ash/extension_ime_util.h"
#include "ui/base/ime/ash/fake_input_method_delegate.h"
#include "ui/base/ime/ash/input_method_util.h"
#include "ui/base/ime/ash/mock_component_extension_ime_manager_delegate.h"
#include "ui/base/ime/ash/mock_input_method_manager.h"
#include "ui/base/l10n/l10n_util.h"
#endif

namespace extensions {

DictionaryStatus;

class MockLanguageSettingsPrivateDelegate
    : public LanguageSettingsPrivateDelegate {};

std::vector<DictionaryStatus>
MockLanguageSettingsPrivateDelegate::GetHunspellDictionaryStatuses() {}

void MockLanguageSettingsPrivateDelegate::RetryDownloadHunspellDictionary(
    const std::string& language) {}

namespace {

std::unique_ptr<KeyedService> BuildEventRouter(
    content::BrowserContext* profile) {}

std::unique_ptr<KeyedService> BuildLanguageSettingsPrivateDelegate(
    content::BrowserContext* profile) {}

std::unique_ptr<KeyedService> BuildSpellcheckService(
    content::BrowserContext* profile) {}

}  // namespace

class LanguageSettingsPrivateApiTest : public ExtensionServiceTestBase {};

TEST_F(LanguageSettingsPrivateApiTest, RetryDownloadHunspellDictionaryTest) {}

TEST_F(LanguageSettingsPrivateApiTest, GetSpellcheckDictionaryStatusesTest) {}

TEST_F(LanguageSettingsPrivateApiTest, SetLanguageAlwaysTranslateStateTest) {}

TEST_F(LanguageSettingsPrivateApiTest, GetAlwaysTranslateLanguagesListTest) {}

TEST_F(LanguageSettingsPrivateApiTest, SetTranslateTargetLanguageTest) {}

TEST_F(LanguageSettingsPrivateApiTest, GetNeverTranslateLanguagesListTest) {}

class LanguageSettingsPrivateApiGetLanguageListTest
    : public LanguageSettingsPrivateApiTest {};

TEST_F(LanguageSettingsPrivateApiGetLanguageListTest, GetLanguageList) {}

void LanguageSettingsPrivateApiTest::RunGetLanguageListTest() {}

#if BUILDFLAG(IS_CHROMEOS_ASH)
namespace {

namespace input_method = ::ash::input_method;
using input_method::InputMethodDescriptor;
using input_method::InputMethodManager;
using input_method::MockComponentExtensionIMEManagerDelegate;

std::string GetExtensionImeId() {
  std::string kExtensionImeId = ash::extension_ime_util::GetInputMethodID(
      crx_file::id_util::GenerateId("test.extension.ime"), "us");
  return kExtensionImeId;
}

std::string GetComponentExtensionImeId() {
  std::string kComponentExtensionImeId =
      ash::extension_ime_util::GetComponentInputMethodID(
          crx_file::id_util::GenerateId("test.component.extension.ime"), "us");
  return kComponentExtensionImeId;
}

std::string GetArcImeId() {
  std::string kArcImeId = ash::extension_ime_util::GetArcInputMethodID(
      crx_file::id_util::GenerateId("test.arc.ime"), "us");
  return kArcImeId;
}

class TestInputMethodManager : public input_method::MockInputMethodManager {
 public:
  class TestState : public input_method::MockInputMethodManager::State {
   public:
    TestState() {
      // Set up three IMEs
      std::string layout("us");
      InputMethodDescriptor extension_ime(
          GetExtensionImeId(), "ExtensionIme", "", layout, {"vi"},
          false /* is_login_keyboard */, GURL(), GURL(),
          /*handwriting_language=*/std::nullopt);
      InputMethodDescriptor component_extension_ime(
          GetComponentExtensionImeId(), "ComponentExtensionIme", "", layout,
          {"en-US", "en"}, false /* is_login_keyboard */, GURL(), GURL(),
          /*handwriting_language=*/std::nullopt);
      InputMethodDescriptor arc_ime(GetArcImeId(), "ArcIme", "", layout,
                                    {ash::extension_ime_util::kArcImeLanguage},
                                    false /* is_login_keyboard */, GURL(),
                                    GURL(),
                                    /*handwriting_language=*/std::nullopt);
      input_methods_ = {extension_ime, component_extension_ime, arc_ime};
    }

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

    void GetInputMethodExtensions(
        input_method::InputMethodDescriptors* descriptors) override {
      for (const auto& descriptor : input_methods_)
        descriptors->push_back(descriptor);
    }

    input_method::InputMethodDescriptors input_methods_;

   protected:
    friend base::RefCounted<InputMethodManager::State>;
    ~TestState() override = default;
  };

  TestInputMethodManager() : state_(new TestState), util_(&delegate_) {
    util_.AppendInputMethods(state_->input_methods_);
    component_ext_mgr_ = std::make_unique<ash::ComponentExtensionIMEManager>(
        std::make_unique<MockComponentExtensionIMEManagerDelegate>());
  }

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

  scoped_refptr<InputMethodManager::State> GetActiveIMEState() override {
    return state_;
  }

  input_method::InputMethodUtil* GetInputMethodUtil() override {
    return &util_;
  }

  ash::ComponentExtensionIMEManager* GetComponentExtensionIMEManager()
      override {
    return component_ext_mgr_.get();
  }

 private:
  scoped_refptr<TestState> state_;
  input_method::FakeInputMethodDelegate delegate_;
  input_method::InputMethodUtil util_;
  std::unique_ptr<ash::ComponentExtensionIMEManager> component_ext_mgr_;
};

}  // namespace

TEST_F(LanguageSettingsPrivateApiTest, GetInputMethodListsTest) {
  TestInputMethodManager::Initialize(new TestInputMethodManager);

  // Initialize relevant prefs.
  StringPrefMember enabled_imes;
  enabled_imes.Init(prefs::kLanguageEnabledImes, profile()->GetPrefs());
  StringPrefMember preload_engines;
  preload_engines.Init(prefs::kLanguagePreloadEngines, profile()->GetPrefs());

  enabled_imes.SetValue(
      base::JoinString({GetExtensionImeId(), GetArcImeId()}, ","));
  preload_engines.SetValue(GetComponentExtensionImeId());

  auto function = base::MakeRefCounted<
      LanguageSettingsPrivateGetInputMethodListsFunction>();
  std::optional<base::Value> result_val =
      api_test_utils::RunFunctionAndReturnSingleResult(function.get(), "[]",
                                                       profile());

  ASSERT_TRUE(result_val) << function->GetError();
  ASSERT_TRUE(result_val->is_dict());

  const base::Value::Dict& result = result_val->GetDict();
  const base::Value::List* input_methods =
      result.FindList("thirdPartyExtensionImes");
  ASSERT_NE(input_methods, nullptr);
  EXPECT_EQ(3u, input_methods->size());

  for (auto& input_method_val : *input_methods) {
    const base::Value::Dict& input_method = input_method_val.GetDict();
    const base::Value::List* ime_tags_ptr = input_method.FindList("tags");
    ASSERT_NE(nullptr, ime_tags_ptr);

    // Check tags contain input method's display name
    const base::Value* ime_name_ptr = input_method.Find("displayName");
    EXPECT_TRUE(base::Contains(*ime_tags_ptr, CHECK_DEREF(ime_name_ptr)));

    // Check tags contain input method's language codes' display names
    const base::Value::List* ime_language_codes_ptr =
        input_method.FindList("languageCodes");
    ASSERT_NE(nullptr, ime_language_codes_ptr);
    for (auto& language_code : *ime_language_codes_ptr) {
      std::u16string language_display_name = l10n_util::GetDisplayNameForLocale(
          language_code.GetString(), "en", true);
      if (!language_display_name.empty()) {
        EXPECT_TRUE(
            base::Contains(*ime_tags_ptr, base::Value(language_display_name)));
      }
    }
  }

  TestInputMethodManager::Shutdown();
}

TEST_F(LanguageSettingsPrivateApiTest, AddInputMethodTest) {
  TestInputMethodManager::Initialize(new TestInputMethodManager);

  // Initialize relevant prefs.
  profile()->GetPrefs()->SetString(language::prefs::kPreferredLanguages,
                                   "en-US");
  StringPrefMember enabled_imes;
  enabled_imes.Init(prefs::kLanguageEnabledImes, profile()->GetPrefs());
  StringPrefMember preload_engines;
  preload_engines.Init(prefs::kLanguagePreloadEngines, profile()->GetPrefs());
  BooleanPrefMember language_menu_enabled;
  language_menu_enabled.Init(prefs::kLanguageImeMenuActivated,
                             profile()->GetPrefs());
  enabled_imes.SetValue(std::string());
  preload_engines.SetValue(std::string());
  language_menu_enabled.SetValue(false);

  {
    // Add an extension IME. kLanguageEnabledImes should be updated.
    auto function =
        base::MakeRefCounted<LanguageSettingsPrivateAddInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetExtensionImeId() + "\"]", profile());

    EXPECT_EQ(GetExtensionImeId(), enabled_imes.GetValue());
    EXPECT_TRUE(preload_engines.GetValue().empty());
    EXPECT_FALSE(language_menu_enabled.GetValue());
  }

  enabled_imes.SetValue(std::string());
  preload_engines.SetValue(std::string());
  language_menu_enabled.SetValue(false);
  {
    // Add a component extension IME. kLanguagePreloadEngines should be
    // updated.
    auto function =
        base::MakeRefCounted<LanguageSettingsPrivateAddInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetComponentExtensionImeId() + "\"]",
        profile());

    EXPECT_TRUE(enabled_imes.GetValue().empty());
    EXPECT_EQ(GetComponentExtensionImeId(), preload_engines.GetValue());
    EXPECT_FALSE(language_menu_enabled.GetValue());
  }

  enabled_imes.SetValue(std::string());
  preload_engines.SetValue(std::string());
  language_menu_enabled.SetValue(false);
  {
    // Add an ARC IME. kLanguageEnabledImes should be updated.
    auto function =
        base::MakeRefCounted<LanguageSettingsPrivateAddInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetArcImeId() + "\"]", profile());

    EXPECT_EQ(GetArcImeId(), enabled_imes.GetValue());
    EXPECT_TRUE(preload_engines.GetValue().empty());
    EXPECT_FALSE(language_menu_enabled.GetValue());
  }

  enabled_imes.SetValue(std::string());
  preload_engines.SetValue(std::string());
  language_menu_enabled.SetValue(false);
  {
    // Add an extension IME and a component extension IME. Both should be
    // updated, and the language menu should be enabled.
    auto function =
        base::MakeRefCounted<LanguageSettingsPrivateAddInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetExtensionImeId() + "\"]", profile());
    function =
        base::MakeRefCounted<LanguageSettingsPrivateAddInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetComponentExtensionImeId() + "\"]",
        profile());

    EXPECT_EQ(GetExtensionImeId(), enabled_imes.GetValue());
    EXPECT_EQ(GetComponentExtensionImeId(), preload_engines.GetValue());
    EXPECT_TRUE(language_menu_enabled.GetValue());
  }

  TestInputMethodManager::Shutdown();
}

TEST_F(LanguageSettingsPrivateApiTest, RemoveInputMethodTest) {
  TestInputMethodManager::Initialize(new TestInputMethodManager);

  // Initialize relevant prefs.
  StringPrefMember enabled_imes;
  enabled_imes.Init(prefs::kLanguageEnabledImes, profile()->GetPrefs());
  StringPrefMember preload_engines;
  preload_engines.Init(prefs::kLanguagePreloadEngines, profile()->GetPrefs());

  enabled_imes.SetValue(
      base::JoinString({GetExtensionImeId(), GetArcImeId()}, ","));
  preload_engines.SetValue(GetComponentExtensionImeId());
  {
    // Remove an extension IME.
    auto function = base::MakeRefCounted<
        LanguageSettingsPrivateRemoveInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetExtensionImeId() + "\"]", profile());

    EXPECT_EQ(GetArcImeId(), enabled_imes.GetValue());
    EXPECT_EQ(GetComponentExtensionImeId(), preload_engines.GetValue());
  }

  {
    // Remove a component extension IME.
    auto function = base::MakeRefCounted<
        LanguageSettingsPrivateRemoveInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetComponentExtensionImeId() + "\"]",
        profile());

    EXPECT_EQ(GetArcImeId(), enabled_imes.GetValue());
    EXPECT_TRUE(preload_engines.GetValue().empty());
  }

  {
    // Remove an ARC IME.
    auto function = base::MakeRefCounted<
        LanguageSettingsPrivateRemoveInputMethodFunction>();
    api_test_utils::RunFunctionAndReturnSingleResult(
        function.get(), "[\"" + GetArcImeId() + "\"]", profile());

    EXPECT_TRUE(enabled_imes.GetValue().empty());
    EXPECT_TRUE(preload_engines.GetValue().empty());
  }

  TestInputMethodManager::Shutdown();
}

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(IS_WIN)
class LanguageSettingsPrivateApiTestDelayInit
    : public LanguageSettingsPrivateApiTest {
 public:
  LanguageSettingsPrivateApiTestDelayInit() = default;

 protected:
  void InitFeatures() override {
    // Force Windows hybrid spellcheck and delayed initialization of the
    // spellcheck service to be enabled.
    feature_list_.InitAndEnableFeature(
        spellcheck::kWinDelaySpellcheckServiceInit);
  }

  void AddSpellcheckLanguagesForTesting(
      const std::vector<std::string>& spellcheck_languages_for_testing)
      override {
    SpellcheckServiceFactory::GetInstance()
        ->GetForContext(profile())
        ->AddSpellcheckLanguagesForTesting(spellcheck_languages_for_testing);
  }
};

TEST_F(LanguageSettingsPrivateApiTestDelayInit, GetLanguageListTest) {
  RunGetLanguageListTest();
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace extensions