OLD | NEW |
---|---|
(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 equivilant a unique |session_tag|, has | |
Marc Treib
2016/09/19 09:24:09
s/which is to equivalent equivilant/which is equiv
skym
2016/09/19 18:54:48
Done.
| |
217 // a limit to the number of suggestions it is allowed to contribute. Here all | |
218 // four suggestions are within the recency threshold, but only three are | |
219 // allowed per device. As such, expect that the oldest of the four will not | |
220 // be 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 | |
OLD | NEW |