// Copyright 2012 The Chromium Authors
// 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 <algorithm>
#include <iterator>
#include <memory>

#include "base/files/scoped_temp_dir.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_backend.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/history_service_test_util.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/fake_autocomplete_provider.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_engines_test_environment.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 FakeShortcutsBackend : public ShortcutsBackend {
 public:
  FakeShortcutsBackend(TemplateURLService* template_url_service,
                       std::unique_ptr<SearchTermsData> search_terms_data,
                       history::HistoryService* history_service,
                       base::FilePath database_path,
                       bool suppress_db)
      : ShortcutsBackend(template_url_service,
                         std::move(search_terms_data),
                         history_service,
                         database_path,
                         suppress_db) {}

  using ShortcutsBackend::AddShortcut;
  using ShortcutsBackend::DeleteOldShortcuts;
  using ShortcutsBackend::DeleteShortcutsWithDeletedOrInactiveKeywords;
  using ShortcutsBackend::DeleteShortcutsWithIDs;
  using ShortcutsBackend::DeleteShortcutsWithURL;
  using ShortcutsBackend::MatchToMatchCore;
  using ShortcutsBackend::shortcuts_map_;
  using ShortcutsBackend::ShutdownOnUIThread;
  using ShortcutsBackend::UpdateShortcut;

 protected:
  ~FakeShortcutsBackend() override = default;
};

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;
  }

  TemplateURLService* template_url_service() {
    return search_engines_test_environment_.template_url_service();
  }

  void InitBackend();
  bool ShortcutExists(const std::u16string& terms) const;
  std::vector<std::u16string> ShortcutsMapTexts() const;
  void ClearShortcutsMap();
  ShortcutsDatabase::Shortcut::MatchCore MatchToMatchCore(
      const AutocompleteMatch& match) {
    SearchTermsData search_terms_data;
    return FakeShortcutsBackend::MatchToMatchCore(match, template_url_service(),
                                                  &search_terms_data);
  }

  FakeShortcutsBackend* 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_;
  search_engines::SearchEnginesTestEnvironment search_engines_test_environment_;
  std::unique_ptr<history::HistoryService> history_service_;

  scoped_refptr<FakeShortcutsBackend> 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);
  return MatchToMatchCore(match);
}

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());
  history_service_ =
      history::CreateHistoryService(profile_dir_.GetPath(), true);
  ASSERT_TRUE(history_service_);

  base::FilePath shortcuts_database_path =
      profile_dir_.GetPath().Append(kShortcutsDatabaseName);
  backend_ = new FakeShortcutsBackend(
      template_url_service(), 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::ShortcutExists(const std::u16string& terms) const {
  return shortcuts_map().find(terms) != shortcuts_map().end();
}

std::vector<std::u16string> ShortcutsBackendTest::ShortcutsMapTexts() const {
  std::vector<std::u16string> texts;
  std::ranges::transform(shortcuts_map(), std::back_inserter(texts),
                         [](const auto& entry) { return entry.second.text; });
  return texts;
}

void ShortcutsBackendTest::ClearShortcutsMap() {
  backend_->shortcuts_map_.clear();
}

// Actual tests ---------------------------------------------------------------

// Verifies that creating MatchCores strips classifications and sanitizes match
// types.
TEST_F(ShortcutsBackendTest, SanitizeMatchCore) {
  struct Cases {
    std::u16string search_terms;
    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;
  };
  auto cases = std::to_array<Cases>({
      {u"test", "0,1,4,0", "0,3,4,1", AutocompleteMatchType::URL_WHAT_YOU_TYPED,
       "0,1,4,0", "0,1", AutocompleteMatchType::HISTORY_URL},
      {u"test", "0,3,5,1", "0,2,5,0", AutocompleteMatchType::NAVSUGGEST, "0,1",
       "0,0", AutocompleteMatchType::HISTORY_URL},
      {u"test", "0,1", "0,0,11,2,15,0",
       AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, "", "",
       AutocompleteMatchType::SEARCH_HISTORY},
      {u"test", "0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST, "", "",
       AutocompleteMatchType::SEARCH_HISTORY},
      {u"test", "0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_ENTITY, "",
       "", AutocompleteMatchType::SEARCH_HISTORY},
      {u"test", "0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_TAIL, "",
       "", AutocompleteMatchType::SEARCH_HISTORY},
      {u"test", "0,1", "0,0",
       AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED, "", "",
       AutocompleteMatchType::SEARCH_HISTORY},
      {u"test", "0,1", "0,0", AutocompleteMatchType::SEARCH_SUGGEST_PROFILE, "",
       "", AutocompleteMatchType::SEARCH_HISTORY},
      {u"", "0,1", "0,0", AutocompleteMatchType::CLIPBOARD_TEXT, "0,1", "0,0",
       AutocompleteMatchType::SEARCH_HISTORY},
  });

  for (size_t i = 0; i < std::size(cases); ++i) {
    AutocompleteMatch match;
    match.contents_class = AutocompleteMatch::ClassificationsFromString(
        cases[i].input_contents_class);
    match.description_class = AutocompleteMatch::ClassificationsFromString(
        cases[i].input_description_class);
    match.type = cases[i].input_type;
    match.search_terms_args =
        cases[i].search_terms.empty()
            ? nullptr
            : std::make_unique<TemplateURLRef::SearchTermsArgs>(
                  cases[i].search_terms);

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    EXPECT_EQ(match_core.contents_class, cases[i].output_contents_class)
        << ":i:" << i << ":type:" << cases[i].input_type;
    EXPECT_EQ(match_core.description_class, cases[i].output_description_class)
        << ":i:" << i << ":type:" << cases[i].input_type;
    EXPECT_EQ(match_core.type, cases[i].output_type)
        << ":i:" << i << ":type:" << cases[i].input_type;
  }
}

// Verifies that creating MatchCores strips keywords and sanitizes match types.
TEST_F(ShortcutsBackendTest, SanitizeMatchCore_Keyword) {
  struct Cases {
    std::u16string search_terms;
    std::u16string input_fill_into_edit;
    AutocompleteMatch::Type input_type;
    std::u16string input_keyword;
    ui::PageTransition input_page_transition;
    std::u16string output_fill_into_edit;
    AutocompleteMatch::Type output_type;
    std::u16string output_keyword;
    ui::PageTransition output_page_transition;
  };
  auto cases = std::to_array<Cases>({
      {u"franklin d roosevelt",
       u"foo http://foo.com/search?bar=franklin+d+roosevelt",
       AutocompleteMatchType::NAVSUGGEST, u"foo", ui::PAGE_TRANSITION_KEYWORD,
       u"http://foo.com/search?bar=franklin+d+roosevelt",
       AutocompleteMatchType::HISTORY_URL, u"", ui::PAGE_TRANSITION_GENERATED},
      {u"franklin d roosevelt",
       u"http://foo.com/search?bar=franklin+d+roosevelt",
       AutocompleteMatchType::NAVSUGGEST, u"foo", ui::PAGE_TRANSITION_GENERATED,
       u"http://foo.com/search?bar=franklin+d+roosevelt",
       AutocompleteMatchType::HISTORY_URL, u"", ui::PAGE_TRANSITION_GENERATED},
      {u"franklin d roosevelt", u"foo franklin d roosevelt",
       AutocompleteMatchType::SEARCH_SUGGEST, u"foo",
       ui::PAGE_TRANSITION_KEYWORD, u"franklin d roosevelt",
       AutocompleteMatchType::SEARCH_HISTORY, u"foo",
       ui::PAGE_TRANSITION_GENERATED},
      {u"franklin d roosevelt", u"franklin d roosevelt",
       AutocompleteMatchType::SEARCH_SUGGEST, u"foo",
       ui::PAGE_TRANSITION_GENERATED, u"franklin d roosevelt",
       AutocompleteMatchType::SEARCH_HISTORY, u"foo",
       ui::PAGE_TRANSITION_GENERATED},
      {u"", u"franklin d roosevelt", AutocompleteMatchType::SEARCH_SUGGEST,
       u"foo", ui::PAGE_TRANSITION_GENERATED, u"franklin d roosevelt",
       AutocompleteMatchType::SEARCH_HISTORY, u"foo",
       ui::PAGE_TRANSITION_GENERATED},
  });

  SetSearchProvider();
  for (size_t i = 0; i < std::size(cases); ++i) {
    AutocompleteMatch match;
    match.keyword = cases[i].input_keyword;
    match.fill_into_edit = cases[i].input_fill_into_edit;
    match.type = cases[i].input_type;
    match.transition = cases[i].input_page_transition;
    match.search_terms_args =
        cases[i].search_terms.empty()
            ? nullptr
            : std::make_unique<TemplateURLRef::SearchTermsArgs>(
                  cases[i].search_terms);

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    EXPECT_EQ(match_core.fill_into_edit, cases[i].output_fill_into_edit)
        << ":i:" << i;
    EXPECT_EQ(match_core.type, cases[i].output_type) << ":i:" << i;
    EXPECT_EQ(match_core.keyword, cases[i].output_keyword) << ":i:" << i;
    EXPECT_TRUE(ui::PageTransitionCoreTypeIs(cases[i].output_page_transition,
                                             match_core.transition))
        << ":i:" << i;
  }
}

TEST_F(ShortcutsBackendTest,
       MatchToMatchCore_SearchTypesWithoutSearchTermsArgs) {
  SetSearchProvider();
  AutocompleteMatchType::Type search_types[] = {
      AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED,
      AutocompleteMatchType::SEARCH_HISTORY,
      AutocompleteMatchType::SEARCH_SUGGEST,
      AutocompleteMatchType::SEARCH_SUGGEST_ENTITY,
      AutocompleteMatchType::SEARCH_SUGGEST_TAIL,
      AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED,
      AutocompleteMatchType::SEARCH_SUGGEST_PROFILE,
      AutocompleteMatchType::SEARCH_OTHER_ENGINE,
      AutocompleteMatchType::CLIPBOARD_TEXT,
      AutocompleteMatchType::CLIPBOARD_IMAGE,
      AutocompleteMatchType::VOICE_SUGGEST,
  };

  for (auto type : search_types) {
    AutocompleteMatch match;
    match.type = type;
    match.search_terms_args = nullptr;
    match.destination_url = GURL("http://foo.com/search?bar=test");

    // This should not crash even if `search_terms_args` is null.
    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);

    // `GetTypeForShortcut()` is not public, so expect `SEARCH_HISTORY` for all
    // search types except `SEARCH_OTHER_ENGINE`.
    AutocompleteMatchType::Type expected_type =
        type == AutocompleteMatchType::SEARCH_OTHER_ENGINE
            ? AutocompleteMatchType::SEARCH_OTHER_ENGINE
            : AutocompleteMatchType::SEARCH_HISTORY;
    EXPECT_EQ(match_core.type, expected_type) << "Failed for type: " << type;
  }
}

TEST_F(ShortcutsBackendTest, SearchSuggestionTest) {
  SetSearchProvider();
  {
    AutocompleteMatch match;
    match.fill_into_edit = u"franklin d roosevelt";
    match.type = AutocompleteMatchType::SEARCH_SUGGEST;
    match.contents = u"franklin d roosevelt";
    match.contents_class =
        AutocompleteMatch::ClassificationsFromString("0,0,5,2");
    match.description = u"";
    match.destination_url =
        GURL("http://www.foo.com/search?bar=franklin+d+roosevelt&gs_ssp=1234");
    match.keyword = u"foo";
    match.from_keyword = false;
    match.transition = ui::PAGE_TRANSITION_GENERATED;
    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
        u"franklin d roosevelt");

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    EXPECT_EQ(match_core.destination_url.spec(),
              "http://foo.com/search?bar=franklin+d+roosevelt");
    EXPECT_EQ(match_core.contents, u"franklin d roosevelt");
    EXPECT_EQ(match_core.contents_class, "0,0");
    EXPECT_EQ(match_core.description, std::u16string());
    EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_GENERATED,
                                             match_core.transition));
  }
  {
    AutocompleteMatch match;
    match.fill_into_edit = u"foo franklin d roosevelt";
    match.type = AutocompleteMatchType::SEARCH_SUGGEST;
    match.contents = u"franklin d roosevelt";
    match.contents_class =
        AutocompleteMatch::ClassificationsFromString("0,0,5,2");
    match.description = u"";
    match.destination_url =
        GURL("http://www.foo.com/search?bar=franklin+d+roosevelt&gs_ssp=1234");
    match.keyword = u"foo";
    match.from_keyword = true;
    match.transition = ui::PAGE_TRANSITION_KEYWORD;
    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
        u"franklin d roosevelt");

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    EXPECT_EQ(match_core.destination_url.spec(),
              "http://foo.com/search?bar=franklin+d+roosevelt");
    EXPECT_EQ(match_core.contents, u"franklin d roosevelt");
    EXPECT_EQ(match_core.contents_class, "0,0");
    EXPECT_EQ(match_core.description, std::u16string());
    EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_GENERATED,
                                             match_core.transition));
  }
  {
    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.from_keyword = false;
    match.transition = ui::PAGE_TRANSITION_GENERATED;
    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
        u"franklin d roosevelt");

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    EXPECT_EQ(match_core.destination_url.spec(),
              "http://foo.com/search?bar=franklin+d+roosevelt");
    EXPECT_EQ(match_core.contents, u"franklin d roosevelt");
    EXPECT_EQ(match_core.contents_class, "0,0");
    EXPECT_EQ(match_core.description, std::u16string());
    EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_GENERATED,
                                             match_core.transition));
  }
  {
    AutocompleteMatch match;
    match.fill_into_edit = u"foo 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.from_keyword = true;
    match.transition = ui::PAGE_TRANSITION_KEYWORD;
    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
        u"franklin d roosevelt");

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    EXPECT_EQ(match_core.destination_url.spec(),
              "http://foo.com/search?bar=franklin+d+roosevelt");
    EXPECT_EQ(match_core.contents, u"franklin d roosevelt");
    EXPECT_EQ(match_core.contents_class, "0,0");
    EXPECT_EQ(match_core.description, std::u16string());
    EXPECT_TRUE(ui::PageTransitionCoreTypeIs(ui::PAGE_TRANSITION_GENERATED,
                                             match_core.transition));
  }
}

TEST_F(ShortcutsBackendTest, MatchCoreDescriptionTest) {
  // When match.description_for_shortcuts is empty, match_core should use
  // match.description.
  {
    AutocompleteMatch match;
    match.type = AutocompleteMatchType::NAVSUGGEST;
    match.description = u"the cat";
    match.description_class =
        AutocompleteMatch::ClassificationsFromString("0,1");

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    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.type = AutocompleteMatchType::NAVSUGGEST;
    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");

    ShortcutsDatabase::Shortcut::MatchCore match_core = MatchToMatchCore(match);
    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(backend()->AddShortcut(shortcut));
  EXPECT_TRUE(changed_notified());
  auto shortcut_iter(shortcuts_map().find(shortcut.text));
  ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
  EXPECT_EQ(shortcut_iter->second.id, shortcut.id);
  EXPECT_EQ(shortcut_iter->second.match_core.contents,
            shortcut.match_core.contents);

  set_changed_notified(false);
  shortcut.match_core.contents = u"Google Web Search";
  EXPECT_TRUE(backend()->UpdateShortcut(shortcut));
  EXPECT_TRUE(changed_notified());
  shortcut_iter = shortcuts_map().find(shortcut.text);
  ASSERT_TRUE(shortcut_iter != shortcuts_map().end());
  EXPECT_EQ(shortcut_iter->second.id, shortcut.id);
  EXPECT_EQ(shortcut_iter->second.match_core.contents,
            shortcut.match_core.contents);
}

// Tests that zero suggest matches are not added to the database.
TEST_F(ShortcutsBackendTest, AddAndUpdateShortcut_ZeroSuggest) {
  InitBackend();
  EXPECT_FALSE(changed_notified());

  scoped_refptr<FakeAutocompleteProvider> zero_suggest_provider =
      new FakeAutocompleteProvider(
          AutocompleteProvider::Type::TYPE_ZERO_SUGGEST);
  AutocompleteMatch zero_suggest_match(
      zero_suggest_provider.get(), 400, true,
      AutocompleteMatchType::TILE_MOST_VISITED_SITE);

  backend()->AddOrUpdateShortcut(u"some text", zero_suggest_match);
  EXPECT_FALSE(changed_notified());
}

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(backend()->AddShortcut(shortcut1));

  ShortcutsDatabase::Shortcut shortcut2(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", u"gle",
      MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
  EXPECT_TRUE(backend()->AddShortcut(shortcut2));

  ShortcutsDatabase::Shortcut shortcut3(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", u"sp",
      MatchCoreForTesting("http://www.sport.com"), base::Time::Now(), 10);
  EXPECT_TRUE(backend()->AddShortcut(shortcut3));

  ShortcutsDatabase::Shortcut shortcut4(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", u"mov",
      MatchCoreForTesting("http://www.film.com"), base::Time::Now(), 10);
  EXPECT_TRUE(backend()->AddShortcut(shortcut4));

  ASSERT_EQ(shortcuts_map().size(), 4U);
  EXPECT_EQ(shortcuts_map().find(shortcut1.text)->second.id, shortcut1.id);
  EXPECT_EQ(shortcuts_map().find(shortcut2.text)->second.id, shortcut2.id);
  EXPECT_EQ(shortcuts_map().find(shortcut3.text)->second.id, shortcut3.id);
  EXPECT_EQ(shortcuts_map().find(shortcut4.text)->second.id, shortcut4.id);

  EXPECT_TRUE(
      backend()->DeleteShortcutsWithURL(shortcut1.match_core.destination_url));

  ASSERT_EQ(shortcuts_map().size(), 2U);
  EXPECT_FALSE(ShortcutExists(shortcut1.text));
  EXPECT_FALSE(ShortcutExists(shortcut2.text));
  EXPECT_TRUE(ShortcutExists(shortcut3.text));
  EXPECT_TRUE(ShortcutExists(shortcut4.text));

  ShortcutsDatabase::ShortcutIDs deleted_ids;
  deleted_ids.push_back(shortcut3.id);
  deleted_ids.push_back(shortcut4.id);
  EXPECT_TRUE(backend()->DeleteShortcutsWithIDs(deleted_ids));

  ASSERT_EQ(shortcuts_map().size(), 0U);
}

TEST_F(ShortcutsBackendTest, DeleteOldShortcuts) {
  InitBackend();

  // Add an inactive keyword.
  TemplateURLData data_inactive;
  data_inactive.SetShortName(u"inactive");
  data_inactive.SetKeyword(u"inactive");
  data_inactive.SetURL("http://www.inactive.com/{searchTerms}");
  template_url_service()->Add(std::make_unique<TemplateURL>(data_inactive));

  // Define shortcuts that are 1, 10, 100 and 1000 days old.
  ShortcutsDatabase::Shortcut shortcut1(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880DF", u"google",
      MatchCoreForTesting("http://www.google.com"),
      base::Time::Now() - base::Days(1), 100);
  EXPECT_TRUE(backend()->AddShortcut(shortcut1));

  ShortcutsDatabase::Shortcut shortcut2(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", u"yahoo",
      MatchCoreForTesting("http://www.yahoo.com"),
      base::Time::Now() - base::Days(10), 10);
  EXPECT_TRUE(backend()->AddShortcut(shortcut2));

  ShortcutsDatabase::Shortcut shortcut3(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", u"baidu",
      MatchCoreForTesting("http://www.baidu.com"),
      base::Time::Now() - base::Days(100), 1000);
  EXPECT_TRUE(backend()->AddShortcut(shortcut3));

  ShortcutsDatabase::Shortcut shortcut4(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", u"bing",
      MatchCoreForTesting("http://www.bing.com"),
      base::Time::Now() - base::Days(1000), 1);
  EXPECT_TRUE(backend()->AddShortcut(shortcut4));

  ShortcutsDatabase::Shortcut shortcut5(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", u"deleted",
      MatchCoreForTesting("http://www.deleted.com"),
      base::Time::Now() - base::Days(10), 1);
  shortcut5.match_core.keyword = u"deleted";
  EXPECT_TRUE(backend()->AddShortcut(shortcut5));

  ShortcutsDatabase::Shortcut shortcut6(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E4", u"inactive",
      MatchCoreForTesting("http://www.inactive.com"),
      base::Time::Now() - base::Days(10), 1);
  shortcut6.match_core.keyword = u"inactive";
  EXPECT_TRUE(backend()->AddShortcut(shortcut6));

  ASSERT_EQ(shortcuts_map().size(), 6U);
  EXPECT_EQ(shortcuts_map().find(shortcut1.text)->second.id, shortcut1.id);
  EXPECT_EQ(shortcuts_map().find(shortcut2.text)->second.id, shortcut2.id);
  EXPECT_EQ(shortcuts_map().find(shortcut3.text)->second.id, shortcut3.id);
  EXPECT_EQ(shortcuts_map().find(shortcut4.text)->second.id, shortcut4.id);
  EXPECT_EQ(shortcuts_map().find(shortcut5.text)->second.id, shortcut5.id);
  EXPECT_EQ(shortcuts_map().find(shortcut6.text)->second.id, shortcut6.id);

  EXPECT_TRUE(backend()->DeleteOldShortcuts());

  // After deleting old shortcuts, the two that are more than 90 days old should
  // no longer be present. The one with a deleted keyword should no longer be
  // present.
  ASSERT_EQ(shortcuts_map().size(), 2U);
  EXPECT_TRUE(ShortcutExists(shortcut1.text));
  EXPECT_TRUE(ShortcutExists(shortcut2.text));
  EXPECT_FALSE(ShortcutExists(shortcut3.text));
  EXPECT_FALSE(ShortcutExists(shortcut4.text));
  EXPECT_FALSE(ShortcutExists(shortcut5.text));
  EXPECT_FALSE(ShortcutExists(shortcut6.text));
}

TEST_F(ShortcutsBackendTest, DeleteShortcutsWithDeletedOrInactiveKeywords) {
  InitBackend();

  // Add a keyword with `is_active` set to `kUnspecified` (default).
  TemplateURLData data;
  data.SetShortName(u"inactive");
  data.SetKeyword(u"inactive");
  data.SetURL("http://www.inactive.com/{searchTerms}");
  template_url_service()->Add(std::make_unique<TemplateURL>(data));

  // Add a prepopulated keyword with `is_active` set to `kUnspecified`
  // (default).
  TemplateURLData data_prepopulated;
  data_prepopulated.SetShortName(u"prepopulated");
  data_prepopulated.SetKeyword(u"prepopulated");
  data_prepopulated.SetURL("http://www.prepopulated.com/{searchTerms}");
  data_prepopulated.prepopulate_id = 1;
  template_url_service()->Add(std::make_unique<TemplateURL>(data_prepopulated));
  template_url_service()->GetTemplateURLForKeyword(u"inactive_prepopulated");

  ShortcutsDatabase::Shortcut shortcut1(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880DF", u"goog",
      MatchCoreForTesting("http://www.google.com"), base::Time::Now(), 100);
  EXPECT_TRUE(backend()->AddShortcut(shortcut1));

  ShortcutsDatabase::Shortcut shortcut2(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E0", u"deleted query",
      MatchCoreForTesting("http://www.deleted.com"), base::Time::Now(), 100);
  shortcut2.match_core.keyword = u"deleted";
  EXPECT_TRUE(backend()->AddShortcut(shortcut2));

  ShortcutsDatabase::Shortcut shortcut3(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E1", u"inactive query",
      MatchCoreForTesting("http://www.inactive.com"), base::Time::Now(), 100);
  shortcut3.match_core.keyword = u"inactive";
  EXPECT_TRUE(backend()->AddShortcut(shortcut3));

  ShortcutsDatabase::Shortcut shortcut4(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E2", u"sp",
      MatchCoreForTesting("http://www.sport.com"), base::Time::Now(), 10);
  EXPECT_TRUE(backend()->AddShortcut(shortcut4));

  ShortcutsDatabase::Shortcut shortcut5(
      "BD85DBA2-8C29-49F9-84AE-48E1E90880E3", u"prepopulated query",
      MatchCoreForTesting("http://www.inactive_prepopulated.com"),
      base::Time::Now(), 100);
  shortcut5.match_core.keyword = u"prepopulated";
  EXPECT_TRUE(backend()->AddShortcut(shortcut5));

  ASSERT_EQ(shortcuts_map().size(), 5U);
  EXPECT_EQ(shortcuts_map().find(shortcut1.text)->second.id, shortcut1.id);
  EXPECT_EQ(shortcuts_map().find(shortcut2.text)->second.id, shortcut2.id);
  EXPECT_EQ(shortcuts_map().find(shortcut3.text)->second.id, shortcut3.id);
  EXPECT_EQ(shortcuts_map().find(shortcut4.text)->second.id, shortcut4.id);
  EXPECT_EQ(shortcuts_map().find(shortcut5.text)->second.id, shortcut5.id);

  // Delete shortcuts with deleted or inactive keywords.
  backend()->DeleteShortcutsWithDeletedOrInactiveKeywords();

  ASSERT_EQ(shortcuts_map().size(), 3U);
  EXPECT_TRUE(ShortcutExists(shortcut1.text));
  EXPECT_FALSE(ShortcutExists(shortcut2.text));
  EXPECT_FALSE(ShortcutExists(shortcut3.text));
  EXPECT_TRUE(ShortcutExists(shortcut4.text));
  EXPECT_TRUE(ShortcutExists(shortcut5.text));
}

TEST_F(ShortcutsBackendTest, AddOrUpdateShortcut_3CharShortening) {
  InitBackend();

  AutocompleteMatch match;
  match.type = AutocompleteMatchType::NAVSUGGEST;
  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 text"));
  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 text"));
  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 fo", match);
  EXPECT_EQ(shortcuts_map().size(), 2u);
  EXPECT_TRUE(ShortcutExists(u"we need very long text"));
  EXPECT_TRUE(ShortcutExists(u"we need very long text for this"));
  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 need"));
  EXPECT_FALSE(ShortcutExists(u"we need very long text"));
  EXPECT_TRUE(ShortcutExists(u"we need very long text for this"));
}

TEST_F(ShortcutsBackendTest, AddOrUpdateShortcut_Expanding) {
  InitBackend();

  AutocompleteMatch match;
  match.type = AutocompleteMatchType::NAVSUGGEST;
  match.destination_url = GURL("https://www.host-sharedB.com/path");
  match.description = u"https://www.description.com";
  match.contents = u"a an app apple i it word ZaZaaZZ symbols(╯°□°）╯ sharedA";
  match.contents_class.emplace_back(0, 0);
  match.description_class.emplace_back(0, 0);

  // Should not have a shortcut initially.
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre());

  // Should expand last word when creating shortcuts.
  backend()->AddOrUpdateShortcut(u"w w", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"w word"));

  // Should expand last word when updating shortcuts.
  backend()->AddOrUpdateShortcut(u"w w", match);
  EXPECT_THAT(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), testing::ElementsAre(u"i"));
  ClearShortcutsMap();

  // Should not expand to words in the `description`.
  backend()->AddOrUpdateShortcut(u"d", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"d"));
  ClearShortcutsMap();

  // Should not expand to words in the URL path.
  backend()->AddOrUpdateShortcut(u"p", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"p"));
  ClearShortcutsMap();

  // Should expand to words in the URL host.
  backend()->AddOrUpdateShortcut(u"h", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"host"));
  ClearShortcutsMap();

  // Should prefer expanding to words in the `contents`.
  backend()->AddOrUpdateShortcut(u"shar", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"sharedA"));
  ClearShortcutsMap();

  // When updating, should expand the last word after appending up to 3 chars.
  backend()->AddOrUpdateShortcut(u"an apple a", match);
  EXPECT_THAT(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), testing::ElementsAre(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(ShortcutsMapTexts(), testing::ElementsAre(u"ZAzaaZZ"));
  ClearShortcutsMap();

  // Should match inconsistent trailing whitespace and expand the last word
  // correctly.
  backend()->AddOrUpdateShortcut(u"appl   ", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"apple"));
  // Likewise for updating shortcuts.
  backend()->AddOrUpdateShortcut(u"appl   ", match);
  EXPECT_THAT(ShortcutsMapTexts(), 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(ShortcutsMapTexts(), testing::ElementsAre(u"(╯°□°"));
  ClearShortcutsMap();
  backend()->AddOrUpdateShortcut(u"    ", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre());
  ClearShortcutsMap();

  // Should not expand words with trailing word-breaking symbols.
  backend()->AddOrUpdateShortcut(u"symb°□°", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"symb°□°"));
  ClearShortcutsMap();

  // Should expand words following symbols.
  backend()->AddOrUpdateShortcut(u"(╯°□°）╯symb", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"(╯°□°）╯symbols"));
  ClearShortcutsMap();

  // Should neither crash nor add a shortcut when text is empty.
  backend()->AddOrUpdateShortcut(u"", match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre());

  // Should not expand when match contents is empty.
  AutocompleteMatch match_without_contents;
  match_without_contents.type = AutocompleteMatchType::NAVSUGGEST;
  match_without_contents.destination_url = GURL("https://www.host.com/google");
  match_without_contents.description = u"google";
  match_without_contents.description_class.emplace_back(0, 0);
  backend()->AddOrUpdateShortcut(u"goo", match_without_contents);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"goo"));
  ClearShortcutsMap();

  // Should expand with description when `swap_contents_and_description` is
  // true.
  AutocompleteMatch swapped_match;
  swapped_match.type = AutocompleteMatchType::NAVSUGGEST;
  swapped_match.swap_contents_and_description = true;
  swapped_match.destination_url = GURL("https://www.google.com");
  swapped_match.contents = u"https://www.googlecontents.com";
  swapped_match.description = u"googledescription";
  swapped_match.contents_class.emplace_back(0, 0);
  swapped_match.description_class.emplace_back(0, 0);
  backend()->AddOrUpdateShortcut(u"goo", swapped_match);
  EXPECT_THAT(ShortcutsMapTexts(), testing::ElementsAre(u"googledescription"));
  ClearShortcutsMap();
}

TEST_F(ShortcutsBackendTest, AddOrUpdateShortcut_Expanding_Prefix) {
  // Test `ExpandToFullWord(), specifically focussing on detecting and handling
  // the cases when `text` is a prefix of `match_text`.

  InitBackend();

  const auto test = [&](const std::string& text, const std::string& match_text,
                        const std::string& expected_expanded_text) {
    SCOPED_TRACE("Text: " + text + ", match_text: " + match_text);
    AutocompleteMatch match;
    match.type = AutocompleteMatchType::NAVSUGGEST;
    match.contents = base::UTF8ToUTF16(match_text);
    match.contents_class.emplace_back(0, 0);

    // Should expand last word when creating shortcuts.
    backend()->AddOrUpdateShortcut(base::UTF8ToUTF16(text), match);
    EXPECT_THAT(
        ShortcutsMapTexts(),
        testing::ElementsAre(base::UTF8ToUTF16(expected_expanded_text)));

    ClearShortcutsMap();
  };

  // When `text` does prefix `match_text`, should expand to the next word in
  // `match_text` instead of the 1st matching word.
  test("x", "xA xB", "xA");
  test("xA x", "xA xB", "xA xB");

  // When `text` doesn't prefix `match_text`, should expand to the 1st matching
  // word in `match_text`.
  test("xB x", "xA xB", "xB xA");
  // Even if that produces repeated words. (It'd be too complicated to avoid
  // this without introducing even greater edge cases).
  test("xA x", "xA y xB", "xA xA");

  // When prefix matching, should use the next word even if its short.
  test("xA x", "xA y xB xyz", "xA xyz");
  // When not prefix matching, should use the 1st word at least 3 chars long if
  // available.
  test("xA x", "xA xB xyz", "xA xB");

  // Both prefix and non prefix matching should handle trailing whitespace.
  // Trailing whitespace should not prompt expansion to the next `match_text`.
  test("xA ", "xA xB", "xA");
  // Trailing whitespace should not prevent expansion of the last `text` word.
  test("xA xy ", "xA xyz", "xA xyz");
  test("xA xyz ", "xA xyz", "xA xyz");
}

TEST_F(ShortcutsBackendTest, AddOrUpdateShortcut_Expanding_Case) {
  // Test `ExpandToFullWord(), specifically focussing on correct upper v lower
  // case.

  InitBackend();

  const auto test = [&](const std::string& text, const std::string& match_text,
                        const std::string& expected_expanded_text) {
    SCOPED_TRACE("Text: " + text + ", match_text: " + match_text);
    AutocompleteMatch match;
    match.type = AutocompleteMatchType::NAVSUGGEST;
    match.contents = base::UTF8ToUTF16(match_text);
    match.contents_class.emplace_back(0, 0);

    // Should expand last word when creating shortcuts.
    backend()->AddOrUpdateShortcut(base::UTF8ToUTF16(text), match);
    EXPECT_THAT(
        ShortcutsMapTexts(),
        testing::ElementsAre(base::UTF8ToUTF16(expected_expanded_text)));

    ClearShortcutsMap();
  };

  // Should preserve input case for the typed portion. Should preserve match
  // case for the expanded portion.
  test("lowerUPPER", "LOWERupperlowerUPPER", "lowerUPPERlowerUPPER");

  // Should not crash when the input or shortcut contain characters that change
  // length when their case changes. E.g. the dotted i 'İ' is 1 character long
  // in upper case, but 'i̇' is 2 characters (i and ̇) in lower case.

  // Upper case 'İ' in input - should preserve input case.
  test("xİx", "xİxy", "xİxy");
  test("xİx", "xi̇xy", "xİxy");
  // Lower case 'i̇' in input - should preserve input case.
  test("xi̇x", "xİxy", "xi̇xy");
  test("xi̇x", "xi̇xy", "xi̇xy");
  // 'İ' not present in input - should not preserve match case; should force
  // lowercase.
  test("x", "xİxy", "xi̇xy");
  test("x", "xi̇xy", "xi̇xy");

  // Also test updating existing shortcuts, which involves an additional
  // case-sensitive operation when append 3 chars to the input.
  const auto test_update = [&](const std::string& text,
                               const std::string& match_text,
                               const std::string& expected_expanded_text) {
    SCOPED_TRACE("Text: " + text + ", match_text: " + match_text);
    AutocompleteMatch match;
    match.type = AutocompleteMatchType::NAVSUGGEST;
    match.contents = base::UTF8ToUTF16(match_text);
    match.contents_class.emplace_back(0, 0);
    match.destination_url = GURL("http://www.url.com");

    backend()->AddOrUpdateShortcut(match.contents, match);

    // Should expand last word when creating shortcuts.
    backend()->AddOrUpdateShortcut(base::UTF8ToUTF16(text), match);
    EXPECT_THAT(
        ShortcutsMapTexts(),
        testing::ElementsAre(base::UTF8ToUTF16(expected_expanded_text)));

    ClearShortcutsMap();
  };

  // Upper case 'İ' in input.
  test_update("xİx", "xİxy", "xİxy");
  test_update("xİx", "xi̇xy", "xİxy");
  // Lower case 'i̇' in input.
  test_update("xi̇x", "xİxy", "xi̇xy");
  test_update("xi̇x", "xi̇xy", "xi̇xy");
  // 'İ' not present in input.
  test_update("x", "xİxy", "xi̇xy");
  test_update("x", "xi̇xy", "xi̇xy");
}
