| // Copyright 2018 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 "chrome/browser/android/download/available_offline_content_provider.h" |
| |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h" |
| #include "chrome/common/available_offline_content.mojom-test-utils.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/ntp_snippets/pref_names.h" |
| #include "components/offline_items_collection/core/offline_content_aggregator.h" |
| #include "components/offline_items_collection/core/offline_item.h" |
| #include "components/offline_items_collection/core/offline_item_state.h" |
| #include "components/offline_items_collection/core/test_support/mock_offline_content_provider.h" |
| #include "components/offline_pages/core/offline_page_feature.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| #include "url/gurl.h" |
| |
| namespace android { |
| namespace { |
| |
| using offline_items_collection::OfflineContentAggregator; |
| using offline_items_collection::OfflineItem; |
| using offline_items_collection::OfflineItemState; |
| using offline_items_collection::OfflineItemVisuals; |
| using testing::_; |
| const char kProviderNamespace[] = "offline_pages"; |
| |
| OfflineItem UninterestingImageItem() { |
| OfflineItem item; |
| item.original_url = GURL("https://uninteresting"); |
| item.filter = offline_items_collection::FILTER_IMAGE; |
| item.id.id = "UninterestingItem"; |
| item.id.name_space = kProviderNamespace; |
| return item; |
| } |
| |
| OfflineItem OfflinePageItem() { |
| OfflineItem item; |
| item.original_url = GURL("https://already_read"); |
| item.filter = offline_items_collection::FILTER_PAGE; |
| item.id.id = "NonSuggestedOfflinePage"; |
| item.id.name_space = kProviderNamespace; |
| item.last_accessed_time = base::Time::Now(); |
| return item; |
| } |
| |
| OfflineItem SuggestedOfflinePageItem() { |
| OfflineItem item; |
| item.original_url = GURL("https://read_prefetched_page"); |
| item.filter = offline_items_collection::FILTER_PAGE; |
| item.id.id = "SuggestedOfflinePage"; |
| item.id.name_space = kProviderNamespace; |
| item.is_suggested = true; |
| item.title = "Page Title"; |
| item.description = "snippet"; |
| // Using Time::Now() isn't ideal, but this should result in "4 hours ago" |
| // even if the test takes 1 hour to run. |
| item.creation_time = |
| base::Time::Now() - base::TimeDelta::FromMinutes(60 * 3.5); |
| item.last_accessed_time = base::Time::Now(); |
| item.attribution = "attribution"; |
| return item; |
| } |
| |
| OfflineItem VideoItem() { |
| OfflineItem item; |
| item.original_url = GURL("https://video"); |
| item.filter = offline_items_collection::FILTER_VIDEO; |
| item.id.id = "VideoItem"; |
| item.id.name_space = kProviderNamespace; |
| return item; |
| } |
| |
| OfflineItem AudioItem() { |
| OfflineItem item; |
| item.original_url = GURL("https://audio"); |
| item.filter = offline_items_collection::FILTER_AUDIO; |
| item.id.id = "AudioItem"; |
| item.id.name_space = kProviderNamespace; |
| return item; |
| } |
| |
| OfflineItem TransientItem() { |
| OfflineItem item = VideoItem(); |
| item.is_transient = true; |
| return item; |
| } |
| |
| OfflineItem OffTheRecordItem() { |
| OfflineItem item = VideoItem(); |
| item.is_off_the_record = true; |
| return item; |
| } |
| |
| OfflineItem IncompleteItem() { |
| OfflineItem item = VideoItem(); |
| item.state = OfflineItemState::PAUSED; |
| return item; |
| } |
| |
| OfflineItem DangerousItem() { |
| OfflineItem item = VideoItem(); |
| item.is_dangerous = true; |
| return item; |
| } |
| |
| OfflineItemVisuals TestThumbnail() { |
| OfflineItemVisuals visuals; |
| visuals.icon = gfx::test::CreateImage(2, 4); |
| visuals.custom_favicon = gfx::test::CreateImage(4, 4); |
| return visuals; |
| } |
| |
| class AvailableOfflineContentTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| aggregator_ = |
| OfflineContentAggregatorFactory::GetForKey(profile_.GetProfileKey()); |
| aggregator_->RegisterProvider(kProviderNamespace, &content_provider_); |
| content_provider_.SetVisuals({}); |
| } |
| |
| std::tuple<bool, std::vector<chrome::mojom::AvailableOfflineContentPtr>> |
| ListAndWait() { |
| bool list_visible_by_prefs; |
| std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions; |
| chrome::mojom::AvailableOfflineContentProviderAsyncWaiter waiter( |
| &provider_); |
| waiter.List(&list_visible_by_prefs, &suggestions); |
| return std::make_tuple(list_visible_by_prefs, std::move(suggestions)); |
| } |
| |
| content::BrowserTaskEnvironment task_environment_; |
| TestingProfile profile_; |
| std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_ = |
| std::make_unique<base::test::ScopedFeatureList>(); |
| OfflineContentAggregator* aggregator_; |
| offline_items_collection::MockOfflineContentProvider content_provider_; |
| AvailableOfflineContentProvider provider_{&profile_}; |
| }; |
| |
| TEST_F(AvailableOfflineContentTest, NoContent) { |
| bool list_visible_by_prefs; |
| std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions; |
| std::tie(list_visible_by_prefs, suggestions) = ListAndWait(); |
| |
| EXPECT_TRUE(suggestions.empty()); |
| EXPECT_TRUE(list_visible_by_prefs); |
| } |
| |
| TEST_F(AvailableOfflineContentTest, TooFewInterestingItems) { |
| // Adds items so that we're one-ff of reaching the minimum required count so |
| // that any extra item considered interesting would effect the results. |
| content_provider_.SetItems({UninterestingImageItem(), OfflinePageItem(), |
| SuggestedOfflinePageItem(), VideoItem(), |
| TransientItem(), OffTheRecordItem(), |
| IncompleteItem(), DangerousItem()}); |
| |
| // Call List(). |
| bool list_visible_by_prefs; |
| std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions; |
| std::tie(list_visible_by_prefs, suggestions) = ListAndWait(); |
| |
| // As interesting items are below the minimum to show, nothing should be |
| // reported. |
| EXPECT_TRUE(suggestions.empty()); |
| EXPECT_TRUE(list_visible_by_prefs); |
| } |
| |
| TEST_F(AvailableOfflineContentTest, FourInterestingItems) { |
| // We need at least 4 interesting items for anything to show up at all. |
| content_provider_.SetItems({UninterestingImageItem(), VideoItem(), |
| SuggestedOfflinePageItem(), AudioItem(), |
| OfflinePageItem()}); |
| |
| content_provider_.SetVisuals( |
| {{SuggestedOfflinePageItem().id, TestThumbnail()}}); |
| |
| // Call List(). |
| bool list_visible_by_prefs; |
| std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions; |
| std::tie(list_visible_by_prefs, suggestions) = ListAndWait(); |
| |
| // Check that the right suggestions have been received in order. |
| EXPECT_EQ(3ul, suggestions.size()); |
| EXPECT_EQ(SuggestedOfflinePageItem().id.id, suggestions[0]->id); |
| EXPECT_EQ(VideoItem().id.id, suggestions[1]->id); |
| EXPECT_EQ(AudioItem().id.id, suggestions[2]->id); |
| EXPECT_TRUE(list_visible_by_prefs); |
| |
| // For a single suggestion, make sure all the fields are correct. We can |
| // assume the other items match. |
| const chrome::mojom::AvailableOfflineContentPtr& first = suggestions[0]; |
| const OfflineItem page_item = SuggestedOfflinePageItem(); |
| EXPECT_EQ(page_item.id.id, first->id); |
| EXPECT_EQ(page_item.id.name_space, first->name_space); |
| EXPECT_EQ(page_item.title, first->title); |
| EXPECT_EQ(page_item.description, first->snippet); |
| EXPECT_EQ("4 hours ago", first->date_modified); |
| // At the time of writing this test, the output was: |
| // data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAECAYAAACk7+45AAAAFk |
| // lEQVQYlWNk+M/wn4GBgYGJAQowGQBCcgIG00vTRwAAAABJRU5ErkJggg== |
| // Since other encodings are possible, just check the prefix. PNGs all have |
| // the same 8 byte header. |
| EXPECT_TRUE(base::StartsWith(first->thumbnail_data_uri.spec(), |
| "data:image/png;base64,iVBORw0K", |
| base::CompareCase::SENSITIVE)); |
| EXPECT_TRUE(base::StartsWith(first->favicon_data_uri.spec(), |
| "data:image/png;base64,iVBORw0K", |
| base::CompareCase::SENSITIVE)); |
| EXPECT_EQ(page_item.attribution, first->attribution); |
| } |
| |
| TEST_F(AvailableOfflineContentTest, ListVisibilityChanges) { |
| // We need at least 4 interesting items for anything to show up at all. |
| content_provider_.SetItems({UninterestingImageItem(), VideoItem(), |
| SuggestedOfflinePageItem(), AudioItem(), |
| OfflinePageItem()}); |
| |
| content_provider_.SetVisuals( |
| {{SuggestedOfflinePageItem().id, TestThumbnail()}}); |
| // Set pref to hide the list. |
| profile_.GetPrefs()->SetBoolean(ntp_snippets::prefs::kArticlesListVisible, |
| false); |
| |
| // Call List(). |
| bool list_visible_by_prefs; |
| std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions; |
| std::tie(list_visible_by_prefs, suggestions) = ListAndWait(); |
| |
| // Check that suggestions have been received and the list is not visible. |
| EXPECT_EQ(3ul, suggestions.size()); |
| EXPECT_FALSE(list_visible_by_prefs); |
| |
| // Simulate visibility changed by the user to "shown". |
| provider_.ListVisibilityChanged(true); |
| |
| EXPECT_TRUE(profile_.GetPrefs()->GetBoolean( |
| ntp_snippets::prefs::kArticlesListVisible)); |
| |
| // Call List() again and check list is not visible. |
| std::tie(list_visible_by_prefs, suggestions) = ListAndWait(); |
| EXPECT_TRUE(list_visible_by_prefs); |
| } |
| |
| } // namespace |
| } // namespace android |