blob: a568493a1e304484d3ee1bdd08b838aab67feaa9 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/shortcuts_backend.h"
#include <stddef.h>
#include <iterator>
#include <memory>
#include "base/files/scoped_temp_dir.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/history_service_test_util.h"
#include "components/omnibox/browser/shortcuts_constants.h"
#include "components/omnibox/browser/shortcuts_database.h"
#include "components/omnibox/common/omnibox_features.h"
#include "components/search_engines/search_terms_data.h"
#include "components/search_engines/template_url_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
// ShortcutsBackendTest -------------------------------------------------------
class ShortcutsBackendTest : public testing::Test,
public ShortcutsBackend::ShortcutsBackendObserver {
public:
ShortcutsBackendTest();
ShortcutsBackendTest(const ShortcutsBackendTest&) = delete;
ShortcutsBackendTest& operator=(const ShortcutsBackendTest&) = delete;
ShortcutsDatabase::Shortcut::MatchCore MatchCoreForTesting(
const std::string& url,
const std::string& contents_class = std::string(),
const std::string& description_class = std::string(),
AutocompleteMatch::Type type = AutocompleteMatchType::URL_WHAT_YOU_TYPED);
void SetSearchProvider();
void SetUp() override;
void TearDown() override;
void OnShortcutsLoaded() override;
void OnShortcutsChanged() override;
const ShortcutsBackend::ShortcutMap& shortcuts_map() const {
return backend_->shortcuts_map();
}
bool changed_notified() const { return changed_notified_; }
void set_changed_notified(bool changed_notified) {
changed_notified_ = changed_notified;
}
void InitBackend();
bool AddShortcut(const ShortcutsDatabase::Shortcut& shortcut);
bool UpdateShortcut(const ShortcutsDatabase::Shortcut& shortcut);
bool DeleteShortcutsWithURL(const GURL& url);
bool DeleteShortcutsWithIDs(
const ShortcutsDatabase::ShortcutIDs& deleted_ids);
bool ShortcutExists(const std::u16string& terms) const;
std::vector<std::u16string> ShortcutsMapKeys() const;
const ShortcutsDatabase::Shortcut FindShortcut(
const std::u16string& terms) const;
void ClearShortcutsMap();
TemplateURLService* GetTemplateURLService();
ShortcutsBackend* backend() { return backend_.get(); }
base::test::ScopedFeatureList& scoped_feature_list() {
return scoped_feature_list_;
}
private:
base::ScopedTempDir profile_dir_;
// `scoped_feature_list_` needs to be destroyed after TaskEnvironment is
// destroyed, so that other threads won't access the feature list while it is
// being destroyed.
base::test::ScopedFeatureList scoped_feature_list_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TemplateURLService> template_url_service_;
std::unique_ptr<history::HistoryService> history_service_;
scoped_refptr<ShortcutsBackend> backend_;
bool load_notified_ = false;
bool changed_notified_ = false;
};
ShortcutsBackendTest::ShortcutsBackendTest() = default;
ShortcutsDatabase::Shortcut::MatchCore
ShortcutsBackendTest::MatchCoreForTesting(const std::string& url,
const std::string& contents_class,
const std::string& description_class,
AutocompleteMatch::Type type) {
AutocompleteMatch match(nullptr, 0, false, type);
match.destination_url = GURL(url);
match.contents = u"test";
match.contents_class =
AutocompleteMatch::ClassificationsFromString(contents_class);
match.description_class =
AutocompleteMatch::ClassificationsFromString(description_class);
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(match.contents);
SearchTermsData search_terms_data;
return ShortcutsBackend::MatchToMatchCore(match, template_url_service_.get(),
&search_terms_data);
}
void ShortcutsBackendTest::SetSearchProvider() {
TemplateURLData data;
data.SetURL("http://foo.com/search?bar={searchTerms}");
data.SetShortName(u"foo");
data.SetKeyword(u"foo");
TemplateURL* template_url =
template_url_service_->Add(std::make_unique<TemplateURL>(data));
template_url_service_->SetUserSelectedDefaultSearchProvider(template_url);
}
void ShortcutsBackendTest::SetUp() {
ASSERT_TRUE(profile_dir_.CreateUniqueTempDir());
template_url_service_ = std::make_unique<TemplateURLService>(nullptr, 0);
history_service_ =
history::CreateHistoryService(profile_dir_.GetPath(), true);
ASSERT_TRUE(history_service_);
base::FilePath shortcuts_database_path =
profile_dir_.GetPath().Append(kShortcutsDatabaseName);
backend_ = new ShortcutsBackend(
template_url_service_.get(), std::make_unique<SearchTermsData>(),
history_service_.get(), shortcuts_database_path, false);
ASSERT_TRUE(backend_.get());
backend_->AddObserver(this);
}
void ShortcutsBackendTest::TearDown() {
backend_->RemoveObserver(this);
backend_->ShutdownOnUIThread();
backend_.reset();
// Explicitly shut down the history service and wait for its backend to be
// destroyed to prevent resource leaks.
base::RunLoop run_loop;
history_service_->SetOnBackendDestroyTask(run_loop.QuitClosure());
history_service_->Shutdown();
run_loop.Run();
task_environment_.RunUntilIdle();
EXPECT_TRUE(profile_dir_.Delete());
}
void ShortcutsBackendTest::OnShortcutsLoaded() {
load_notified_ = true;
}
void ShortcutsBackendTest::OnShortcutsChanged() {
changed_notified_ = true;
}
void ShortcutsBackendTest::InitBackend() {
ASSERT_TRUE(backend_);
ASSERT_FALSE(load_notified_);
ASSERT_FALSE(backend_->initialized());
backend_->Init();
task_environment_.RunUntilIdle();
EXPECT_TRUE(load_notified_);
EXPECT_TRUE(backend_->initialized());
}
bool ShortcutsBackendTest::AddShortcut(
const ShortcutsDatabase::Shortcut& shortcut) {
return backend_->AddShortcut(shortcut);
}
bool ShortcutsBackendTest::UpdateShortcut(
const ShortcutsDatabase::Shortcut& shortcut) {
return backend_->UpdateShortcut(shortcut);
}
bool ShortcutsBackendTest::DeleteShortcutsWithURL(const GURL& url) {
return backend_->DeleteShortcutsWithURL(url);
}
bool ShortcutsBackendTest::DeleteShortcutsWithIDs(
const ShortcutsDatabase::ShortcutIDs& deleted_ids) {
return backend_->DeleteShortcutsWithIDs(deleted_ids);
}
bool ShortcutsBackendTest::ShortcutExists(const std::u16string& terms) const {
return shortcuts_map().find(terms) != shortcuts_map().end();
}
std::vector<std::u16string> ShortcutsBackendTest::ShortcutsMapKeys() const {
std::vector<std::u16string> keys;
base::ranges::transform(shortcuts_map(), std::back_inserter(keys),
[](const auto& entry) { return entry.first; });
return keys;
}
const ShortcutsDatabase::Shortcut ShortcutsBackendTest::FindShortcut(
const std::u16string& terms) const {
const auto iter = shortcuts_map().find(terms);
return iter == shortcuts_map().end() ? ShortcutsDatabase::Shortcut{}
: iter->second;
}
void ShortcutsBackendTest::ClearShortcutsMap() {
backend_->shortcuts_map_.clear();
}
TemplateURLService* ShortcutsBackendTest::GetTemplateURLService() {
return template_url_service_.get();
}
// Actual tests ---------------------------------------------------------------
// Verifies that creating MatchCores strips classifications and sanitizes match
// types.
TEST_F(ShortcutsBackendTest, SanitizeMatchCore) {
struct {
std::string input_contents_class;
std::string input_description_class;
AutocompleteMatch::Type input_type;
std::string output_contents_class;
std::string output_description_class;
AutocompleteMatch::Type output_type;
} cases[] = {
{"0,1,4,0", "0,3,4,1", AutocompleteMatchType::URL_WHAT_YOU_TYPED,
"0,1,4,0", "0,1", AutocompleteMatchType::HISTORY_URL},
{"0,3,5,1", "0,2,5,0", AutocompleteMatchType::NAVSUGGEST, "0,1", "0,0",
AutocompleteMatchType::HISTORY_URL},
{"0,1", "0,0,11,2,15,0", AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
"0,1", "0,0", AutocompleteMatchType::SEARCH_HISTORY},
{"0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST, "0,1", "0,0",
AutocompleteMatchType::SEARCH_HISTORY},
{"0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, "", "",
AutocompleteMatchType::SEARCH_HISTORY},
{"0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_TAIL, "", "",
AutocompleteMatchType::SEARCH_HISTORY},
{"0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, "", "",
AutocompleteMatchType::SEARCH_HISTORY},
{"0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_PROFILE, "", "",
AutocompleteMatchType::SEARCH_HISTORY},
};
for (size_t i = 0; i < std::size(cases); ++i) {
ShortcutsDatabase::Shortcut::MatchCore match_core(MatchCoreForTesting(
std::string(), cases[i].input_contents_class,
cases[i].input_description_class, cases[i].input_type));
EXPECT_EQ(cases[i].output_contents_class, match_core.contents_class)
<< ":i:" << i << ":type:" << cases[i].input_type;
EXPECT_EQ(cases[i].output_description_class, match_core.description_class)
<< ":i:" << i << ":type:" << cases[i].input_type;
EXPECT_EQ(cases[i].output_type, match_core.type)
<< ":i:" << i << ":type:" << cases[i].input_type;
}
}
TEST_F(ShortcutsBackendTest, EntitySuggestionTest) {
SetSearchProvider();
AutocompleteMatch match;
match.fill_into_edit = u"franklin d roosevelt";
match.type = AutocompleteMatchType::SEARCH_SUGGEST_ENTITY;
match.contents = u"roosevelt";
match.contents_class =
AutocompleteMatch::ClassificationsFromString("0,0,5,2");
match.description = u"Franklin D. Roosevelt";
match.description_class = AutocompleteMatch::ClassificationsFromString("0,4");
match.destination_url =
GURL("http://www.foo.com/search?bar=franklin+d+roosevelt&gs_ssp=1234");
match.keyword = u"foo";
match.search_terms_args =
std::make_unique<TemplateURLRef::SearchTermsArgs>(match.fill_into_edit);
SearchTermsData search_terms_data;
ShortcutsDatabase::Shortcut::MatchCore match_core =
ShortcutsBackend::MatchToMatchCore(match, GetTemplateURLService(),
&search_terms_data);
EXPECT_EQ("http://foo.com/search?bar=franklin+d+roosevelt",
match_core.destination_url.spec());
EXPECT_EQ(match.fill_into_edit, match_core.contents);
EXPECT_EQ("0,0", match_core.contents_class);
EXPECT_EQ(std::u16string(), match_core.description);
EXPECT_TRUE(match_core.description_class.empty());
}
TEST_F(ShortcutsBackendTest, MatchCoreDescriptionTest) {
// When match.description_for_shortcuts is empty, match_core should use
// match.description.
{
AutocompleteMatch match;
match.description = u"the cat";
match.description_class =
AutocompleteMatch::ClassificationsFromString("0,1");
SearchTermsData search_terms_data;
ShortcutsDatabase::Shortcut::MatchCore match_core =
ShortcutsBackend::MatchToMatchCore(match, GetTemplateURLService(),
&search_terms_data);
EXPECT_EQ(match_core.description, match.description);
EXPECT_EQ(
match_core.description_class,
AutocompleteMatch::ClassificationsToString(match.description_class));
}
// When match.description_for_shortcuts is set, match_core should use it
// instead of match.description.
{
AutocompleteMatch match;
match.description = u"the cat";
match.description_class =
AutocompleteMatch::ClassificationsFromString("0,1");
match.description_for_shortcuts = u"the elephant";
match.description_class_for_shortcuts =
AutocompleteMatch::ClassificationsFromString("0,4");
SearchTermsData search_terms_data;
ShortcutsDatabase::Shortcut::MatchCore match_core =
ShortcutsBackend::MatchToMatchCore(match, GetTemplateURLService(),
&search_terms_data);
EXPECT_EQ(match_core.description, match.description_for_shortcuts);
EXPECT_EQ(match_core.description_class,
AutocompleteMatch::ClassificationsToString(
match.description_class_for_shortcuts));
}
}
TEST_F(ShortcutsBackendTest, AddAndUpdateShortcut) {
InitBackend();
EXPECT_FALSE(changed_notified());
ShortcutsDatabase::Shortcut shortcut(
"BD85DBA2-8C29-49F9-84AE-48E1E90880DF", u"goog",
MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
EXPECT_TRUE(AddShortcut(shortcut));
EXPECT_TRUE(changed_notified());
auto shortcut_iter(shortcuts_map().find(shortcut.text));
ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
EXPECT_EQ(shortcut.id, shortcut_iter->second.id);
EXPECT_EQ(shortcut.match_core.contents,
shortcut_iter->second.match_core.contents);
set_changed_notified(false);
shortcut.match_core.contents = u"Google Web Search";
EXPECT_TRUE(UpdateShortcut(shortcut));
EXPECT_TRUE(changed_notified());
shortcut_iter = shortcuts_map().find(shortcut.text);
ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
EXPECT_EQ(shortcut.id, shortcut_iter->second.id);
EXPECT_EQ(shortcut.match_core.contents,
shortcut_iter->second.match_core.contents);
}
TEST_F(ShortcutsBackendTest, DeleteShortcuts) {
InitBackend();
ShortcutsDatabase::Shortcut shortcut1(
"BD85DBA2-8C29-49F9-84AE-48E1E90880DF", u"goog",
MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
EXPECT_TRUE(AddShortcut(shortcut1));
ShortcutsDatabase::Shortcut shortcut2(
"BD85DBA2-8C29-49F9-84AE-48E1E90880E0", u"gle",
MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
EXPECT_TRUE(AddShortcut(shortcut2));
ShortcutsDatabase::Shortcut shortcut3(
"BD85DBA2-8C29-49F9-84AE-48E1E90880E1", u"sp",
MatchCoreForTesting("http://www.sport.com"), base::Time::Now(), 10);
EXPECT_TRUE(AddShortcut(shortcut3));
ShortcutsDatabase::Shortcut shortcut4(
"BD85DBA2-8C29-49F9-84AE-48E1E90880E2", u"mov",
MatchCoreForTesting("http://www.film.com"), base::Time::Now(), 10);
EXPECT_TRUE(AddShortcut(shortcut4));
ASSERT_EQ(4U, shortcuts_map().size());
EXPECT_EQ(shortcut1.id, shortcuts_map().find(shortcut1.text)->second.id);
EXPECT_EQ(shortcut2.id, shortcuts_map().find(shortcut2.text)->second.id);
EXPECT_EQ(shortcut3.id, shortcuts_map().find(shortcut3.text)->second.id);
EXPECT_EQ(shortcut4.id, shortcuts_map().find(shortcut4.text)->second.id);
EXPECT_TRUE(DeleteShortcutsWithURL(shortcut1.match_core.destination_url));
ASSERT_EQ(2U, shortcuts_map().size());
EXPECT_EQ(0U, shortcuts_map().count(shortcut1.text));
EXPECT_EQ(0U, shortcuts_map().count(shortcut2.text));
const ShortcutsBackend::ShortcutMap::const_iterator shortcut3_iter(
shortcuts_map().find(shortcut3.text));
ASSERT_TRUE(shortcut3_iter != shortcuts_map().end());
EXPECT_EQ(shortcut3.id, shortcut3_iter->second.id);
const ShortcutsBackend::ShortcutMap::const_iterator shortcut4_iter(
shortcuts_map().find(shortcut4.text));
ASSERT_TRUE(shortcut4_iter != shortcuts_map().end());
EXPECT_EQ(shortcut4.id, shortcut4_iter->second.id);
ShortcutsDatabase::ShortcutIDs deleted_ids;
deleted_ids.push_back(shortcut3.id);
deleted_ids.push_back(shortcut4.id);
EXPECT_TRUE(DeleteShortcutsWithIDs(deleted_ids));
ASSERT_EQ(0U, shortcuts_map().size());
}
TEST_F(ShortcutsBackendTest, AddOrUpdateShortcut_3CharShortening) {
InitBackend();
AutocompleteMatch match;
match.destination_url = GURL("https://www.google.com");
// Should not have a shortcut initially.
EXPECT_EQ(shortcuts_map().size(), 0u);
EXPECT_FALSE(ShortcutExists(u"we need very long text for this test"));
// Should have shortcut after shortcut is added to a match.
backend()->AddOrUpdateShortcut(u"we need very long text for this test",
match);
EXPECT_EQ(shortcuts_map().size(), 1u);
EXPECT_TRUE(ShortcutExists(u"we need very long text for this test"));
// Should not shorten shortcut when a slightly shorter input (less than 3
// chars shorter) is used for the match.
backend()->AddOrUpdateShortcut(u"we need very long text for this t", match);
EXPECT_EQ(shortcuts_map().size(), 1u);
EXPECT_FALSE(ShortcutExists(u"we need very long text for this t"));
EXPECT_TRUE(ShortcutExists(u"we need very long text for this test"));
// Should shorten shortcut when a shorter input is used for the match.
backend()->AddOrUpdateShortcut(u"we need very long", match);
EXPECT_EQ(shortcuts_map().size(), 1u);
EXPECT_TRUE(ShortcutExists(u"we need very long te"));
EXPECT_FALSE(ShortcutExists(u"we need very long text for this test"));
// Should add new shortcut when a longer input is used for the match.
backend()->AddOrUpdateShortcut(u"we need very long text for this test",
match);
EXPECT_EQ(shortcuts_map().size(), 2u);
EXPECT_TRUE(ShortcutExists(u"we need very long te"));
EXPECT_TRUE(ShortcutExists(u"we need very long text for this test"));
// Should shorten shortcut when a shorter input is used for the match. The
// shorter shortcut to the same match should remain.
backend()->AddOrUpdateShortcut(u"we need very long text", match);
EXPECT_EQ(shortcuts_map().size(), 2u);
EXPECT_TRUE(ShortcutExists(u"we need very long te"));
EXPECT_TRUE(ShortcutExists(u"we need very long text fo"));
EXPECT_FALSE(ShortcutExists(u"we need very long text for this test"));
// Should only touch the shortest shortcut. The longer shortcut to the same
// match should remain.
backend()->AddOrUpdateShortcut(u"we", match);
EXPECT_EQ(shortcuts_map().size(), 2u);
EXPECT_TRUE(ShortcutExists(u"we ne"));
EXPECT_FALSE(ShortcutExists(u"we need very long te"));
EXPECT_TRUE(ShortcutExists(u"we need very long text fo"));
}
TEST_F(ShortcutsBackendTest, AddOrUpdateShortcut_Expanding) {
scoped_feature_list().InitAndEnableFeature(omnibox::kShortcutExpanding);
InitBackend();
AutocompleteMatch match;
match.destination_url = GURL("https://www.google.com");
match.contents = u"https://www.google.com";
match.description = u"a an app apple i it word ZaZaaZZ symbols(╯°□°)╯";
match.contents_class.emplace_back(0, 0);
match.description_class.emplace_back(0, 0);
// Should not have a shortcut initially.
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre());
// Should expand last word when creating shortcuts.
backend()->AddOrUpdateShortcut(u"w w", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"w word"));
// Should expand last word when updating shortcuts.
backend()->AddOrUpdateShortcut(u"w w", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"w word"));
ClearShortcutsMap();
// Should not expand other words when the last word is already expanded.
backend()->AddOrUpdateShortcut(u"w word", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"w word"));
ClearShortcutsMap();
// Should prefer to expand to words at least 3 chars long. Should pick words
// that come first, if there are multiple matches at least 3 chars long.
backend()->AddOrUpdateShortcut(u"a", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"app"));
ClearShortcutsMap();
// Should prefer to expand to words that come first if all matches are shorter
// than 3 chars long.
backend()->AddOrUpdateShortcut(u"i", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"i"));
ClearShortcutsMap();
// Should not expand to words in the URL or contents.
backend()->AddOrUpdateShortcut(u"g", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"g"));
ClearShortcutsMap();
// When updating, should expand the last word after appending up to 3 chars.
backend()->AddOrUpdateShortcut(u"an apple a", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"an apple app"));
// 'an appl[e a]' should be expanded to 'an apple app'.
backend()->AddOrUpdateShortcut(u"an appl", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"an apple app"));
// But 'an app[le ]' should be expanded to 'an apple', removing the word 'app'
// and trailing whitespace.
backend()->AddOrUpdateShortcut(u"an app", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"an apple"));
ClearShortcutsMap();
// Should be case-insensitive when matching, but preserve case when creating
// shortcut text. The match word is 'ZaZaaZZ'.
// '[zAz][aaZZ]': the matched shortcut text should preserve the case of the
// input, while the expanded shortcut text should preserve the case of the
// match title.
backend()->AddOrUpdateShortcut(u"zAz", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"zazaazz"));
EXPECT_EQ(FindShortcut(u"zazaazz").text, u"zAzaaZZ");
// When updating, '[Z][Az][aaZZ]': the matched shortcut text should preserve
// the case of the input, while the expanded shortcut text should preserve the
// case of the previous shortcut text rather than the match title.
backend()->AddOrUpdateShortcut(u"Z", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"zazaazz"));
EXPECT_EQ(FindShortcut(u"zazaazz").text, u"ZAzaaZZ");
ClearShortcutsMap();
// Should match inconsistent trailing whitespace and expand the last word
// correctly.
backend()->AddOrUpdateShortcut(u"appl ", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"apple"));
// Likewise for updating shortcuts.
backend()->AddOrUpdateShortcut(u"appl ", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"apple"));
ClearShortcutsMap();
// Should neither crash nor expand texts containing no words. Should still add
// the text if it contains symbols, but not if it contains only whitespace.
backend()->AddOrUpdateShortcut(u"(╯°□°", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"(╯°□°"));
ClearShortcutsMap();
backend()->AddOrUpdateShortcut(u" ", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre());
ClearShortcutsMap();
// Should not expand words with trailing word-breaking symbols.
backend()->AddOrUpdateShortcut(u"symb°□°", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"symb°□°"));
ClearShortcutsMap();
// Should expand words following symbols.
backend()->AddOrUpdateShortcut(u"(╯°□°)╯symb", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"(╯°□°)╯symbols"));
ClearShortcutsMap();
// Should neither crash nor add a shortcut when text is empty.
backend()->AddOrUpdateShortcut(u"", match);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre());
// Should not expand when match description is empty.
AutocompleteMatch match_without_description;
match_without_description.destination_url = GURL("https://www.google.com");
match_without_description.contents = u"https://www.google.com";
match_without_description.contents_class.emplace_back(0, 0);
backend()->AddOrUpdateShortcut(u"xyz", match_without_description);
EXPECT_THAT(ShortcutsMapKeys(), testing::ElementsAre(u"xyz"));
ClearShortcutsMap();
}