chromium/ash/system/phonehub/app_stream_launcher_view_unittest.cc

// Copyright 2022 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/system/phonehub/app_stream_launcher_view.h"

#include "ash/constants/ash_features.h"
#include "ash/style/ash_color_provider.h"
#include "ash/system/phonehub/app_stream_launcher_item.h"
#include "ash/system/phonehub/app_stream_launcher_list_item.h"
#include "ash/system/phonehub/phone_hub_metrics.h"
#include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/phonehub/app_stream_launcher_data_model.h"
#include "chromeos/ash/components/phonehub/fake_phone_hub_manager.h"
#include "chromeos/ash/components/phonehub/notification.h"
#include "chromeos/ash/components/phonehub/phone_hub_manager.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget_utils.h"

namespace ash {

class AppStreamLauncherViewTest : public views::ViewsTestBase {
 public:
  AppStreamLauncherViewTest() = default;
  ~AppStreamLauncherViewTest() override = default;

  // ViewsTestBase:
  void SetUp() override {
    views::ViewsTestBase::SetUp();
    CreateWidget();
    generator_ =
        std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_));

    // All unit tests related to ListView will enable the feature themselves.
    // Currently keeping the grid view unit tests in the event the design goes
    // back to it.
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kEcheLauncher, features::kEcheSWA},
        /*disabled_features=*/{features::kEcheLauncherListView});
  }

  // AshTestBase:
  void TearDown() override {
    app_stream_launcher_view_.reset();
    generator_.reset();
    views::ViewsTestBase::TearDown();
  }

 protected:
  views::Widget* widget() { return widget_; }
  ui::test::EventGenerator* generator() { return generator_.get(); }
  AppStreamLauncherView* app_stream_launcher_view() {
    return app_stream_launcher_view_.get();
  }

  void GenerateLauncherView() {
    app_stream_launcher_view_ =
        std::make_unique<AppStreamLauncherView>(&fake_phone_hub_manager_);
    widget_->SetContentsView(app_stream_launcher_view_.get());
    widget_->Show();
    widget_->LayoutRootViewIfNecessary();
  }

  AppStreamLauncherItem* GetItemView(int index) {
    return static_cast<AppStreamLauncherItem*>(
        app_stream_launcher_view()->items_container_for_test()->children().at(
            index));
  }

  AppStreamLauncherListItem* GetListItemView(int index) {
    return static_cast<AppStreamLauncherListItem*>(
        app_stream_launcher_view()->items_container_for_test()->children().at(
            index));
  }

  const gfx::Image CreateTestImage() {
    gfx::ImageSkia image_skia = gfx::test::CreateImageSkia(/*size=*/60);
    image_skia.MakeThreadSafe();
    return gfx::Image(image_skia);
  }

  void CreateWidget() {
    DCHECK(!widget_);
    widget_ = new views::Widget;
    views::Widget::InitParams params =
        CreateParams(views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
                     views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
    params.bounds = gfx::Rect(0, 0, 600, 800);
    widget_->Init(std::move(params));
  }

  phonehub::FakePhoneHubManager* fake_phone_hub_manager() {
    return &fake_phone_hub_manager_;
  }

  base::test::ScopedFeatureList feature_list_;

 private:
  // This is required in order for the context to find color provider
  AshColorProvider color_provider_;
  std::unique_ptr<AppStreamLauncherView> app_stream_launcher_view_;
  phonehub::FakePhoneHubManager fake_phone_hub_manager_;
  raw_ptr<views::Widget, DanglingUntriaged> widget_ = nullptr;
  std::unique_ptr<ui::test::EventGenerator> generator_;
};

TEST_F(AppStreamLauncherViewTest, OpenView) {
  fake_phone_hub_manager()
      ->fake_app_stream_launcher_data_model()
      ->SetLauncherSize(1000, 1000);
  GenerateLauncherView();
  EXPECT_TRUE(app_stream_launcher_view()->GetVisible());
}

TEST_F(AppStreamLauncherViewTest, AddItems) {
  GenerateLauncherView();
  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  EXPECT_EQ(0U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  EXPECT_EQ(u"Fake App", GetItemView(0)->GetLabelForTest()->GetText());
}

TEST_F(AppStreamLauncherViewTest, AddItemsListView) {
  GenerateLauncherView();
  feature_list_.Reset();
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{features::kEcheLauncher, features::kEcheSWA,
                            features::kEcheLauncherListView},
      /*disabled_features=*/{});

  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";
  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  EXPECT_EQ(u"Fake App", GetListItemView(0)->GetText());
}

TEST_F(AppStreamLauncherViewTest, RemoveItem) {
  GenerateLauncherView();
  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  EXPECT_EQ(u"Fake App", GetItemView(0)->GetLabelForTest()->GetText());

  apps.clear();
  data_model->SetAppList(apps);

  EXPECT_EQ(0U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());
}

TEST_F(AppStreamLauncherViewTest, RemoveItemListView) {
  GenerateLauncherView();
  feature_list_.Reset();
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{features::kEcheLauncher, features::kEcheSWA,
                            features::kEcheLauncherListView},
      /*disabled_features=*/{});

  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  EXPECT_EQ(u"Fake App", GetListItemView(0)->GetText());

  apps.clear();
  data_model->SetAppList(apps);

  EXPECT_EQ(0U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());
}

TEST_F(AppStreamLauncherViewTest, ClickOnItem) {
  GenerateLauncherView();
  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);
  widget()->LayoutRootViewIfNecessary();

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  ui::test::EventGenerator generator(
      GetRootWindow(app_stream_launcher_view()->GetWidget()));

  EXPECT_TRUE(GetItemView(0)->GetVisible());
  EXPECT_TRUE(GetItemView(0)->GetIconForTest()->GetEnabled());
  EXPECT_TRUE(GetItemView(0)->GetLabelForTest()->GetEnabled());

  gfx::Point cursor_location =
      GetItemView(0)->GetIconForTest()->GetBoundsInScreen().CenterPoint();
  generator.MoveMouseTo(cursor_location);
  generator.ClickLeftButton();

  EXPECT_EQ(1U, fake_phone_hub_manager()
                    ->fake_recent_apps_interaction_handler()
                    ->HandledRecentAppsCount(package_name));
}

TEST_F(AppStreamLauncherViewTest, ClickOnItemListView) {
  GenerateLauncherView();
  feature_list_.Reset();
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{features::kEcheLauncher, features::kEcheSWA,
                            features::kEcheLauncherListView},
      /*disabled_features=*/{});

  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::STREAMABLE);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);
  widget()->LayoutRootViewIfNecessary();

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  ui::test::EventGenerator generator(
      GetRootWindow(app_stream_launcher_view()->GetWidget()));

  EXPECT_TRUE(GetListItemView(0)->GetVisible());
  EXPECT_TRUE(GetListItemView(0)->GetEnabled());

  gfx::Point cursor_location =
      GetListItemView(0)->GetBoundsInScreen().CenterPoint();
  generator.MoveMouseTo(cursor_location);
  generator.ClickLeftButton();

  EXPECT_EQ(1U, fake_phone_hub_manager()
                    ->fake_recent_apps_interaction_handler()
                    ->HandledRecentAppsCount(package_name));
}

TEST_F(AppStreamLauncherViewTest, DisabledItem) {
  GenerateLauncherView();
  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::BLOCK_LISTED);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);
  widget()->LayoutRootViewIfNecessary();

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  ui::test::EventGenerator generator(
      GetRootWindow(app_stream_launcher_view()->GetWidget()));

  EXPECT_TRUE(GetItemView(0)->GetVisible());
  EXPECT_FALSE(GetItemView(0)->GetIconForTest()->GetEnabled());
  EXPECT_FALSE(GetItemView(0)->GetLabelForTest()->GetEnabled());
  EXPECT_EQ(u"Not supported",
            GetItemView(0)->GetIconForTest()->GetTooltipText());
}

TEST_F(AppStreamLauncherViewTest, DisabledItemListView) {
  GenerateLauncherView();
  feature_list_.Reset();
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{features::kEcheLauncher, features::kEcheSWA,
                            features::kEcheLauncherListView},
      /*disabled_features=*/{});

  const int64_t user_id = 1;
  const char16_t app_visible_name[] = u"Fake App";
  const char package_name[] = "com.fakeapp";

  auto app1 = phonehub::Notification::AppMetadata(
      app_visible_name, package_name, /*color_icon=*/CreateTestImage(),
      /*monochrome_icon_mask=*/std::nullopt,
      /*icon_color=*/std::nullopt, /*icon_is_monochrome=*/true, user_id,
      phonehub::proto::AppStreamabilityStatus::BLOCK_LISTED);
  std::vector<phonehub::Notification::AppMetadata> apps;
  apps.push_back(app1);

  phonehub::AppStreamLauncherDataModel* data_model =
      fake_phone_hub_manager()->fake_app_stream_launcher_data_model();
  data_model->SetAppList(apps);
  widget()->LayoutRootViewIfNecessary();

  EXPECT_EQ(1U, app_stream_launcher_view()
                    ->items_container_for_test()
                    ->children()
                    .size());

  ui::test::EventGenerator generator(
      GetRootWindow(app_stream_launcher_view()->GetWidget()));

  EXPECT_TRUE(GetListItemView(0)->GetVisible());
  EXPECT_FALSE(GetListItemView(0)->GetEnabled());
  EXPECT_EQ(u"Not supported", GetListItemView(0)->GetTooltipText());
}

}  // namespace ash