Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(800)

Side by Side Diff: components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc

Issue 2279123002: [Sync] Initial implementation of foreign sessions suggestions provider. (Closed)
Patch Set: Updating for comments, again! Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/ntp_snippets/sessions/foreign_sessions_suggestions_provider .h"
6
7 #include <map>
8 #include <utility>
9
10 #include "base/callback_forward.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "components/ntp_snippets/category.h"
14 #include "components/ntp_snippets/category_factory.h"
15 #include "components/ntp_snippets/content_suggestions_provider.h"
16 #include "components/ntp_snippets/mock_content_suggestions_provider_observer.h"
17 #include "components/prefs/testing_pref_service.h"
18 #include "components/sessions/core/serialized_navigation_entry.h"
19 #include "components/sessions/core/serialized_navigation_entry_test_helper.h"
20 #include "components/sessions/core/session_types.h"
21 #include "components/sync_sessions/synced_session.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24
25 using base::Time;
26 using base::TimeDelta;
27 using sessions::SerializedNavigationEntry;
28 using sessions::SessionTab;
29 using sessions::SessionWindow;
30 using sync_sessions::SyncedSession;
31 using testing::ElementsAre;
32 using testing::IsEmpty;
33 using testing::Property;
34 using testing::Test;
35 using testing::UnorderedElementsAre;
36 using testing::_;
37
38 namespace ntp_snippets {
39 namespace {
40
41 const char kUrl1[] = "http://www.fake1.com/";
42 const char kUrl2[] = "http://www.fake2.com/";
43 const char kUrl3[] = "http://www.fake3.com/";
44 const char kUrl4[] = "http://www.fake4.com/";
45 const char kUrl5[] = "http://www.fake5.com/";
46 const char kUrl6[] = "http://www.fake6.com/";
47 const char kUrl7[] = "http://www.fake7.com/";
48 const char kUrl8[] = "http://www.fake8.com/";
49 const char kUrl9[] = "http://www.fake9.com/";
50 const char kUrl10[] = "http://www.fake10.com/";
51 const char kUrl11[] = "http://www.fake11.com/";
52 const char kTitle[] = "title is ignored";
53
54 SessionWindow* GetOrCreateWindow(SyncedSession* session, int window_id) {
55 if (session->windows.find(window_id) == session->windows.end()) {
56 // The session deletes the windows it points at upon destruction.
57 session->windows[window_id] = new SessionWindow();
58 }
59 return session->windows[window_id];
60 }
61
62 void AddTabToSession(SyncedSession* session,
63 int window_id,
64 const std::string& url,
65 TimeDelta age) {
66 SerializedNavigationEntry navigation =
67 sessions::SerializedNavigationEntryTestHelper::CreateNavigation(url,
68 kTitle);
69
70 std::unique_ptr<SessionTab> tab = base::MakeUnique<SessionTab>();
71 tab->timestamp = Time::Now() - age;
72 tab->navigations.push_back(navigation);
73
74 SessionWindow* window = GetOrCreateWindow(session, window_id);
75 // The window deletes the tabs it points at upon destruction.
76 window->tabs.push_back(tab.release());
77 }
78
79 class FakeForeignSessionsProvider : public ForeignSessionsProvider {
80 public:
81 ~FakeForeignSessionsProvider() override {}
82 void SetAllForeignSessions(std::vector<const SyncedSession*> sessions) {
83 sessions_ = sessions;
84 change_callback_.Run();
85 }
86
87 // ForeignSessionsProvider implementation.
88 void SubscribeForForeignTabChange(
89 const base::Closure& change_callback) override {
90 change_callback_ = change_callback;
91 }
92 bool HasSessionsData() override { return true; }
93 std::vector<const sync_sessions::SyncedSession*> GetAllForeignSessions()
94 override {
95 return sessions_;
96 }
97
98 private:
99 std::vector<const SyncedSession*> sessions_;
100 base::Closure change_callback_;
101 };
102 } // namespace
103
104 class ForeignSessionsSuggestionsProviderTest : public Test {
105 public:
106 ForeignSessionsSuggestionsProviderTest() {
107 ForeignSessionsSuggestionsProvider::RegisterProfilePrefs(
108 pref_service_.registry());
109
110 std::unique_ptr<FakeForeignSessionsProvider>
111 fake_foreign_sessions_provider =
112 base::MakeUnique<FakeForeignSessionsProvider>();
113 fake_foreign_sessions_provider_ = fake_foreign_sessions_provider.get();
114
115 // During the provider's construction the following mock calls occur.
116 EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty()));
117 EXPECT_CALL(*observer(), OnCategoryStatusChanged(
118 _, category(), CategoryStatus::AVAILABLE));
119
120 provider_ = base::MakeUnique<ForeignSessionsSuggestionsProvider>(
121 &observer_, &category_factory_,
122 std::move(fake_foreign_sessions_provider), &pref_service_);
123 }
124
125 protected:
126 SyncedSession* GetOrCreateSession(int session_id) {
127 if (sessions_map_.find(session_id) == sessions_map_.end()) {
128 std::string id_as_string = base::IntToString(session_id);
129 std::unique_ptr<SyncedSession> owned_session =
130 base::MakeUnique<SyncedSession>();
131 owned_session->session_tag = id_as_string;
132 owned_session->session_name = id_as_string;
133 sessions_map_[session_id] = std::move(owned_session);
134 }
135 return sessions_map_[session_id].get();
136 }
137
138 void AddTab(int session_id,
139 int window_id,
140 const std::string& url,
141 TimeDelta age) {
142 AddTabToSession(GetOrCreateSession(session_id), window_id, url, age);
143 }
144
145 void TriggerOnChange() {
146 std::vector<const SyncedSession*> sessions;
147 for (const auto& kv : sessions_map_) {
148 sessions.push_back(kv.second.get());
149 }
150 fake_foreign_sessions_provider_->SetAllForeignSessions(sessions);
151 }
152
153 void Dismiss(const std::string& url) {
154 // The url of a given suggestion is used as the |within_category_id|.
155 provider_->DismissSuggestion(provider_->MakeUniqueID(category(), url));
156 }
157
158 Category category() {
159 return category_factory_.FromKnownCategory(KnownCategories::FOREIGN_TABS);
160 }
161
162 MockContentSuggestionsProviderObserver* observer() { return &observer_; }
163
164 private:
165 FakeForeignSessionsProvider* fake_foreign_sessions_provider_;
166 MockContentSuggestionsProviderObserver observer_;
167 CategoryFactory category_factory_;
168 TestingPrefServiceSimple pref_service_;
169 std::unique_ptr<ForeignSessionsSuggestionsProvider> provider_;
170 std::map<int, std::unique_ptr<SyncedSession>> sessions_map_;
171
172 DISALLOW_COPY_AND_ASSIGN(ForeignSessionsSuggestionsProviderTest);
173 };
174
175 TEST_F(ForeignSessionsSuggestionsProviderTest, Empty) {
176 // When no sessions data is added, expect no suggestions.
177 EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty()));
178 TriggerOnChange();
179 }
180
181 TEST_F(ForeignSessionsSuggestionsProviderTest, Single) {
182 // Expect a single valid tab because that is what has been added.
183 EXPECT_CALL(*observer(),
184 OnNewSuggestions(
185 _, category(),
186 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)))));
187 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
188 TriggerOnChange();
189 }
190
191 TEST_F(ForeignSessionsSuggestionsProviderTest, Old) {
192 // The only sessions data is too old to be suggested, so expect empty.
193 EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty()));
194 AddTab(0, 0, kUrl1, TimeDelta::FromHours(4));
195 TriggerOnChange();
196 }
197
198 TEST_F(ForeignSessionsSuggestionsProviderTest, Ordered) {
199 // Suggestions ordering should be in reverse chronological order, or youngest
200 // first.
201 EXPECT_CALL(*observer(),
202 OnNewSuggestions(
203 _, category(),
204 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)),
205 Property(&ContentSuggestion::url, GURL(kUrl2)),
206 Property(&ContentSuggestion::url, GURL(kUrl3)),
207 Property(&ContentSuggestion::url, GURL(kUrl4)))));
208 AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2));
209 AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4));
210 AddTab(0, 1, kUrl3, TimeDelta::FromMinutes(3));
211 AddTab(1, 0, kUrl1, TimeDelta::FromMinutes(1));
212 TriggerOnChange();
213 }
214
215 TEST_F(ForeignSessionsSuggestionsProviderTest, MaxPerDevice) {
216 // Each device, which is to equivalent a unique |session_tag|, has a limit to
217 // the number of suggestions it is allowed to contribute. Here all four
218 // suggestions are within the recency threshold, but only three are allowed
219 // per device. As such, expect that the oldest of the four will not be
220 // suggested.
221 EXPECT_CALL(*observer(),
222 OnNewSuggestions(
223 _, category(),
224 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)),
225 Property(&ContentSuggestion::url, GURL(kUrl2)),
226 Property(&ContentSuggestion::url, GURL(kUrl3)))));
227 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
228 AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2));
229 AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3));
230 AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4));
231 TriggerOnChange();
232 }
233
234 TEST_F(ForeignSessionsSuggestionsProviderTest, MaxTotal) {
235 // There's a limit to the total nubmer of suggestions that the provider will
236 // ever return, which should be ten. Here there are eleven valid suggestion
237 // entries, spread out over multiple devices/sessions to avoid the per device
238 // cutoff. Expect that the least recent of the eleven to be dropped.
239 EXPECT_CALL(
240 *observer(),
241 OnNewSuggestions(
242 _, category(),
243 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)),
244 Property(&ContentSuggestion::url, GURL(kUrl2)),
245 Property(&ContentSuggestion::url, GURL(kUrl3)),
246 Property(&ContentSuggestion::url, GURL(kUrl4)),
247 Property(&ContentSuggestion::url, GURL(kUrl5)),
248 Property(&ContentSuggestion::url, GURL(kUrl6)),
249 Property(&ContentSuggestion::url, GURL(kUrl7)),
250 Property(&ContentSuggestion::url, GURL(kUrl8)),
251 Property(&ContentSuggestion::url, GURL(kUrl9)),
252 Property(&ContentSuggestion::url, GURL(kUrl10)))));
253 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
254 AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2));
255 AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3));
256 AddTab(1, 0, kUrl4, TimeDelta::FromMinutes(4));
257 AddTab(1, 0, kUrl5, TimeDelta::FromMinutes(5));
258 AddTab(1, 0, kUrl6, TimeDelta::FromMinutes(6));
259 AddTab(2, 0, kUrl7, TimeDelta::FromMinutes(7));
260 AddTab(2, 0, kUrl8, TimeDelta::FromMinutes(8));
261 AddTab(2, 0, kUrl9, TimeDelta::FromMinutes(9));
262 AddTab(3, 0, kUrl10, TimeDelta::FromMinutes(10));
263 AddTab(3, 0, kUrl11, TimeDelta::FromMinutes(11));
264 TriggerOnChange();
265 }
266
267 TEST_F(ForeignSessionsSuggestionsProviderTest, Duplicates) {
268 // The same url is never suggested more than once at a time. All the session
269 // data has the same url so only expect a single suggestion.
270 EXPECT_CALL(*observer(),
271 OnNewSuggestions(
272 _, category(),
273 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl1)))));
274 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
275 AddTab(0, 1, kUrl1, TimeDelta::FromMinutes(2));
276 AddTab(1, 1, kUrl1, TimeDelta::FromMinutes(3));
277 TriggerOnChange();
278 }
279
280 TEST_F(ForeignSessionsSuggestionsProviderTest, DuplicatesChangingOtherSession) {
281 // Normally |kUrl4| wouldn't show up, because session_id=0 already has 3
282 // younger tabs, but session_id=1 has a younger |kUrl3| which gives |kUrl4| a
283 // spot.
284 EXPECT_CALL(*observer(),
285 OnNewSuggestions(
286 _, category(),
287 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl3)),
288 Property(&ContentSuggestion::url, GURL(kUrl1)),
289 Property(&ContentSuggestion::url, GURL(kUrl2)),
290 Property(&ContentSuggestion::url, GURL(kUrl4)))));
291 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
292 AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2));
293 AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3));
294 AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4));
295 AddTab(1, 0, kUrl3, TimeDelta::FromMinutes(0));
296 TriggerOnChange();
297 }
298
299 TEST_F(ForeignSessionsSuggestionsProviderTest, Dismissed) {
300 // Dimissed urls should not be suggested.
301 EXPECT_CALL(*observer(), OnNewSuggestions(_, category(), IsEmpty()));
302 Dismiss(kUrl1);
303 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
304 TriggerOnChange();
305 }
306
307 TEST_F(ForeignSessionsSuggestionsProviderTest, DismissedChangingOwnSession) {
308 // Similar to DuplicatesChangingOtherSession, without dismissals we would
309 // expect urls 1-3. However, because of dismissals we reach all the down to
310 // |kUrl5| before the per device cutoff is hit.
311 EXPECT_CALL(*observer(),
312 OnNewSuggestions(
313 _, category(),
314 ElementsAre(Property(&ContentSuggestion::url, GURL(kUrl2)),
315 Property(&ContentSuggestion::url, GURL(kUrl3)),
316 Property(&ContentSuggestion::url, GURL(kUrl5)))));
317 Dismiss(kUrl1);
318 Dismiss(kUrl4);
319 AddTab(0, 0, kUrl1, TimeDelta::FromMinutes(1));
320 AddTab(0, 0, kUrl2, TimeDelta::FromMinutes(2));
321 AddTab(0, 0, kUrl3, TimeDelta::FromMinutes(3));
322 AddTab(0, 0, kUrl4, TimeDelta::FromMinutes(4));
323 AddTab(0, 0, kUrl5, TimeDelta::FromMinutes(5));
324 AddTab(0, 0, kUrl6, TimeDelta::FromMinutes(6));
325 TriggerOnChange();
326 }
327
328 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698