OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/ntp_snippets/content_suggestions_service.h" | 5 #include "components/ntp_snippets/content_suggestions_service.h" |
6 | 6 |
7 #include <memory> | 7 #include <memory> |
8 #include <utility> | 8 #include <utility> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
70 void FireCategoryStatusChanged(Category category, CategoryStatus new_status) { | 70 void FireCategoryStatusChanged(Category category, CategoryStatus new_status) { |
71 statuses_[category.id()] = new_status; | 71 statuses_[category.id()] = new_status; |
72 observer()->OnCategoryStatusChanged(this, category, new_status); | 72 observer()->OnCategoryStatusChanged(this, category, new_status); |
73 } | 73 } |
74 | 74 |
75 void FireCategoryStatusChangedWithCurrentStatus(Category category) { | 75 void FireCategoryStatusChangedWithCurrentStatus(Category category) { |
76 observer()->OnCategoryStatusChanged(this, category, | 76 observer()->OnCategoryStatusChanged(this, category, |
77 statuses_[category.id()]); | 77 statuses_[category.id()]); |
78 } | 78 } |
79 | 79 |
80 void FireSuggestionInvalidated(Category category, | 80 void FireSuggestionInvalidated(const ContentSuggestion::ID& suggestion_id) { |
81 const std::string& suggestion_id) { | 81 observer()->OnSuggestionInvalidated(this, suggestion_id); |
82 observer()->OnSuggestionInvalidated(this, category, suggestion_id); | |
83 } | 82 } |
84 | 83 |
85 MOCK_METHOD3(ClearHistory, | 84 MOCK_METHOD3(ClearHistory, |
86 void(base::Time begin, | 85 void(base::Time begin, |
87 base::Time end, | 86 base::Time end, |
88 const base::Callback<bool(const GURL& url)>& filter)); | 87 const base::Callback<bool(const GURL& url)>& filter)); |
89 MOCK_METHOD1(ClearCachedSuggestions, void(Category category)); | 88 MOCK_METHOD1(ClearCachedSuggestions, void(Category category)); |
90 MOCK_METHOD2(GetDismissedSuggestionsForDebugging, | 89 MOCK_METHOD2(GetDismissedSuggestionsForDebugging, |
91 void(Category category, | 90 void(Category category, |
92 const DismissedSuggestionsCallback& callback)); | 91 const DismissedSuggestionsCallback& callback)); |
93 MOCK_METHOD1(ClearDismissedSuggestionsForDebugging, void(Category category)); | 92 MOCK_METHOD1(ClearDismissedSuggestionsForDebugging, void(Category category)); |
94 MOCK_METHOD1(DismissSuggestion, void(const std::string& suggestion_id)); | 93 MOCK_METHOD1(DismissSuggestion, |
| 94 void(const ContentSuggestion::ID& suggestion_id)); |
95 MOCK_METHOD2(FetchSuggestionImage, | 95 MOCK_METHOD2(FetchSuggestionImage, |
96 void(const std::string& suggestion_id, | 96 void(const ContentSuggestion::ID& suggestion_id, |
97 const ImageFetchedCallback& callback)); | 97 const ImageFetchedCallback& callback)); |
98 | 98 |
99 private: | 99 private: |
100 std::vector<Category> provided_categories_; | 100 std::vector<Category> provided_categories_; |
101 std::map<int, CategoryStatus> statuses_; | 101 std::map<int, CategoryStatus> statuses_; |
102 }; | 102 }; |
103 | 103 |
104 class MockServiceObserver : public ContentSuggestionsService::Observer { | 104 class MockServiceObserver : public ContentSuggestionsService::Observer { |
105 public: | 105 public: |
106 MockServiceObserver() = default; | 106 MockServiceObserver() = default; |
107 ~MockServiceObserver() override = default; | 107 ~MockServiceObserver() override = default; |
108 | 108 |
109 MOCK_METHOD1(OnNewSuggestions, void(Category category)); | 109 MOCK_METHOD1(OnNewSuggestions, void(Category category)); |
110 MOCK_METHOD2(OnCategoryStatusChanged, | 110 MOCK_METHOD2(OnCategoryStatusChanged, |
111 void(Category changed_category, CategoryStatus new_status)); | 111 void(Category changed_category, CategoryStatus new_status)); |
112 MOCK_METHOD2(OnSuggestionInvalidated, | 112 MOCK_METHOD1(OnSuggestionInvalidated, |
113 void(Category category, const std::string& suggestion_id)); | 113 void(const ContentSuggestion::ID& suggestion_id)); |
114 MOCK_METHOD0(ContentSuggestionsServiceShutdown, void()); | 114 MOCK_METHOD0(ContentSuggestionsServiceShutdown, void()); |
115 | 115 |
116 private: | 116 private: |
117 DISALLOW_COPY_AND_ASSIGN(MockServiceObserver); | 117 DISALLOW_COPY_AND_ASSIGN(MockServiceObserver); |
118 }; | 118 }; |
119 | 119 |
120 } // namespace | 120 } // namespace |
121 | 121 |
122 class ContentSuggestionsServiceTest : public testing::Test { | 122 class ContentSuggestionsServiceTest : public testing::Test { |
123 public: | 123 public: |
(...skipping 12 matching lines...) Expand all Loading... |
136 // returned by the service for the given |category|. | 136 // returned by the service for the given |category|. |
137 void ExpectThatSuggestionsAre(Category category, std::vector<int> numbers) { | 137 void ExpectThatSuggestionsAre(Category category, std::vector<int> numbers) { |
138 std::vector<Category> categories = service()->GetCategories(); | 138 std::vector<Category> categories = service()->GetCategories(); |
139 auto position = std::find(categories.begin(), categories.end(), category); | 139 auto position = std::find(categories.begin(), categories.end(), category); |
140 if (!numbers.empty()) { | 140 if (!numbers.empty()) { |
141 EXPECT_NE(categories.end(), position); | 141 EXPECT_NE(categories.end(), position); |
142 } | 142 } |
143 | 143 |
144 for (const auto& suggestion : | 144 for (const auto& suggestion : |
145 service()->GetSuggestionsForCategory(category)) { | 145 service()->GetSuggestionsForCategory(category)) { |
146 std::string within_category_id = | 146 std::string id_within_category = suggestion.id().id_within_category(); |
147 service()->category_factory()->GetWithinCategoryIDFromUniqueID( | |
148 suggestion.id()); | |
149 int id; | 147 int id; |
150 ASSERT_TRUE(base::StringToInt(within_category_id, &id)); | 148 ASSERT_TRUE(base::StringToInt(id_within_category, &id)); |
151 auto position = std::find(numbers.begin(), numbers.end(), id); | 149 auto position = std::find(numbers.begin(), numbers.end(), id); |
152 if (position == numbers.end()) { | 150 if (position == numbers.end()) { |
153 ADD_FAILURE() << "Unexpected suggestion with ID " << id; | 151 ADD_FAILURE() << "Unexpected suggestion with ID " << id; |
154 } else { | 152 } else { |
155 numbers.erase(position); | 153 numbers.erase(position); |
156 } | 154 } |
157 } | 155 } |
158 for (int number : numbers) { | 156 for (int number : numbers) { |
159 ADD_FAILURE() << "Suggestion number " << number | 157 ADD_FAILURE() << "Suggestion number " << number |
160 << " not present, though expected"; | 158 << " not present, though expected"; |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
198 service_.reset(new ContentSuggestionsService(enabled, | 196 service_.reset(new ContentSuggestionsService(enabled, |
199 nullptr /* history_service */, | 197 nullptr /* history_service */, |
200 nullptr /* pref_service */)); | 198 nullptr /* pref_service */)); |
201 } | 199 } |
202 | 200 |
203 ContentSuggestionsService* service() { return service_.get(); } | 201 ContentSuggestionsService* service() { return service_.get(); } |
204 | 202 |
205 // Returns a suggestion instance for testing. | 203 // Returns a suggestion instance for testing. |
206 ContentSuggestion CreateSuggestion(Category category, int number) { | 204 ContentSuggestion CreateSuggestion(Category category, int number) { |
207 return ContentSuggestion( | 205 return ContentSuggestion( |
208 service()->category_factory()->MakeUniqueID(category, | 206 category, base::IntToString(number), |
209 base::IntToString(number)), | |
210 GURL("http://testsuggestion/" + base::IntToString(number))); | 207 GURL("http://testsuggestion/" + base::IntToString(number))); |
211 } | 208 } |
212 | 209 |
213 std::vector<ContentSuggestion> CreateSuggestions( | 210 std::vector<ContentSuggestion> CreateSuggestions( |
214 Category category, | 211 Category category, |
215 const std::vector<int>& numbers) { | 212 const std::vector<int>& numbers) { |
216 std::vector<ContentSuggestion> result; | 213 std::vector<ContentSuggestion> result; |
217 for (int number : numbers) { | 214 for (int number : numbers) { |
218 result.push_back(CreateSuggestion(category, number)); | 215 result.push_back(CreateSuggestion(category, number)); |
219 } | 216 } |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
292 | 289 |
293 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) { | 290 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectFetchSuggestionImage) { |
294 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); | 291 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
295 Category offline_pages_category = | 292 Category offline_pages_category = |
296 FromKnownCategory(KnownCategories::DOWNLOADS); | 293 FromKnownCategory(KnownCategories::DOWNLOADS); |
297 MockProvider* provider1 = RegisterProvider(articles_category); | 294 MockProvider* provider1 = RegisterProvider(articles_category); |
298 MockProvider* provider2 = RegisterProvider(offline_pages_category); | 295 MockProvider* provider2 = RegisterProvider(offline_pages_category); |
299 | 296 |
300 provider1->FireSuggestionsChanged(articles_category, | 297 provider1->FireSuggestionsChanged(articles_category, |
301 CreateSuggestions(articles_category, {1})); | 298 CreateSuggestions(articles_category, {1})); |
302 std::string suggestion_id = CreateSuggestion(articles_category, 1).id(); | 299 ContentSuggestion::ID suggestion_id(articles_category, "1"); |
303 | 300 |
304 EXPECT_CALL(*provider1, FetchSuggestionImage(suggestion_id, _)); | 301 EXPECT_CALL(*provider1, FetchSuggestionImage(suggestion_id, _)); |
305 EXPECT_CALL(*provider2, FetchSuggestionImage(_, _)).Times(0); | 302 EXPECT_CALL(*provider2, FetchSuggestionImage(_, _)).Times(0); |
306 service()->FetchSuggestionImage( | 303 service()->FetchSuggestionImage( |
307 suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, | 304 suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, |
308 base::Unretained(this))); | 305 base::Unretained(this))); |
309 } | 306 } |
310 | 307 |
311 TEST_F(ContentSuggestionsServiceTest, | 308 TEST_F(ContentSuggestionsServiceTest, |
312 ShouldCallbackEmptyImageForUnavailableProvider) { | 309 ShouldCallbackEmptyImageForUnavailableProvider) { |
313 // Setup the current thread's MessageLoop. | 310 // Setup the current thread's MessageLoop. |
314 base::MessageLoop message_loop; | 311 base::MessageLoop message_loop; |
315 | 312 |
316 base::RunLoop run_loop; | 313 base::RunLoop run_loop; |
317 // Assuming there will never be a category with the id below. | 314 // Assuming there will never be a category with the id below. |
318 std::string suggestion_id = "21563|TestID"; | 315 ContentSuggestion::ID suggestion_id(category_factory()->FromIDValue(21563), |
| 316 "TestID"); |
319 EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true)))) | 317 EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true)))) |
320 .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); | 318 .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
321 service()->FetchSuggestionImage( | 319 service()->FetchSuggestionImage( |
322 suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, | 320 suggestion_id, base::Bind(&ContentSuggestionsServiceTest::OnImageFetched, |
323 base::Unretained(this))); | 321 base::Unretained(this))); |
324 run_loop.Run(); | 322 run_loop.Run(); |
325 } | 323 } |
326 | 324 |
327 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectDismissSuggestion) { | 325 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectDismissSuggestion) { |
328 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); | 326 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
329 Category offline_pages_category = | 327 Category offline_pages_category = |
330 FromKnownCategory(KnownCategories::DOWNLOADS); | 328 FromKnownCategory(KnownCategories::DOWNLOADS); |
331 MockProvider* provider1 = RegisterProvider(articles_category); | 329 MockProvider* provider1 = RegisterProvider(articles_category); |
332 MockProvider* provider2 = RegisterProvider(offline_pages_category); | 330 MockProvider* provider2 = RegisterProvider(offline_pages_category); |
333 | 331 |
334 provider2->FireSuggestionsChanged( | 332 provider2->FireSuggestionsChanged( |
335 offline_pages_category, CreateSuggestions(offline_pages_category, {11})); | 333 offline_pages_category, CreateSuggestions(offline_pages_category, {11})); |
336 std::string suggestion_id = CreateSuggestion(offline_pages_category, 11).id(); | 334 ContentSuggestion::ID suggestion_id(offline_pages_category, "11"); |
337 | 335 |
338 EXPECT_CALL(*provider1, DismissSuggestion(_)).Times(0); | 336 EXPECT_CALL(*provider1, DismissSuggestion(_)).Times(0); |
339 EXPECT_CALL(*provider2, DismissSuggestion(suggestion_id)); | 337 EXPECT_CALL(*provider2, DismissSuggestion(suggestion_id)); |
340 service()->DismissSuggestion(suggestion_id); | 338 service()->DismissSuggestion(suggestion_id); |
341 } | 339 } |
342 | 340 |
343 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectSuggestionInvalidated) { | 341 TEST_F(ContentSuggestionsServiceTest, ShouldRedirectSuggestionInvalidated) { |
344 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); | 342 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
345 | 343 |
346 MockProvider* provider = RegisterProvider(articles_category); | 344 MockProvider* provider = RegisterProvider(articles_category); |
347 MockServiceObserver observer; | 345 MockServiceObserver observer; |
348 service()->AddObserver(&observer); | 346 service()->AddObserver(&observer); |
349 | 347 |
350 provider->FireSuggestionsChanged( | 348 provider->FireSuggestionsChanged( |
351 articles_category, CreateSuggestions(articles_category, {11, 12, 13})); | 349 articles_category, CreateSuggestions(articles_category, {11, 12, 13})); |
352 ExpectThatSuggestionsAre(articles_category, {11, 12, 13}); | 350 ExpectThatSuggestionsAre(articles_category, {11, 12, 13}); |
353 | 351 |
354 std::string suggestion_id = CreateSuggestion(articles_category, 12).id(); | 352 ContentSuggestion::ID suggestion_id(articles_category, "12"); |
355 EXPECT_CALL(observer, | 353 EXPECT_CALL(observer, OnSuggestionInvalidated(suggestion_id)); |
356 OnSuggestionInvalidated(articles_category, suggestion_id)); | 354 provider->FireSuggestionInvalidated(suggestion_id); |
357 provider->FireSuggestionInvalidated(articles_category, suggestion_id); | |
358 ExpectThatSuggestionsAre(articles_category, {11, 13}); | 355 ExpectThatSuggestionsAre(articles_category, {11, 13}); |
359 Mock::VerifyAndClearExpectations(&observer); | 356 Mock::VerifyAndClearExpectations(&observer); |
360 | 357 |
361 // Unknown IDs must be forwarded (though no change happens to the service's | 358 // Unknown IDs must be forwarded (though no change happens to the service's |
362 // internal data structures) because previously opened UIs, which can still | 359 // internal data structures) because previously opened UIs, which can still |
363 // show the invalidated suggestion, must be notified. | 360 // show the invalidated suggestion, must be notified. |
364 std::string unknown_id = CreateSuggestion(articles_category, 1234).id(); | 361 ContentSuggestion::ID unknown_id(articles_category, "1234"); |
365 EXPECT_CALL(observer, OnSuggestionInvalidated(articles_category, unknown_id)); | 362 EXPECT_CALL(observer, OnSuggestionInvalidated(unknown_id)); |
366 provider->FireSuggestionInvalidated(articles_category, unknown_id); | 363 provider->FireSuggestionInvalidated(unknown_id); |
367 ExpectThatSuggestionsAre(articles_category, {11, 13}); | 364 ExpectThatSuggestionsAre(articles_category, {11, 13}); |
368 Mock::VerifyAndClearExpectations(&observer); | 365 Mock::VerifyAndClearExpectations(&observer); |
369 | 366 |
370 service()->RemoveObserver(&observer); | 367 service()->RemoveObserver(&observer); |
371 } | 368 } |
372 | 369 |
373 TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) { | 370 TEST_F(ContentSuggestionsServiceTest, ShouldForwardSuggestions) { |
374 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); | 371 Category articles_category = FromKnownCategory(KnownCategories::ARTICLES); |
375 Category offline_pages_category = | 372 Category offline_pages_category = |
376 FromKnownCategory(KnownCategories::DOWNLOADS); | 373 FromKnownCategory(KnownCategories::DOWNLOADS); |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
569 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); | 566 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
570 // Dismiss the second suggestion; now bookmarks should go back to the end. | 567 // Dismiss the second suggestion; now bookmarks should go back to the end. |
571 service()->DismissSuggestion(CreateSuggestion(bookmarks, 2).id()); | 568 service()->DismissSuggestion(CreateSuggestion(bookmarks, 2).id()); |
572 EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); | 569 EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
573 | 570 |
574 // Same thing, but invalidate instead of dismissing. | 571 // Same thing, but invalidate instead of dismissing. |
575 bookmarks_provider->FireSuggestionsChanged( | 572 bookmarks_provider->FireSuggestionsChanged( |
576 bookmarks, CreateSuggestions(bookmarks, {1, 2})); | 573 bookmarks, CreateSuggestions(bookmarks, {1, 2})); |
577 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); | 574 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
578 bookmarks_provider->FireSuggestionInvalidated( | 575 bookmarks_provider->FireSuggestionInvalidated( |
579 bookmarks, CreateSuggestion(bookmarks, 1).id()); | 576 ContentSuggestion::ID(bookmarks, "1")); |
580 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); | 577 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
581 bookmarks_provider->FireSuggestionInvalidated( | 578 bookmarks_provider->FireSuggestionInvalidated( |
582 bookmarks, CreateSuggestion(bookmarks, 2).id()); | 579 ContentSuggestion::ID(bookmarks, "2")); |
583 EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); | 580 EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
584 | 581 |
585 // Same thing, but now the bookmarks category updates "naturally". | 582 // Same thing, but now the bookmarks category updates "naturally". |
586 bookmarks_provider->FireSuggestionsChanged( | 583 bookmarks_provider->FireSuggestionsChanged( |
587 bookmarks, CreateSuggestions(bookmarks, {1, 2})); | 584 bookmarks, CreateSuggestions(bookmarks, {1, 2})); |
588 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); | 585 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
589 bookmarks_provider->FireSuggestionsChanged(bookmarks, | 586 bookmarks_provider->FireSuggestionsChanged(bookmarks, |
590 CreateSuggestions(bookmarks, {1})); | 587 CreateSuggestions(bookmarks, {1})); |
591 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); | 588 EXPECT_THAT(service()->GetCategories(), ElementsAre(bookmarks, remote)); |
592 bookmarks_provider->FireSuggestionsChanged( | 589 bookmarks_provider->FireSuggestionsChanged( |
593 bookmarks, CreateSuggestions(bookmarks, std::vector<int>())); | 590 bookmarks, CreateSuggestions(bookmarks, std::vector<int>())); |
594 EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); | 591 EXPECT_THAT(service()->GetCategories(), ElementsAre(remote, bookmarks)); |
595 } | 592 } |
596 | 593 |
597 TEST_F(ContentSuggestionsServiceTest, ShouldForwardClearHistory) { | 594 TEST_F(ContentSuggestionsServiceTest, ShouldForwardClearHistory) { |
598 Category category = FromKnownCategory(KnownCategories::DOWNLOADS); | 595 Category category = FromKnownCategory(KnownCategories::DOWNLOADS); |
599 MockProvider* provider = RegisterProvider(category); | 596 MockProvider* provider = RegisterProvider(category); |
600 base::Time begin = base::Time::FromTimeT(123), | 597 base::Time begin = base::Time::FromTimeT(123), |
601 end = base::Time::FromTimeT(456); | 598 end = base::Time::FromTimeT(456); |
602 EXPECT_CALL(*provider, ClearHistory(begin, end, _)); | 599 EXPECT_CALL(*provider, ClearHistory(begin, end, _)); |
603 base::Callback<bool(const GURL& url)> filter; | 600 base::Callback<bool(const GURL& url)> filter; |
604 service()->ClearHistory(begin, end, filter); | 601 service()->ClearHistory(begin, end, filter); |
605 } | 602 } |
606 | 603 |
607 } // namespace ntp_snippets | 604 } // namespace ntp_snippets |
OLD | NEW |