#include "chrome/browser/spellchecker/spellcheck_service.h"
#include <optional>
#include <ostream>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/supports_user_data.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/testing_pref_service.h"
#include "components/spellcheck/browser/pref_names.h"
#include "components/spellcheck/browser/spellcheck_platform.h"
#include "components/spellcheck/common/spellcheck_features.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
struct TestCase { … };
bool operator==(const SpellcheckService::Dictionary& lhs,
const SpellcheckService::Dictionary& rhs) { … }
std::ostream& operator<<(std::ostream& out,
const SpellcheckService::Dictionary& dictionary) { … }
std::ostream& operator<<(std::ostream& out, const TestCase& test_case) { … }
static std::unique_ptr<KeyedService> BuildSpellcheckService(
content::BrowserContext* profile) { … }
class SpellcheckServiceUnitTestBase : public testing::Test { … };
class SpellcheckServiceUnitTest : public SpellcheckServiceUnitTestBase,
public testing::WithParamInterface<TestCase> { … };
INSTANTIATE_TEST_SUITE_P(…);
TEST_P(SpellcheckServiceUnitTest, GetDictionaries) { … }
#if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
class SpellcheckServiceHybridUnitTestBase
: public SpellcheckServiceUnitTestBase {
public:
SpellcheckServiceHybridUnitTestBase() = default;
protected:
void SetUp() override {
InitFeatures();
SpellcheckServiceFactory::GetInstance()->SetTestingFactoryAndUse(
&profile_, base::BindRepeating(&BuildSpellcheckService));
}
virtual void InitFeatures() {}
virtual void InitializeSpellcheckService(
const std::vector<std::string>& spellcheck_languages_for_testing) {
spellcheck_service_ =
SpellcheckServiceFactory::GetInstance()->GetForContext(
browser_context());
spellcheck_service_->InitWindowsDictionaryLanguages(
spellcheck_languages_for_testing);
ASSERT_TRUE(spellcheck_service_->dictionaries_loaded());
}
void RunGetDictionariesTest(
const std::string accept_languages,
const std::vector<std::string> spellcheck_dictionaries,
const std::vector<SpellcheckService::Dictionary> expected_dictionaries);
void RunDictionaryMappingTest(
const std::string full_tag,
const std::string expected_accept_language,
const std::string expected_tag_passed_to_spellcheck,
const std::string expected_accept_language_generic,
const std::string expected_tag_passed_to_spellcheck_generic);
static const std::vector<std::string>
windows_spellcheck_languages_for_testing_;
base::test::ScopedFeatureList feature_list_;
raw_ptr<SpellcheckService> spellcheck_service_;
};
void SpellcheckServiceHybridUnitTestBase::RunGetDictionariesTest(
const std::string accept_languages,
const std::vector<std::string> spellcheck_dictionaries,
const std::vector<SpellcheckService::Dictionary> expected_dictionaries) {
prefs()->SetString(language::prefs::kAcceptLanguages, accept_languages);
base::Value::List spellcheck_dictionaries_list;
for (std::string dict : spellcheck_dictionaries) {
spellcheck_dictionaries_list.Append(dict);
}
prefs()->SetList(spellcheck::prefs::kSpellCheckDictionaries,
std::move(spellcheck_dictionaries_list));
SpellcheckService::EnableFirstUserLanguageForSpellcheck(prefs());
InitializeSpellcheckService(windows_spellcheck_languages_for_testing_);
std::vector<SpellcheckService::Dictionary> dictionaries;
SpellcheckService::GetDictionaries(browser_context(), &dictionaries);
EXPECT_EQ(expected_dictionaries, dictionaries);
}
void SpellcheckServiceHybridUnitTestBase::RunDictionaryMappingTest(
const std::string full_tag,
const std::string expected_accept_language,
const std::string expected_tag_passed_to_spellcheck,
const std::string expected_accept_language_generic,
const std::string expected_tag_passed_to_spellcheck_generic) {
InitializeSpellcheckService({full_tag});
std::string supported_dictionary;
if (!expected_accept_language.empty()) {
supported_dictionary =
spellcheck_service_->GetSupportedWindowsDictionaryLanguage(
expected_accept_language);
EXPECT_FALSE(supported_dictionary.empty());
EXPECT_EQ(full_tag, supported_dictionary);
EXPECT_EQ(expected_tag_passed_to_spellcheck,
SpellcheckService::GetTagToPassToWindowsSpellchecker(
expected_accept_language, full_tag));
if (base::EqualsCaseInsensitiveASCII(
"sr-Cyrl", SpellcheckService::GetLanguageAndScriptTag(
full_tag,
true))) {
EXPECT_EQ(
full_tag,
spellcheck_service_->GetSupportedWindowsDictionaryLanguage("sr"));
} else {
EXPECT_TRUE(
spellcheck_service_->GetSupportedWindowsDictionaryLanguage("sr")
.empty());
}
if (!expected_accept_language_generic.empty()) {
supported_dictionary =
spellcheck_service_->GetSupportedWindowsDictionaryLanguage(
expected_accept_language_generic);
EXPECT_FALSE(supported_dictionary.empty());
EXPECT_EQ(expected_accept_language_generic, supported_dictionary);
EXPECT_EQ(expected_tag_passed_to_spellcheck_generic,
SpellcheckService::GetTagToPassToWindowsSpellchecker(
expected_accept_language_generic, supported_dictionary));
} else {
EXPECT_EQ(1u,
spellcheck_service_->windows_spellcheck_dictionary_map_.size());
}
} else {
EXPECT_TRUE(
spellcheck_service_->windows_spellcheck_dictionary_map_.empty());
}
}
const std::vector<std::string> SpellcheckServiceHybridUnitTestBase::
windows_spellcheck_languages_for_testing_ = {
"fr-FR",
"es-MX",
"gl-ES",
"fi-FI",
"it-IT",
"pt-BR",
"haw-US",
"ast",
"kok-Deva-IN",
"sr-Cyrl-ME",
"sr-Latn-ME",
"ja-Latn-JP-x-ext",
};
class GetDictionariesHybridUnitTestNoDelayInit
: public SpellcheckServiceHybridUnitTestBase,
public testing::WithParamInterface<TestCase> {
protected:
void InitFeatures() override {
feature_list_.InitAndDisableFeature(
spellcheck::kWinDelaySpellcheckServiceInit);
}
};
static const TestCase kHybridGetDictionariesParams[] = {
TestCase("gl", {}, {"gl"}, {"gl"}),
TestCase("gl", {"gl"}, {"gl"}, {"gl"}),
TestCase("gl,hr", {}, {"gl", "hr"}, {"gl"}),
TestCase("gl,hr", {"gl"}, {"gl", "hr"}, {"gl"}),
TestCase("gl,hr", {"hr"}, {"gl", "hr"}, {"gl", "hr"}),
TestCase("gl,hr", {"gl", "hr"}, {"gl", "hr"}, {"gl", "hr"}),
TestCase("hr", {}, {"hr"}, {"hr"}),
TestCase("hr", {"hr"}, {"hr"}, {"hr"}),
TestCase("hr,gl", {"hr"}, {"hr", "gl"}, {"hr"}),
TestCase("ceb", {}, {}, {}),
TestCase("ceb,gl,hr", {"gl", "hr"}, {"gl", "hr"}, {"gl", "hr"}),
TestCase("fi-FI,fi,en-US,en",
{"en-US"},
{"fi", "en-US", "en"},
{"fi", "en-US"}),
TestCase("ja,gl", {"gl"}, {"gl"}, {"gl"}),
TestCase("eu", {"eu"}, {}, {}),
TestCase("es-419,es-MX",
{"es-419", "es-MX"},
{"es-419", "es-MX"},
{"es-419", "es-MX"}),
TestCase("fr-FR,es-MX,gl,pt-BR,hr,it",
{"fr-FR", "gl", "pt-BR", "it"},
{"fr-FR", "es-MX", "gl", "pt-BR", "hr", "it"},
{"fr-FR", "gl", "pt-BR", "it"}),
TestCase("ha", {"ha"}, {}, {}),
TestCase("st", {"st"}, {}, {}),
TestCase("sr,sr-Latn-RS", {"sr", "sr-Latn-RS"}, {"sr"}, {"sr"}),
TestCase("pt,pt-BR", {"pt", "pt-BR"}, {"pt", "pt-BR"}, {"pt", "pt-BR"}),
TestCase("it,it-IT", {"it", "it-IT"}, {"it", "it-IT"}, {"it", "it-IT"}),
};
INSTANTIATE_TEST_SUITE_P(TestCases,
GetDictionariesHybridUnitTestNoDelayInit,
testing::ValuesIn(kHybridGetDictionariesParams));
TEST_P(GetDictionariesHybridUnitTestNoDelayInit, GetDictionaries) {
RunGetDictionariesTest(GetParam().accept_languages,
GetParam().spellcheck_dictionaries,
GetParam().expected_dictionaries);
}
struct DictionaryMappingTestCase {
std::string full_tag;
std::string expected_accept_language;
std::string expected_tag_passed_to_spellcheck;
std::string expected_accept_language_generic;
std::string expected_tag_passed_to_spellcheck_generic;
};
std::ostream& operator<<(std::ostream& out,
const DictionaryMappingTestCase& test_case) {
out << "full_tag=" << test_case.full_tag
<< ", expected_accept_language=" << test_case.expected_accept_language
<< ", expected_tag_passed_to_spellcheck="
<< test_case.expected_tag_passed_to_spellcheck
<< ", expected_accept_language_generic="
<< test_case.expected_accept_language_generic
<< ", expected_tag_passed_to_spellcheck_generic="
<< test_case.expected_tag_passed_to_spellcheck_generic;
return out;
}
class SpellcheckServiceWindowsDictionaryMappingUnitTest
: public SpellcheckServiceHybridUnitTestBase,
public testing::WithParamInterface<DictionaryMappingTestCase> {
protected:
void InitFeatures() override {
feature_list_.InitAndDisableFeature(
spellcheck::kWinDelaySpellcheckServiceInit);
}
};
static const DictionaryMappingTestCase kHybridDictionaryMappingsParams[] = {
DictionaryMappingTestCase({"en-CA", "en-CA", "en-CA", "en", "en"}),
DictionaryMappingTestCase({"en-PH", "en", "en", "", ""}),
DictionaryMappingTestCase({"es-MX", "es-MX", "es-MX", "es", "es"}),
DictionaryMappingTestCase({"ar-SA", "ar", "ar", "", ""}),
DictionaryMappingTestCase({"kok-Deva-IN", "kok", "kok-Deva", "", ""}),
DictionaryMappingTestCase({"sr-Cyrl-RS", "sr", "sr-Cyrl", "", ""}),
DictionaryMappingTestCase({"sr-Cyrl-ME", "sr", "sr-Cyrl", "", ""}),
DictionaryMappingTestCase({"sr-Latn-RS", "", "sr-Latn", "", ""}),
DictionaryMappingTestCase({"sr-Latn-ME", "", "sr-Latn", "", ""}),
DictionaryMappingTestCase({"ca-ES", "ca", "ca", "", ""}),
DictionaryMappingTestCase({"ca-ES-valencia", "ca", "ca", "", ""}),
DictionaryMappingTestCase({"it-IT", "it-IT", "it-IT", "it", "it"}),
DictionaryMappingTestCase({"pt-BR", "pt-BR", "pt-BR", "pt", "pt"}),
};
INSTANTIATE_TEST_SUITE_P(TestCases,
SpellcheckServiceWindowsDictionaryMappingUnitTest,
testing::ValuesIn(kHybridDictionaryMappingsParams));
TEST_P(SpellcheckServiceWindowsDictionaryMappingUnitTest, CheckMappings) {
RunDictionaryMappingTest(
GetParam().full_tag, GetParam().expected_accept_language,
GetParam().expected_tag_passed_to_spellcheck,
GetParam().expected_accept_language_generic,
GetParam().expected_tag_passed_to_spellcheck_generic);
}
class SpellcheckServiceHybridUnitTestDelayInitBase
: public SpellcheckServiceHybridUnitTestBase {
public:
SpellcheckServiceHybridUnitTestDelayInitBase() = default;
void OnDictionariesInitialized() {
dictionaries_initialized_received_ = true;
if (quit_)
std::move(quit_).Run();
}
protected:
void InitFeatures() override {
feature_list_.InitAndEnableFeature(
spellcheck::kWinDelaySpellcheckServiceInit);
}
void InitializeSpellcheckService(
const std::vector<std::string>& spellcheck_languages_for_testing)
override {
spellcheck_service_ =
SpellcheckServiceFactory::GetInstance()->GetForContext(
browser_context());
spellcheck_service_->AddSpellcheckLanguagesForTesting(
spellcheck_languages_for_testing);
ASSERT_FALSE(spellcheck_service_->dictionaries_loaded());
spellcheck_service_->InitializeDictionaries(
base::BindOnce(&SpellcheckServiceHybridUnitTestDelayInitBase::
OnDictionariesInitialized,
base::Unretained(this)));
RunUntilCallbackReceived();
ASSERT_TRUE(spellcheck_service_->dictionaries_loaded());
}
void RunUntilCallbackReceived() {
if (dictionaries_initialized_received_)
return;
base::RunLoop run_loop;
quit_ = run_loop.QuitClosure();
run_loop.Run();
dictionaries_initialized_received_ = false;
}
private:
bool dictionaries_initialized_received_ = false;
base::OnceClosure quit_;
};
class SpellcheckServiceHybridUnitTestDelayInit
: public SpellcheckServiceHybridUnitTestDelayInitBase,
public testing::WithParamInterface<TestCase> {};
INSTANTIATE_TEST_SUITE_P(TestCases,
SpellcheckServiceHybridUnitTestDelayInit,
testing::ValuesIn(kHybridGetDictionariesParams));
TEST_P(SpellcheckServiceHybridUnitTestDelayInit, GetDictionaries) {
RunGetDictionariesTest(GetParam().accept_languages,
GetParam().spellcheck_dictionaries,
GetParam().expected_dictionaries);
}
class SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit
: public SpellcheckServiceHybridUnitTestDelayInitBase,
public testing::WithParamInterface<DictionaryMappingTestCase> {};
INSTANTIATE_TEST_SUITE_P(
TestCases,
SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit,
testing::ValuesIn(kHybridDictionaryMappingsParams));
TEST_P(SpellcheckServiceWindowsDictionaryMappingUnitTestDelayInit,
CheckMappings) {
RunDictionaryMappingTest(
GetParam().full_tag, GetParam().expected_accept_language,
GetParam().expected_tag_passed_to_spellcheck,
GetParam().expected_accept_language_generic,
GetParam().expected_tag_passed_to_spellcheck_generic);
}
#endif