diff options
Diffstat (limited to 'chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc')
-rw-r--r-- | chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc new file mode 100644 index 00000000000..e36cf2351d8 --- /dev/null +++ b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc @@ -0,0 +1,326 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h" + +#include <map> +#include <utility> + +#include "base/callback_forward.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "components/ntp_snippets/category.h" +#include "components/ntp_snippets/category_factory.h" +#include "components/ntp_snippets/content_suggestions_provider.h" +#include "components/ntp_snippets/mock_content_suggestions_provider_observer.h" +#include "components/prefs/testing_pref_service.h" +#include "components/sessions/core/serialized_navigation_entry.h" +#include "components/sessions/core/serialized_navigation_entry_test_helper.h" +#include "components/sessions/core/session_types.h" +#include "components/sync_sessions/synced_session.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; +using base::TimeDelta; +using sessions::SerializedNavigationEntry; +using sessions::SessionTab; +using sessions::SessionWindow; +using sync_sessions::SyncedSession; +using testing::ElementsAre; +using testing::IsEmpty; +using testing::Property; +using testing::Test; +using testing::_; + +namespace ntp_snippets { +namespace { + +const char kUrl1[] = "http://www.fake1.com/"; +const char kUrl2[] = "http://www.fake2.com/"; +const char kUrl3[] = "http://www.fake3.com/"; +const char kUrl4[] = "http://www.fake4.com/"; +const char kUrl5[] = "http://www.fake5.com/"; +const char kUrl6[] = "http://www.fake6.com/"; +const char kUrl7[] = "http://www.fake7.com/"; +const char kUrl8[] = "http://www.fake8.com/"; +const char kUrl9[] = "http://www.fake9.com/"; +const char kUrl10[] = "http://www.fake10.com/"; +const char kUrl11[] = "http://www.fake11.com/"; +const char kTitle[] = "title is ignored"; + +SessionWindow* GetOrCreateWindow(SyncedSession* session, int window_id) { + if (session->windows.find(window_id) == session->windows.end()) + session->windows[window_id] = base::MakeUnique<SessionWindow>(); + + return session->windows[window_id].get(); +} + +void AddTabToSession(SyncedSession* session, + int window_id, + const std::string& url, + TimeDelta age) { + SerializedNavigationEntry navigation = + sessions::SerializedNavigationEntryTestHelper::CreateNavigation(url, + kTitle); + + std::unique_ptr<SessionTab> tab = base::MakeUnique<SessionTab>(); + tab->timestamp = Time::Now() - age; + tab->navigations.push_back(navigation); + + SessionWindow* window = GetOrCreateWindow(session, window_id); + // The window deletes the tabs it points at upon destruction. + window->tabs.push_back(std::move(tab)); +} + +class FakeForeignSessionsProvider : public ForeignSessionsProvider { + public: + ~FakeForeignSessionsProvider() override = default; + void SetAllForeignSessions(std::vector<const SyncedSession*> sessions) { + sessions_ = std::move(sessions); + change_callback_.Run(); + } + + // ForeignSessionsProvider implementation. + void SubscribeForForeignTabChange( + const base::Closure& change_callback) override { + change_callback_ = change_callback; + } + bool HasSessionsData() override { return true; } + std::vector<const sync_sessions::SyncedSession*> GetAllForeignSessions() + override { + return sessions_; + } + + private: + std::vector<const SyncedSession*> sessions_; + base::Closure change_callback_; +}; +} // namespace + +class ForeignSessionsSuggestionsProviderTest : public Test { + public: + ForeignSessionsSuggestionsProviderTest() { + ForeignSessionsSuggestionsProvider::RegisterProfilePrefs( + pref_service_.registry()); + + std::unique_ptr<FakeForeignSessionsProvider> + fake_foreign_sessions_provider = + base::MakeUnique<FakeForeignSessionsProvider>(); + fake_foreign_sessions_provider_ = fake_foreign_sessions_provider.get(); + + // During the provider's construction the following mock calls occur. + EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty())); + EXPECT_CALL(*observer(), OnCategoryStatusChanged( + _, category(), CategoryStatus::AVAILABLE)); + + provider_ = base::MakeUnique<ForeignSessionsSuggestionsProvider>( + &observer_, &category_factory_, + std::move(fake_foreign_sessions_provider), &pref_service_); + } + + protected: + SyncedSession* GetOrCreateSession(int session_id) { + if (sessions_map_.find(session_id) == sessions_map_.end()) { + std::string id_as_string = base::IntToString(session_id); + std::unique_ptr<SyncedSession> owned_session = + base::MakeUnique<SyncedSession>(); + owned_session->session_tag = id_as_string; + owned_session->session_name = id_as_string; + sessions_map_[session_id] = std::move(owned_session); + } + return sessions_map_[session_id].get(); + } + + void AddTab(int session_id, + int window_id, + const std::string& url, + TimeDelta age) { + AddTabToSession(GetOrCreateSession(session_id), window_id, url, age); + } + + void TriggerOnChange() { + std::vector<const SyncedSession*> sessions; + for (const auto& kv : sessions_map_) { + sessions.push_back(kv.second.get()); + } + fake_foreign_sessions_provider_->SetAllForeignSessions(std::move(sessions)); + } + + void Dismiss(const std::string& url) { + // The url of a given suggestion is used as the |id_within_category|. + provider_->DismissSuggestion(ContentSuggestion::ID(category(), url)); + } + + Category category() { + return category_factory_.FromKnownCategory(KnownCategories::FOREIGN_TABS); + } + + MockContentSuggestionsProviderObserver* observer() { return &observer_; } + + private: + FakeForeignSessionsProvider* fake_foreign_sessions_provider_; + MockContentSuggestionsProviderObserver observer_; + CategoryFactory category_factory_; + TestingPrefServiceSimple pref_service_; + std::unique_ptr<ForeignSessionsSuggestionsProvider> provider_; + std::map<int, std::unique_ptr<SyncedSession>> sessions_map_; + + DISALLOW_COPY_AND_ASSIGN(ForeignSessionsSuggestionsProviderTest); +}; + +TEST_F(ForeignSessionsSuggestionsProviderTest, Empty) { + // When no sessions data is added, expect no suggestions. + EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty())); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, Single) { + // Expect a single valid tab because that is what has been added. + EXPECT_CALL(*observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1))))); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, Old) { + // The only sessions data is too old to be suggested, so expect empty. + EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty())); + AddTab(0, 0, kUrl1, TimeDelta::FromHours(4)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, Ordered) { + // Suggestions ordering should be in reverse chronological order, or youngest + // first. + EXPECT_CALL(*observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)), + Property(&ContentSuggestion::url, GURL(kUrl2)), + Property(&ContentSuggestion::url, GURL(kUrl3)), + Property(&ContentSuggestion::url, GURL(kUrl4))))); + AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2)); + AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4)); + AddTab(0, 1, kUrl3, TimeDelta::FromMinutes(3)); + AddTab(1, 0, kUrl1, TimeDelta::FromMinutes(1)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, MaxPerDevice) { + // Each device, which is to equivalent a unique |session_tag|, has a limit to + // the number of suggestions it is allowed to contribute. Here all four + // suggestions are within the recency threshold, but only three are allowed + // per device. As such, expect that the oldest of the four will not be + // suggested. + EXPECT_CALL(*observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)), + Property(&ContentSuggestion::url, GURL(kUrl2)), + Property(&ContentSuggestion::url, GURL(kUrl3))))); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2)); + AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3)); + AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, MaxTotal) { + // There's a limit to the total nubmer of suggestions that the provider will + // ever return, which should be ten. Here there are eleven valid suggestion + // entries, spread out over multiple devices/sessions to avoid the per device + // cutoff. Expect that the least recent of the eleven to be dropped. + EXPECT_CALL( + *observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)), + Property(&ContentSuggestion::url, GURL(kUrl2)), + Property(&ContentSuggestion::url, GURL(kUrl3)), + Property(&ContentSuggestion::url, GURL(kUrl4)), + Property(&ContentSuggestion::url, GURL(kUrl5)), + Property(&ContentSuggestion::url, GURL(kUrl6)), + Property(&ContentSuggestion::url, GURL(kUrl7)), + Property(&ContentSuggestion::url, GURL(kUrl8)), + Property(&ContentSuggestion::url, GURL(kUrl9)), + Property(&ContentSuggestion::url, GURL(kUrl10))))); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2)); + AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3)); + AddTab(1, 0, kUrl4, TimeDelta::FromMinutes(4)); + AddTab(1, 0, kUrl5, TimeDelta::FromMinutes(5)); + AddTab(1, 0, kUrl6, TimeDelta::FromMinutes(6)); + AddTab(2, 0, kUrl7, TimeDelta::FromMinutes(7)); + AddTab(2, 0, kUrl8, TimeDelta::FromMinutes(8)); + AddTab(2, 0, kUrl9, TimeDelta::FromMinutes(9)); + AddTab(3, 0, kUrl10, TimeDelta::FromMinutes(10)); + AddTab(3, 0, kUrl11, TimeDelta::FromMinutes(11)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, Duplicates) { + // The same url is never suggested more than once at a time. All the session + // data has the same url so only expect a single suggestion. + EXPECT_CALL(*observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1))))); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + AddTab(0, 1, kUrl1, TimeDelta::FromMinutes(2)); + AddTab(1, 1, kUrl1, TimeDelta::FromMinutes(3)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, DuplicatesChangingOtherSession) { + // Normally |kUrl4| wouldn't show up, because session_id=0 already has 3 + // younger tabs, but session_id=1 has a younger |kUrl3| which gives |kUrl4| a + // spot. + EXPECT_CALL(*observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl3)), + Property(&ContentSuggestion::url, GURL(kUrl1)), + Property(&ContentSuggestion::url, GURL(kUrl2)), + Property(&ContentSuggestion::url, GURL(kUrl4))))); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2)); + AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3)); + AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4)); + AddTab(1, 0, kUrl3, TimeDelta::FromMinutes(0)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, Dismissed) { + // Dimissed urls should not be suggested. + EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty())); + Dismiss(kUrl1); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + TriggerOnChange(); +} + +TEST_F(ForeignSessionsSuggestionsProviderTest, DismissedChangingOwnSession) { + // Similar to DuplicatesChangingOtherSession, without dismissals we would + // expect urls 1-3. However, because of dismissals we reach all the down to + // |kUrl5| before the per device cutoff is hit. + EXPECT_CALL(*observer(), + OnNewSuggestions( + _, category(), + ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl2)), + Property(&ContentSuggestion::url, GURL(kUrl3)), + Property(&ContentSuggestion::url, GURL(kUrl5))))); + Dismiss(kUrl1); + Dismiss(kUrl4); + AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1)); + AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2)); + AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3)); + AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4)); + AddTab(0, 0, kUrl5, TimeDelta::FromMinutes(5)); + AddTab(0, 0, kUrl6, TimeDelta::FromMinutes(6)); + TriggerOnChange(); +} + +} // namespace ntp_snippets |