Chromium Code Reviews| 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 "chrome/browser/ntp_snippets/download_suggestions_provider.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/observer_list.h" | |
| 9 #include "base/strings/string_number_conversions.h" | |
| 10 #include "chrome/browser/ntp_snippets/fake_download_item.h" | |
| 11 #include "components/ntp_snippets/category.h" | |
| 12 #include "components/ntp_snippets/category_factory.h" | |
| 13 #include "components/ntp_snippets/mock_content_suggestions_provider_observer.h" | |
| 14 #include "components/ntp_snippets/offline_pages/offline_pages_test_utils.h" | |
| 15 #include "components/offline_pages/client_namespace_constants.h" | |
| 16 #include "components/prefs/testing_pref_service.h" | |
| 17 #include "content/public/test/mock_download_item.h" | |
| 18 #include "content/public/test/mock_download_manager.h" | |
| 19 #include "testing/gtest/include/gtest/gtest.h" | |
| 20 | |
| 21 using content::DownloadItem; | |
| 22 using content::MockDownloadManager; | |
| 23 using ntp_snippets::Category; | |
| 24 using ntp_snippets::CategoryFactory; | |
| 25 using ntp_snippets::ContentSuggestion; | |
| 26 using ntp_snippets::ContentSuggestionsProvider; | |
| 27 using ntp_snippets::MockContentSuggestionsProviderObserver; | |
| 28 using ntp_snippets::OfflinePageProxy; | |
| 29 using ntp_snippets::test::CaptureDismissedSuggestions; | |
| 30 using ntp_snippets::test::FakeOfflinePageModel; | |
| 31 using ntp_snippets::CategoryStatus; | |
| 32 using offline_pages::ClientId; | |
| 33 using offline_pages::OfflinePageItem; | |
| 34 using test::FakeDownloadItem; | |
| 35 using testing::_; | |
| 36 using testing::AnyNumber; | |
| 37 using testing::ElementsAre; | |
| 38 using testing::IsEmpty; | |
| 39 using testing::Mock; | |
| 40 using testing::Return; | |
| 41 using testing::SizeIs; | |
| 42 using testing::StrictMock; | |
| 43 using testing::UnorderedElementsAre; | |
| 44 using testing::Lt; | |
| 45 | |
| 46 namespace ntp_snippets { | |
| 47 // These functions are implicitly used to print out values during the tests. | |
| 48 void PrintTo(const ContentSuggestion& value, std::ostream* os) { | |
| 49 *os << "{ url: " << value.url() << ", publish_date: " << value.publish_date() | |
| 50 << "}"; | |
| 51 } | |
| 52 | |
| 53 void PrintTo(const CategoryStatus& value, std::ostream* os) { | |
|
Marc Treib
2016/10/17 10:18:41
If multiple tests need this, it's also okay to def
vitaliii
2016/10/26 00:07:55
I will do this as a part of clean up later.
| |
| 54 *os << "CategoryStatus::"; | |
| 55 switch (value) { | |
| 56 case CategoryStatus::INITIALIZING: | |
| 57 *os << "INITIALIZING"; | |
| 58 break; | |
| 59 case CategoryStatus::AVAILABLE: | |
| 60 *os << "AVAILABLE"; | |
| 61 break; | |
| 62 case CategoryStatus::AVAILABLE_LOADING: | |
| 63 *os << "AVAILABLE_LOADING"; | |
| 64 break; | |
| 65 case CategoryStatus::NOT_PROVIDED: | |
| 66 *os << "NOT_PROVIDED"; | |
| 67 break; | |
| 68 case CategoryStatus::ALL_SUGGESTIONS_EXPLICITLY_DISABLED: | |
| 69 *os << "ALL_SUGGESTIONS_EXPLICITLY_DISABLED"; | |
| 70 break; | |
| 71 case CategoryStatus::CATEGORY_EXPLICITLY_DISABLED: | |
| 72 *os << "CATEGORY_EXPLICITLY_DISABLED"; | |
| 73 break; | |
| 74 case CategoryStatus::SIGNED_OUT: | |
| 75 *os << "SIGNED_OUT"; | |
| 76 break; | |
| 77 case CategoryStatus::LOADING_ERROR: | |
| 78 *os << "LOADING_ERROR"; | |
| 79 break; | |
| 80 } | |
| 81 } | |
| 82 | |
| 83 } // namespace ntp_snippets | |
| 84 | |
| 85 namespace { | |
| 86 | |
| 87 // TODO(vitaliii): Move this and |PrintTo| above to common file and replace | |
| 88 // remaining |Property(&ContentSuggestion::url, GURL("some_url"))|. | |
| 89 // See crbug.com/655513. | |
| 90 MATCHER_P(HasUrl, url, "") { | |
| 91 *result_listener << "expected URL: " << url | |
| 92 << "has URL: " << arg.url().spec(); | |
| 93 return arg.url().spec() == url; | |
| 94 } | |
| 95 | |
| 96 OfflinePageItem CreateDummyOfflinePage(int id) { | |
| 97 return ntp_snippets::test::CreateDummyOfflinePageItem( | |
| 98 id, offline_pages::kAsyncNamespace); | |
| 99 } | |
| 100 | |
| 101 std::vector<OfflinePageItem> CreateDummyOfflinePages( | |
| 102 const std::vector<int>& ids) { | |
| 103 std::vector<OfflinePageItem> result; | |
| 104 for (int id : ids) { | |
| 105 result.push_back(CreateDummyOfflinePage(id)); | |
| 106 } | |
| 107 return result; | |
| 108 } | |
| 109 | |
| 110 OfflinePageItem CreateDummyOfflinePage(int id, base::Time time) { | |
| 111 OfflinePageItem item = CreateDummyOfflinePage(id); | |
| 112 item.creation_time = time; | |
| 113 return item; | |
| 114 } | |
| 115 | |
| 116 std::unique_ptr<FakeDownloadItem> CreateDummyAssetDownload(int id) { | |
| 117 std::unique_ptr<FakeDownloadItem> item = base::MakeUnique<FakeDownloadItem>(); | |
| 118 item->SetId(id); | |
| 119 std::string id_string = base::IntToString(id); | |
| 120 item->SetTargetFilePath( | |
| 121 base::FilePath::FromUTF8Unsafe("folder/file" + id_string + ".mhtml")); | |
| 122 item->SetURL(GURL("http://dummy_file.com/" + id_string)); | |
| 123 item->SetEndTime(base::Time::Now()); | |
| 124 item->SetFileExternallyRemoved(false); | |
| 125 item->SetState(DownloadItem::DownloadState::COMPLETE); | |
| 126 return item; | |
| 127 } | |
| 128 | |
| 129 std::unique_ptr<FakeDownloadItem> CreateDummyAssetDownload( | |
| 130 int id, | |
| 131 const base::Time end_time) { | |
| 132 std::unique_ptr<FakeDownloadItem> item = CreateDummyAssetDownload(id); | |
| 133 item->SetEndTime(end_time); | |
| 134 return item; | |
| 135 } | |
| 136 | |
| 137 std::vector<std::unique_ptr<FakeDownloadItem>> CreateDummyAssetDownloads( | |
| 138 const std::vector<int>& ids) { | |
| 139 std::vector<std::unique_ptr<FakeDownloadItem>> result; | |
| 140 // The time is set to enforce the provider to cache the first items in the | |
| 141 // list first. | |
| 142 base::Time current_time = base::Time::Now(); | |
| 143 for (int id : ids) { | |
| 144 result.push_back(CreateDummyAssetDownload(id, current_time)); | |
| 145 current_time -= base::TimeDelta::FromDays(1); | |
| 146 } | |
| 147 return result; | |
| 148 } | |
| 149 | |
| 150 class ObservedMockDownloadManager : public MockDownloadManager { | |
| 151 public: | |
| 152 ObservedMockDownloadManager() {} | |
| 153 ~ObservedMockDownloadManager() override { | |
| 154 for (auto& observer : observers_) | |
| 155 observer.ManagerGoingDown(this); | |
| 156 } | |
| 157 | |
| 158 // Observer accessors. | |
| 159 void AddObserver(Observer* observer) override { | |
| 160 observers_.AddObserver(observer); | |
| 161 } | |
| 162 | |
| 163 void RemoveObserver(Observer* observer) override { | |
| 164 observers_.RemoveObserver(observer); | |
| 165 } | |
| 166 | |
| 167 void NotifyDownloadCreated(DownloadItem* item) { | |
| 168 for (auto& observer : observers_) | |
| 169 observer.OnDownloadCreated(this, item); | |
| 170 } | |
| 171 | |
| 172 std::vector<std::unique_ptr<FakeDownloadItem>>* mutable_items() { | |
| 173 return &items_; | |
| 174 } | |
| 175 | |
| 176 const std::vector<std::unique_ptr<FakeDownloadItem>>& items() const { | |
| 177 return items_; | |
| 178 } | |
| 179 | |
| 180 void GetAllDownloads(std::vector<DownloadItem*>* all_downloads) override { | |
| 181 all_downloads->clear(); | |
| 182 for (const auto& item : items_) | |
| 183 all_downloads->push_back(item.get()); | |
| 184 } | |
| 185 | |
| 186 private: | |
| 187 base::ObserverList<Observer> observers_; | |
| 188 std::vector<std::unique_ptr<FakeDownloadItem>> items_; | |
| 189 }; | |
| 190 | |
| 191 } // namespace | |
| 192 | |
| 193 class DownloadSuggestionsProviderTest : public testing::Test { | |
| 194 public: | |
| 195 DownloadSuggestionsProviderTest() | |
| 196 : pref_service_(new TestingPrefServiceSimple()) { | |
| 197 DownloadSuggestionsProvider::RegisterProfilePrefs( | |
| 198 pref_service()->registry()); | |
| 199 } | |
| 200 | |
| 201 void IgnoreOnCategoryStatusChanged() { | |
|
Marc Treib
2016/10/17 10:18:41
nit: This sounds like it ignores all changes, but
vitaliii
2016/10/26 00:07:55
Done.
| |
| 202 EXPECT_CALL(observer_, OnCategoryStatusChanged(_, downloads_category(), | |
| 203 CategoryStatus::AVAILABLE)) | |
| 204 .Times(AnyNumber()); | |
| 205 EXPECT_CALL(observer_, | |
| 206 OnCategoryStatusChanged(_, downloads_category(), | |
| 207 CategoryStatus::AVAILABLE_LOADING)) | |
| 208 .Times(AnyNumber()); | |
| 209 } | |
| 210 | |
| 211 void IgnoreOnSuggestionInvalidated() { | |
| 212 EXPECT_CALL(observer_, OnSuggestionInvalidated(_, _)).Times(AnyNumber()); | |
| 213 } | |
| 214 | |
| 215 DownloadSuggestionsProvider* CreateProvider() { | |
| 216 scoped_refptr<OfflinePageProxy> proxy( | |
|
Marc Treib
2016/10/17 10:18:41
DCHECK(!provider_) ?
vitaliii
2016/10/26 00:07:55
Done.
| |
| 217 new OfflinePageProxy(&offline_pages_model_)); | |
| 218 provider_ = base::MakeUnique<DownloadSuggestionsProvider>( | |
| 219 &observer_, &category_factory_, proxy, &downloads_manager_, | |
| 220 pref_service(), | |
| 221 /*download_manager_ui_enabled=*/false); | |
| 222 return provider_.get(); | |
| 223 } | |
| 224 | |
| 225 void DestroyProvider() { provider_.release(); } | |
|
Marc Treib
2016/10/17 10:18:41
This will leak memory - it gives up ownership and
vitaliii
2016/10/26 00:07:55
I misunderstood what |release()| does, I meant |re
| |
| 226 | |
| 227 Category downloads_category() { | |
| 228 return category_factory_.FromKnownCategory( | |
| 229 ntp_snippets::KnownCategories::DOWNLOADS); | |
| 230 } | |
| 231 | |
| 232 void FireOfflinePageModelChanged(const std::vector<OfflinePageItem>& items) { | |
| 233 DCHECK(provider_.get()); | |
|
Marc Treib
2016/10/17 10:18:41
nit: I think the ".get()" isn't required
vitaliii
2016/10/26 00:07:55
Done.
| |
| 234 provider_->OfflinePageModelChanged(items); | |
| 235 } | |
| 236 | |
| 237 void FireOfflinePageDeleted(const OfflinePageItem& item) { | |
| 238 DCHECK(provider_.get()); | |
| 239 provider_->OfflinePageDeleted(item.offline_id, item.client_id); | |
| 240 } | |
| 241 | |
| 242 void FireDownloadCreated(DownloadItem* item) { | |
| 243 DCHECK(provider_.get()); | |
| 244 downloads_manager_.NotifyDownloadCreated(item); | |
| 245 } | |
| 246 | |
| 247 void FireDownloadsCreated( | |
| 248 const std::vector<std::unique_ptr<FakeDownloadItem>>& items) { | |
| 249 for (const auto& item : items) | |
| 250 FireDownloadCreated(item.get()); | |
| 251 } | |
| 252 | |
| 253 ContentSuggestion::ID GetDummySuggestionId(int id, bool is_offline_page) { | |
| 254 return ContentSuggestion::ID( | |
| 255 downloads_category(), | |
| 256 (is_offline_page ? "O" : "D") + base::IntToString(id)); | |
| 257 } | |
| 258 | |
| 259 std::vector<ContentSuggestion> GetDismissedSuggestions() { | |
| 260 std::vector<ContentSuggestion> dismissed_suggestions; | |
| 261 provider()->GetDismissedSuggestionsForDebugging( | |
| 262 downloads_category(), | |
| 263 base::Bind(&CaptureDismissedSuggestions, &dismissed_suggestions)); | |
|
Marc Treib
2016/10/17 10:18:41
Does this always return synchronously? If so, that
vitaliii
2016/10/26 00:07:55
FakeOfflinePageModel (which we own) returns items
| |
| 264 return dismissed_suggestions; | |
| 265 } | |
| 266 | |
| 267 ContentSuggestionsProvider* provider() { | |
| 268 DCHECK(provider_.get()); | |
| 269 return provider_.get(); | |
| 270 } | |
| 271 ObservedMockDownloadManager* downloads_manager() { | |
| 272 return &downloads_manager_; | |
| 273 } | |
| 274 FakeOfflinePageModel* offline_pages_model() { return &offline_pages_model_; } | |
| 275 StrictMock<MockContentSuggestionsProviderObserver>* observer() { | |
|
Marc Treib
2016/10/17 10:18:41
I think just returning MockContentSuggestionsProvi
vitaliii
2016/10/26 00:07:55
Done.
| |
| 276 return &observer_; | |
| 277 } | |
| 278 TestingPrefServiceSimple* pref_service() { return pref_service_.get(); } | |
| 279 | |
| 280 private: | |
| 281 ObservedMockDownloadManager downloads_manager_; | |
| 282 FakeOfflinePageModel offline_pages_model_; | |
| 283 StrictMock<MockContentSuggestionsProviderObserver> observer_; | |
| 284 CategoryFactory category_factory_; | |
| 285 std::unique_ptr<TestingPrefServiceSimple> pref_service_; | |
| 286 // Last so that the dependencies are deleted after the provider. | |
| 287 std::unique_ptr<DownloadSuggestionsProvider> provider_; | |
| 288 | |
| 289 DISALLOW_COPY_AND_ASSIGN(DownloadSuggestionsProviderTest); | |
| 290 }; | |
| 291 | |
| 292 TEST_F(DownloadSuggestionsProviderTest, | |
| 293 ShouldConvertOfflinePagesToSuggestions) { | |
| 294 IgnoreOnCategoryStatusChanged(); | |
| 295 | |
| 296 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 297 EXPECT_CALL(*observer(), OnNewSuggestions(_, downloads_category(), | |
| 298 UnorderedElementsAre( | |
| 299 HasUrl("http://dummy.com/1"), | |
| 300 HasUrl("http://dummy.com/2")))); | |
| 301 CreateProvider(); | |
| 302 } | |
| 303 | |
| 304 TEST_F(DownloadSuggestionsProviderTest, | |
| 305 ShouldConvertDownloadItemsToSuggestions) { | |
| 306 IgnoreOnCategoryStatusChanged(); | |
| 307 IgnoreOnSuggestionInvalidated(); | |
| 308 | |
| 309 EXPECT_CALL(*observer(), | |
| 310 OnNewSuggestions(_, downloads_category(), SizeIs(0))); | |
| 311 CreateProvider(); | |
| 312 | |
| 313 std::vector<std::unique_ptr<FakeDownloadItem>> asset_downloads = | |
| 314 CreateDummyAssetDownloads({1, 2}); | |
| 315 | |
| 316 EXPECT_CALL(*observer(), | |
| 317 OnNewSuggestions( | |
| 318 _, downloads_category(), | |
| 319 UnorderedElementsAre(HasUrl("file:///folder/file1.mhtml")))); | |
| 320 FireDownloadCreated(asset_downloads[0].get()); | |
| 321 | |
| 322 EXPECT_CALL(*observer(), | |
| 323 OnNewSuggestions( | |
| 324 _, downloads_category(), | |
| 325 UnorderedElementsAre(HasUrl("file:///folder/file1.mhtml"), | |
| 326 HasUrl("file:///folder/file2.mhtml")))); | |
| 327 FireDownloadCreated(asset_downloads[1].get()); | |
| 328 } | |
| 329 | |
| 330 TEST_F(DownloadSuggestionsProviderTest, ShouldMixInBothSources) { | |
| 331 IgnoreOnCategoryStatusChanged(); | |
| 332 IgnoreOnSuggestionInvalidated(); | |
| 333 | |
| 334 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 335 EXPECT_CALL(*observer(), OnNewSuggestions(_, downloads_category(), | |
| 336 UnorderedElementsAre( | |
| 337 HasUrl("http://dummy.com/1"), | |
| 338 HasUrl("http://dummy.com/2")))); | |
| 339 CreateProvider(); | |
| 340 | |
| 341 std::vector<std::unique_ptr<FakeDownloadItem>> asset_downloads = | |
| 342 CreateDummyAssetDownloads({1, 2}); | |
| 343 | |
| 344 EXPECT_CALL(*observer(), | |
| 345 OnNewSuggestions( | |
| 346 _, downloads_category(), | |
| 347 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 348 HasUrl("http://dummy.com/2"), | |
| 349 HasUrl("file:///folder/file1.mhtml")))); | |
| 350 FireDownloadCreated(asset_downloads[0].get()); | |
| 351 | |
| 352 EXPECT_CALL(*observer(), | |
| 353 OnNewSuggestions( | |
| 354 _, downloads_category(), | |
| 355 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 356 HasUrl("http://dummy.com/2"), | |
| 357 HasUrl("file:///folder/file1.mhtml"), | |
| 358 HasUrl("file:///folder/file2.mhtml")))); | |
| 359 FireDownloadCreated(asset_downloads[1].get()); | |
| 360 } | |
| 361 | |
| 362 TEST_F(DownloadSuggestionsProviderTest, ShouldSortSuggestions) { | |
| 363 IgnoreOnCategoryStatusChanged(); | |
| 364 IgnoreOnSuggestionInvalidated(); | |
| 365 | |
| 366 base::Time now = base::Time::Now(); | |
| 367 base::Time yesterday = now - base::TimeDelta::FromDays(1); | |
| 368 base::Time tomorrow = now + base::TimeDelta::FromDays(1); | |
| 369 base::Time next_week = now + base::TimeDelta::FromDays(7); | |
| 370 | |
| 371 (*offline_pages_model()->mutable_items()) | |
| 372 .push_back(CreateDummyOfflinePage(1, yesterday)); | |
| 373 (*offline_pages_model()->mutable_items()) | |
| 374 .push_back(CreateDummyOfflinePage(2, tomorrow)); | |
| 375 | |
| 376 EXPECT_CALL(*observer(), | |
| 377 OnNewSuggestions(_, downloads_category(), | |
| 378 ElementsAre(HasUrl("http://dummy.com/2"), | |
| 379 HasUrl("http://dummy.com/1")))); | |
| 380 CreateProvider(); | |
| 381 | |
| 382 std::vector<std::unique_ptr<FakeDownloadItem>> asset_downloads; | |
| 383 asset_downloads.push_back(CreateDummyAssetDownload(3, next_week)); | |
| 384 asset_downloads.push_back(CreateDummyAssetDownload(4, now)); | |
| 385 | |
| 386 EXPECT_CALL(*observer(), | |
| 387 OnNewSuggestions(_, downloads_category(), | |
| 388 ElementsAre(HasUrl("file:///folder/file3.mhtml"), | |
| 389 HasUrl("http://dummy.com/2"), | |
| 390 HasUrl("http://dummy.com/1")))); | |
| 391 FireDownloadCreated(asset_downloads[0].get()); | |
| 392 | |
| 393 EXPECT_CALL(*observer(), | |
| 394 OnNewSuggestions(_, downloads_category(), | |
| 395 ElementsAre(HasUrl("file:///folder/file3.mhtml"), | |
| 396 HasUrl("http://dummy.com/2"), | |
| 397 HasUrl("file:///folder/file4.mhtml"), | |
| 398 HasUrl("http://dummy.com/1")))); | |
| 399 FireDownloadCreated(asset_downloads[1].get()); | |
| 400 } | |
| 401 | |
| 402 TEST_F(DownloadSuggestionsProviderTest, | |
| 403 ShouldDismissWithoutNotifyingObservers) { | |
| 404 IgnoreOnCategoryStatusChanged(); | |
| 405 | |
| 406 EXPECT_CALL(*observer(), | |
|
Marc Treib
2016/10/17 10:18:41
optional: If you expect calls in a particular orde
vitaliii
2016/10/26 00:07:55
It breaks my |IgnoreOn*| functions.
Marc Treib
2016/10/26 08:54:08
Interesting - why?
vitaliii
2016/10/27 15:49:19
I do not know precise reason, but I speculate that
| |
| 407 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(4ul)))) | |
| 408 .Times(2); | |
| 409 EXPECT_CALL(*observer(), | |
| 410 OnNewSuggestions( | |
| 411 _, downloads_category(), | |
| 412 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 413 HasUrl("http://dummy.com/2"), | |
| 414 HasUrl("file:///folder/file1.mhtml"), | |
| 415 HasUrl("file:///folder/file2.mhtml")))); | |
| 416 | |
| 417 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 418 CreateProvider(); | |
| 419 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 420 FireDownloadsCreated(downloads_manager()->items()); | |
| 421 | |
| 422 EXPECT_CALL(*observer(), OnNewSuggestions(_, _, _)).Times(0); | |
| 423 EXPECT_CALL(*observer(), OnSuggestionInvalidated(_, _)).Times(0); | |
| 424 provider()->DismissSuggestion( | |
| 425 GetDummySuggestionId(1, /*is_offline_page=*/true)); | |
| 426 provider()->DismissSuggestion( | |
| 427 GetDummySuggestionId(1, /*is_offline_page=*/false)); | |
| 428 | |
| 429 IgnoreOnSuggestionInvalidated(); | |
|
Marc Treib
2016/10/17 10:18:41
Does this do anything here? If so, worth a comment
vitaliii
2016/10/26 00:07:55
No, but the reason is quite nontrivial. Basically,
| |
| 430 } | |
| 431 | |
| 432 TEST_F(DownloadSuggestionsProviderTest, | |
| 433 ShouldNotReportDismissedSuggestionsOnNewData) { | |
| 434 IgnoreOnCategoryStatusChanged(); | |
| 435 IgnoreOnSuggestionInvalidated(); | |
| 436 | |
| 437 EXPECT_CALL(*observer(), | |
| 438 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(4ul)))) | |
| 439 .Times(2); | |
| 440 EXPECT_CALL(*observer(), | |
| 441 OnNewSuggestions( | |
| 442 _, downloads_category(), | |
| 443 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 444 HasUrl("http://dummy.com/2"), | |
| 445 HasUrl("file:///folder/file1.mhtml"), | |
| 446 HasUrl("file:///folder/file2.mhtml")))); | |
| 447 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 448 CreateProvider(); | |
| 449 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 450 FireDownloadsCreated(downloads_manager()->items()); | |
| 451 | |
| 452 provider()->DismissSuggestion( | |
| 453 GetDummySuggestionId(1, /*is_offline_page=*/true)); | |
| 454 provider()->DismissSuggestion( | |
| 455 GetDummySuggestionId(1, /*is_offline_page=*/false)); | |
| 456 | |
| 457 EXPECT_CALL(*observer(), | |
| 458 OnNewSuggestions( | |
| 459 _, downloads_category(), | |
| 460 UnorderedElementsAre(HasUrl("http://dummy.com/2"), | |
| 461 HasUrl("file:///folder/file2.mhtml")))); | |
| 462 FireOfflinePageModelChanged(offline_pages_model()->items()); | |
| 463 } | |
| 464 | |
| 465 TEST_F(DownloadSuggestionsProviderTest, ShouldReturnDismissedSuggestions) { | |
| 466 IgnoreOnCategoryStatusChanged(); | |
| 467 IgnoreOnSuggestionInvalidated(); | |
| 468 | |
| 469 EXPECT_CALL(*observer(), | |
| 470 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(4ul)))) | |
| 471 .Times(2); | |
| 472 EXPECT_CALL(*observer(), | |
| 473 OnNewSuggestions( | |
| 474 _, downloads_category(), | |
| 475 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 476 HasUrl("http://dummy.com/2"), | |
| 477 HasUrl("file:///folder/file1.mhtml"), | |
| 478 HasUrl("file:///folder/file2.mhtml")))); | |
| 479 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 480 CreateProvider(); | |
| 481 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 482 FireDownloadsCreated(downloads_manager()->items()); | |
| 483 | |
| 484 provider()->DismissSuggestion( | |
| 485 GetDummySuggestionId(1, /*is_offline_page=*/true)); | |
| 486 provider()->DismissSuggestion( | |
| 487 GetDummySuggestionId(1, /*is_offline_page=*/false)); | |
| 488 | |
| 489 EXPECT_THAT(GetDismissedSuggestions(), | |
| 490 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 491 HasUrl("file:///folder/file1.mhtml"))); | |
| 492 } | |
| 493 | |
| 494 TEST_F(DownloadSuggestionsProviderTest, ShouldClearDismissedSuggestions) { | |
| 495 IgnoreOnCategoryStatusChanged(); | |
| 496 IgnoreOnSuggestionInvalidated(); | |
| 497 | |
| 498 EXPECT_CALL(*observer(), | |
| 499 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(4ul)))) | |
| 500 .Times(2); | |
| 501 EXPECT_CALL(*observer(), | |
| 502 OnNewSuggestions( | |
| 503 _, downloads_category(), | |
| 504 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 505 HasUrl("http://dummy.com/2"), | |
| 506 HasUrl("file:///folder/file1.mhtml"), | |
| 507 HasUrl("file:///folder/file2.mhtml")))); | |
| 508 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 509 CreateProvider(); | |
| 510 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 511 FireDownloadsCreated(downloads_manager()->items()); | |
| 512 | |
| 513 provider()->DismissSuggestion( | |
| 514 GetDummySuggestionId(1, /*is_offline_page=*/true)); | |
| 515 provider()->DismissSuggestion( | |
| 516 GetDummySuggestionId(1, /*is_offline_page=*/false)); | |
| 517 | |
| 518 EXPECT_CALL(*observer(), | |
| 519 OnNewSuggestions( | |
| 520 _, downloads_category(), | |
| 521 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 522 HasUrl("http://dummy.com/2"), | |
| 523 HasUrl("file:///folder/file1.mhtml"), | |
| 524 HasUrl("file:///folder/file2.mhtml")))); | |
| 525 provider()->ClearDismissedSuggestionsForDebugging(downloads_category()); | |
| 526 EXPECT_THAT(GetDismissedSuggestions(), IsEmpty()); | |
| 527 } | |
| 528 | |
| 529 TEST_F(DownloadSuggestionsProviderTest, | |
| 530 ShouldNotDismissOtherTypeWithTheSameID) { | |
| 531 IgnoreOnCategoryStatusChanged(); | |
| 532 IgnoreOnSuggestionInvalidated(); | |
| 533 | |
| 534 EXPECT_CALL(*observer(), | |
| 535 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(4ul)))) | |
| 536 .Times(2); | |
| 537 EXPECT_CALL(*observer(), | |
| 538 OnNewSuggestions( | |
| 539 _, downloads_category(), | |
| 540 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 541 HasUrl("http://dummy.com/2"), | |
| 542 HasUrl("file:///folder/file1.mhtml"), | |
| 543 HasUrl("file:///folder/file2.mhtml")))); | |
| 544 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 545 CreateProvider(); | |
| 546 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 547 FireDownloadsCreated(downloads_manager()->items()); | |
| 548 | |
| 549 provider()->DismissSuggestion( | |
| 550 GetDummySuggestionId(1, /*is_offline_page=*/true)); | |
| 551 | |
| 552 EXPECT_CALL(*observer(), | |
| 553 OnNewSuggestions( | |
| 554 _, downloads_category(), | |
| 555 UnorderedElementsAre(HasUrl("http://dummy.com/2"), | |
| 556 HasUrl("file:///folder/file1.mhtml"), | |
| 557 HasUrl("file:///folder/file2.mhtml")))); | |
| 558 FireOfflinePageModelChanged(offline_pages_model()->items()); | |
| 559 } | |
| 560 | |
| 561 TEST_F(DownloadSuggestionsProviderTest, ShouldReplaceDismissedItemWithNewData) { | |
| 562 IgnoreOnCategoryStatusChanged(); | |
| 563 IgnoreOnSuggestionInvalidated(); | |
| 564 | |
| 565 EXPECT_CALL(*observer(), | |
| 566 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(5ul)))) | |
| 567 .Times(5); | |
| 568 EXPECT_CALL(*observer(), | |
| 569 OnNewSuggestions( | |
| 570 _, downloads_category(), | |
| 571 UnorderedElementsAre(HasUrl("file:///folder/file1.mhtml"), | |
| 572 HasUrl("file:///folder/file2.mhtml"), | |
| 573 HasUrl("file:///folder/file3.mhtml"), | |
| 574 HasUrl("file:///folder/file4.mhtml"), | |
| 575 HasUrl("file:///folder/file5.mhtml")))); | |
| 576 CreateProvider(); | |
| 577 // Currently the provider stores five items in its internal cache, so six | |
| 578 // items are needed to check whether all downloads are fetched on dismissal. | |
| 579 *(downloads_manager()->mutable_items()) = | |
| 580 CreateDummyAssetDownloads({1, 2, 3, 4, 5, 6}); | |
| 581 FireDownloadsCreated(downloads_manager()->items()); | |
| 582 | |
| 583 provider()->DismissSuggestion( | |
| 584 GetDummySuggestionId(1, /*is_offline_page=*/false)); | |
| 585 | |
| 586 // The provider is not notified about the 6th item, however, it must report | |
| 587 // it now. | |
| 588 EXPECT_CALL(*observer(), | |
| 589 OnNewSuggestions( | |
| 590 _, downloads_category(), | |
| 591 UnorderedElementsAre(HasUrl("file:///folder/file2.mhtml"), | |
| 592 HasUrl("file:///folder/file3.mhtml"), | |
| 593 HasUrl("file:///folder/file4.mhtml"), | |
| 594 HasUrl("file:///folder/file5.mhtml"), | |
| 595 HasUrl("file:///folder/file6.mhtml")))); | |
| 596 FireOfflinePageModelChanged(offline_pages_model()->items()); | |
| 597 } | |
| 598 | |
| 599 TEST_F(DownloadSuggestionsProviderTest, | |
| 600 ShouldInvalidateWhenUnderlyingItemDeleted) { | |
| 601 IgnoreOnCategoryStatusChanged(); | |
| 602 | |
| 603 EXPECT_CALL(*observer(), | |
| 604 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(3ul)))); | |
| 605 EXPECT_CALL(*observer(), | |
| 606 OnNewSuggestions( | |
| 607 _, downloads_category(), | |
| 608 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 609 HasUrl("http://dummy.com/2"), | |
| 610 HasUrl("file:///folder/file1.mhtml")))); | |
| 611 *(offline_pages_model()->mutable_items()) = CreateDummyOfflinePages({1, 2}); | |
| 612 CreateProvider(); | |
| 613 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1}); | |
| 614 FireDownloadsCreated(downloads_manager()->items()); | |
| 615 | |
| 616 // We add another item manually, so that when it gets deleted it is not | |
| 617 // present in DownloadsManager list. | |
| 618 std::unique_ptr<FakeDownloadItem> removed_item = CreateDummyAssetDownload(2); | |
| 619 EXPECT_CALL(*observer(), | |
| 620 OnNewSuggestions( | |
| 621 _, downloads_category(), | |
| 622 UnorderedElementsAre(HasUrl("http://dummy.com/1"), | |
| 623 HasUrl("http://dummy.com/2"), | |
| 624 HasUrl("file:///folder/file1.mhtml"), | |
| 625 HasUrl("file:///folder/file2.mhtml")))); | |
| 626 FireDownloadCreated(removed_item.get()); | |
| 627 | |
| 628 EXPECT_CALL(*observer(), | |
| 629 OnSuggestionInvalidated( | |
| 630 _, GetDummySuggestionId(1, /*is_offline_page=*/true))); | |
| 631 FireOfflinePageDeleted(offline_pages_model()->items()[0]); | |
| 632 | |
| 633 EXPECT_CALL(*observer(), | |
| 634 OnSuggestionInvalidated( | |
| 635 _, GetDummySuggestionId(2, /*is_offline_page=*/false))); | |
| 636 // |OnDownloadItemDestroyed| is called from its destructor. | |
|
Marc Treib
2016/10/17 10:18:41
nit: s/its/|removed_item|'s/
vitaliii
2016/10/26 00:07:55
Done.
| |
| 637 removed_item.reset(); | |
| 638 | |
| 639 IgnoreOnSuggestionInvalidated(); | |
|
Marc Treib
2016/10/17 10:18:41
Is this necessary?
vitaliii
2016/10/26 00:07:55
The answer is the same as for the case above.
No,
| |
| 640 } | |
| 641 | |
| 642 TEST_F(DownloadSuggestionsProviderTest, ShouldReplaceRemovedItemWithNewData) { | |
| 643 IgnoreOnCategoryStatusChanged(); | |
| 644 IgnoreOnSuggestionInvalidated(); | |
| 645 | |
| 646 EXPECT_CALL(*observer(), | |
| 647 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(5ul)))) | |
| 648 .Times(5); | |
| 649 EXPECT_CALL(*observer(), | |
| 650 OnNewSuggestions( | |
| 651 _, downloads_category(), | |
| 652 UnorderedElementsAre(HasUrl("file:///folder/file1.mhtml"), | |
| 653 HasUrl("file:///folder/file2.mhtml"), | |
| 654 HasUrl("file:///folder/file3.mhtml"), | |
| 655 HasUrl("file:///folder/file4.mhtml"), | |
| 656 HasUrl("file:///folder/file5.mhtml")))); | |
| 657 CreateProvider(); | |
| 658 *(downloads_manager()->mutable_items()) = | |
| 659 CreateDummyAssetDownloads({1, 2, 3, 4, 5}); | |
| 660 FireDownloadsCreated(downloads_manager()->items()); | |
| 661 | |
| 662 // Note that |CreateDummyAssetDownloads| creates items "downloaded" before | |
| 663 // |base::Time::Now()|, so for a new item the time is set in future to enforce | |
| 664 // the provider to show the new item. | |
| 665 std::unique_ptr<FakeDownloadItem> removed_item = CreateDummyAssetDownload( | |
| 666 100, base::Time::Now() + base::TimeDelta::FromDays(1)); | |
| 667 EXPECT_CALL(*observer(), | |
| 668 OnNewSuggestions(_, downloads_category(), | |
| 669 UnorderedElementsAre( | |
| 670 HasUrl("file:///folder/file1.mhtml"), | |
| 671 HasUrl("file:///folder/file2.mhtml"), | |
| 672 HasUrl("file:///folder/file3.mhtml"), | |
| 673 HasUrl("file:///folder/file4.mhtml"), | |
| 674 HasUrl("file:///folder/file100.mhtml")))); | |
| 675 FireDownloadCreated(removed_item.get()); | |
| 676 | |
| 677 // |OnDownloadDestroyed| notification is called in |DownloadItem|'s | |
| 678 // destructor. | |
| 679 removed_item.reset(); | |
| 680 | |
| 681 EXPECT_CALL(*observer(), | |
| 682 OnNewSuggestions( | |
| 683 _, downloads_category(), | |
| 684 UnorderedElementsAre(HasUrl("file:///folder/file1.mhtml"), | |
| 685 HasUrl("file:///folder/file2.mhtml"), | |
| 686 HasUrl("file:///folder/file3.mhtml"), | |
| 687 HasUrl("file:///folder/file4.mhtml"), | |
| 688 HasUrl("file:///folder/file5.mhtml")))); | |
| 689 FireOfflinePageModelChanged(offline_pages_model()->items()); | |
|
Marc Treib
2016/10/17 10:18:41
Is the FireOfflinePageModelChanged needed here? Sh
vitaliii
2016/10/26 00:07:55
Yes, it is.
Marc Treib
2016/10/26 08:54:08
Ah yes, you're right. Destroying the download shou
vitaliii
2016/10/27 15:49:19
Acknowledged.
| |
| 690 } | |
| 691 | |
| 692 TEST_F(DownloadSuggestionsProviderTest, ShouldPruneOfflinePagesDismissedIDs) { | |
| 693 IgnoreOnCategoryStatusChanged(); | |
| 694 IgnoreOnSuggestionInvalidated(); | |
| 695 | |
| 696 *(offline_pages_model()->mutable_items()) = | |
| 697 CreateDummyOfflinePages({1, 2, 3}); | |
| 698 EXPECT_CALL(*observer(), OnNewSuggestions(_, downloads_category(), | |
| 699 UnorderedElementsAre( | |
| 700 HasUrl("http://dummy.com/1"), | |
| 701 HasUrl("http://dummy.com/2"), | |
| 702 HasUrl("http://dummy.com/3")))); | |
| 703 CreateProvider(); | |
| 704 | |
| 705 provider()->DismissSuggestion( | |
| 706 GetDummySuggestionId(1, /*is_offline_page=*/true)); | |
| 707 provider()->DismissSuggestion( | |
| 708 GetDummySuggestionId(2, /*is_offline_page=*/true)); | |
| 709 provider()->DismissSuggestion( | |
| 710 GetDummySuggestionId(3, /*is_offline_page=*/true)); | |
| 711 EXPECT_THAT(GetDismissedSuggestions(), SizeIs(3)); | |
| 712 | |
| 713 // Prune on getting all offline pages. Note that the first suggestion is not | |
| 714 // removed from |offline_pages_model| storage, because otherwise | |
| 715 // |GetDismissedSuggestions| cannot return it. | |
| 716 EXPECT_CALL(*observer(), | |
| 717 OnNewSuggestions(_, downloads_category(), IsEmpty())); | |
| 718 FireOfflinePageModelChanged(CreateDummyOfflinePages({2, 3})); | |
| 719 EXPECT_THAT(GetDismissedSuggestions(), SizeIs(2)); | |
| 720 | |
| 721 // Prune when offline page is deleted. | |
| 722 FireOfflinePageDeleted(offline_pages_model()->items()[1]); | |
| 723 EXPECT_THAT(GetDismissedSuggestions(), SizeIs(1)); | |
| 724 } | |
| 725 | |
| 726 TEST_F(DownloadSuggestionsProviderTest, ShouldPruneAssetDownloadsDismissedIDs) { | |
| 727 IgnoreOnCategoryStatusChanged(); | |
| 728 IgnoreOnSuggestionInvalidated(); | |
| 729 | |
| 730 EXPECT_CALL(*observer(), | |
| 731 OnNewSuggestions(_, downloads_category(), SizeIs(Lt(3ul)))) | |
| 732 .Times(3); | |
| 733 CreateProvider(); | |
| 734 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 735 FireDownloadsCreated(downloads_manager()->items()); | |
| 736 | |
| 737 provider()->DismissSuggestion( | |
| 738 GetDummySuggestionId(1, /*is_offline_page=*/false)); | |
| 739 provider()->DismissSuggestion( | |
| 740 GetDummySuggestionId(2, /*is_offline_page=*/false)); | |
| 741 EXPECT_THAT(GetDismissedSuggestions(), SizeIs(2)); | |
| 742 | |
| 743 downloads_manager()->items()[0]->NotifyDownloadDestroyed(); | |
| 744 EXPECT_THAT(GetDismissedSuggestions(), SizeIs(1)); | |
| 745 } | |
| 746 | |
| 747 TEST_F(DownloadSuggestionsProviderTest, ShouldNotFetchAssetDownloadsOnStartup) { | |
| 748 IgnoreOnCategoryStatusChanged(); | |
| 749 | |
| 750 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1, 2}); | |
| 751 EXPECT_CALL(*observer(), | |
| 752 OnNewSuggestions(_, downloads_category(), IsEmpty())); | |
| 753 CreateProvider(); | |
| 754 } | |
| 755 | |
| 756 TEST_F(DownloadSuggestionsProviderTest, | |
| 757 ShouldInvalidateAssetDownloadWhenItsFileRemoved) { | |
| 758 IgnoreOnCategoryStatusChanged(); | |
| 759 | |
| 760 EXPECT_CALL(*observer(), | |
| 761 OnNewSuggestions(_, downloads_category(), IsEmpty())); | |
| 762 EXPECT_CALL(*observer(), | |
| 763 OnNewSuggestions(_, downloads_category(), SizeIs(1))); | |
| 764 CreateProvider(); | |
| 765 *(downloads_manager()->mutable_items()) = CreateDummyAssetDownloads({1}); | |
| 766 FireDownloadsCreated(downloads_manager()->items()); | |
| 767 | |
| 768 EXPECT_CALL(*observer(), | |
| 769 OnSuggestionInvalidated( | |
| 770 _, GetDummySuggestionId(1, /*is_offline_page=*/false))); | |
| 771 (*downloads_manager()->mutable_items())[0]->SetFileExternallyRemoved(true); | |
| 772 (*downloads_manager()->mutable_items())[0]->NotifyDownloadUpdated(); | |
| 773 } | |
| OLD | NEW |