| // Copyright 2014 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 "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/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->SetDefaultIcon(CreateSquareBitmapWithColor( |
| SharedAppListConfig::instance().default_grid_icon_dimension(), |
| icon_color)); |
| 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::kLarge, |
| AppListConfigType::kMedium, |
| AppListConfigType::kSmall), |
| ::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")->SetDefaultIcon(CreateSquareBitmapWithColor( |
| SharedAppListConfig::instance().default_grid_icon_dimension(), |
| SK_ColorMAGENTA)); |
| 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->folder_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()), |
| gfx::Rect(config->folder_unclipped_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_unclipped_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->folder_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()), |
| gfx::Rect(config->folder_unclipped_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_unclipped_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->folder_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()), |
| gfx::Rect(config->folder_unclipped_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_unclipped_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_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() - 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(expected_icon_rect_size, item_3_bounds.size()); |
| EXPECT_EQ(test_rect_center.x(), item_3_bounds.CenterPoint().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->folder_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()), |
| gfx::Rect(config->folder_unclipped_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_unclipped_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->folder_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_icon_size()), |
| gfx::Rect(config->folder_unclipped_icon_size()), |
| gfx::Rect(gfx::Point(10, 10), config->folder_unclipped_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 |