// Copyright 2015 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/ntp_tiles/most_visited_sites.h"

#include <stddef.h>

#include <map>
#include <memory>
#include <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "base/callback_list.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/history/core/browser/top_sites.h"
#include "components/history/core/browser/top_sites_observer.h"
#include "components/ntp_tiles/constants.h"
#include "components/ntp_tiles/custom_links_manager.h"
#include "components/ntp_tiles/enterprise/enterprise_shortcuts_manager.h"
#include "components/ntp_tiles/features.h"
#include "components/ntp_tiles/icon_cacher.h"
#include "components/ntp_tiles/popular_sites_impl.h"
#include "components/ntp_tiles/pref_names.h"
#include "components/ntp_tiles/section_type.h"
#include "components/ntp_tiles/switches.h"
#include "components/search/ntp_features.h"
#include "components/supervised_user/core/common/buildflags.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/webapps/common/constants.h"
#include "extensions/buildflags/buildflags.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
#include "components/supervised_user/core/browser/supervised_user_preferences.h"
#endif

namespace ntp_tiles {

// Defined for googletest. Must be defined in the same namespace.
void PrintTo(const NTPTile& tile, std::ostream* os) {
  *os << "{\"" << tile.title << "\", \"" << tile.url << "\", "
      << static_cast<int>(tile.source) << "}";
}

namespace {

using history::MostVisitedURL;
using history::MostVisitedURLList;
using history::TopSites;
using testing::_;
using testing::AllOf;
using testing::AnyNumber;
using testing::AtLeast;
using testing::ByMove;
using testing::Contains;
using testing::DoAll;
using testing::Each;
using testing::ElementsAre;
using testing::Eq;
using testing::Ge;
using testing::InSequence;
using testing::IsEmpty;
using testing::Key;
using testing::Mock;
using testing::Not;
using testing::Pair;
using testing::Return;
using testing::ReturnRef;
using testing::SaveArg;
using testing::SizeIs;
using testing::StrictMock;

const char kHomepageUrl[] = "http://homepa.ge/";
const char16_t kHomepageTitle[] = u"Homepage";

std::string PrintTile(const std::u16string& title,
                      const std::string& url,
                      TileSource source) {
  return std::string("has title \"") + base::UTF16ToUTF8(title) +
         std::string("\" and url \"") + url + std::string("\" and source ") +
         testing::PrintToString(static_cast<int>(source));
}

MATCHER_P3(MatchesTile, title, url, source, PrintTile(title, url, source)) {
  return arg.title == title && arg.url == GURL(url) && arg.source == source;
}

MATCHER_P3(LastTileIs,
           title,
           url,
           source,
           std::string("last tile ") + PrintTile(title, url, source)) {
  const NTPTilesVector& tiles = arg.at(SectionType::PERSONALIZED);
  if (tiles.empty()) {
    return false;
  }

  const NTPTile& last = tiles.back();
  return last.title == title && last.url == GURL(url) && last.source == source;
}

MATCHER_P3(FirstPersonalizedTileIs,
           title,
           url,
           source,
           std::string("first tile ") + PrintTile(title, url, source)) {
  if (arg.count(SectionType::PERSONALIZED) == 0) {
    return false;
  }
  const NTPTilesVector& tiles = arg.at(SectionType::PERSONALIZED);
  return !tiles.empty() && tiles[0].title == title &&
         tiles[0].url == GURL(url) && tiles[0].source == source;
}

NTPTile MakeTile(const std::u16string& title,
                 const std::string& url,
                 TileSource source) {
  NTPTile tile;
  tile.title = title;
  tile.url = GURL(url);
  tile.source = source;
  return tile;
}

MostVisitedURL MakeMostVisitedURL(const std::u16string& title,
                                  const std::string& url) {
  MostVisitedURL result;
  result.title = title;
  result.url = GURL(url);
  return result;
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || \
    BUILDFLAG(IS_CHROMEOS)
EnterpriseShortcut MakeEnterpriseShortcut(const std::u16string& title,
                                          const std::string& url,
                                          bool allow_user_edit = false,
                                          bool allow_user_delete = false,
                                          bool is_hidden_by_user = false) {
  EnterpriseShortcut shortcut;
  shortcut.title = title;
  shortcut.url = GURL(url);
  shortcut.allow_user_edit = allow_user_edit;
  shortcut.allow_user_delete = allow_user_delete;
  shortcut.is_hidden_by_user = is_hidden_by_user;
  return shortcut;
}
#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) ||
        // BUILDFLAG(IS_CHROMEOS)

class MockTopSites : public TopSites {
 public:
  MOCK_METHOD0(ShutdownOnUIThread, void());
  MOCK_METHOD1(GetMostVisitedURLs, void(GetMostVisitedURLsCallback callback));
  MOCK_METHOD0(SyncWithHistory, void());
  MOCK_CONST_METHOD0(HasBlockedUrls, bool());
  MOCK_METHOD1(AddBlockedUrl, void(const GURL& url));
  MOCK_METHOD1(RemoveBlockedUrl, void(const GURL& url));
  MOCK_METHOD1(IsBlocked, bool(const GURL& url));
  MOCK_METHOD0(ClearBlockedUrls, void());
  MOCK_METHOD0(StartQueryForMostVisited, base::CancelableTaskTracker::TaskId());
  MOCK_METHOD1(IsKnownURL, bool(const GURL& url));
  MOCK_CONST_METHOD1(GetCanonicalURLString,
                     const std::string&(const GURL& url));
  MOCK_METHOD0(IsFull, bool());
  MOCK_CONST_METHOD0(loaded, bool());
  MOCK_METHOD0(GetPrepopulatedPages, history::PrepopulatedPageList());
  MOCK_METHOD1(OnNavigationCommitted, void(const GURL& url));
  MOCK_CONST_METHOD0(NumBlockedSites, int());

  // Publicly expose notification to observers, since the implementation cannot
  // be overriden.
  using TopSites::NotifyTopSitesChanged;

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

class MockMostVisitedSitesObserver : public MostVisitedSites::Observer {
 public:
  MOCK_METHOD2(OnURLsAvailable,
               void(bool is_user_triggered,
                    const std::map<SectionType, NTPTilesVector>& sections));
  MOCK_METHOD1(OnIconMadeAvailable, void(const GURL& site_url));
};

class FakeHomepageClient : public MostVisitedSites::HomepageClient {
 public:
  FakeHomepageClient()
      : homepage_tile_enabled_(false), homepage_url_(kHomepageUrl) {}
  ~FakeHomepageClient() override = default;

  bool IsHomepageTileEnabled() const override { return homepage_tile_enabled_; }

  GURL GetHomepageUrl() const override { return homepage_url_; }

  void QueryHomepageTitle(TitleCallback title_callback) override {
    std::move(title_callback).Run(homepage_title_);
  }

  void SetHomepageTileEnabled(bool homepage_tile_enabled) {
    homepage_tile_enabled_ = homepage_tile_enabled;
  }

  void SetHomepageUrl(GURL homepage_url) { homepage_url_ = homepage_url; }

  void SetHomepageTitle(const std::optional<std::u16string>& homepage_title) {
    homepage_title_ = homepage_title;
  }

 private:
  bool homepage_tile_enabled_;
  GURL homepage_url_;
  std::optional<std::u16string> homepage_title_;
};

class MockIconCacher : public IconCacher {
 public:
  MOCK_METHOD3(StartFetchPopularSites,
               void(PopularSites::Site site,
                    base::OnceClosure icon_available,
                    base::OnceClosure preliminary_icon_available));
  MOCK_METHOD2(StartFetchMostLikely,
               void(const GURL& page_url, base::OnceClosure icon_available));
};

class MockCustomLinksManager : public CustomLinksManager {
 public:
  MOCK_METHOD1(Initialize, bool(const NTPTilesVector& tiles));
  MOCK_METHOD0(Uninitialize, void());
  MOCK_CONST_METHOD0(IsInitialized, bool());
  MOCK_CONST_METHOD0(GetLinks, const std::vector<CustomLinksManager::Link>&());
  MOCK_CONST_METHOD0(GetMaxLinks, size_t());
  MOCK_METHOD3(AddLinkTo,
               bool(const GURL& url, const std::u16string& title, size_t pos));
  MOCK_METHOD2(AddLink, bool(const GURL& url, const std::u16string& title));
  MOCK_METHOD3(UpdateLink,
               bool(const GURL& url,
                    const GURL& new_url,
                    const std::u16string& new_title));
  MOCK_METHOD2(ReorderLink, bool(const GURL& url, size_t new_pos));
  MOCK_METHOD1(DeleteLink, bool(const GURL& url));
  MOCK_METHOD0(UndoAction, bool());
  MOCK_METHOD1(RegisterCallbackForOnChanged,
               base::CallbackListSubscription(base::RepeatingClosure callback));
};

class MockEnterpriseShortcutsManager : public EnterpriseShortcutsManager {
 public:
  MOCK_METHOD0(RestorePolicyLinks, void());
  MOCK_CONST_METHOD0(GetLinks, const std::vector<EnterpriseShortcut>&());
  MOCK_METHOD2(UpdateLink, bool(const GURL& url, const std::u16string& title));
  MOCK_METHOD2(ReorderLink, bool(const GURL& url, size_t new_pos));
  MOCK_METHOD1(DeleteLink, bool(const GURL& url));
  MOCK_METHOD0(UndoAction, bool());
  MOCK_METHOD1(RegisterCallbackForOnChanged,
               base::CallbackListSubscription(base::RepeatingClosure callback));
};

class PopularSitesFactoryForTest {
 public:
  explicit PopularSitesFactoryForTest(
      sync_preferences::TestingPrefServiceSyncable* pref_service)
      : prefs_(pref_service) {
    test_shared_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_);
    PopularSitesImpl::RegisterProfilePrefs(pref_service->registry());
  }

  void SeedWithSampleData() {
    prefs_->SetString(prefs::kPopularSitesOverrideCountry, "IN");
    prefs_->SetString(prefs::kPopularSitesOverrideVersion, "5");

    test_url_loader_factory_.ClearResponses();
    test_url_loader_factory_.AddResponse(
        "https://www.gstatic.com/chrome/ntp/suggested_sites_IN_5.json",
        R"([{
              "title": "PopularSite1",
              "url": "http://popularsite1/",
              "favicon_url": "http://popularsite1/favicon.ico"
            },
            {
              "title": "PopularSite2",
              "url": "http://popularsite2/",
              "favicon_url": "http://popularsite2/favicon.ico"
            }
           ])");

    test_url_loader_factory_.AddResponse(
        "https://www.gstatic.com/chrome/ntp/suggested_sites_US_5.json",
        R"([{
              "title": "ESPN",
              "url": "http://www.espn.com",
              "favicon_url": "http://www.espn.com/favicon.ico"
            }, {
              "title": "Mobile",
              "url": "http://www.mobile.de",
              "favicon_url": "http://www.mobile.de/favicon.ico"
            }, {
              "title": "Google News",
              "url": "http://news.google.com",
              "favicon_url": "http://news.google.com/favicon.ico"
            }
           ])");
#if BUILDFLAG(IS_IOS)
    test_url_loader_factory_.AddResponse(
        "https://www.gstatic.com/chrome/ntp/ios/"
        "suggested_sites_US_2023q1_mvt_experiment_with_popular_sites.json",
        R"([{
              "title": "ESPN",
              "url": "http://www.espn.com",
              "favicon_url": "http://www.espn.com/favicon.ico"
            }, {
              "title": "Mobile",
              "url": "http://www.mobile.de",
              "favicon_url": "http://www.mobile.de/favicon.ico"
            }, {
              "title": "Google News",
              "url": "http://news.google.com",
              "favicon_url": "http://news.google.com/favicon.ico"
            }
           ])");
#endif

    test_url_loader_factory_.AddResponse(
        "https://www.gstatic.com/chrome/ntp/suggested_sites_IN_6.json",
        R"([{
              "section": 1, // PERSONALIZED
              "sites": [{
                  "title": "PopularSite1",
                  "url": "http://popularsite1/",
                  "favicon_url": "http://popularsite1/favicon.ico"
                },
                {
                  "title": "PopularSite2",
                  "url": "http://popularsite2/",
                  "favicon_url": "http://popularsite2/favicon.ico"
                },
               ]
            },
            {
                "section": 4,  // NEWS
                "sites": [{
                    "large_icon_url": "https://news.google.com/icon.ico",
                    "title": "Google News",
                    "url": "https://news.google.com/"
                },
                {
                    "favicon_url": "https://news.google.com/icon.ico",
                    "title": "Google News Germany",
                    "url": "https://news.google.de/"
                }]
            },
            {
                "section": 2,  // SOCIAL
                "sites": [{
                    "large_icon_url": "https://ssl.gstatic.com/icon.png",
                    "title": "Google+",
                    "url": "https://plus.google.com/"
                }]
            },
            {
                "section": 3,  // ENTERTAINMENT
                "sites": [
                    // Intentionally empty site list.
                ]
            }
        ])");
  }

  std::unique_ptr<PopularSites> New() {
    return std::make_unique<PopularSitesImpl>(prefs_,
                                              /*template_url_service=*/nullptr,
                                              /*variations_service=*/nullptr,
                                              test_shared_loader_factory_);
  }

 private:
  raw_ptr<PrefService> prefs_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
};

}  // namespace

TEST(CustomLinksCacheTest, Main) {
  std::u16string kTestTitle1 = u"Example1: Amazing Website";
  std::string kTestUrl1 = "https://example1.com/";
  std::u16string kTestTitle2 = u"Example2; Amazing Website";
  std::string kTestUrl2 = "https://example2.com/";

  CustomLinksCache cache;
  EXPECT_TRUE(cache.GetList().empty());
  EXPECT_FALSE(cache.HasUrl(GURL(kTestUrl1)));
  EXPECT_FALSE(cache.HasUrl(GURL(kTestUrl2)));

  cache.PushBack(MakeTile(kTestTitle1, kTestUrl1, TileSource::CUSTOM_LINKS));
  EXPECT_THAT(cache.GetList(),
              ElementsAre(MatchesTile(kTestTitle1, kTestUrl1,
                                      TileSource::CUSTOM_LINKS)));
  EXPECT_TRUE(cache.HasUrl(GURL(kTestUrl1)));
  EXPECT_FALSE(cache.HasUrl(GURL(kTestUrl2)));

  cache.PushBack(MakeTile(kTestTitle2, kTestUrl2, TileSource::CUSTOM_LINKS));
  EXPECT_THAT(
      cache.GetList(),
      ElementsAre(
          MatchesTile(kTestTitle1, kTestUrl1, TileSource::CUSTOM_LINKS),
          MatchesTile(kTestTitle2, kTestUrl2, TileSource::CUSTOM_LINKS)));
  EXPECT_TRUE(cache.HasUrl(GURL(kTestUrl1)));
  EXPECT_TRUE(cache.HasUrl(GURL(kTestUrl2)));

  cache.Clear();
  EXPECT_TRUE(cache.GetList().empty());
  EXPECT_FALSE(cache.HasUrl(GURL(kTestUrl1)));
  EXPECT_FALSE(cache.HasUrl(GURL(kTestUrl2)));
}

// Param specifies whether Popular Sites is enabled via variations.
class MostVisitedSitesTest : public ::testing::Test {
 protected:
  using TopSitesCallbackList =
      base::OnceCallbackList<TopSites::GetMostVisitedURLsCallback::RunType>;

  MostVisitedSitesTest() {
    MostVisitedSites::RegisterProfilePrefs(pref_service_.registry());
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
    supervised_user::RegisterProfilePrefs(pref_service_.registry());
#endif

    // Updating list value in pref with default gmail URL for unit testing.
    // Also adding migration feature to be enabled for unit test.
    auto defaults =
        base::ListValue().Append("pjkljhegncpnkpknbcohdijeoejaedia");
    pref_service_.registry()->RegisterListPref(
        webapps::kWebAppsMigratedPreinstalledApps, std::move(defaults));

    feature_list_.InitAndDisableFeature(
        kNtpMostLikelyFaviconsFromServerFeature);
    popular_sites_factory_.SeedWithSampleData();

    RecreateMostVisitedSites();
  }

  void RecreateMostVisitedSites() {
    // We use StrictMock to make sure the object is not used unless Popular
    // Sites is enabled.
    auto icon_cacher = std::make_unique<StrictMock<MockIconCacher>>();
    icon_cacher_ = icon_cacher.get();

    // Custom links needs to be nullptr when MostVisitedSites is created, unless
    // the custom links feature is enabled. Custom links is disabled for
    // third-party NTPs.
    std::unique_ptr<StrictMock<MockCustomLinksManager>>
        mock_custom_links_manager;
    if (is_custom_links_enabled_) {
      mock_custom_links_manager =
          std::make_unique<StrictMock<MockCustomLinksManager>>();
      mock_custom_links_manager_ = mock_custom_links_manager.get();
      size_t expected_max_links =
          base::FeatureList::IsEnabled(ntp_features::kNtpShortcutsRedesign)
              ? static_cast<size_t>(
                    ntp_features::GetMaxShortcutsInExpandedState())
              : ntp_tiles::kMaxNumCustomLinks;
      EXPECT_CALL(*mock_custom_links_manager, GetMaxLinks())
          .Times(testing::AnyNumber())
          .WillRepeatedly(Return(expected_max_links));
    }

    // Enterprise custom links needs to be nullptr when MostVisitedSites is
    // created, unless the enterprise shortcuts feature is enabled. Custom
    // links is disabled for Android, iOS, and third-party NTPs.
    std::unique_ptr<StrictMock<MockEnterpriseShortcutsManager>>
        mock_enterprise_shortcuts_manager;
    if (is_enterprise_shortcuts_enabled_) {
      mock_enterprise_shortcuts_manager =
          std::make_unique<StrictMock<MockEnterpriseShortcutsManager>>();
      mock_enterprise_shortcuts_manager_ =
          mock_enterprise_shortcuts_manager.get();
    }

    // Populate Popular Sites' internal cache by mimicking a past usage of
    // PopularSitesImpl.
    auto tmp_popular_sites = popular_sites_factory_.New();
    base::RunLoop loop;
    bool save_success = false;
    tmp_popular_sites->MaybeStartFetch(
        /*force_download=*/true,
        base::BindOnce(
            [](bool* save_success, base::RunLoop* loop, bool success) {
              *save_success = success;
              loop->Quit();
            },
            &save_success, &loop));
    loop.Run();
    EXPECT_TRUE(save_success);

    // With PopularSites enabled, blocked urls is exercised.
    EXPECT_CALL(*mock_top_sites_, IsBlocked(_)).WillRepeatedly(Return(false));
    // Mock icon cacher never replies, and we also don't verify whether the
    // code uses it correctly.
    EXPECT_CALL(*icon_cacher, StartFetchPopularSites(_, _, _))
        .Times(AtLeast(0));

    EXPECT_CALL(*icon_cacher, StartFetchMostLikely(_, _)).Times(AtLeast(0));

    most_visited_sites_ = std::make_unique<MostVisitedSites>(
        &pref_service_, /*identity_manager=*/nullptr,
        /*supervised_user_service=*/nullptr,
        /*supervised_user_url_filtering_service=*/nullptr, mock_top_sites_,
        popular_sites_factory_.New(), std::move(mock_custom_links_manager),
        std::move(mock_enterprise_shortcuts_manager), std::move(icon_cacher),
        /*is_default_chrome_app_migrated=*/true);
  }

  bool IsCustomLinkMixingEnabled() const {
    return is_top_sites_enabled_ && is_custom_links_enabled_;
  }

  bool VerifyAndClearExpectations() {
    base::RunLoop().RunUntilIdle();
    const bool success =
        Mock::VerifyAndClearExpectations(mock_top_sites_.get()) &&
        Mock::VerifyAndClearExpectations(&mock_observer_);
    // For convenience, restore the expectations for IsBlocked().
    EXPECT_CALL(*mock_top_sites_, IsBlocked(_)).WillRepeatedly(Return(false));
    return success;
  }

  FakeHomepageClient* RegisterNewHomepageClient() {
    auto homepage_client = std::make_unique<FakeHomepageClient>();
    FakeHomepageClient* raw_client_ptr = homepage_client.get();
    most_visited_sites_->SetHomepageClient(std::move(homepage_client));
    return raw_client_ptr;
  }

  void EnableTopSites() { is_top_sites_enabled_ = true; }
  void EnableCustomLinks() { is_custom_links_enabled_ = true; }
  void EnableEnterpriseShortcuts() { is_enterprise_shortcuts_enabled_ = true; }

  bool is_top_sites_enabled_ = false;
  bool is_custom_links_enabled_ = false;
  bool is_enterprise_shortcuts_enabled_ = false;
  TopSitesCallbackList top_sites_callbacks_;

  base::test::TaskEnvironment task_environment_;
  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
  sync_preferences::TestingPrefServiceSyncable pref_service_;
  PopularSitesFactoryForTest popular_sites_factory_{&pref_service_};
  scoped_refptr<StrictMock<MockTopSites>> mock_top_sites_ =
      base::MakeRefCounted<StrictMock<MockTopSites>>();
  StrictMock<MockMostVisitedSitesObserver> mock_observer_;
  StrictMock<MockMostVisitedSitesObserver> mock_other_observer_;
  std::unique_ptr<MostVisitedSites> most_visited_sites_;
  base::test::ScopedFeatureList feature_list_;
  raw_ptr<MockCustomLinksManager> mock_custom_links_manager_;
  raw_ptr<MockEnterpriseShortcutsManager> mock_enterprise_shortcuts_manager_;
  raw_ptr<MockIconCacher> icon_cacher_;
};

TEST_F(MostVisitedSitesTest, ShouldStartNoCallInConstructor) {
  // No call to mocks expected by the mere fact of instantiating
  // MostVisitedSites.
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldRefreshBackends) {
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  most_visited_sites_->Refresh();
}

TEST_F(MostVisitedSitesTest, ShouldIncludeTileForHomepage) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_,
              OnURLsAvailable(_, FirstPersonalizedTileIs(
                                     u"", kHomepageUrl, TileSource::HOMEPAGE)));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldNotIncludeHomepageWithoutClient) {
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(SectionType::PERSONALIZED,
                           Not(Contains(MatchesTile(u"", kHomepageUrl,
                                                    TileSource::HOMEPAGE)))))));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldIncludeHomeTileWithUrlBeforeQueryingName) {
  // Because the query time for the real name might take a while, provide the
  // home tile with URL as title immediately and update the tiles as soon as the
  // real title was found.
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  homepage_client->SetHomepageTitle(kHomepageTitle);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  {
    testing::Sequence seq;
    EXPECT_CALL(
        mock_observer_,
        OnURLsAvailable(
            _, Contains(Pair(SectionType::PERSONALIZED,
                             Not(Contains(MatchesTile(
                                 u"", kHomepageUrl, TileSource::HOMEPAGE)))))));
    EXPECT_CALL(
        mock_observer_,
        OnURLsAvailable(
            _,
            Contains(Pair(SectionType::PERSONALIZED,
                          Not(Contains(MatchesTile(kHomepageTitle, kHomepageUrl,
                                                   TileSource::HOMEPAGE)))))));
  }
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldUpdateHomepageTileWhenRefreshHomepageTile) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);

  // Ensure that home tile is available as usual.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_,
              OnURLsAvailable(_, FirstPersonalizedTileIs(
                                     u"", kHomepageUrl, TileSource::HOMEPAGE)));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
  VerifyAndClearExpectations();

  // Disable home page and rebuild _without_ Resync. The tile should be gone.
  homepage_client->SetHomepageTileEnabled(false);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory()).Times(0);
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(_, Not(FirstPersonalizedTileIs(u"", kHomepageUrl,
                                                     TileSource::HOMEPAGE))));
  most_visited_sites_->RefreshTiles();
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldNotIncludeHomepageIfNoTileRequested) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(_, Contains(Pair(SectionType::PERSONALIZED, IsEmpty()))));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/0);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldReturnHomepageIfOneTileRequested) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>((
          MostVisitedURLList{MakeMostVisitedURL(u"Site 1", "http://site1/")})));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(SectionType::PERSONALIZED,
                           ElementsAre(MatchesTile(u"", kHomepageUrl,
                                                   TileSource::HOMEPAGE))))));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldHaveHomepageFirstInListWhenFull) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>((MostVisitedURLList{
              MakeMostVisitedURL(u"Site 1", "http://site1/"),
              MakeMostVisitedURL(u"Site 2", "http://site2/"),
              MakeMostVisitedURL(u"Site 3", "http://site3/"),
              MakeMostVisitedURL(u"Site 4", "http://site4/"),
              MakeMostVisitedURL(u"Site 5", "http://site5/"),
          })));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  std::map<SectionType, NTPTilesVector> sections;
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/4);
  base::RunLoop().RunUntilIdle();
  ASSERT_THAT(sections, Contains(Key(SectionType::PERSONALIZED)));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(4ul));
  // Assert that the home page is appended as the final tile.
  EXPECT_THAT(tiles[0], MatchesTile(u"", kHomepageUrl, TileSource::HOMEPAGE));
}

// The following test exercises behavior with a preinstalled chrome app; this
// is only relevant if extensions and apps are enabled.
#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(MostVisitedSitesTest, ShouldNotContainDefaultPreinstalledApp) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  const char kGmailUrl[] =
      "chrome-extension://pjkljhegncpnkpknbcohdijeoejaedia/index.html";
  const char16_t kGmailTitle[] = u"Gmail";

  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kGmailTitle, kGmailUrl),
                             MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  std::map<SectionType, NTPTilesVector> sections;
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillRepeatedly(SaveArg<1>(&sections));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();

  EXPECT_THAT(sections.at(SectionType::PERSONALIZED),
              AllOf(Not(Contains(MatchesTile(kGmailTitle, kGmailUrl,
                                             TileSource::TOP_SITES))),
                    Contains(MatchesTile(kTestTitle, kTestUrl,
                                         TileSource::TOP_SITES))));
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

TEST_F(MostVisitedSitesTest, ShouldHaveHomepageFirstInListWhenNotFull) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>((MostVisitedURLList{
              MakeMostVisitedURL(u"Site 1", "http://site1/"),
              MakeMostVisitedURL(u"Site 2", "http://site2/"),
              MakeMostVisitedURL(u"Site 3", "http://site3/"),
              MakeMostVisitedURL(u"Site 4", "http://site4/"),
              MakeMostVisitedURL(u"Site 5", "http://site5/"),
          })));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  std::map<SectionType, NTPTilesVector> sections;
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/8);
  base::RunLoop().RunUntilIdle();
  ASSERT_THAT(sections, Contains(Key(SectionType::PERSONALIZED)));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(6ul));
  // Assert that the home page is the first tile.
  EXPECT_THAT(tiles[0], MatchesTile(u"", kHomepageUrl, TileSource::HOMEPAGE));
}

TEST_F(MostVisitedSitesTest, ShouldDeduplicateHomepageWithTopSites) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          (MostVisitedURLList{MakeMostVisitedURL(u"Site 1", "http://site1/"),
                              MakeMostVisitedURL(u"", kHomepageUrl)})));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(
                 SectionType::PERSONALIZED,
                 AllOf(Contains(MatchesTile(u"", kHomepageUrl,
                                            TileSource::HOMEPAGE)),
                       Not(Contains(MatchesTile(u"", kHomepageUrl,
                                                TileSource::TOP_SITES))))))));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldNotIncludeHomepageIfThereIsNone) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(false);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(SectionType::PERSONALIZED,
                           Not(Contains(MatchesTile(u"", kHomepageUrl,
                                                    TileSource::HOMEPAGE)))))));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldNotIncludeHomepageIfEmptyUrl) {
  const std::string kEmptyHomepageUrl;
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  homepage_client->SetHomepageUrl(GURL(kEmptyHomepageUrl));
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(kEmptyHomepageUrl)))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(_, Not(FirstPersonalizedTileIs(u"", kEmptyHomepageUrl,
                                                     TileSource::HOMEPAGE))));
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesTest, ShouldNotIncludeHomepageIfBlocked) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          (MostVisitedURLList{MakeMostVisitedURL(u"", kHomepageUrl)})));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AnyNumber())
      .WillRepeatedly(Return(false));

  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AtLeast(1))
      .WillRepeatedly(Return(true));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(SectionType::PERSONALIZED,
                           Not(Contains(MatchesTile(u"", kHomepageUrl,
                                                    TileSource::HOMEPAGE)))))));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
}

#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
TEST_F(MostVisitedSitesTest, ShouldPinHomepageAgainIfBlockedUndone) {
  FakeHomepageClient* homepage_client = RegisterNewHomepageClient();
  homepage_client->SetHomepageTileEnabled(true);
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillOnce(base::test::RunOnceCallback<0>(
          (MostVisitedURLList{MakeMostVisitedURL(u"", kHomepageUrl)})));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AtLeast(1))
      .WillRepeatedly(Return(true));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(SectionType::PERSONALIZED,
                           Not(Contains(MatchesTile(u"", kHomepageUrl,
                                                    TileSource::HOMEPAGE)))))));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();
  VerifyAndClearExpectations();
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillOnce(base::test::RunOnceCallback<0>(MostVisitedURLList{}));
  EXPECT_CALL(*mock_top_sites_, IsBlocked(Eq(GURL(kHomepageUrl))))
      .Times(AtLeast(1))
      .WillRepeatedly(Return(false));
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _, Contains(Pair(SectionType::PERSONALIZED,
                           Contains(MatchesTile(u"", kHomepageUrl,
                                                TileSource::HOMEPAGE))))));

  most_visited_sites_->OnUrlFilteringServiceChanged();

  base::RunLoop().RunUntilIdle();
}
#endif

TEST_F(MostVisitedSitesTest, ShouldInformSuggestionSourcesWhenBlocked) {
  EXPECT_CALL(*mock_top_sites_, AddBlockedUrl(Eq(GURL(kHomepageUrl)))).Times(1);
  most_visited_sites_->AddOrRemoveBlockedUrl(GURL(kHomepageUrl),
                                             /*add_url=*/true);
  EXPECT_CALL(*mock_top_sites_, RemoveBlockedUrl(Eq(GURL(kHomepageUrl))))
      .Times(1);
  most_visited_sites_->AddOrRemoveBlockedUrl(GURL(kHomepageUrl),
                                             /*add_url=*/false);
}

TEST_F(MostVisitedSitesTest,
       ShouldDeduplicatePopularSitesWithMostVisitedIffHostAndTitleMatches) {
  pref_service_.SetString(prefs::kPopularSitesOverrideCountry, "US");
  RecreateMostVisitedSites();  // Refills cache with ESPN and Google News.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{
              MakeMostVisitedURL(u"ESPN", "http://espn.com/"),
              MakeMostVisitedURL(u"Mobile", "http://m.mobile.de/"),
              MakeMostVisitedURL(u"Google", "http://www.google.com/")}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  std::map<SectionType, NTPTilesVector> sections;
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/6);
  base::RunLoop().RunUntilIdle();
  ASSERT_THAT(sections, Contains(Key(SectionType::PERSONALIZED)));
  EXPECT_THAT(sections.at(SectionType::PERSONALIZED),
              Contains(MatchesTile(u"Google", "http://www.google.com/",
                                   TileSource::TOP_SITES)));
  EXPECT_THAT(sections.at(SectionType::PERSONALIZED),
              Contains(MatchesTile(u"Google News", "http://news.google.com/",
                                   TileSource::POPULAR)));
  EXPECT_THAT(sections.at(SectionType::PERSONALIZED),
              AllOf(Contains(MatchesTile(u"ESPN", "http://espn.com/",
                                         TileSource::TOP_SITES)),
                    Contains(MatchesTile(u"Mobile", "http://m.mobile.de/",
                                         TileSource::TOP_SITES)),
                    Not(Contains(MatchesTile(u"ESPN", "http://www.espn.com/",
                                             TileSource::POPULAR))),
                    Not(Contains(MatchesTile(u"Mobile", "http://www.mobile.de/",
                                             TileSource::POPULAR)))));
}

TEST_F(MostVisitedSitesTest, ShouldHandleTopSitesCacheHit) {
  // If cached, TopSites returns the tiles synchronously, running the callback
  // even before the function returns.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(u"Site 1", "http://site1/")}));

  InSequence seq;
  EXPECT_CALL(
      mock_observer_,
      OnURLsAvailable(
          _,
          Contains(Pair(
              SectionType::PERSONALIZED,
              ElementsAre(MatchesTile(u"Site 1", "http://site1/",
                                      TileSource::TOP_SITES),
                          MatchesTile(u"PopularSite1", "http://popularsite1/",
                                      TileSource::POPULAR),
                          MatchesTile(u"PopularSite2", "http://popularsite2/",
                                      TileSource::POPULAR))))));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  VerifyAndClearExpectations();
  CHECK(top_sites_callbacks_.empty());

  // Update by TopSites is propagated.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillOnce(base::test::RunOnceCallback<0>(
          MostVisitedURLList{MakeMostVisitedURL(u"Site 2", "http://site2/")}));
  EXPECT_CALL(*mock_top_sites_, IsBlocked(_)).WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _));
  mock_top_sites_->NotifyTopSitesChanged(
      history::TopSitesObserver::ChangeReason::MOST_VISITED);
  base::RunLoop().RunUntilIdle();
}

// Tests that multiple observers can be added to the MostVisitedSites.
TEST_F(MostVisitedSitesTest, MultipleObservers) {
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(MostVisitedURLList{
              MakeMostVisitedURL(u"ESPN", "http://espn.com/"),
              MakeMostVisitedURL(u"Mobile", "http://m.mobile.de/"),
              MakeMostVisitedURL(u"Google", "http://www.google.com/")}));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  std::map<SectionType, NTPTilesVector> sections;
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(
      sections.at(SectionType::PERSONALIZED),
      AllOf(Contains(MatchesTile(u"ESPN", "http://espn.com/",
                                 TileSource::TOP_SITES)),
            Contains(MatchesTile(u"Mobile", "http://m.mobile.de/",
                                 TileSource::TOP_SITES)),
            Not(Contains(MatchesTile(u"Google", "http://www.google.com/",
                                     TileSource::TOP_SITES)))));

  // Verifies that multiple observers can be added.
  sections.clear();
  std::map<SectionType, NTPTilesVector> sections_other;
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory()).Times(1);
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillRepeatedly(SaveArg<1>(&sections));
  EXPECT_CALL(mock_other_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections_other));
  most_visited_sites_->RefreshTiles();
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_other_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();

  // Verifies that two observers will be notified with the same suggestions.
  EXPECT_TRUE(sections == sections_other);
  ASSERT_THAT(sections, Contains(Key(SectionType::PERSONALIZED)));
  EXPECT_THAT(
      sections.at(SectionType::PERSONALIZED),
      AllOf(Contains(MatchesTile(u"ESPN", "http://espn.com/",
                                 TileSource::TOP_SITES)),
            Contains(MatchesTile(u"Mobile", "http://m.mobile.de/",
                                 TileSource::TOP_SITES)),
            Not(Contains(MatchesTile(u"Google", "http://www.google.com/",
                                     TileSource::TOP_SITES)))));
}

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
TEST_F(MostVisitedSitesTest, MostVisitedRedesignEnforcesExpandedBoundsCleanly) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{ntp_features::kNtpShortcutsRedesign},
      /*disabled_features=*/{});

  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>((MostVisitedURLList{
              MakeMostVisitedURL(u"Site 1", "http://site1/"),
              MakeMostVisitedURL(u"Site 2", "http://site2/"),
              MakeMostVisitedURL(u"Site 3", "http://site3/"),
              MakeMostVisitedURL(u"Site 4", "http://site4/"),
              MakeMostVisitedURL(u"Site 5", "http://site5/"),
              MakeMostVisitedURL(u"Site 6", "http://site6/"),
              MakeMostVisitedURL(u"Site 7", "http://site7/"),
              MakeMostVisitedURL(u"Site 8", "http://site8/"),
              MakeMostVisitedURL(u"Site 9", "http://site9/"),
              MakeMostVisitedURL(u"Site 10", "http://site10/"),
              MakeMostVisitedURL(u"Site 11", "http://site11/"),
              MakeMostVisitedURL(u"Site 12", "http://site12/"),
          })));

  base::RunLoop run_loop;
  std::map<SectionType, NTPTilesVector> sections;

  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(
          testing::DoAll(testing::SaveArg<1>(&sections),
                         base::test::RunOnceClosure(run_loop.QuitClosure())));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/8);
  run_loop.Run();

  ASSERT_THAT(sections,
              testing::Contains(testing::Key(SectionType::PERSONALIZED)));
  EXPECT_EQ(10ul, sections.at(SectionType::PERSONALIZED).size());
}

TEST_F(MostVisitedSitesTest, CustomLinksCappedAtTwentyWhenRedesignEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(ntp_features::kNtpShortcutsRedesign);

  EnableCustomLinks();
  RecreateMostVisitedSites();

  std::vector<CustomLinksManager::Link> custom_links;
  for (int i = 0; i < 25; ++i) {
    custom_links.push_back({GURL("http://site/"), u"Title"});
  }

  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(custom_links));

  base::RunLoop run_loop;
  std::map<SectionType, NTPTilesVector> sections;

  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(
          testing::DoAll(testing::SaveArg<1>(&sections),
                         base::test::RunOnceClosure(run_loop.QuitClosure())));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/8);
  run_loop.Run();

  ASSERT_THAT(sections,
              testing::Contains(testing::Key(SectionType::PERSONALIZED)));
  EXPECT_EQ(20ul, sections.at(SectionType::PERSONALIZED).size());
}

TEST_F(MostVisitedSitesTest, CustomLinksCappedAtTenWhenRedesignDisabled) {
  // Feature kNtpShortcutsRedesign is disabled by default.

  EnableCustomLinks();
  RecreateMostVisitedSites();

  std::vector<CustomLinksManager::Link> custom_links;
  for (int i = 0; i < 15; ++i) {
    custom_links.push_back({GURL("http://site/"), u"Title"});
  }

  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(custom_links));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));

  base::RunLoop run_loop;
  std::map<SectionType, NTPTilesVector> sections;

  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(
          testing::DoAll(testing::SaveArg<1>(&sections),
                         base::test::RunOnceClosure(run_loop.QuitClosure())));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/8);
  run_loop.Run();

  ASSERT_THAT(sections,
              testing::Contains(testing::Key(SectionType::PERSONALIZED)));
  EXPECT_EQ(10ul, sections.at(SectionType::PERSONALIZED).size());
}
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

// Existing tests check the default 8-link mobile cap. This dedicated test
// verifies the 10-link limit configured on WebUI NTP (AL) builds.
TEST_F(MostVisitedSitesTest, CustomLinksCappedAtTenWhenWebUiNtpEnabled) {
  EnableCustomLinks();
  RecreateMostVisitedSites();

  std::vector<CustomLinksManager::Link> custom_links;
  for (int i = 0; i < 15; ++i) {
    custom_links.push_back({GURL("http://site/"), u"Title"});
  }

  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetMaxLinks())
      .WillRepeatedly(Return(10));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(custom_links));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));

  base::RunLoop run_loop;
  std::map<SectionType, NTPTilesVector> sections;

  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(
          testing::DoAll(testing::SaveArg<1>(&sections),
                         base::test::RunOnceClosure(run_loop.QuitClosure())));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/8);
  run_loop.Run();

  EXPECT_THAT(
      sections,
      Contains(Pair(
          SectionType::PERSONALIZED,
          AllOf(SizeIs(10), Each(MatchesTile(u"Title", "http://site/",
                                             TileSource::CUSTOM_LINKS))))));
}

TEST_F(MostVisitedSitesTest, ShouldDeduplicateDomainWithNoWwwDomain) {
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"www.mobile.de"},
                                                        "mobile.de"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"mobile.de"},
                                                        "www.mobile.de"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"mobile.co.uk"},
                                                        "www.mobile.co.uk"));
}

TEST_F(MostVisitedSitesTest, ShouldDeduplicateDomainByRemovingMobilePrefixes) {
  EXPECT_TRUE(
      MostVisitedSites::IsHostOrMobilePageKnown({"bbc.co.uk"}, "m.bbc.co.uk"));
  EXPECT_TRUE(
      MostVisitedSites::IsHostOrMobilePageKnown({"m.bbc.co.uk"}, "bbc.co.uk"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"cnn.com"},
                                                        "edition.cnn.com"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"edition.cnn.com"},
                                                        "cnn.com"));
  EXPECT_TRUE(
      MostVisitedSites::IsHostOrMobilePageKnown({"cnn.com"}, "mobile.cnn.com"));
  EXPECT_TRUE(
      MostVisitedSites::IsHostOrMobilePageKnown({"mobile.cnn.com"}, "cnn.com"));
}

TEST_F(MostVisitedSitesTest, ShouldDeduplicateDomainByReplacingMobilePrefixes) {
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"www.bbc.co.uk"},
                                                        "m.bbc.co.uk"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"m.mobile.de"},
                                                        "www.mobile.de"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"www.cnn.com"},
                                                        "edition.cnn.com"));
  EXPECT_TRUE(MostVisitedSites::IsHostOrMobilePageKnown({"mobile.cnn.com"},
                                                        "www.cnn.com"));
}

// TODO(crbug.com/397422358): Adapt MostVisitedSitesWithCustomLinksTest for
// Android, which will require calling EnableCustomLinkMixing() in the CTOR.
class MostVisitedSitesWithCustomLinksTest : public MostVisitedSitesTest {
 public:
  MostVisitedSitesWithCustomLinksTest() {
    EnableCustomLinks();
    RecreateMostVisitedSites();
  }

  void SetUpBuildWithTopSites(const MostVisitedURLList& expected_list,
                              std::map<SectionType, NTPTilesVector>* sections) {
    EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
        .WillRepeatedly(
            base::test::RunOnceCallbackRepeatedly<0>(expected_list));
    EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
    EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
        .WillRepeatedly(Return(false));
    EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
        .WillOnce(SaveArg<1>(sections));
  }

  void SetUpBuildWithCustomLinks(
      const std::vector<CustomLinksManager::Link>& expected_links,
      std::map<SectionType, NTPTilesVector>* sections) {
    EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
        .WillRepeatedly(Return(true));
    EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
        .WillOnce(ReturnRef(expected_links));
    EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
        .WillOnce(SaveArg<1>(sections));
  }

  void SetUpBuildWithTopSitesAndCustomLinks(
      const MostVisitedURLList& expected_list,
      const std::vector<CustomLinksManager::Link>& expected_links,
      std::map<SectionType, NTPTilesVector>* sections) {
    EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
        .WillRepeatedly(
            base::test::RunOnceCallbackRepeatedly<0>(expected_list));
    EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
    EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
        .WillRepeatedly(Return(true));
    EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
        .WillOnce(ReturnRef(expected_links));
    EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
        .WillOnce(SaveArg<1>(sections));
  }

  // `expected_url` is assumed to be duplicated in the Custom link and the
  // Top Sites link.
  void CheckSingleCustomLink(const NTPTilesVector& tiles,
                             const char16_t* expected_title,
                             const char* expected_url) {
    if (IsCustomLinkMixingEnabled()) {
      // Custom link is mixed with Top Sites and Popular links. `expected_url`
      // duplicated causes Top Sites link removal.
      EXPECT_THAT(tiles, ElementsAre(MatchesTile(expected_title, expected_url,
                                                 TileSource::CUSTOM_LINKS),
                                     MatchesTile(u"PopularSite1",
                                                 "http://popularsite1/",
                                                 TileSource::POPULAR)));

    } else {
      // Custom Links replaces Top Sites links (no Popular). For both mixing and
      // non-mixing cases, Top Sites link is replaced.
      EXPECT_THAT(tiles, ElementsAre(MatchesTile(expected_title, expected_url,
                                                 TileSource::CUSTOM_LINKS)));
    }
  }
};

TEST_F(MostVisitedSitesWithCustomLinksTest, ChangeVisibility) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when custom links is not initialized. Tiles should be Top
  // Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  EXPECT_TRUE(most_visited_sites_->IsCustomLinksEnabled());
  EXPECT_TRUE(most_visited_sites_->IsShortcutsVisible());

  // Hide shortcuts. Observer should get notified.
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->SetShortcutsVisible(false);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(most_visited_sites_->IsCustomLinksEnabled());
  EXPECT_FALSE(most_visited_sites_->IsShortcutsVisible());

  // Attempt to hide the shortcuts again. This should be ignored.
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(0);
  most_visited_sites_->SetShortcutsVisible(false);
  base::RunLoop().RunUntilIdle();

  // Make the shortcuts visible. Observer should get notified.
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->SetShortcutsVisible(true);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(most_visited_sites_->IsCustomLinksEnabled());
  EXPECT_TRUE(most_visited_sites_->IsShortcutsVisible());
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       ShouldOnlyBuildCustomLinksWhenInitialized) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when custom links is not initialized. Tiles should be Top
  // Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Initialize custom links and rebuild tiles. Tiles should be custom links.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  SetUpBuildWithCustomLinks(expected_links, &sections);
  most_visited_sites_->InitializeCustomLinks();
  most_visited_sites_->RefreshTiles();
  base::RunLoop().RunUntilIdle();
  CheckSingleCustomLink(sections.at(SectionType::PERSONALIZED), kTestTitle,
                        kTestUrl);

  // Uninitialize custom links and rebuild tiles. Tiles should be Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->UninitializeCustomLinks();
  base::RunLoop().RunUntilIdle();
  tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       ShouldFavorCustomLinksOverTopSites) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when custom links is not initialized. Tiles should be Top
  // Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Initialize custom links and rebuild tiles. Tiles should be custom links.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  SetUpBuildWithCustomLinks(expected_links, &sections);
  most_visited_sites_->InitializeCustomLinks();
  most_visited_sites_->RefreshTiles();
  base::RunLoop().RunUntilIdle();
  CheckSingleCustomLink(sections.at(SectionType::PERSONALIZED), kTestTitle,
                        kTestUrl);

  // Initiate notification for new Top Sites. This should be ignored.
  VerifyAndClearExpectations();
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(0);
  top_sites_callbacks_.Notify(
      MostVisitedURLList({MakeMostVisitedURL(u"Site 2", "http://site2/")}));
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       DisableCustomLinksWhenNotInitialized) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when custom links is not initialized. Tiles should be from
  // Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Disable custom links. Tiles should rebuild.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions().with_top_sites(true));
  base::RunLoop().RunUntilIdle();

  // Try to disable custom links again. This should not rebuild the tiles.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_)).Times(0);
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks()).Times(0);
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions().with_top_sites(true));
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesWithCustomLinksTest, DisableCustomLinksWhenInitialized) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when custom links is initialized and not disabled. Tiles should
  // contain custom links.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  if (IsCustomLinkMixingEnabled()) {
    SetUpBuildWithTopSitesAndCustomLinks(
        MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)},
        expected_links, &sections);
  } else {
    EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
    SetUpBuildWithCustomLinks(expected_links, &sections);
  }
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  CheckSingleCustomLink(sections.at(SectionType::PERSONALIZED), kTestTitle,
                        kTestUrl);

  // Disable custom links. Tiles should rebuild and return Top Sites.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));

  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions().with_top_sites(true));
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(
      sections.at(SectionType::PERSONALIZED),
      ElementsAre(MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES),
                  MatchesTile(u"PopularSite1", "http://popularsite1/",
                              TileSource::POPULAR)));

  // Re-enable custom links. Tiles should rebuild and return custom links.
  SetUpBuildWithCustomLinks(expected_links, &sections);
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions().with_custom_links(true));
  base::RunLoop().RunUntilIdle();
  CheckSingleCustomLink(sections.at(SectionType::PERSONALIZED), kTestTitle,
                        kTestUrl);
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       ShouldGenerateShortTitleForTopSites) {
  std::string kTestUrl1 = "https://www.imdb.com/";
  std::u16string kTestTitle1 = u"IMDb - Movies, TV and Celebrities - IMDb";
  std::string kTestUrl2 = "https://drive.google.com/";
  std::u16string kTestTitle2 =
      u"Google Drive - Cloud Storage & File Backup for Photos, Docs & More";
  std::string kTestUrl3 = "https://amazon.com/";
  std::u16string kTestTitle3 =
      u"Amazon.com: Online Shopping for Electronics, Apparel, Computers, "
      u"Books, "
      u"DVDs & more";
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles from Top Sites. The tiles should have short titles.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1),
                         MakeMostVisitedURL(kTestTitle2, kTestUrl2),
                         MakeMostVisitedURL(kTestTitle3, kTestUrl3)},
      &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/3);
  base::RunLoop().RunUntilIdle();

  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(3ul));
  ASSERT_THAT(
      tiles[0],
      MatchesTile(/* The short title generated by the heuristic */ u"IMDb",
                  kTestUrl1, TileSource::TOP_SITES));
  ASSERT_THAT(
      tiles[1],
      MatchesTile(
          /* The short title generated by the heuristic */ u"Google Drive",
          kTestUrl2, TileSource::TOP_SITES));
  ASSERT_THAT(
      tiles[2],
      MatchesTile(
          /* The short title generated by the heuristic */ u"Amazon.com",
          kTestUrl3, TileSource::TOP_SITES));
}

// Test all delimiters
TEST_F(MostVisitedSitesWithCustomLinksTest,
       ShouldSplitTitleWithSpaceAfterDelimiter) {
  // No space before delimiter
  std::string kTestUrl1 = "https://example1.com/";
  std::u16string kTestTitle1 = u"Example1: Amazing Website";
  std::string kTestUrl2 = "https://example2.com/";
  std::u16string kTestTitle2 = u"Example2; Amazing Website";
  // Has space before delimiter
  std::string kTestUrl3 = "https://example3.com/";
  std::u16string kTestTitle3 = u"Example3 - Amazing Website";
  std::string kTestUrl4 = "https://example4.com/";
  std::u16string kTestTitle4 = u"Example4 | Amazing Website";
  // Repeated delimiter
  std::string kTestUrl5 = "https://example5.com/";
  std::u16string kTestTitle5 = u"Example5 :: Amazing Website";
  // Multiple delimiters
  std::string kTestUrl6 = "https://example6.com/";
  std::u16string kTestTitle6 = u"Example6 - Amazing Website - More Title";
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles from Top Sites. The tiles should have short titles.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1),
                         MakeMostVisitedURL(kTestTitle2, kTestUrl2),
                         MakeMostVisitedURL(kTestTitle3, kTestUrl3),
                         MakeMostVisitedURL(kTestTitle4, kTestUrl4),
                         MakeMostVisitedURL(kTestTitle5, kTestUrl5),
                         MakeMostVisitedURL(kTestTitle6, kTestUrl6)},
      &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/6);
  base::RunLoop().RunUntilIdle();

  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(6ul));
  ASSERT_THAT(
      tiles[0],
      MatchesTile(/* The short title generated by the heuristic */ u"Example1",
                  kTestUrl1, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[1],
              MatchesTile(
                  /* The short title generated by the heuristic */ u"Example2",
                  kTestUrl2, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[2],
              MatchesTile(
                  /* The short title generated by the heuristic */ u"Example3",
                  kTestUrl3, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[3],
              MatchesTile(
                  /* The short title generated by the heuristic */ u"Example4",
                  kTestUrl4, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[4],
              MatchesTile(
                  /* The short title generated by the heuristic */ u"Example5",
                  kTestUrl5, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[5],
              MatchesTile(
                  /* The short title generated by the heuristic */ u"Example6",
                  kTestUrl6, TileSource::TOP_SITES));
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       ShouldUseFullTitleIfTitleDoesNotContainDelimiterFollowedBySpace) {
  // No space after delimiter
  std::string kTestUrl1 = "https://example1.com/";
  std::u16string kTestTitle1 = u"Example1 Web Services Sign-In";
  std::string kTestUrl2 = "https://example2.com/";
  std::u16string kTestTitle2 = u"Example2 Sign-In";
  // No delimiter
  std::string kTestUrl3 = "https://example3.com/";
  std::u16string kTestTitle3 = u"Example3 is an Amazing Website";
  // Many spaces between words
  std::string kTestUrl4 = "https://example4.com/";
  std::u16string kTestTitle4 = u"Example4   Many   Spaces";
  std::string kTestUrl5 = "https://example5.com/";
  std::u16string kTestTitle5 = u"   Example5   Padded   With   Spaces  ";
  // No space after delimiter
  std::string kTestUrl6 = "https://example6.com/";
  std::u16string kTestTitle6 = u"This::Is::Fancy::Title";
  std::string kTestUrl7 = "https://example7.com/";
  std::u16string kTestTitle7 = u"Example7-Hello";
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles from Top Sites. The tiles should have short titles.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1),
                         MakeMostVisitedURL(kTestTitle2, kTestUrl2),
                         MakeMostVisitedURL(kTestTitle3, kTestUrl3),
                         MakeMostVisitedURL(kTestTitle4, kTestUrl4),
                         MakeMostVisitedURL(kTestTitle5, kTestUrl5),
                         MakeMostVisitedURL(kTestTitle6, kTestUrl6),
                         MakeMostVisitedURL(kTestTitle7, kTestUrl7)},
      &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/7);
  base::RunLoop().RunUntilIdle();

  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(7ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle1, kTestUrl1, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[1],
              MatchesTile(kTestTitle2, kTestUrl2, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[2],
              MatchesTile(kTestTitle3, kTestUrl3, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[3],
              MatchesTile(kTestTitle4, kTestUrl4, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[4], MatchesTile(u"Example5   Padded   With   Spaces",
                                    kTestUrl5, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[5],
              MatchesTile(kTestTitle6, kTestUrl6, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[6],
              MatchesTile(kTestTitle7, kTestUrl7, TileSource::TOP_SITES));
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       ShouldNotCrashIfReceiveAnEmptyTitle) {
  std::string kTestUrl1 = "https://site1/";
  std::u16string kTestTitle1 = u"";  // Empty title
  std::string kTestUrl2 = "https://site2/";
  std::u16string kTestTitle2 = u"       ";  // Title only contains spaces
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles from Top Sites. The tiles should have short titles.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1),
                         MakeMostVisitedURL(kTestTitle2, kTestUrl2)},
      &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();

  // Both cases should not crash and generate an empty title tile.
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(2ul));
  ASSERT_THAT(tiles[0], MatchesTile(u"", kTestUrl1, TileSource::TOP_SITES));
  ASSERT_THAT(tiles[1], MatchesTile(u"", kTestUrl2, TileSource::TOP_SITES));
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       UninitializeCustomLinksOnUndoAfterFirstAction) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build initial tiles with Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Initialize custom links and complete a custom link action.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, AddLink(_, _))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_links));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->AddCustomLink(GURL("test.com"), u"test");
  base::RunLoop().RunUntilIdle();
  CheckSingleCustomLink(sections.at(SectionType::PERSONALIZED), kTestTitle,
                        kTestUrl);

  EXPECT_TRUE(most_visited_sites_->HasCustomLink(GURL(kTestUrl)));
  EXPECT_FALSE(
      most_visited_sites_->HasCustomLink(GURL("https://not-added.com")));

  // Undo the action. This should uninitialize custom links.
  EXPECT_CALL(*mock_custom_links_manager_, UndoAction()).Times(0);
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->UndoCustomLinkAction();
  base::RunLoop().RunUntilIdle();
  tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       DontUninitializeCustomLinksOnUndoAfterMultipleActions) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build initial tiles with Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Initialize custom links and complete a custom link action.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, UpdateLink(_, _, _))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_links));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillRepeatedly(SaveArg<1>(&sections));
  most_visited_sites_->UpdateCustomLink(GURL("test.com"), GURL("test.com"),
                                        u"test");
  base::RunLoop().RunUntilIdle();
  CheckSingleCustomLink(sections.at(SectionType::PERSONALIZED), kTestTitle,
                        kTestUrl);

  // Complete a second custom link action.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, DeleteLink(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(expected_links));
  most_visited_sites_->DeleteCustomLink(GURL("test.com"));
  base::RunLoop().RunUntilIdle();

  // Undo the second action. This should not uninitialize custom links.
  EXPECT_CALL(*mock_custom_links_manager_, UndoAction()).WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize()).Times(0);
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(expected_links));
  most_visited_sites_->UndoCustomLinkAction();
  base::RunLoop().RunUntilIdle();

  // Exercise AddCustomLinkTo().
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, AddLinkTo(_, _, _))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_links));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->AddCustomLinkTo(GURL("test2.com"), u"test2", 0);
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesWithCustomLinksTest,
       UninitializeCustomLinksIfFirstActionFails) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build initial tiles with Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Fail to add a custom link. This should not initialize custom links but
  // notify.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, AddLink(_, _))
      .WillOnce(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->AddCustomLink(GURL(kTestUrl), u"test");
  base::RunLoop().RunUntilIdle();

  // Fail to edit a custom link. This should not initialize custom links but
  // notify.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, UpdateLink(_, _, _))
      .WillOnce(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->UpdateCustomLink(GURL("test.com"), GURL("test2.com"),
                                        u"test");
  base::RunLoop().RunUntilIdle();

  // Fail to reorder a custom link. This should not initialize custom links but
  // notify.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, ReorderLink(_, _))
      .WillOnce(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->ReorderCustomLink(GURL("test.com"), 1);
  base::RunLoop().RunUntilIdle();

  // Fail to delete a custom link. This should not initialize custom links but
  // notify.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, DeleteLink(_))
      .WillOnce(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _)).Times(1);
  most_visited_sites_->DeleteCustomLink(GURL("test.com"));
  base::RunLoop().RunUntilIdle();
}

TEST_F(MostVisitedSitesWithCustomLinksTest, RebuildTilesOnCustomLinksChanged) {
  const char kTestUrl1[] = "http://site1/";
  const char kTestUrl2[] = "http://site2/";
  const char16_t kTestTitle1[] = u"Site 1";
  const char16_t kTestTitle2[] = u"Site 2";
  std::vector<CustomLinksManager::Link> expected_links(
      {CustomLinksManager::Link{GURL(kTestUrl2), kTestTitle2}});
  std::map<SectionType, NTPTilesVector> sections;

  // Build initial tiles with Top Sites.
  base::RepeatingClosure custom_links_callback;
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_))
      .WillOnce(DoAll(SaveArg<0>(&custom_links_callback),
                      Return(ByMove(base::CallbackListSubscription()))));
  SetUpBuildWithTopSites(
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1)},
      &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  base::RunLoop().RunUntilIdle();
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle1, kTestUrl1, TileSource::TOP_SITES));

  // Notify that there is a new set of custom links.
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_links));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  custom_links_callback.Run();
  base::RunLoop().RunUntilIdle();
  // Not using CheckSingleCustomLink(), since URLs in the Custom link and the
  // Top Sites links are different.
  if (IsCustomLinkMixingEnabled()) {
    // Custom links mix with current tiles (different URL).
    EXPECT_THAT(
        sections.at(SectionType::PERSONALIZED),
        ElementsAre(
            MatchesTile(kTestTitle2, kTestUrl2, TileSource::CUSTOM_LINKS),
            MatchesTile(kTestTitle1, kTestUrl1, TileSource::TOP_SITES)));
  } else {
    // Custom links replace current tiles.
    EXPECT_THAT(sections.at(SectionType::PERSONALIZED),
                ElementsAre(MatchesTile(kTestTitle2, kTestUrl2,
                                        TileSource::CUSTOM_LINKS)));
  }

  // Notify that custom links have been uninitialized. This should rebuild the
  // tiles with Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  custom_links_callback.Run();
  base::RunLoop().RunUntilIdle();
  tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle1, kTestUrl1, TileSource::TOP_SITES));
}

// The number of none custom links should be limited to the
// `max_num_non_custom_sites` parameter.
TEST_F(MostVisitedSitesWithCustomLinksTest, ShouldHonorMaxNumNonCustomLinks) {
  // Establish mock sites.
  const char kTestCustomUrl[] = "http://customsite/";
  const char16_t kTestCustomTitle[] = u"Custom Site";
  std::string kTestUrl1 = "https://site1/";
  std::u16string kTestTitle1 = u"Site 1";
  std::string kTestUrl2 = "https://site2/";
  std::u16string kTestTitle2 = u"Site 2";
  MostVisitedURLList mock_top_sites =
      MostVisitedURLList{MakeMostVisitedURL(kTestTitle1, kTestUrl1),
                         MakeMostVisitedURL(kTestTitle2, kTestUrl2)};
  std::vector<CustomLinksManager::Link> mock_custom_links(
      {CustomLinksManager::Link{GURL(kTestCustomUrl), kTestCustomTitle}});
  std::map<SectionType, NTPTilesVector> sections;
  // Enable site mixing.
  EnableTopSites();
  // Build tiles when custom links is initialized and not disabled. Tiles should
  // contain custom links.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(mock_top_sites));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(mock_custom_links));
  // Enable top sites as well as custom links.
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions()
          .with_top_sites(true)
          .with_custom_links(true));
  base::RunLoop().RunUntilIdle();
  // Observe the sites.
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  SetUpBuildWithTopSitesAndCustomLinks(mock_top_sites, mock_custom_links,
                                       &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(
      &mock_observer_, /*max_num_sites=*/3, /*max_num_non_custom_sites=*/1);
  base::RunLoop().RunUntilIdle();
  // Check that there is only one custom link and one top site instead of two.
  EXPECT_THAT(
      sections.at(SectionType::PERSONALIZED),
      ElementsAre(MatchesTile(kTestCustomTitle, kTestCustomUrl,
                              TileSource::CUSTOM_LINKS),
                  MatchesTile(kTestTitle1, kTestUrl1, TileSource::TOP_SITES)));
}

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || \
    BUILDFLAG(IS_CHROMEOS)
class MostVisitedSitesWithEnterpriseShortcutsTest
    : public MostVisitedSitesTest {
 public:
  MostVisitedSitesWithEnterpriseShortcutsTest() {
    EnableEnterpriseShortcuts();
    EnableCustomLinks();
    RecreateMostVisitedSites();
    SetupInitialState();
  }

  void SetUpBuildWithEnterpriseShortcuts(
      const std::vector<EnterpriseShortcut>& expected_links,
      std::map<SectionType, NTPTilesVector>* sections) {
    EXPECT_CALL(*mock_enterprise_shortcuts_manager_, GetLinks())
        .WillOnce(ReturnRef(expected_links));
    EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
        .WillOnce(SaveArg<1>(sections));
  }

 private:
  void SetupInitialState() {
    const std::vector<EnterpriseShortcut> empty_links;
    base::RunLoop run_loop;
    EXPECT_CALL(*mock_enterprise_shortcuts_manager_, GetLinks())
        .WillOnce(DoAll(testing::InvokeWithoutArgs([&]() { run_loop.Quit(); }),
                        ReturnRef(empty_links)));
    most_visited_sites_->EnableTileTypes(
        MostVisitedSites::EnableTileTypesOptions().with_enterprise_shortcuts(
            true));
    run_loop.Run();
    Mock::VerifyAndClearExpectations(mock_top_sites_.get());
    Mock::VerifyAndClearExpectations(mock_custom_links_manager_);
    if (mock_custom_links_manager_) {
      size_t expected_max_links =
          base::FeatureList::IsEnabled(ntp_features::kNtpShortcutsRedesign)
              ? static_cast<size_t>(
                    ntp_features::GetMaxShortcutsInExpandedState())
              : ntp_tiles::kMaxNumCustomLinks;
      EXPECT_CALL(*mock_custom_links_manager_, GetMaxLinks())
          .Times(testing::AnyNumber())
          .WillRepeatedly(Return(expected_max_links));
    }
    Mock::VerifyAndClearExpectations(mock_enterprise_shortcuts_manager_);
  }
};

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest,
       ShouldToggleEnterpriseShortcuts) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  const std::vector<EnterpriseShortcut> expected_links = {
      MakeEnterpriseShortcut(kTestTitle, kTestUrl)};
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when enterprise shortcuts is enabled. Tiles should be
  // enterprise shortcuts.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(_)).WillRepeatedly(Return(false));
  SetUpBuildWithEnterpriseShortcuts(expected_links, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0], MatchesTile(kTestTitle, kTestUrl,
                                    TileSource::ENTERPRISE_SHORTCUTS));

  // Disable enterprise shortcuts and rebuild tiles. Tiles should be Top Sites.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions().with_top_sites(true));
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::TOP_SITES));

  // Enable and build custom links.
  base::RunLoop run_loop;
  const std::vector<CustomLinksManager::Link> expected_custom_links(
      {CustomLinksManager::Link{GURL(kTestUrl), kTestTitle,
                                /*is_most_visited=*/true}});
  // First, enable custom links mode. This will trigger a rebuild that still
  // shows top sites, since custom links are not yet initialized.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillOnce(base::test::RunOnceCallback<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTestTitle, kTestUrl)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(testing::InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions().with_custom_links(true));
  run_loop.Run();

  // Then, initialize custom links from the current set of tiles.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(Return(true));
  most_visited_sites_->InitializeCustomLinks();

  // A refresh should now show the custom links.
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillOnce(ReturnRef(expected_custom_links));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->RefreshTiles();
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0],
              MatchesTile(kTestTitle, kTestUrl, TileSource::CUSTOM_LINKS));
}

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest, UpdateEnterpriseShortcut) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  const std::u16string kNewTitle(u"New Site");
  const std::vector<EnterpriseShortcut> initial_links = {
      MakeEnterpriseShortcut(kTestTitle, kTestUrl)};
  const std::vector<EnterpriseShortcut> updated_links = {
      MakeEnterpriseShortcut(kNewTitle, kTestUrl)};
  std::map<SectionType, NTPTilesVector> sections;

  // Initial build.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  SetUpBuildWithEnterpriseShortcuts(initial_links, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  // Update link.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              UpdateLink(GURL(kTestUrl), kNewTitle))
      .WillOnce(Return(true));
  SetUpBuildWithEnterpriseShortcuts(updated_links, &sections);
  most_visited_sites_->UpdateEnterpriseShortcut(GURL(kTestUrl), kNewTitle);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0], MatchesTile(kNewTitle, kTestUrl,
                                    TileSource::ENTERPRISE_SHORTCUTS));
}

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest, DeleteEnterpriseShortcut) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  const std::vector<EnterpriseShortcut> initial_links = {
      MakeEnterpriseShortcut(kTestTitle, kTestUrl)};
  const std::vector<EnterpriseShortcut> empty_links;
  std::map<SectionType, NTPTilesVector> sections;

  // Initial build.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  SetUpBuildWithEnterpriseShortcuts(initial_links, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  // Delete link.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_, DeleteLink(GURL(kTestUrl)))
      .WillOnce(Return(true));
  SetUpBuildWithEnterpriseShortcuts(empty_links, &sections);
  most_visited_sites_->DeleteEnterpriseShortcut(GURL(kTestUrl));
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles, IsEmpty());
}

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest,
       UndoEnterpriseShortcutAction) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  const std::vector<EnterpriseShortcut> initial_links = {
      MakeEnterpriseShortcut(kTestTitle, kTestUrl)};
  const std::vector<EnterpriseShortcut> empty_links;
  std::map<SectionType, NTPTilesVector> sections;

  // Initial build.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  SetUpBuildWithEnterpriseShortcuts(initial_links, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  // Delete link.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_, DeleteLink(GURL(kTestUrl)))
      .WillOnce(Return(true));
  SetUpBuildWithEnterpriseShortcuts(empty_links, &sections);
  most_visited_sites_->DeleteEnterpriseShortcut(GURL(kTestUrl));
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  ASSERT_THAT(sections.at(SectionType::PERSONALIZED), IsEmpty());

  // Undo delete.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_, UndoAction())
      .WillOnce(Return(true));
  SetUpBuildWithEnterpriseShortcuts(initial_links, &sections);
  most_visited_sites_->UndoEnterpriseShortcutAction();
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles.size(), Ge(1ul));
  ASSERT_THAT(tiles[0], MatchesTile(kTestTitle, kTestUrl,
                                    TileSource::ENTERPRISE_SHORTCUTS));
}

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest,
       ShouldNotShowHiddenEnterpriseShortcuts) {
  const char kVisibleUrl[] = "http://site1/";
  const char16_t kVisibleTitle[] = u"Site 1";
  const char kHiddenUrl[] = "http://site2/";
  const char16_t kHiddenTitle[] = u"Site 2";
  const std::vector<EnterpriseShortcut> expected_links = {
      MakeEnterpriseShortcut(kVisibleTitle, kVisibleUrl),
      MakeEnterpriseShortcut(kHiddenTitle, kHiddenUrl,
                             /*allow_user_edit=*/false,
                             /*allow_user_delete=*/false,
                             /*is_hidden_by_user=*/true)};
  std::map<SectionType, NTPTilesVector> sections;

  // Build tiles when enterprise shortcuts is enabled.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  SetUpBuildWithEnterpriseShortcuts(expected_links, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  ASSERT_THAT(tiles, SizeIs(1));
  EXPECT_THAT(tiles[0], MatchesTile(kVisibleTitle, kVisibleUrl,
                                    TileSource::ENTERPRISE_SHORTCUTS));
}

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest,
       ShouldReturnNoTilesWhenNoTypesAreEnabled) {
  const char kTestUrl[] = "http://site1/";
  const char16_t kTestTitle[] = u"Site 1";
  const std::vector<EnterpriseShortcut> initial_links = {
      MakeEnterpriseShortcut(kTestTitle, kTestUrl)};
  const std::vector<EnterpriseShortcut> empty_links;
  std::map<SectionType, NTPTilesVector> sections;

  // Initial build.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  SetUpBuildWithEnterpriseShortcuts(initial_links, &sections);
  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/1);
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  // Disable all types.
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions());
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));

  ASSERT_THAT(sections, Contains(Key(SectionType::PERSONALIZED)));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  EXPECT_THAT(tiles, IsEmpty());
}
TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest,
       ShouldMixEnterpriseShortcutsAndTopSites) {
  const char kEnterpriseUrl[] = "http://enterprise.com/";
  const char16_t kEnterpriseTitle[] = u"Enterprise";
  const char kTopSiteUrl[] = "http://topsite.com/";
  const char16_t kTopSiteTitle[] = u"Top Site";

  const std::vector<EnterpriseShortcut> expected_enterprise_links = {
      MakeEnterpriseShortcut(kEnterpriseTitle, kEnterpriseUrl)};
  const MostVisitedURLList expected_top_sites = {
      MakeMostVisitedURL(kTopSiteTitle, kTopSiteUrl)};
  std::map<SectionType, NTPTilesVector> sections;

  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(_)).WillRepeatedly(Return(false));

  // Get enterprise shortcuts.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_enterprise_links));
  // Followed by top sites.
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(
          base::test::RunOnceCallbackRepeatedly<0>(expected_top_sites));
  // Custom links are not initialized.
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));

  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillRepeatedly(SaveArg<1>(&sections));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions()
          .with_enterprise_shortcuts(true)
          .with_top_sites(true));

  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  EXPECT_THAT(sections.at(SectionType::PERSONALIZED),
              ElementsAre(MatchesTile(kEnterpriseTitle, kEnterpriseUrl,
                                      TileSource::ENTERPRISE_SHORTCUTS),
                          MatchesTile(kTopSiteTitle, kTopSiteUrl,
                                      TileSource::TOP_SITES),
                          MatchesTile(u"PopularSite1", "http://popularsite1/",
                                      TileSource::POPULAR)));
}

TEST_F(MostVisitedSitesWithEnterpriseShortcutsTest,
       ShouldMixEnterpriseAndCustomShortcuts) {
  const char kEnterpriseUrl[] = "http://enterprise.com/";
  const char16_t kEnterpriseTitle[] = u"Enterprise";
  const char kCustomLinkUrl[] = "http://custom.com/";
  const char16_t kCustomLinkTitle[] = u"Custom";
  const char kTopSiteUrl[] = "http://site1/";
  const char16_t kTopSiteTitle[] = u"Site 1";

  const std::vector<EnterpriseShortcut> expected_enterprise_links = {
      MakeEnterpriseShortcut(kEnterpriseTitle, kEnterpriseUrl)};
  const std::vector<CustomLinksManager::Link> expected_custom_links(
      {CustomLinksManager::Link{GURL(kCustomLinkUrl), kCustomLinkTitle}});
  std::map<SectionType, NTPTilesVector> sections;

  EXPECT_CALL(*mock_enterprise_shortcuts_manager_,
              RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_custom_links_manager_, RegisterCallbackForOnChanged(_));
  EXPECT_CALL(*mock_top_sites_, SyncWithHistory());
  EXPECT_CALL(*mock_top_sites_, IsBlocked(_)).WillRepeatedly(Return(false));

  // Get enterprise shortcuts.
  EXPECT_CALL(*mock_enterprise_shortcuts_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_enterprise_links));
  // Custom links are initialized.
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(true));
  EXPECT_CALL(*mock_custom_links_manager_, GetLinks())
      .WillRepeatedly(ReturnRef(expected_custom_links));

  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillRepeatedly(SaveArg<1>(&sections));

  most_visited_sites_->AddMostVisitedURLsObserver(&mock_observer_,
                                                  /*max_num_sites=*/2);
  most_visited_sites_->EnableTileTypes(
      MostVisitedSites::EnableTileTypesOptions()
          .with_enterprise_shortcuts(true)
          .with_custom_links(true));

  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  NTPTilesVector tiles = sections.at(SectionType::PERSONALIZED);
  EXPECT_THAT(tiles, ElementsAre(MatchesTile(kEnterpriseTitle, kEnterpriseUrl,
                                             TileSource::ENTERPRISE_SHORTCUTS),
                                 MatchesTile(kCustomLinkTitle, kCustomLinkUrl,
                                             TileSource::CUSTOM_LINKS)));

  // Uninitialize custom links and rebuild tiles. Tiles should be Top Sites.
  EXPECT_CALL(*mock_custom_links_manager_, Uninitialize());
  EXPECT_CALL(*mock_top_sites_, GetMostVisitedURLs(_))
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<0>(
          MostVisitedURLList{MakeMostVisitedURL(kTopSiteTitle, kTopSiteUrl)}));
  EXPECT_CALL(*mock_custom_links_manager_, IsInitialized())
      .WillRepeatedly(Return(false));
  EXPECT_CALL(mock_observer_, OnURLsAvailable(_, _))
      .WillOnce(SaveArg<1>(&sections));
  most_visited_sites_->UninitializeCustomLinks();
  ASSERT_TRUE(base::test::RunUntil([&] { return !sections.empty(); }));
  tiles = sections.at(SectionType::PERSONALIZED);
  EXPECT_THAT(
      tiles, ElementsAre(
                 MatchesTile(kEnterpriseTitle, kEnterpriseUrl,
                             TileSource::ENTERPRISE_SHORTCUTS),
                 MatchesTile(kTopSiteTitle, kTopSiteUrl, TileSource::TOP_SITES),
                 MatchesTile(u"PopularSite1", "http://popularsite1/",
                             TileSource::POPULAR),
                 MatchesTile(u"PopularSite2", "http://popularsite2/",
                             TileSource::POPULAR)));

  // Initializing custom links should initialize to current tiles
  // excluding enterprise shortcuts.
  EXPECT_CALL(*mock_custom_links_manager_, Initialize(_))
      .WillOnce(DoAll(SaveArg<0>(&tiles), Return(true)));
  most_visited_sites_->InitializeCustomLinks();
  tiles = sections.at(SectionType::PERSONALIZED);
  EXPECT_THAT(
      tiles, ElementsAre(
                 MatchesTile(kEnterpriseTitle, kEnterpriseUrl,
                             TileSource::ENTERPRISE_SHORTCUTS),
                 MatchesTile(kTopSiteTitle, kTopSiteUrl, TileSource::TOP_SITES),
                 MatchesTile(u"PopularSite1", "http://popularsite1/",
                             TileSource::POPULAR),
                 MatchesTile(u"PopularSite2", "http://popularsite2/",
                             TileSource::POPULAR)));
}

#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) ||
        // BUILDFLAG(IS_CHROMEOS)

// This a test for MostVisitedSites::MergeTiles(...) method, and thus has the
// same scope as the method itself. This tests merging popular sites with
// personal tiles.
// More important things out of the scope of testing presently:
// - Removing blocked tiles.
// - Correct host extraction from the URL.
// - Ensuring personal tiles are not duplicated in popular tiles.
TEST(MostVisitedSitesMergeTest, ShouldMergeTilesWithPersonalOnly) {
  std::vector<NTPTile> personal_tiles{
      MakeTile(u"Site 1", "https://www.site1.com/", TileSource::TOP_SITES),
      MakeTile(u"Site 2", "https://www.site2.com/", TileSource::TOP_SITES),
      MakeTile(u"Site 3", "https://www.site3.com/", TileSource::TOP_SITES),
      MakeTile(u"Site 4", "https://www.site4.com/", TileSource::TOP_SITES),
  };
  // Without any popular tiles, the result after merge should be the personal
  // tiles.
  EXPECT_THAT(MostVisitedSites::MergeTiles(std::move(personal_tiles),
                                           /*popular_tiles=*/NTPTilesVector()),
              ElementsAre(MatchesTile(u"Site 1", "https://www.site1.com/",
                                      TileSource::TOP_SITES),
                          MatchesTile(u"Site 2", "https://www.site2.com/",
                                      TileSource::TOP_SITES),
                          MatchesTile(u"Site 3", "https://www.site3.com/",
                                      TileSource::TOP_SITES),
                          MatchesTile(u"Site 4", "https://www.site4.com/",
                                      TileSource::TOP_SITES)));
}

TEST(MostVisitedSitesMergeTest, ShouldMergeTilesWithPopularOnly) {
  std::vector<NTPTile> popular_tiles{
      MakeTile(u"Site 1", "https://www.site1.com/", TileSource::POPULAR),
      MakeTile(u"Site 2", "https://www.site2.com/", TileSource::POPULAR),
      MakeTile(u"Site 3", "https://www.site3.com/", TileSource::POPULAR),
      MakeTile(u"Site 4", "https://www.site4.com/", TileSource::POPULAR),
  };
  // Without any personal tiles, the result after merge should be the popular
  // tiles.
  EXPECT_THAT(
      MostVisitedSites::MergeTiles(/*personal_tiles=*/NTPTilesVector(),
                                   /*popular_tiles=*/std::move(popular_tiles)),
      ElementsAre(
          MatchesTile(u"Site 1", "https://www.site1.com/", TileSource::POPULAR),
          MatchesTile(u"Site 2", "https://www.site2.com/", TileSource::POPULAR),
          MatchesTile(u"Site 3", "https://www.site3.com/", TileSource::POPULAR),
          MatchesTile(u"Site 4", "https://www.site4.com/",
                      TileSource::POPULAR)));
}

TEST(MostVisitedSitesMergeTest, ShouldMergeTilesFavoringPersonalOverPopular) {
  std::vector<NTPTile> popular_tiles{
      MakeTile(u"Site 1", "https://www.site1.com/", TileSource::POPULAR),
      MakeTile(u"Site 2", "https://www.site2.com/", TileSource::POPULAR),
  };
  std::vector<NTPTile> personal_tiles{
      MakeTile(u"Site 3", "https://www.site3.com/", TileSource::TOP_SITES),
      MakeTile(u"Site 4", "https://www.site4.com/", TileSource::TOP_SITES),
  };
  EXPECT_THAT(
      MostVisitedSites::MergeTiles(std::move(personal_tiles),
                                   /*popular_tiles=*/std::move(popular_tiles)),
      ElementsAre(
          MatchesTile(u"Site 3", "https://www.site3.com/",
                      TileSource::TOP_SITES),
          MatchesTile(u"Site 4", "https://www.site4.com/",
                      TileSource::TOP_SITES),
          MatchesTile(u"Site 1", "https://www.site1.com/", TileSource::POPULAR),
          MatchesTile(u"Site 2", "https://www.site2.com/",
                      TileSource::POPULAR)));
}

}  // namespace ntp_tiles
