blob: 88e776df81a649e83cb943e8bbf1a0b010a3a4e7 [file] [log] [blame]
// 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 <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/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/prefs/pref_service.h"
#include "content/public/test/test_browser_thread_bundle.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";
std::unique_ptr<KeyedService> BuildOfflineContentAggregator(
content::BrowserContext* context) {
return std::make_unique<OfflineContentAggregator>();
}
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();
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);
return visuals;
}
class AvailableOfflineContentTest : public testing::Test {
protected:
void SetUp() override {
scoped_feature_list_->InitAndEnableFeature(features::kNewNetErrorPageUI);
// To control the items in the aggregator, we create it and register a
// single MockOfflineContentProvider.
aggregator_ = static_cast<OfflineContentAggregator*>(
OfflineContentAggregatorFactory::GetInstance()->SetTestingFactoryAndUse(
&profile_, base::BindRepeating(&BuildOfflineContentAggregator)));
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));
}
chrome::mojom::AvailableOfflineContentSummaryPtr SummarizeAndWait() {
chrome::mojom::AvailableOfflineContentSummaryPtr summary;
chrome::mojom::AvailableOfflineContentProviderAsyncWaiter waiter(
&provider_);
waiter.Summarize(&summary);
return summary;
}
content::TestBrowserThreadBundle thread_bundle_;
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();
chrome::mojom::AvailableOfflineContentSummaryPtr summary = SummarizeAndWait();
EXPECT_EQ(0u, summary->total_items);
EXPECT_FALSE(summary->has_prefetched_page);
EXPECT_FALSE(summary->has_offline_page);
EXPECT_FALSE(summary->has_video);
EXPECT_FALSE(summary->has_audio);
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() and Summary().
bool list_visible_by_prefs;
std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions;
std::tie(list_visible_by_prefs, suggestions) = ListAndWait();
chrome::mojom::AvailableOfflineContentSummaryPtr summary = SummarizeAndWait();
// As interesting items are below the minimum to show, nothing should be
// reported.
EXPECT_EQ(0u, summary->total_items);
EXPECT_FALSE(summary->has_prefetched_page);
EXPECT_FALSE(summary->has_offline_page);
EXPECT_FALSE(summary->has_video);
EXPECT_FALSE(summary->has_audio);
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() and Summary().
bool list_visible_by_prefs;
std::vector<chrome::mojom::AvailableOfflineContentPtr> suggestions;
std::tie(list_visible_by_prefs, suggestions) = ListAndWait();
chrome::mojom::AvailableOfflineContentSummaryPtr summary = SummarizeAndWait();
// Check summary.
EXPECT_EQ(5u, summary->total_items);
EXPECT_TRUE(summary->has_prefetched_page);
EXPECT_TRUE(summary->has_offline_page);
EXPECT_TRUE(summary->has_video);
EXPECT_TRUE(summary->has_audio);
// 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));
// TODO(crbug.com/852872): Add attribution.
EXPECT_EQ("", first->attribution);
}
TEST_F(AvailableOfflineContentTest, NotEnabled) {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list_->InitAndDisableFeature(features::kNewNetErrorPageUI);
content_provider_.SetItems({SuggestedOfflinePageItem()});
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, 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