| // 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/download/android/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.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h" |
| #include "chrome/browser/profiles/profile_key.h" |
| #include "chrome/common/available_offline_content.mojom-test-utils.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/feed/core/shared_prefs/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::Minutes(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 ChromeRenderViewHostTestHarness { |
| protected: |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| content_provider_ = std::make_unique< |
| offline_items_collection::MockOfflineContentProvider>(); |
| provider_ = std::make_unique<AvailableOfflineContentProvider>( |
| main_rfh()->GetProcess()->GetID()); |
| |
| aggregator_ = |
| OfflineContentAggregatorFactory::GetForKey(profile()->GetProfileKey()); |
| aggregator_->RegisterProvider(kProviderNamespace, content_provider_.get()); |
| content_provider_->SetVisuals({}); |
| } |
| |
| void TearDown() override { |
| provider_.release(); |
| content_provider_.release(); |
| |
| ChromeRenderViewHostTestHarness::TearDown(); |
| } |
| |
| 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_.get()); |
| waiter.List(&list_visible_by_prefs, &suggestions); |
| return std::make_tuple(list_visible_by_prefs, std::move(suggestions)); |
| } |
| |
| std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_ = |
| std::make_unique<base::test::ScopedFeatureList>(); |
| OfflineContentAggregator* aggregator_; |
| std::unique_ptr<offline_items_collection::MockOfflineContentProvider> |
| content_provider_; |
| std::unique_ptr<AvailableOfflineContentProvider> provider_; |
| }; |
| |
| 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(feed::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(feed::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 |