chromium/ash/app_list/views/apps_collection_section_view_unittest.cc

// Copyright 2024 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/views/apps_collection_section_view.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/app_list/app_collections_constants.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/model/app_list_model.h"
#include "ash/app_list/model/app_list_test_model.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_item_view.h"
#include "ash/app_list/views/apps_grid_view_test_api.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/label.h"
#include "ui/views/view_utils.h"

namespace ash {
// Parameterized to test apps collections in the app list bubble apps
// collection page.
class AppsCollectionSectionViewTest : public AshTestBase {
 public:
  AppsCollectionSectionViewTest() = default;

  // AshTestBase:
  void SetUp() override {
    scoped_feature_list_.InitWithFeatures(
        {app_list_features::kAppsCollections,
         app_list_features::kForceShowAppsCollections},
        {});
    AshTestBase::SetUp();
  }

  void ShowAppList() {
    Shell::Get()->app_list_controller()->ShowAppList(
        AppListShowSource::kSearchKey);
  }

  void AddAppListItemWithCollection(const std::string& id,
                                    AppCollection collection_id) {
    AppListModel* model = AppListModelProvider::Get()->model();
    auto item = std::make_unique<AppListItem>(id);
    item->SetAppCollectionId(collection_id);
    AppListItem* item_ptr = model->AddItem(std::move(item));

    // Give each item a name so that the accessibility paint checks pass.
    // (Focusable items should have accessible names.)
    model->SetItemName(item_ptr, item_ptr->id());
  }

  AppsCollectionSectionView* GetViewForCollection(AppCollection id) {
    views::View* collections_container =
        GetAppListTestHelper()->GetAppCollectionsSectionsContainer();
    for (views::View* child : collections_container->children()) {
      AppsCollectionSectionView* collection =
          views::AsViewClass<AppsCollectionSectionView>(child);
      if (collection->collection() == id) {
        return collection;
      }
    }
    return nullptr;
  }

  AppListItemView* GetAppItemAtIndex(AppsCollectionSectionView* collection,
                                     size_t index) {
    return index < collection->item_views_.view_size()
               ? collection->item_views_.view_at(index)
               : nullptr;
  }

  void RemoveApp(const std::string& id) {
    AppListModelProvider::Get()->model()->DeleteItem(id);
  }

 protected:
  AppListItemView::DragState GetDragState(AppListItemView* view) {
    return view->drag_state_;
  }

  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(AppsCollectionSectionViewTest, CreatesIconsForAppsWithinCollection) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  AddAppListItemWithCollection("id5", AppCollection::kProductivity);
  AddAppListItemWithCollection("id6", AppCollection::kProductivity);

  ShowAppList();

  ASSERT_TRUE(GetViewForCollection(AppCollection::kEntertainment));
  EXPECT_EQ(
      GetViewForCollection(AppCollection::kEntertainment)->GetItemViewCount(),
      4u);
  ASSERT_TRUE(GetViewForCollection(AppCollection::kProductivity));
  EXPECT_EQ(
      GetViewForCollection(AppCollection::kProductivity)->GetItemViewCount(),
      2u);
}

TEST_F(AppsCollectionSectionViewTest, DoesNotCreateIconsForEmptyCollection) {
  AddAppListItemWithCollection("id1", AppCollection::kProductivity);
  AddAppListItemWithCollection("id2", AppCollection::kProductivity);

  ShowAppList();

  AppsCollectionSectionView* entertainment_collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(entertainment_collection);
  EXPECT_FALSE(entertainment_collection->GetVisible());
  EXPECT_EQ(entertainment_collection->GetItemViewCount(), 0u);

  AppsCollectionSectionView* productivity_collection =
      GetViewForCollection(AppCollection::kProductivity);
  ASSERT_TRUE(productivity_collection);
  EXPECT_TRUE(productivity_collection->GetVisible());
  EXPECT_EQ(productivity_collection->GetItemViewCount(), 2u);
}

TEST_F(AppsCollectionSectionViewTest, ClickOrTapOnCollectionApp) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  ASSERT_GT(collection->GetItemViewCount(), 0u);

  // Click or tap on the first icon.
  views::View* icon = GetAppItemAtIndex(collection, 0);

  GetEventGenerator()->MoveMouseTo(icon->GetBoundsInScreen().CenterPoint());
  GetEventGenerator()->ClickLeftButton();

  // The item was activated.
  EXPECT_EQ(1, GetTestAppListClient()->activate_item_count());
  EXPECT_EQ("id1", GetTestAppListClient()->activate_item_last_id());
}

TEST_F(AppsCollectionSectionViewTest, AccessibleDescription) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  ASSERT_GT(collection->GetItemViewCount(), 0u);

  views::View* view = GetAppItemAtIndex(collection, 0);

  EXPECT_EQ(view->GetViewAccessibility().GetCachedDescription(),
            l10n_util::GetStringUTF16(
                IDS_ASH_LAUNCHER_APPS_COLLECTIONS_ENTERTAINMENT_NAME));
}

TEST_F(AppsCollectionSectionViewTest, AttemptTouchDragApp) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  ASSERT_GT(collection->GetItemViewCount(), 0u);

  // Click or tap on the first icon.
  AppListItemView* view = GetAppItemAtIndex(collection, 0);
  EXPECT_EQ(GetDragState(view), AppListItemView::DragState::kNone);

  auto* generator = GetEventGenerator();
  gfx::Point from = view->GetBoundsInScreen().CenterPoint();
  generator->MoveTouch(from);
  generator->PressTouch();

  // Attempt to fire the touch drag timer. The grid view should not trigger
  // the timer.
  EXPECT_FALSE(view->FireTouchDragTimerForTest());

  // Verify the apps did not enter dragged state.
  EXPECT_EQ(GetDragState(view), AppListItemView::DragState::kNone);
  EXPECT_TRUE(view->title()->GetVisible());
}

TEST_F(AppsCollectionSectionViewTest, AttemptMouseDragApp) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  ASSERT_GT(collection->GetItemViewCount(), 0u);

  // Click or tap on the first icon.
  AppListItemView* view = GetAppItemAtIndex(collection, 0);
  EXPECT_EQ(GetDragState(view), AppListItemView::DragState::kNone);

  auto* generator = GetEventGenerator();
  gfx::Point from = view->GetBoundsInScreen().CenterPoint();
  generator->MoveMouseTo(from);
  generator->PressLeftButton();

  // Attempt to fire the mouse drag timer. The grid view should not trigger
  // the timer.
  EXPECT_FALSE(view->FireMouseDragTimerForTest());

  // Verify the apps did not enter dragged state.
  EXPECT_EQ(GetDragState(view), AppListItemView::DragState::kNone);
  EXPECT_TRUE(view->title()->GetVisible());
}

TEST_F(AppsCollectionSectionViewTest, RemoveAppFromModel) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  EXPECT_EQ(collection->GetItemViewCount(), 4u);

  RemoveApp("id2");

  ASSERT_TRUE(collection);
  EXPECT_EQ(collection->GetItemViewCount(), 3u);
}

TEST_F(AppsCollectionSectionViewTest, AddAppToModel) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  EXPECT_EQ(collection->GetItemViewCount(), 4u);

  AddAppListItemWithCollection("id5", AppCollection::kEntertainment);

  ASSERT_TRUE(collection);
  EXPECT_EQ(collection->GetItemViewCount(), 5u);
}

TEST_F(AppsCollectionSectionViewTest, AddAppToModelOnDifferentCollection) {
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id3", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id4", AppCollection::kEntertainment);

  ShowAppList();

  AppsCollectionSectionView* collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(collection);
  EXPECT_EQ(collection->GetItemViewCount(), 4u);

  AddAppListItemWithCollection("id5", AppCollection::kProductivity);

  ASSERT_TRUE(collection);
  EXPECT_EQ(collection->GetItemViewCount(), 4u);
}

TEST_F(AppsCollectionSectionViewTest, RecordMetricsForAppLaunchByEntity) {
  base::HistogramTester histograms;
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kUnknown);

  ShowAppList();

  AppsCollectionSectionView* entertainment_collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(entertainment_collection);
  EXPECT_EQ(entertainment_collection->GetItemViewCount(), 1u);

  AppsCollectionSectionView* unknown_collection =
      GetViewForCollection(AppCollection::kUnknown);
  ASSERT_TRUE(unknown_collection);
  EXPECT_EQ(unknown_collection->GetItemViewCount(), 1u);

  histograms.ExpectUniqueSample(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByEntity",
      AppEntity::kDefaultApp, 0);
  histograms.ExpectUniqueSample(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByEntity",
      AppEntity::kThirdPartyApp, 0);

  LeftClickOn(GetAppItemAtIndex(entertainment_collection, 0));
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByEntity",
      AppEntity::kDefaultApp, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByEntity",
      AppEntity::kThirdPartyApp, 0);

  LeftClickOn(GetAppItemAtIndex(unknown_collection, 0));
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByEntity",
      AppEntity::kDefaultApp, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByEntity",
      AppEntity::kThirdPartyApp, 1);
}

TEST_F(AppsCollectionSectionViewTest, RecordMetricsForAppLaunchByCategory) {
  base::HistogramTester histograms;
  AddAppListItemWithCollection("id1", AppCollection::kEntertainment);
  AddAppListItemWithCollection("id2", AppCollection::kProductivity);
  AddAppListItemWithCollection("id3", AppCollection::kProductivity);
  AddAppListItemWithCollection("id4", AppCollection::kUnknown);

  ShowAppList();

  AppsCollectionSectionView* entertainment_collection =
      GetViewForCollection(AppCollection::kEntertainment);
  ASSERT_TRUE(entertainment_collection);
  ASSERT_EQ(entertainment_collection->GetItemViewCount(), 1u);

  AppsCollectionSectionView* productivity_collection =
      GetViewForCollection(AppCollection::kProductivity);
  ASSERT_TRUE(productivity_collection);
  ASSERT_EQ(productivity_collection->GetItemViewCount(), 2u);

  AppsCollectionSectionView* unknown_collection =
      GetViewForCollection(AppCollection::kUnknown);
  ASSERT_TRUE(unknown_collection);
  ASSERT_EQ(unknown_collection->GetItemViewCount(), 1u);

  histograms.ExpectTotalCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory", 0);

  // TODO(anasalazar): Investigate why after adding margin to the
  // AppsCollections apps container, this tests fails to click on apps unless we
  // request focus here.
  GetAppItemAtIndex(entertainment_collection, 0)->RequestFocus();

  LeftClickOn(GetAppItemAtIndex(entertainment_collection, 0));
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kEntertainment, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kProductivity, 0);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kUnknown, 0);

  LeftClickOn(GetAppItemAtIndex(productivity_collection, 0));
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kEntertainment, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kProductivity, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kUnknown, 0);

  LeftClickOn(GetAppItemAtIndex(unknown_collection, 0));
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kEntertainment, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kProductivity, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kUnknown, 1);

  LeftClickOn(GetAppItemAtIndex(productivity_collection, 1));
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kEntertainment, 1);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kProductivity, 2);
  histograms.ExpectBucketCount(
      "Apps.AppListBubble.AppsCollectionsPage.AppLaunchesByCategory",
      AppCollection::kUnknown, 1);
}

}  // namespace ash