chromium/ash/shelf/shelf_unittest.cc

// Copyright 2013 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/shelf/shelf.h"

#include <memory>
#include <utility>

#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/test/test_shelf_item_delegate.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shelf/shelf_app_button.h"
#include "ash/shelf/shelf_controller.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/unified/feature_pod_button.h"
#include "ash/test/ash_test_base.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "components/session_manager/session_manager_types.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_host.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

class ShelfTest : public AshTestBase {
 public:
  ShelfTest() = default;

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

  ~ShelfTest() override = default;

  void SetUp() override {
    AshTestBase::SetUp();

    shelf_view_ = GetPrimaryShelf()->GetShelfViewForTesting();
    shelf_model_ = shelf_view_->model();

    test_ = std::make_unique<ShelfViewTestAPI>(shelf_view_);
  }

  ShelfView* shelf_view() { return shelf_view_; }
  ShelfModel* shelf_model() { return shelf_model_; }

  ShelfViewTestAPI* test_api() { return test_.get(); }

 protected:
  Shelf* GetSecondaryShelf() {
    return Shell::GetRootWindowControllerWithDisplayId(
               GetSecondaryDisplay().id())
        ->shelf();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_{features::kSnapGroup};

  raw_ptr<ShelfView, DanglingUntriaged> shelf_view_ = nullptr;
  raw_ptr<ShelfModel, DanglingUntriaged> shelf_model_ = nullptr;
  std::unique_ptr<ShelfViewTestAPI> test_;
};

// Confirms that ShelfItem reflects the appropriated state.
TEST_F(ShelfTest, StatusReflection) {
  // Initially we have the app list.
  size_t button_count = test_api()->GetButtonCount();

  // Add a running app.
  ShelfItem item;
  item.id = ShelfID("foo");
  item.type = TYPE_APP;
  item.status = STATUS_RUNNING;
  int index = shelf_model()->Add(
      item, std::make_unique<TestShelfItemDelegate>(item.id));
  ASSERT_EQ(++button_count, test_api()->GetButtonCount());
  ShelfAppButton* button = test_api()->GetButton(index);
  EXPECT_EQ(ShelfAppButton::STATE_RUNNING, button->state());

  // Remove it.
  shelf_model()->RemoveItemAt(index);
  ASSERT_EQ(--button_count, test_api()->GetButtonCount());
}

TEST_F(ShelfTest, AppIconInkDropBaseColor) {
  // Initially we have the app list.
  size_t button_count = test_api()->GetButtonCount();

  // Add a running app.
  ShelfItem item;
  item.id = ShelfID("foo");
  item.type = TYPE_APP;
  item.status = STATUS_RUNNING;
  int index = shelf_model()->Add(
      item, std::make_unique<TestShelfItemDelegate>(item.id));

  ASSERT_EQ(++button_count, test_api()->GetButtonCount());
  ShelfAppButton* button = test_api()->GetButton(index);
  EXPECT_EQ(button->GetColorProvider()->GetColor(
                cros_tokens::kCrosSysRippleNeutralOnSubtle),
            views::InkDrop::Get(button)->GetBaseColor());
  EXPECT_EQ(1.0f, views::InkDrop::Get(button)->GetVisibleOpacity());
}

// Confirm that using the menu will clear the hover attribute. To avoid another
// browser test we check this here.
TEST_F(ShelfTest, CheckHoverAfterMenu) {
  // Initially we have the app list.
  size_t button_count = test_api()->GetButtonCount();

  // Add a running app.
  ShelfItem item;
  item.id = ShelfID("foo");
  item.type = TYPE_APP;
  item.status = STATUS_RUNNING;
  int index = shelf_model()->Add(
      item, std::make_unique<TestShelfItemDelegate>(item.id));

  ASSERT_EQ(++button_count, test_api()->GetButtonCount());
  ShelfAppButton* button = test_api()->GetButton(index);
  button->AddState(ShelfAppButton::STATE_HOVERED);
  button->ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE);
  EXPECT_FALSE(button->state() & ShelfAppButton::STATE_HOVERED);

  // Remove it.
  shelf_model()->RemoveItemAt(index);
}

// Various assertions around auto-hide behavior.
TEST_F(ShelfTest, ToggleAutoHide) {
  std::unique_ptr<aura::Window> window =
      std::make_unique<aura::Window>(nullptr);
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  window->SetType(aura::client::WINDOW_TYPE_NORMAL);
  window->Init(ui::LAYER_TEXTURED);
  ParentWindowInPrimaryRootWindow(window.get());
  window->Show();
  wm::ActivateWindow(window.get());

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());
}

// Various assertions around disabling auto-hide.
TEST_F(ShelfTest, DisableAutoHide) {
  // Create and activate a `window`.
  auto window = std::make_unique<aura::Window>(nullptr);
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  window->SetType(aura::client::WINDOW_TYPE_NORMAL);
  window->Init(ui::LAYER_TEXTURED);
  ParentWindowInPrimaryRootWindow(window.get());
  window->Show();
  wm::ActivateWindow(window.get());

  // Set `shelf` to always auto-hide.
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Verify `shelf` is auto-hidden.
  ShelfLayoutManager* shelf_layout_manager = shelf->shelf_layout_manager();
  EXPECT_TRUE(shelf_layout_manager->is_shelf_auto_hidden());

  {
    // Verify that auto-hide can be disabled using `ScopedDisableAutoHide`.
    Shelf::ScopedDisableAutoHide disable_auto_hide(shelf);
    EXPECT_FALSE(shelf_layout_manager->is_shelf_auto_hidden());
  }

  // Verify `shelf` is auto-hidden.
  EXPECT_TRUE(shelf_layout_manager->is_shelf_auto_hidden());

  // Lock shelf in auto-hidden state.
  Shelf::ScopedAutoHideLock auto_hide_lock(shelf);
  EXPECT_TRUE(shelf_layout_manager->is_shelf_auto_hidden());

  {
    // Verify that auto-hide cannot be disabled using `ScopedDisableAutoHide`.
    Shelf::ScopedDisableAutoHide disable_auto_hide(shelf);
    EXPECT_TRUE(shelf_layout_manager->is_shelf_auto_hidden());
  }

  // Verify `shelf` is auto-hidden.
  EXPECT_TRUE(shelf_layout_manager->is_shelf_auto_hidden());
}

// Tests if shelf is hidden on secondary display after the primary display is
// changed.
TEST_F(ShelfTest, ShelfHiddenOnScreenOnSecondaryDisplay) {
  for (const auto& state : {session_manager::SessionState::LOCKED,
                            session_manager::SessionState::LOGIN_PRIMARY}) {
    SCOPED_TRACE(
        base::StringPrintf("Testing state: %d", static_cast<int>(state)));
    GetSessionControllerClient()->SetSessionState(state);
    UpdateDisplay("800x600,800x600");

    EXPECT_EQ(SHELF_VISIBLE, GetPrimaryShelf()->GetVisibilityState());
    EXPECT_EQ(SHELF_HIDDEN, GetSecondaryShelf()->GetVisibilityState());

    SwapPrimaryDisplay();

    EXPECT_EQ(SHELF_VISIBLE, GetPrimaryShelf()->GetVisibilityState());
    EXPECT_EQ(SHELF_HIDDEN, GetSecondaryShelf()->GetVisibilityState());
  }
}

TEST_F(ShelfTest, ShelfNavigationWidgetAccessibleProperties) {
  ShelfNavigationWidget::TestApi widget_api(
      GetPrimaryShelf()->navigation_widget());
  ui::AXNodeData data;

  widget_api.GetWidgetDelegateView()
      ->GetViewAccessibility()
      .GetAccessibleNodeData(&data);
  EXPECT_EQ(ax::mojom::Role::kToolbar, data.role);
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME),
            data.GetStringAttribute(ax::mojom::StringAttribute::kName));
}

using NoSessionShelfTest = NoSessionAshTestBase;

// Regression test for crash in Shelf::SetAlignment(). https://crbug.com/937495
TEST_F(NoSessionShelfTest, SetAlignmentDuringDisplayDisconnect) {
  UpdateDisplay("1024x768,800x600");
  base::RunLoop().RunUntilIdle();

  // The task indirectly triggers Shelf::SetAlignment() via a SessionObserver.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(
          [](TestSessionControllerClient* session) {
            session->SetSessionState(session_manager::SessionState::ACTIVE);
          },
          GetSessionControllerClient()));

  // Remove the secondary display.
  UpdateDisplay("1280x1024");

  // The session activation task runs before the RootWindowController and the
  // Shelf are deleted.
  base::RunLoop().RunUntilIdle();

  // No crash.
}

}  // namespace
}  // namespace ash