summaryrefslogtreecommitdiff
path: root/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc
diff options
context:
space:
mode:
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.cc326
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