chromium/ash/shelf/test/shelf_layout_manager_test_base.cc

// Copyright 2020 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/test/shelf_layout_manager_test_base.h"

#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shell.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/workspace_controller.h"
#include "base/functional/bind.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/window.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/view.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

using ::chromeos::kImmersiveIsActive;

ShelfWidget* GetShelfWidget() {
  return AshTestBase::GetPrimaryShelf()->shelf_widget();
}

ShelfLayoutManager* GetShelfLayoutManager() {
  return AshTestBase::GetPrimaryShelf()->shelf_layout_manager();
}

class ShelfDragCallback {
 public:
  ShelfDragCallback(const gfx::Rect& auto_hidden_shelf_bounds,
                    const gfx::Rect& visible_shelf_bounds)
      : auto_hidden_shelf_bounds_(auto_hidden_shelf_bounds),
        visible_shelf_bounds_(visible_shelf_bounds) {
    EXPECT_EQ(auto_hidden_shelf_bounds_.size(), visible_shelf_bounds_.size());
  }

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

  virtual ~ShelfDragCallback() = default;

  void ProcessScroll(ui::EventType type, const gfx::Vector2dF& delta) {
    ProcessScrollInternal(type, delta, true);
  }

  void ProcessScrollNoBoundsCheck(ui::EventType type,
                                  const gfx::Vector2dF& delta) {
    ProcessScrollInternal(type, delta, false);
  }

  void ProcessScrollInternal(ui::EventType type,
                             const gfx::Vector2dF& delta,
                             bool bounds_check) {
    if (GetShelfLayoutManager()->visibility_state() == SHELF_HIDDEN)
      return;

    if (type == ui::EventType::kGestureScrollBegin) {
      scroll_ = gfx::Vector2dF();
      was_visible_on_drag_start_ = GetShelfLayoutManager()->IsVisible();
      return;
    }

    // The state of the shelf at the end of the gesture is tested separately.
    if (type == ui::EventType::kGestureScrollEnd) {
      return;
    }

    if (type == ui::EventType::kGestureScrollUpdate) {
      scroll_.Add(delta);
    }

    Shelf* shelf = AshTestBase::GetPrimaryShelf();
    gfx::Rect shelf_bounds = GetShelfWidget()->GetWindowBoundsInScreen();

    float scroll_delta = shelf->PrimaryAxisValue(scroll_.y(), scroll_.x());
    bool increasing_drag = shelf->SelectValueForShelfAlignment(
        scroll_delta<0, scroll_delta> 0, scroll_delta < 0);
    const int shelf_size =
        shelf->PrimaryAxisValue(shelf_bounds.height(), shelf_bounds.width());
    if (was_visible_on_drag_start_) {
      if (increasing_drag) {
        // If dragging inwards from the visible state, then the shelf should
        // 'overshoot', but not by more than the scroll delta.
        const int bounds_delta = shelf->SelectValueForShelfAlignment(
            visible_shelf_bounds_.y() - shelf_bounds.y(),
            shelf_bounds.x() - visible_shelf_bounds_.x(),
            visible_shelf_bounds_.x() - shelf_bounds.x());
        EXPECT_GE(bounds_delta, 0);
        EXPECT_LE(bounds_delta, std::abs(scroll_delta));
      } else {
        // If dragging outwards from the visible state, then the shelf should
        // move out.
        if (ShelfAlignment::kBottom == shelf->alignment())
          EXPECT_LE(visible_shelf_bounds_.y(), shelf_bounds.y());
        else if (ShelfAlignment::kLeft == shelf->alignment())
          EXPECT_LE(shelf_bounds.x(), visible_shelf_bounds_.x());
        else if (ShelfAlignment::kRight == shelf->alignment())
          EXPECT_LE(visible_shelf_bounds_.x(), shelf_bounds.x());
      }
    } else {
      // The shelf is invisible at the start of the drag.
      if (increasing_drag && bounds_check) {
        constexpr float kEpsilon = 1.f;
        // Moving the shelf into the screen.
        if (std::abs(scroll_delta) < shelf_size) {
          // Tests that the shelf sticks with the touch point during the drag
          // until the shelf is completely visible.
          if (ShelfAlignment::kBottom == shelf->alignment()) {
            EXPECT_NEAR(
                shelf_bounds.y(),
                auto_hidden_shelf_bounds_.y() +
                    ShelfConfig::Get()->hidden_shelf_in_screen_portion() -
                    std::abs(scroll_delta),
                kEpsilon);
          } else if (ShelfAlignment::kLeft == shelf->alignment()) {
            EXPECT_NEAR(
                shelf_bounds.x(),
                auto_hidden_shelf_bounds_.x() -
                    ShelfConfig::Get()->hidden_shelf_in_screen_portion() +
                    std::abs(scroll_delta),
                kEpsilon);
          } else if (ShelfAlignment::kRight == shelf->alignment()) {
            EXPECT_NEAR(
                shelf_bounds.x(),
                auto_hidden_shelf_bounds_.x() +
                    ShelfConfig::Get()->hidden_shelf_in_screen_portion() -
                    std::abs(scroll_delta),
                kEpsilon);
          }
        } else {
          // Tests that after the shelf is completely visible, the shelf starts
          // resisting the drag.
          if (ShelfAlignment::kBottom == shelf->alignment()) {
            EXPECT_GT(shelf_bounds.y(),
                      auto_hidden_shelf_bounds_.y() +
                          ShelfConfig::Get()->hidden_shelf_in_screen_portion() -
                          std::abs(scroll_delta));
          } else if (ShelfAlignment::kLeft == shelf->alignment()) {
            EXPECT_LT(shelf_bounds.x(),
                      auto_hidden_shelf_bounds_.x() -
                          ShelfConfig::Get()->hidden_shelf_in_screen_portion() +
                          std::abs(scroll_delta));
          } else if (ShelfAlignment::kRight == shelf->alignment()) {
            EXPECT_GT(shelf_bounds.x(),
                      auto_hidden_shelf_bounds_.x() +
                          ShelfConfig::Get()->hidden_shelf_in_screen_portion() -
                          std::abs(scroll_delta));
          }
        }
      }
    }
  }

 private:
  const gfx::Rect auto_hidden_shelf_bounds_;
  const gfx::Rect visible_shelf_bounds_;
  gfx::Vector2dF scroll_;
  bool was_visible_on_drag_start_ = false;
};
}  // namespace

void ShelfLayoutManagerTestBase::SetState(ShelfLayoutManager* layout_manager,
                                          ShelfVisibilityState state) {
  layout_manager->SetState(state, /*force_layout=*/false);
}

void ShelfLayoutManagerTestBase::UpdateAutoHideStateNow() {
  GetShelfLayoutManager()->UpdateAutoHideStateNow();
}

aura::Window* ShelfLayoutManagerTestBase::CreateTestWindow() {
  aura::Window* window = new 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);
  return window;
}

aura::Window* ShelfLayoutManagerTestBase::CreateTestWindowInParent(
    aura::Window* root_window) {
  aura::Window* window = new aura::Window(nullptr);
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  window->SetType(aura::client::WINDOW_TYPE_NORMAL);
  window->Init(ui::LAYER_TEXTURED);
  aura::client::ParentWindowWithContext(window, root_window, gfx::Rect(),
                                        display::kInvalidDisplayId);
  return window;
}

views::Widget* ShelfLayoutManagerTestBase::CreateTestWidget() {
  views::Widget::InitParams params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  params.bounds = gfx::Rect(0, 0, 200, 200);
  params.context = GetContext();
  views::Widget* widget = new views::Widget;
  widget->Init(std::move(params));
  widget->Show();
  return widget;
}

gfx::Rect ShelfLayoutManagerTestBase::GetVisibleShelfWidgetBoundsInScreen() {
  gfx::Rect bounds = GetShelfWidget()->GetWindowBoundsInScreen();
  bounds.Intersect(display::Screen::GetScreen()->GetPrimaryDisplay().bounds());
  return bounds;
}

void ShelfLayoutManagerTestBase::LockScreen() {
  GetSessionControllerClient()->LockScreen();
}

void ShelfLayoutManagerTestBase::UnlockScreen() {
  GetSessionControllerClient()->UnlockScreen();
}

int64_t ShelfLayoutManagerTestBase::GetPrimaryDisplayId() {
  return display::Screen::GetScreen()->GetPrimaryDisplay().id();
}

void ShelfLayoutManagerTestBase::StartScroll(gfx::Point start) {
  timestamp_ = base::TimeTicks::Now();
  current_point_ = start;
  ui::GestureEvent event = ui::GestureEvent(
      current_point_.x(), current_point_.y(), ui::EF_NONE, timestamp_,
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin, 0, -1.0f));
  GetShelfLayoutManager()->ProcessGestureEvent(event);
}

void ShelfLayoutManagerTestBase::UpdateScroll(const gfx::Vector2d& delta) {
  IncreaseTimestamp();
  current_point_ += delta;
  ui::GestureEvent event = ui::GestureEvent(
      current_point_.x(), current_point_.y(), ui::EF_NONE, timestamp_,
      ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, delta.x(),
                              delta.y()));
  GetShelfLayoutManager()->ProcessGestureEvent(event);
}

void ShelfLayoutManagerTestBase::EndScroll(bool is_fling, float velocity_y) {
  IncreaseTimestamp();
  ui::GestureEventDetails event_details =
      is_fling ? ui::GestureEventDetails(ui::EventType::kScrollFlingStart, 0,
                                         velocity_y)
               : ui::GestureEventDetails(ui::EventType::kGestureScrollEnd);
  ui::GestureEvent event =
      ui::GestureEvent(current_point_.x(), current_point_.y(), ui::EF_NONE,
                       timestamp_, event_details);
  GetShelfLayoutManager()->ProcessGestureEvent(event);
}

void ShelfLayoutManagerTestBase::IncreaseTimestamp() {
  timestamp_ += base::Milliseconds(25);
}

WorkspaceWindowState ShelfLayoutManagerTestBase::GetWorkspaceWindowState()
    const {
  // Shelf window does not belong to any desk, use the root to get the active
  // desk's workspace state.
  auto* shelf_window = GetShelfWidget()->GetNativeWindow();
  auto* controller =
      GetActiveWorkspaceController(shelf_window->GetRootWindow());
  DCHECK(controller);

  return controller->GetWindowState();
}

const ui::Layer*
ShelfLayoutManagerTestBase::GetNonLockScreenContainersContainerLayer() const {
  const auto* shelf_window = GetShelfWidget()->GetNativeWindow();
  return shelf_window->GetRootWindow()
      ->GetChildById(kShellWindowId_NonLockScreenContainersContainer)
      ->layer();
}

// If |layout_manager->auto_hide_timer_| is running, stops it, runs its task,
// and returns true. Otherwise, returns false.
bool ShelfLayoutManagerTestBase::TriggerAutoHideTimeout() const {
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  if (!layout_manager->auto_hide_timer_.IsRunning())
    return false;

  layout_manager->auto_hide_timer_.FireNow();
  return true;
}

// Performs a swipe up gesture to show an auto-hidden shelf.
void ShelfLayoutManagerTestBase::SwipeUpOnShelf() {
  gfx::Rect display_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  const gfx::Point start(display_bounds.bottom_center());
  const gfx::Point end(start + gfx::Vector2d(0, -80));
  const base::TimeDelta kTimeDelta = base::Milliseconds(100);
  const int kNumScrollSteps = 4;
  GetEventGenerator()->GestureScrollSequence(start, end, kTimeDelta,
                                             kNumScrollSteps);
}

void ShelfLayoutManagerTestBase::SwipeDownOnShelf() {
  gfx::Point start(GetPrimaryShelf()
                       ->shelf_widget()
                       ->shelf_view_for_testing()
                       ->GetBoundsInScreen()
                       .top_center());
  const gfx::Point end(start + gfx::Vector2d(0, 40));
  const base::TimeDelta kTimeDelta = base::Milliseconds(100);
  const int kNumScrollSteps = 4;
  GetEventGenerator()->GestureScrollSequence(start, end, kTimeDelta,
                                             kNumScrollSteps);
}

void ShelfLayoutManagerTestBase::FlingUpOnShelf() {
  const gfx::Point location_start(display::Screen::GetScreen()
                                      ->GetPrimaryDisplay()
                                      .bounds()
                                      .bottom_center());
  const gfx::Point location_end(location_start.x(), 10);
  FlingBetweenLocations(location_start, location_end);
}

void ShelfLayoutManagerTestBase::FlingBetweenLocations(gfx::Point start,
                                                       gfx::Point end) {
  const base::TimeDelta kTimeDelta = base::Milliseconds(10);
  const int kNumScrollSteps = 4;
  GetEventGenerator()->GestureScrollSequence(start, end, kTimeDelta,
                                             kNumScrollSteps);
}

void ShelfLayoutManagerTestBase::DragHotseatDownToBezel() {
  gfx::Rect shelf_widget_bounds = GetShelfWidget()->GetWindowBoundsInScreen();
  gfx::Rect hotseat_bounds =
      GetShelfWidget()->hotseat_widget()->GetWindowBoundsInScreen();
  gfx::Point start = hotseat_bounds.top_center();
  const gfx::Point end =
      gfx::Point(shelf_widget_bounds.x() + shelf_widget_bounds.width() / 2,
                 shelf_widget_bounds.bottom() + 1);
  const base::TimeDelta kTimeDelta = base::Milliseconds(100);
  const int kNumScrollSteps = 4;
  GetEventGenerator()->GestureScrollSequence(start, end, kTimeDelta,
                                             kNumScrollSteps);
}

// Drag Shelf from |start| to |target| by mouse.
void ShelfLayoutManagerTestBase::MouseDragShelfTo(const gfx::Point& start,
                                                  const gfx::Point& target) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->MoveMouseTo(start);
  generator->PressLeftButton();
  generator->DragMouseTo(target);
  generator->ReleaseLeftButton();
}

// Move mouse to show Shelf in auto-hide mode.
void ShelfLayoutManagerTestBase::MoveMouseToShowAutoHiddenShelf() {
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  const int display_bottom = display.bounds().bottom();
  GetEventGenerator()->MoveMouseTo(1, display_bottom - 1);
  ASSERT_TRUE(TriggerAutoHideTimeout());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, GetPrimaryShelf()->GetAutoHideState());
}

// Move mouse to |location| and do a two-finger scroll.
void ShelfLayoutManagerTestBase::DoTwoFingerScrollAtLocation(
    gfx::Point location,
    int x_offset,
    int y_offset,
    bool reverse_scroll) {
  PrefService* prefs =
      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  prefs->SetBoolean(prefs::kNaturalScroll, reverse_scroll);
  y_offset = reverse_scroll ? -y_offset : y_offset;
  GetEventGenerator()->ScrollSequence(location, base::TimeDelta(), x_offset,
                                      y_offset, /*steps=*/1,
                                      /*num_fingers=*/2);
}

// Move mouse to |location| and do a mousewheel scroll.
void ShelfLayoutManagerTestBase::DoMouseWheelScrollAtLocation(
    gfx::Point location,
    int delta_y,
    bool reverse_scroll) {
  PrefService* prefs =
      Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  prefs->SetBoolean(prefs::kMouseReverseScroll, reverse_scroll);
  delta_y = reverse_scroll ? -delta_y : delta_y;
  GetEventGenerator()->MoveMouseTo(location);
  GetEventGenerator()->MoveMouseWheel(/*delta_x=*/0, delta_y);
}

void ShelfLayoutManagerTestBase::RunGestureDragTests(
    const gfx::Point& edge_to_hide,
    const gfx::Point& edge_to_show) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  generator->MoveMouseTo(display.bounds().CenterPoint());

  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);

  views::Widget* widget = CreateTestWidget();
  widget->Maximize();

  // The time delta should be large enough to prevent accidental fling creation.
  const base::TimeDelta kTimeDelta = base::Milliseconds(100);

  aura::Window* window = widget->GetNativeWindow();
  ShelfLayoutManager* layout_manager = GetShelfLayoutManager();
  layout_manager->LayoutShelf();

  gfx::Rect shelf_shown = GetShelfWidget()->GetWindowBoundsInScreen();
  gfx::Rect window_bounds_with_shelf = window->bounds();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  gfx::Rect window_bounds_with_noshelf = window->bounds();
  gfx::Rect shelf_hidden = GetShelfWidget()->GetWindowBoundsInScreen();

  // Tests the gesture drag on always shown shelf.
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever);
  layout_manager->LayoutShelf();

  const int kNumScrollSteps = 4;
  ShelfDragCallback handler(shelf_hidden, shelf_shown);

  // Swipe down on the always shown shelf should not auto-hide it.
  {
    SCOPED_TRACE("SWIPE_DOWN_ALWAYS_SHOWN");
    generator->GestureScrollSequenceWithCallback(
        edge_to_hide, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_shelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Verify that the shelf can still enter auto hide if the |widget_| has been
  // put into fullscreen.
  widget->SetFullscreen(true);
  WindowState* window_state = WindowState::Get(window);
  window_state->SetHideShelfWhenFullscreen(false);
  window->SetProperty(kImmersiveIsActive, true);
  layout_manager->UpdateVisibilityState(/*force_layout=*/false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  // Swiping up should show the shelf if shelf is hidden in fullscreen mode.
  generator->GestureScrollSequence(edge_to_hide, edge_to_show, kTimeDelta,
                                   kNumScrollSteps);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  // Swiping down should hide the shelf.
  generator->GestureScrollSequence(edge_to_show, edge_to_hide, kTimeDelta,
                                   kNumScrollSteps);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  // Verify that after toggling fullscreen to off, the shelf is visible.
  widget->SetFullscreen(false);
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());

  // Minimize the visible window, the shelf should be shown if there are no
  // visible windows, even in auto-hide mode.
  window_state->Minimize();
  EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());

  // Tests gesture drag on auto-hide shelf.
  window_state->Maximize();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
  layout_manager->LayoutShelf();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Swipe up the auto-hide shelf should show it.
  {
    SCOPED_TRACE("SWIPE_UP_AUTO_HIDE_SHOW");
    generator->GestureScrollSequenceWithCallback(
        edge_to_hide, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  // Gesture drag should not change the auto hide behavior of shelf, even though
  // its visibility has been changed.
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  // The auto-hide shelf is above the window, which should not change the bounds
  // of the window.
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Swipe down very little. It shouldn't change any state.
  gfx::Point new_point(edge_to_show);
  gfx::Vector2d diff = edge_to_hide - edge_to_show;
  new_point.Offset(diff.x() * 3 / 10, diff.y() * 3 / 10);
  generator->GestureScrollSequence(edge_to_show, new_point, kTimeDelta, 5);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  {
    SCOPED_TRACE("SWIPE_DOWN_AUTO_HIDE_1");
    generator->GestureScrollSequenceWithCallback(
        edge_to_show, edge_to_hide, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_hidden.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Swipe up in extended hit region to show it.
  gfx::Point extended_start = edge_to_show;
  if (shelf->IsHorizontalAlignment())
    extended_start.set_y(GetShelfWidget()->GetWindowBoundsInScreen().y() - 1);
  else if (ShelfAlignment::kLeft == shelf->alignment())
    extended_start.set_x(GetShelfWidget()->GetWindowBoundsInScreen().right() +
                         1);
  else if (ShelfAlignment::kRight == shelf->alignment())
    extended_start.set_x(GetShelfWidget()->GetWindowBoundsInScreen().x() - 1);
  {
    SCOPED_TRACE("SWIPE_UP_EXTENDED_HIT");
    generator->GestureScrollSequenceWithCallback(
        extended_start, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Swipe down again to hide.
  {
    SCOPED_TRACE("SWIPE_DOWN_AUTO_HIDE_2");
    generator->GestureScrollSequenceWithCallback(
        edge_to_show, edge_to_hide, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_hidden.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Swipe up outside the hit area. This should not change anything.
  gfx::Point outside_start =
      GetShelfWidget()->GetWindowBoundsInScreen().top_center();
  outside_start.set_y(outside_start.y() - 50);
  gfx::Vector2d delta = edge_to_hide - edge_to_show;
  generator->GestureScrollSequence(outside_start, outside_start + delta,
                                   kTimeDelta, kNumScrollSteps);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_hidden.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());
  // Swipe up from the bottom of the shelf, this should show the shelf.
  gfx::Point below_start = edge_to_hide;
  generator->GestureScrollSequence(edge_to_hide, edge_to_show, kTimeDelta,
                                   kNumScrollSteps);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Swipe down again to hide.
  {
    SCOPED_TRACE("SWIPE_DOWN_AUTO_HIDE_3");
    generator->GestureScrollSequenceWithCallback(
        edge_to_show, edge_to_hide, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(window_bounds_with_noshelf.ToString(), window->bounds().ToString());
  EXPECT_EQ(shelf_hidden.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Put |widget| into fullscreen. Set the shelf to be auto hidden when |widget|
  // is fullscreen. (eg browser immersive fullscreen).
  widget->SetFullscreen(true);
  WindowState::Get(window)->SetHideShelfWhenFullscreen(false);
  layout_manager->UpdateVisibilityState(/*force_layout=*/false);

  gfx::Rect window_bounds_fullscreen = window->bounds();
  EXPECT_TRUE(widget->IsFullscreen());

  EXPECT_EQ(window_bounds_with_noshelf.ToString(),
            window_bounds_fullscreen.ToString());

  // Swipe up. This should show the shelf.
  {
    SCOPED_TRACE("SWIPE_UP_AUTO_HIDE_1");
    // Do not check bounds because the events outside of the bounds
    // will be clipped.
    generator->GestureScrollSequenceWithCallback(
        below_start, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScrollNoBoundsCheck,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());
  EXPECT_EQ(window_bounds_fullscreen.ToString(), window->bounds().ToString());

  // Swipe down to hide the shelf.
  {
    SCOPED_TRACE("SWIPE_DOWN_AUTO_HIDE_4");
    generator->GestureScrollSequenceWithCallback(
        edge_to_show, edge_to_hide, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(shelf_hidden.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());
  EXPECT_EQ(window_bounds_fullscreen.ToString(), window->bounds().ToString());

  // Set the shelf to be hidden when |widget| is fullscreen. (eg tab fullscreen
  // with or without immersive browser fullscreen).
  WindowState::Get(window)->SetHideShelfWhenFullscreen(true);

  layout_manager->UpdateVisibilityState(/*force_layout=*/false);
  EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());

  // Swipe-up. This should not change anything.
  {
    SCOPED_TRACE("SWIPE_UP_NO_CHANGE");
    generator->GestureScrollSequenceWithCallback(
        below_start, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
    EXPECT_EQ(SHELF_HIDDEN, shelf->GetVisibilityState());
    EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
    EXPECT_EQ(window_bounds_fullscreen.ToString(), window->bounds().ToString());
  }

  // Minimize actually, otherwise further event may be affected since widget
  // is fullscreen status.
  widget->Minimize();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(layout_manager->HasVisibleWindow());

  // The shelf should be shown because there are no more visible windows.
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());

  // Swipe-down to hide. This should have no effect because there are no visible
  // windows.
  {
    SCOPED_TRACE("SWIPE_DOWN_AUTO_HIDE_5");
    generator->GestureScrollSequenceWithCallback(
        edge_to_show, edge_to_hide, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Change the window state back to its Normal state. We do that by sending
  // a WM_EVENT_NORMAL to the window, instead of calling Widget::Restore()
  // function, because restoring from a kMinimized window state will take
  // the window back to its pre-minimized window state.
  WMEvent restore_event(WM_EVENT_NORMAL);
  WindowState::Get(widget->GetNativeWindow())->OnWMEvent(&restore_event);

  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(layout_manager->HasVisibleWindow());

  // Swipe up on the shelf. This should show the shelf but should not change the
  // auto-hide behavior, since auto-hide behavior can only be changed through
  // context menu of the shelf.
  {
    SCOPED_TRACE("SWIPE_UP_AUTO_HIDE_2");
    // Do not check bounds because the events outside of the bounds
    // will be clipped.
    generator->GestureScrollSequenceWithCallback(
        below_start, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScrollNoBoundsCheck,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  widget->Close();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(layout_manager->HasVisibleWindow());

  // Swipe-down to hide. This should have no effect because there are no visible
  // windows.
  {
    SCOPED_TRACE("SWIPE_DOWN_AUTO_HIDE_6");
    generator->GestureScrollSequenceWithCallback(
        edge_to_show, edge_to_hide, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(ShelfAutoHideBehavior::kAlways, shelf->auto_hide_behavior());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());

  // Swipe up again on AUTO_HIDE_SHOWN shelf shouldn't change any state.
  // Swipe up on auto-hide shown shelf should still keep shelf shown.
  {
    SCOPED_TRACE("SWIPE_UP_AUTO_HIDE_4");
    generator->GestureScrollSequenceWithCallback(
        edge_to_hide, edge_to_show, kTimeDelta, kNumScrollSteps,
        base::BindRepeating(&ShelfDragCallback::ProcessScroll,
                            base::Unretained(&handler)));
  }
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
  EXPECT_EQ(shelf_shown.ToString(),
            GetShelfWidget()->GetWindowBoundsInScreen().ToString());
}

bool ShelfLayoutManagerTestBase::RunVisibilityUpdateForTrayCallback() {
  if (!GetShelfLayoutManager()
           ->visibility_update_for_tray_callback_.callback()) {
    return false;
  }
  GetShelfLayoutManager()
      ->visibility_update_for_tray_callback_.callback()
      .Run();
  return true;
}

}  //  namespace ash