chromium/ash/app_list/folder_image_unittest.cc

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

#include "ash/app_list/model/folder_image.h"

#include <string>
#include <utility>

#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/model/app_list_item_list.h"
#include "ash/app_list/model/app_list_test_model.h"
#include "ash/public/cpp/app_list/app_list_config.h"
#include "ash/public/cpp/app_list/app_list_config_provider.h"
#include "base/test/icu_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/skia_util.h"

namespace ash {

namespace {

gfx::ImageSkia CreateSquareBitmapWithColor(int size, SkColor color) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(size, size);
  bitmap.eraseColor(color);
  return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
}

bool ImagesAreEqual(const gfx::ImageSkia& image1,
                    const gfx::ImageSkia& image2) {
  return gfx::BitmapsAreEqual(*image1.bitmap(), *image2.bitmap());
}

// Listens for OnFolderImageUpdated and sets a flag upon receiving the signal.
class TestFolderImageObserver : public FolderImageObserver {
 public:
  TestFolderImageObserver() : updated_flag_(false) {}

  TestFolderImageObserver(const TestFolderImageObserver&) = delete;
  TestFolderImageObserver& operator=(const TestFolderImageObserver&) = delete;

  bool updated() const { return updated_flag_; }

  void Reset() { updated_flag_ = false; }

  // FolderImageObserver overrides:
  void OnFolderImageUpdated(ash::AppListConfigType config_type) override {
    updated_flag_ = true;
  }

 private:
  bool updated_flag_;
};

}  // namespace

class FolderImageTest : public testing::Test,
                        public ::testing::WithParamInterface<
                            std::tuple<AppListConfigType, bool>> {
 public:
  FolderImageTest() : scoped_locale_(std::get<1>(GetParam()) ? "he" : "") {}

  FolderImageTest(const FolderImageTest&) = delete;
  FolderImageTest& operator=(const FolderImageTest&) = delete;

  ~FolderImageTest() override = default;

  void SetUp() override {
    app_list_model_ = std::make_unique<test::AppListTestModel>();
    folder_image_ =
        std::make_unique<FolderImage>(GetAppListConfig(/*can_create=*/true),
                                      app_list_model_->top_level_item_list());

    // Populate the AppListModel with three items (to test that the FolderImage
    // correctly supports having fewer than four icons).
    AddAppWithColoredIcon("app1", SK_ColorRED);
    AddAppWithColoredIcon("app2", SK_ColorGREEN);
    AddAppWithColoredIcon("app3", SK_ColorBLUE);

    observer_.Reset();
    folder_image_->AddObserver(&observer_);
  }

  void TearDown() override {
    folder_image_->RemoveObserver(&observer_);
    AppListConfigProvider::Get().ResetForTesting();
  }

  AppListConfig* GetAppListConfig(bool can_create) {
    return AppListConfigProvider::Get().GetConfigForType(
        std::get<0>(GetParam()), can_create);
  }

  bool is_rtl() { return std::get<1>(GetParam()); }

 protected:
  void AddAppWithColoredIcon(const std::string& id, SkColor icon_color) {
    std::unique_ptr<AppListItem> item(new AppListItem(id));
    item->SetDefaultIconAndColor(
        CreateSquareBitmapWithColor(
            SharedAppListConfig::instance().default_grid_icon_dimension(),
            icon_color),
        IconColor(), /*is_placeholder_icon=*/false);
    static_cast<AppListModel*>(app_list_model_.get())->AddItem(std::move(item));
  }

  base::test::ScopedRestoreICUDefaultLocale scoped_locale_;

  std::unique_ptr<test::AppListTestModel> app_list_model_;

  std::unique_ptr<FolderImage> folder_image_;

  TestFolderImageObserver observer_;
};
INSTANTIATE_TEST_SUITE_P(
    All,
    FolderImageTest,
    ::testing::Combine(::testing::Values(AppListConfigType::kRegular,
                                         AppListConfigType::kDense),
                       ::testing::Bool()));

TEST_P(FolderImageTest, UpdateListTest) {
  gfx::ImageSkia icon1 = folder_image_->icon();

  // Call UpdateIcon and ensure that the observer event fired.
  folder_image_->UpdateIcon();
  EXPECT_TRUE(observer_.updated());
  observer_.Reset();
  // The icon should not have changed.
  EXPECT_TRUE(ImagesAreEqual(icon1, folder_image_->icon()));

  // Swap two items. Ensure that the observer fired and the icon changed.
  app_list_model_->top_level_item_list()->MoveItem(2, 1);
  EXPECT_TRUE(observer_.updated());
  observer_.Reset();
  gfx::ImageSkia icon2 = folder_image_->icon();
  EXPECT_FALSE(ImagesAreEqual(icon1, icon2));

  // Swap back items. Ensure that the observer fired and the icon changed back.
  app_list_model_->top_level_item_list()->MoveItem(2, 1);
  EXPECT_TRUE(observer_.updated());
  observer_.Reset();
  EXPECT_TRUE(ImagesAreEqual(icon1, folder_image_->icon()));

  // Add a new item. Ensure that the observer fired and the icon changed.
  AddAppWithColoredIcon("app4", SK_ColorYELLOW);
  EXPECT_TRUE(observer_.updated());
  observer_.Reset();
  gfx::ImageSkia icon3 = folder_image_->icon();
  EXPECT_FALSE(ImagesAreEqual(icon1, icon3));

  // Add a new item. The observer should not fire, nor should the icon change
  // (because it does not affect the first four icons).
  AddAppWithColoredIcon("app5", SK_ColorCYAN);
  EXPECT_FALSE(observer_.updated());
  observer_.Reset();
  EXPECT_TRUE(ImagesAreEqual(icon3, folder_image_->icon()));

  // Delete an item. Ensure that the observer fired and the icon changed.
  app_list_model_->DeleteItem("app2");
  EXPECT_TRUE(observer_.updated());
  observer_.Reset();
  gfx::ImageSkia icon4 = folder_image_->icon();
  EXPECT_FALSE(ImagesAreEqual(icon3, icon4));
}

TEST_P(FolderImageTest, UpdateItemTest) {
  gfx::ImageSkia icon1 = folder_image_->icon();

  // Change an item's icon. Ensure that the observer fired and the icon changed.
  app_list_model_->FindItem("app2")->SetDefaultIconAndColor(
      CreateSquareBitmapWithColor(
          SharedAppListConfig::instance().default_grid_icon_dimension(),
          SK_ColorMAGENTA),
      IconColor(), /*is_placeholder_icon=*/false);
  EXPECT_TRUE(observer_.updated());
  observer_.Reset();
  EXPECT_FALSE(ImagesAreEqual(icon1, folder_image_->icon()));
}

TEST_P(FolderImageTest, GetTargetIconRectInFolderWithSingleItem) {
  app_list_model_->DeleteItem("app2");
  app_list_model_->DeleteItem("app3");
  const AppListConfig* config = GetAppListConfig(/*can_create=*/false);
  ASSERT_TRUE(config);

  const gfx::Rect test_rects[] = {
      gfx::Rect(config->icon_visible_size()),
      gfx::Rect(gfx::Point(10, 10), config->icon_visible_size()),
      gfx::Rect(config->folder_icon_size()),
      gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()),
  };

  for (const auto& test_rect : test_rects) {
    SCOPED_TRACE(::testing::Message()
                 << "Target folder icon bounds: " << test_rect.ToString());

    const gfx::Point test_rect_center = test_rect.CenterPoint();

    const gfx::Size expected_icon_rect_size(
        config->item_icon_in_folder_icon_dimension(),
        config->item_icon_in_folder_icon_dimension());

    gfx::Rect item_1_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app1"), test_rect);
    EXPECT_EQ(expected_icon_rect_size, item_1_bounds.size());
    EXPECT_EQ(test_rect_center, item_1_bounds.CenterPoint());
  }
}

TEST_P(FolderImageTest, GetTargetIconRectInFolderWithTwoItems) {
  app_list_model_->DeleteItem("app3");
  const AppListConfig* config = GetAppListConfig(/*can_create=*/false);
  ASSERT_TRUE(config);

  const gfx::Rect test_rects[] = {
      gfx::Rect(config->icon_visible_size()),
      gfx::Rect(gfx::Point(10, 10), config->icon_visible_size()),
      gfx::Rect(config->folder_icon_size()),
      gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()),
  };

  for (const auto& test_rect : test_rects) {
    SCOPED_TRACE(::testing::Message()
                 << "Target folder icon bounds: " << test_rect.ToString());

    const gfx::Point test_rect_center = test_rect.CenterPoint();

    const gfx::Size expected_icon_rect_size(
        config->item_icon_in_folder_icon_dimension(),
        config->item_icon_in_folder_icon_dimension());

    gfx::Rect item_1_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app1"), test_rect);
    gfx::Rect item_2_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app2"), test_rect);

    if (is_rtl())
      std::swap(item_1_bounds, item_2_bounds);

    EXPECT_EQ(expected_icon_rect_size, item_1_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() - config->item_icon_in_folder_icon_margin() / 2,
        item_1_bounds.right());
    EXPECT_EQ(test_rect_center.y(), item_1_bounds.CenterPoint().y());

    EXPECT_EQ(expected_icon_rect_size, item_2_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() + config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.x());
    EXPECT_EQ(test_rect_center.y(), item_2_bounds.CenterPoint().y());
  }
}

TEST_P(FolderImageTest, GetTargetIconRectInFolderWithThreeItems) {
  const AppListConfig* config = GetAppListConfig(/*can_create=*/false);
  ASSERT_TRUE(config);

  const gfx::Rect test_rects[] = {
      gfx::Rect(config->icon_visible_size()),
      gfx::Rect(gfx::Point(10, 10), config->icon_visible_size()),
      gfx::Rect(config->folder_icon_size()),
      gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()),
  };

  for (const auto& test_rect : test_rects) {
    SCOPED_TRACE(::testing::Message()
                 << "Target folder icon bounds: " << test_rect.ToString());

    const gfx::Point test_rect_center = test_rect.CenterPoint();

    const gfx::Size expected_icon_rect_size(
        config->item_icon_in_folder_icon_dimension(),
        config->item_icon_in_folder_icon_dimension());

    gfx::Rect item_1_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app1"), test_rect);
    gfx::Rect item_2_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app2"), test_rect);
    gfx::Rect item_3_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app3"), test_rect);

    if (is_rtl())
      std::swap(item_2_bounds, item_3_bounds);

    EXPECT_EQ(expected_icon_rect_size, item_1_bounds.size());
    EXPECT_EQ(test_rect_center.x(), item_1_bounds.CenterPoint().x());
    EXPECT_EQ(
        test_rect_center.y() - config->item_icon_in_folder_icon_margin() / 2,
        item_1_bounds.bottom());
    EXPECT_EQ(expected_icon_rect_size, item_2_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() - config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.right());
    EXPECT_EQ(
        test_rect_center.y() + config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.y());

    EXPECT_EQ(expected_icon_rect_size, item_3_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() + config->item_icon_in_folder_icon_margin() / 2,
        item_3_bounds.x());
    EXPECT_EQ(
        test_rect_center.y() + config->item_icon_in_folder_icon_margin() / 2,
        item_3_bounds.y());
  }
}

TEST_P(FolderImageTest, GetTargetIconRectInFolderWithFourItems) {
  AddAppWithColoredIcon("app4", SK_ColorYELLOW);

  const AppListConfig* config = GetAppListConfig(/*can_create=*/false);
  ASSERT_TRUE(config);

  const gfx::Rect test_rects[] = {
      gfx::Rect(config->icon_visible_size()),
      gfx::Rect(gfx::Point(10, 10), config->icon_visible_size()),
      gfx::Rect(config->folder_icon_size()),
      gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()),
  };

  for (const auto& test_rect : test_rects) {
    SCOPED_TRACE(::testing::Message()
                 << "Target folder icon bounds: " << test_rect.ToString());

    const gfx::Point test_rect_center = test_rect.CenterPoint();

    const gfx::Size expected_icon_rect_size(
        config->item_icon_in_folder_icon_dimension(),
        config->item_icon_in_folder_icon_dimension());

    gfx::Rect item_1_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app1"), test_rect);
    gfx::Rect item_2_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app2"), test_rect);
    gfx::Rect item_3_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app3"), test_rect);
    gfx::Rect item_4_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app4"), test_rect);

    if (is_rtl()) {
      std::swap(item_1_bounds, item_2_bounds);
      std::swap(item_3_bounds, item_4_bounds);
    }

    EXPECT_EQ(expected_icon_rect_size, item_1_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() - config->item_icon_in_folder_icon_margin() / 2,
        item_1_bounds.right());
    EXPECT_EQ(
        test_rect_center.y() - config->item_icon_in_folder_icon_margin() / 2,
        item_1_bounds.bottom());

    EXPECT_EQ(expected_icon_rect_size, item_2_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() + config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.x());
    EXPECT_EQ(
        test_rect_center.y() - config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.bottom());

    EXPECT_EQ(
        test_rect_center.x() - config->item_icon_in_folder_icon_margin() / 2,
        item_3_bounds.right());
    EXPECT_EQ(
        test_rect_center.y() + config->item_icon_in_folder_icon_margin() / 2,
        item_3_bounds.y());

    EXPECT_EQ(expected_icon_rect_size, item_4_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() + config->item_icon_in_folder_icon_margin() / 2,
        item_4_bounds.x());
    EXPECT_EQ(
        test_rect_center.y() + config->item_icon_in_folder_icon_margin() / 2,
        item_4_bounds.y());
  }
}

TEST_P(FolderImageTest, GetTargetIconRectInFolderWithFiveItems) {
  AddAppWithColoredIcon("app4", SK_ColorYELLOW);
  AddAppWithColoredIcon("app5", SK_ColorYELLOW);

  const AppListConfig* config = GetAppListConfig(/*can_create=*/false);
  ASSERT_TRUE(config);

  const gfx::Rect test_rects[] = {
      gfx::Rect(config->icon_visible_size()),
      gfx::Rect(gfx::Point(10, 10), config->icon_visible_size()),
      gfx::Rect(config->folder_icon_size()),
      gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()),
  };

  for (const auto& test_rect : test_rects) {
    SCOPED_TRACE(::testing::Message()
                 << "Target folder icon bounds: " << test_rect.ToString());

    const gfx::Point test_rect_center = test_rect.CenterPoint();

    const gfx::Size expected_icon_rect_size(
        config->item_icon_in_folder_icon_dimension(),
        config->item_icon_in_folder_icon_dimension());

    gfx::Rect item_1_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app1"), test_rect);
    gfx::Rect item_2_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app2"), test_rect);
    gfx::Rect item_3_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app3"), test_rect);
    gfx::Rect item_4_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app4"), test_rect);
    gfx::Rect item_5_bounds = folder_image_->GetTargetIconRectInFolderForItem(
        *config, app_list_model_->FindItem("app5"), test_rect);

    if (is_rtl()) {
      std::swap(item_1_bounds, item_2_bounds);
      std::swap(item_3_bounds, item_4_bounds);
    }

    EXPECT_EQ(expected_icon_rect_size, item_1_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() - config->item_icon_in_folder_icon_margin() / 2,
        item_1_bounds.right());
    EXPECT_EQ(
        test_rect_center.y() - config->item_icon_in_folder_icon_margin() / 2,
        item_1_bounds.bottom());

    EXPECT_EQ(expected_icon_rect_size, item_2_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() + config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.x());
    EXPECT_EQ(
        test_rect_center.y() - config->item_icon_in_folder_icon_margin() / 2,
        item_2_bounds.bottom());

    EXPECT_EQ(
        test_rect_center.x() - config->item_icon_in_folder_icon_margin() / 2,
        item_3_bounds.right());
    EXPECT_EQ(
        test_rect_center.y() + config->item_icon_in_folder_icon_margin() / 2,
        item_3_bounds.y());

    EXPECT_EQ(expected_icon_rect_size, item_4_bounds.size());
    EXPECT_EQ(
        test_rect_center.x() + config->item_icon_in_folder_icon_margin() / 2,
        item_4_bounds.x());
    EXPECT_EQ(
        test_rect_center.y() + config->item_icon_in_folder_icon_margin() / 2,
        item_4_bounds.y());

    EXPECT_EQ(expected_icon_rect_size, item_5_bounds.size());
    EXPECT_EQ(test_rect_center, item_5_bounds.CenterPoint());
  }
}

}  // namespace ash