chromium/ios/chrome/browser/favicon/model/favicon_loader_unittest.mm

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/favicon/model/favicon_loader.h"

#import "components/favicon/core/large_icon_service_impl.h"
#import "components/favicon_base/fallback_icon_style.h"
#import "components/favicon_base/favicon_types.h"
#import "ios/chrome/common/ui/favicon/favicon_attributes.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
#import "third_party/skia/include/core/SkBitmap.h"
#import "ui/gfx/codec/png_codec.h"
#import "url/gurl.h"

namespace {

// Dummy URL for the favicon case.
const char kTestFaviconURL[] = "http://test/favicon";
// Dummy URL for the fallback case.
const char kTestFallbackURL[] = "http://test/fallback";

// Size of dummy favicon image.
const CGFloat kTestFaviconSize = 57;

// FaviconLoaderTest is parameterized on this enum to test both
// FaviconLoader::FaviconForPageUrl and FaviconLoader::FaviconForIconUrl.
enum FaviconUrlType { TEST_PAGE_URL, TEST_ICON_URL };

// FakeLargeIconService mimics a LargeIconService that returns a LargeIconResult
// with a test favicon image for kTestFaviconURL and a LargeIconResult
// initialized with FallbackIconStyle.
class FakeLargeIconService : public favicon::LargeIconServiceImpl {
 public:
  FakeLargeIconService()
      : favicon::LargeIconServiceImpl(
            /*favicon_service=*/nullptr,
            /*image_fetcher=*/nullptr,
            /*desired_size_in_dip_for_server_requests=*/0,
            /*icon_type_for_server_requests=*/
            favicon_base::IconType::kTouchIcon,
            /*google_server_client_param=*/"test_chrome") {}

  // Returns LargeIconResult with valid bitmap if `page_url` is
  // `kTestFaviconURL`, or LargeIconResult with fallback style.
  base::CancelableTaskTracker::TaskId
  GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
      const GURL& page_url,
      int min_source_size_in_pixel,
      int desired_size_in_pixel,
      favicon_base::LargeIconCallback callback,
      base::CancelableTaskTracker* tracker) override {
    if (page_url.spec() == kTestFaviconURL) {
      favicon_base::FaviconRawBitmapResult bitmapResult;
      bitmapResult.expired = false;

      // Create bitmap.
      auto data = base::MakeRefCounted<base::RefCountedBytes>();
      SkBitmap bitmap;
      bitmap.allocN32Pixels(30, 30);
      gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &data->as_vector());
      bitmapResult.bitmap_data = data;

      favicon_base::LargeIconResult result(bitmapResult);
      std::move(callback).Run(result);
    } else {
      favicon_base::FallbackIconStyle* fallback =
          new favicon_base::FallbackIconStyle();
      favicon_base::LargeIconResult result(fallback);
      fallback = NULL;
      std::move(callback).Run(result);
    }

    return 1;
  }

  // Returns the same as `GetLargeIconRawBitmapOrFallbackStyleForPageUrl`.
  base::CancelableTaskTracker::TaskId
  GetLargeIconRawBitmapOrFallbackStyleForIconUrl(
      const GURL& icon_url,
      int min_source_size_in_pixel,
      int desired_size_in_pixel,
      favicon_base::LargeIconCallback callback,
      base::CancelableTaskTracker* tracker) override {
    return GetLargeIconRawBitmapOrFallbackStyleForPageUrl(
        icon_url, min_source_size_in_pixel, desired_size_in_pixel,
        std::move(callback), tracker);
  }
};

class FaviconLoaderTest : public PlatformTest,
                          public ::testing::WithParamInterface<FaviconUrlType> {
 public:
  FaviconLoaderTest(const FaviconLoaderTest&) = delete;
  FaviconLoaderTest& operator=(const FaviconLoaderTest&) = delete;

 protected:
  FaviconLoaderTest() : favicon_loader_(&large_icon_service_) {}

  FakeLargeIconService large_icon_service_;
  FaviconLoader favicon_loader_;

  // Returns FaviconLoader::FaviconForPageUrl or
  // FaviconLoader::FaviconForIconUrl depending on the TEST_P param.
  void FaviconForUrl(const GURL& url,
                     FaviconLoader::FaviconAttributesCompletionBlock callback) {
    if (GetParam() == TEST_PAGE_URL) {
      favicon_loader_.FaviconForPageUrl(url, kTestFaviconSize, kTestFaviconSize,
                                        /*fallback_to_google_server=*/false,
                                        callback);
    } else {
      favicon_loader_.FaviconForIconUrl(url, kTestFaviconSize, kTestFaviconSize,
                                        callback);
    }
  }
};

// Tests that image is returned when a favicon is retrieved from
// LargeIconService.
TEST_P(FaviconLoaderTest, FaviconForPageUrl) {
  __block bool callback_executed = false;
  auto confirmation_block = ^(FaviconAttributes* favicon_attributes) {
    callback_executed = true;
    EXPECT_TRUE(favicon_attributes.faviconImage);
  };
  FaviconForUrl(GURL(kTestFaviconURL), confirmation_block);
  EXPECT_TRUE(callback_executed);
}

// Tests that fallback data is provided when no favicon is retrieved from
// LargeIconService.
TEST_P(FaviconLoaderTest, FallbackIcon) {
  __block int callback_executed_count = 0;
  auto confirmation_block = ^(FaviconAttributes* favicon_attributes) {
    if (callback_executed_count == 0) {
      // Check that a placeholder image is received.
      EXPECT_TRUE(favicon_attributes.faviconImage);
    } else {
      // Check that a monogram is used as a fallback.
      EXPECT_FALSE(favicon_attributes.faviconImage);
      EXPECT_TRUE(favicon_attributes.monogramString);
      EXPECT_TRUE(favicon_attributes.textColor);
      EXPECT_TRUE(favicon_attributes.backgroundColor);
    }

    ++callback_executed_count;
  };
  FaviconForUrl(GURL(kTestFallbackURL), confirmation_block);
  EXPECT_EQ(callback_executed_count, 2);
}

// Tests that when favicon is in cache, the callback is synchronously called.
TEST_P(FaviconLoaderTest, Cache) {
  // Favicon retrieval that should put it in the cache.
  FaviconForUrl(GURL(kTestFaviconURL), ^(FaviconAttributes* attributes){
                });
  __block bool callback_executed = false;
  __block UIImage* faviconImage = nil;
  __block int callback_executed_count = 0;
  auto confirmation_block = ^(FaviconAttributes* faviconAttributes) {
    callback_executed = true;
    faviconImage = faviconAttributes.faviconImage;
    ++callback_executed_count;
  };
  FaviconForUrl(GURL(kTestFaviconURL), confirmation_block);
  EXPECT_EQ(callback_executed_count, 1);
  // The callback should be immediately executed.
  EXPECT_TRUE(callback_executed);
  // The cached image should be available immediately.
  EXPECT_TRUE(faviconImage);
}

INSTANTIATE_TEST_SUITE_P(ProgrammaticFaviconLoaderTest,
                         FaviconLoaderTest,
                         ::testing::Values(FaviconUrlType::TEST_PAGE_URL,
                                           FaviconUrlType::TEST_ICON_URL));

}  // namespace