chromium/ash/wm/overview/overview_window_drag_controller_unittest.cc

// Copyright 2019 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/wm/overview/overview_window_drag_controller.h"

#include "ash/display/screen_orientation_controller.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desks_constants.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_histogram_enums.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/overview_desk_bar_view.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_drop_target.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_grid_test_api.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_item_base.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_drag_indicators.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_util.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "ui/aura/window_tree_host.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/views/widget/widget.h"

namespace ash {

namespace {

// Drags the item by |x| and |y| and does not drop it.
void StartDraggingItemBy(OverviewItemBase* item,
                         int x,
                         int y,
                         bool by_touch_gestures,
                         ui::test::EventGenerator* event_generator) {
  const gfx::Point item_center =
      gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
  event_generator->set_current_screen_location(item_center);
  if (by_touch_gestures) {
    event_generator->PressTouch();
    event_generator->MoveTouchBy(x, y);
  } else {
    event_generator->PressLeftButton();
    event_generator->MoveMouseBy(x, y);
  }
}

// Given x, and y of a point, returns the screen in pixels corresponding point
// which can be used to provide event locations to the event generator when
// the display is rotated.
gfx::Point GetScreenInPixelsPoint(int x, int y) {
  gfx::Point point{x, y};
  Shell::GetPrimaryRootWindow()->GetHost()->ConvertDIPToScreenInPixels(&point);
  return point;
}

}  // namespace

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

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

  ~OverviewWindowDragControllerTest() override = default;

  SplitViewController* split_view_controller() {
    return SplitViewController::Get(Shell::GetPrimaryRootWindow());
  }

  OverviewSession* overview_session() {
    return OverviewController::Get()->overview_session();
  }

  OverviewWindowDragController* drag_controller() {
    return overview_session()->window_drag_controller();
  }

  const SplitViewDragIndicators* drag_indicators() const {
    return OverviewController::Get()
        ->overview_session()
        ->grid_list()[0]
        ->split_view_drag_indicators();
  }

  OverviewGrid* overview_grid() {
    return overview_session()->GetGridWithRootWindow(
        Shell::GetPrimaryRootWindow());
  }

  const views::Widget* desks_bar_widget() {
    DCHECK(overview_grid()->desks_bar_view());
    return overview_grid()->desks_bar_view()->GetWidget();
  }

  OverviewItemBase* GetOverviewItemForWindow(aura::Window* window) {
    return overview_session()->GetOverviewItemForWindow(window);
  }

  int GetExpectedDesksBarShiftAmount() const {
    return drag_indicators()->GetLeftHighlightViewBounds().bottom() +
           kHighlightScreenEdgePaddingDp;
  }

  void StartDraggingAndValidateDesksBarShifted(aura::Window* window) {
    // Enter overview mode, and start dragging the window. Validate that the
    // desks bar widget is shifted down to make room for the indicators.
    EnterOverview();
    EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
    auto* overview_item = GetOverviewItemForWindow(window);
    ASSERT_TRUE(overview_item);
    StartDraggingItemBy(overview_item, 100, 200, /*by_touch_gestures=*/false,
                        GetEventGenerator());
    ASSERT_TRUE(drag_controller());
    EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
              drag_controller()->current_drag_behavior_for_testing());
    ASSERT_TRUE(drag_indicators());
    EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
              drag_indicators()->current_window_dragging_state());
    // Note that it's ok to use screen bounds here since we only have a single
    // primary display.
    EXPECT_EQ(GetExpectedDesksBarShiftAmount(),
              desks_bar_widget()->GetWindowBoundsInScreen().y());
  }

  int GetDesksBarViewExpandedStateHeight(
      const OverviewDeskBarView* desks_bar_view) {
    return DeskBarViewBase::GetPreferredBarHeight(
        desks_bar_view->GetWidget()->GetNativeWindow()->GetRootWindow(),
        DeskBarViewBase::Type::kOverview, DeskBarViewBase::State::kExpanded);
  }
};

TEST_F(OverviewWindowDragControllerTest, NoDragToCloseUsingMouse) {
  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  wm::ActivateWindow(window.get());
  EXPECT_EQ(window.get(), window_util::GetActiveWindow());

  // Enter tablet mode and enter overview mode.
  // Avoid TabletModeController::OnGetSwitchStates() from disabling tablet mode.
  base::RunLoop().RunUntilIdle();
  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
  auto* overview_controller = OverviewController::Get();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  auto* overview_session = overview_controller->overview_session();
  auto* overview_item =
      overview_session->GetOverviewItemForWindow(window.get());
  ASSERT_TRUE(overview_item);
  const gfx::RectF target_bounds_before_drag = overview_item->target_bounds();
  auto* event_generator = GetEventGenerator();

  // Drag with mouse by a bigger Y-component than X, which would normally
  // trigger the drag-to-close mode, but won't since this mode only work with
  // touch gestures.
  StartDraggingItemBy(overview_item, 30, 200, /*by_touch_gestures=*/false,
                      event_generator);
  OverviewWindowDragController* drag_controller =
      overview_session->window_drag_controller();
  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
            drag_controller->current_drag_behavior_for_testing());
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
}

// Tests that the bounds of the drop target for `OverviewItem` will match that
// of the corresponding item which the drop target is a placeholder for.
TEST_F(OverviewWindowDragControllerTest, DropTargetBoundsTest) {
  auto* desk_controller = DesksController::Get();
  desk_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desk_controller->desks().size());

  std::unique_ptr<aura::Window> window = CreateAppWindow();

  OverviewController* overview_controller = OverviewController::Get();
  overview_controller->StartOverview(OverviewStartAction::kTests,
                                     OverviewEnterExitType::kImmediateEnter);
  ASSERT_TRUE(overview_controller->InOverviewSession());

  auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
  ASSERT_TRUE(overview_grid);
  const auto& window_list = overview_grid->item_list();
  ASSERT_EQ(window_list.size(), 1u);

  OverviewSession* overview_session = overview_controller->overview_session();
  auto* overview_item =
      overview_session->GetOverviewItemForWindow(window.get());
  auto* event_generator = GetEventGenerator();
  const gfx::RectF target_bounds_before_dragging =
      overview_item->target_bounds();

  for (const bool by_touch : {false, true}) {
    DragItemToPoint(
        overview_item,
        Shell::GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint(),
        event_generator, by_touch, /*drop=*/false);
    EXPECT_TRUE(overview_controller->InOverviewSession());

    const OverviewItemBase* drop_target = overview_grid->drop_target();
    EXPECT_TRUE(drop_target);
    EXPECT_EQ(gfx::RectF(drop_target->item_widget()->GetWindowBoundsInScreen()),
              target_bounds_before_dragging);
    if (by_touch) {
      event_generator->ReleaseTouch();
    } else {
      event_generator->ReleaseLeftButton();
    }
  }
}

TEST_F(OverviewWindowDragControllerTest,
       SwitchDragToCloseToNormalDragWhenDraggedToDesk) {
  UpdateDisplay("600x800");
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, controller->desks().size());

  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  wm::ActivateWindow(window.get());
  EXPECT_EQ(window.get(), window_util::GetActiveWindow());

  auto* overview_controller = Shell::Get()->overview_controller();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  auto* overview_session = overview_controller->overview_session();
  const auto* overview_grid =
      overview_session->GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
  ASSERT_TRUE(overview_grid);
  const auto* desks_bar_view = overview_grid->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);
  auto* overview_item =
      overview_session->GetOverviewItemForWindow(window.get());
  ASSERT_TRUE(overview_item);
  const gfx::RectF target_bounds_before_drag = overview_item->target_bounds();

  // Drag with touch gesture only vertically without intersecting with the desk
  // bar, which should trigger the drag-to-close mode.
  const int item_center_to_desks_bar_bottom =
      gfx::ToRoundedPoint(target_bounds_before_drag.CenterPoint()).y() -
      desks_bar_view->GetBoundsInScreen().bottom();
  EXPECT_GT(item_center_to_desks_bar_bottom, 0);
  const int space_to_leave = 20;
  auto* event_generator = GetEventGenerator();
  StartDraggingItemBy(overview_item, 0,
                      -(item_center_to_desks_bar_bottom - space_to_leave),
                      /*by_touch_gestures=*/true, event_generator);
  OverviewWindowDragController* drag_controller =
      overview_session->window_drag_controller();
  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kDragToClose,
            drag_controller->current_drag_behavior_for_testing());
  // Continue dragging vertically up such that the drag location intersects with
  // the desks bar. Expect that normal drag is now triggered.
  event_generator->MoveTouchBy(0, -(space_to_leave + 10));
  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
            drag_controller->current_drag_behavior_for_testing());
  // Now it's possible to drop it on desk_2's mini_view.
  auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
  ASSERT_TRUE(desk_2_mini_view);
  event_generator->MoveTouch(
      desk_2_mini_view->GetBoundsInScreen().CenterPoint());
  event_generator->ReleaseTouch();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  EXPECT_TRUE(overview_grid->empty());
  const Desk* desk_2 = controller->GetDeskAtIndex(1);
  EXPECT_TRUE(base::Contains(desk_2->windows(), window.get()));
  EXPECT_TRUE(const_cast<OverviewGrid*>(overview_grid)->no_windows_widget());
}

// Test that if window is destroyed during dragging, no crash should happen and
// drag should be reset.
TEST_F(OverviewWindowDragControllerTest, WindowDestroyedDuringDragging) {
  std::unique_ptr<aura::Window> window =
      CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto* overview_controller = Shell::Get()->overview_controller();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  auto* overview_session = overview_controller->overview_session();
  auto* overview_item =
      overview_session->GetOverviewItemForWindow(window.get());
  ASSERT_TRUE(overview_item);

  auto* event_generator = GetEventGenerator();
  StartDraggingItemBy(overview_item, 30, 200, /*by_touch_gestures=*/false,
                      event_generator);
  OverviewWindowDragController* drag_controller =
      overview_session->window_drag_controller();
  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNormalDrag,
            drag_controller->current_drag_behavior_for_testing());

  window.reset();
  EXPECT_EQ(OverviewWindowDragController::DragBehavior::kNoDrag,
            drag_controller->current_drag_behavior_for_testing());
}

TEST_F(OverviewWindowDragControllerTest,
       DragAndDropWindowInPortraitModeWithOneDesk) {
  // Update the display to make it portrait mode.
  UpdateDisplay("768x1000");
  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));

  wm::ActivateWindow(window.get());
  EXPECT_EQ(window.get(), window_util::GetActiveWindow());

  StartDraggingAndValidateDesksBarShifted(window.get());
  const auto* desks_bar_view = overview_grid()->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);

  // Check the height of the desks bar view. Desks bar is transformed to
  // expanded state immediately at the beginning of the drag.
  EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
            desks_bar_view->bounds().height());

  // Now drop `window`. Check the height of the desks bar view. It should still
  // be `kDeskBarZeroStateHeight`.
  auto* event_generator = GetEventGenerator();
  event_generator->ReleaseLeftButton();

  // Desks bar never goes back to zero state after it is initialized.
  EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
            desks_bar_view->bounds().height());

  // Click on the zero state new desk button to create a new desk. This
  // shouldn't end overview mode. The desks bar view should be transformed to
  // the expanded state.
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  LeftClickOn(desks_bar_view->new_desk_button());
  EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
            desks_bar_view->bounds().height());

  // Now remove the newly created desk. This shouldn't end overview mode. The
  // desks bar view should stay in expanded state.
  auto* controller = Shell::Get()->desks_controller();
  controller->RemoveDesk(controller->desks().back().get(),
                         DesksCreationRemovalSource::kButton,
                         DeskCloseType::kCombineDesks);
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  EXPECT_EQ(DeskBarViewBase::State::kExpanded, desks_bar_view->state());
  EXPECT_EQ(GetDesksBarViewExpandedStateHeight(desks_bar_view),
            desks_bar_view->bounds().height());
}

// Tests that dragging window in portrait mode won't cause overview items
// overlap with desks bar. Regression test for https://crbug.com/1275285.
TEST_F(OverviewWindowDragControllerTest, DragWindowInPortraitMode) {
  // Update the display to make it portrait mode.
  UpdateDisplay("768x1000");

  // Create 10 windows with size the same as the maximized window's size.
  std::vector<std::unique_ptr<aura::Window>> windows;
  for (int i = 0; i < 10; ++i)
    windows.push_back(CreateAppWindow(gfx::Rect(0, 0, 768, 1269)));

  StartDraggingAndValidateDesksBarShifted(windows.back().get());
  const auto* desks_bar_view = overview_grid()->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);

  // Check there's no overlap between overview items and desks bar view. Since
  // the first overview item is still being dragged, we should use the second
  // item in the list to check if there's overlap or not.
  EXPECT_FALSE(
      desks_bar_view->GetBoundsInScreen().Intersects(gfx::ToEnclosedRect(
          overview_grid()->item_list()[1].get()->target_bounds())));
}

// Tests that if a user starts dragging an overview item and the desks bar(s)
// are in zero state, they will become expanded state.
TEST_F(OverviewWindowDragControllerTest, DesksBarState) {
  UpdateDisplay("800x600, 800x600");

  std::unique_ptr<aura::Window> window = CreateAppWindow();

  EnterOverview();
  ASSERT_TRUE(OverviewController::Get()->InOverviewSession());

  // Since we only have one desk, the desk bars should be zero state currently.
  aura::Window::Windows roots = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, roots.size());

  for (aura::Window* root : roots) {
    const auto* desks_bar_view = GetOverviewGridForRoot(root)->desks_bar_view();
    ASSERT_TRUE(desks_bar_view);
    ASSERT_EQ(DeskBarViewBase::State::kZero, desks_bar_view->state());
  }

  // Start dragging the item, but not enough to snap or move to another desk.
  auto* overview_item = GetOverviewItemForWindow(window.get());
  ASSERT_TRUE(overview_item);
  StartDraggingItemBy(overview_item, /*x=*/5, /*y=*/5,
                      /*by_touch_gestures=*/false, GetEventGenerator());

  // All the desks bar should be expanded.
  for (aura::Window* root : roots) {
    const auto* desks_bar_view = GetOverviewGridForRoot(root)->desks_bar_view();
    ASSERT_TRUE(desks_bar_view);
    EXPECT_EQ(DeskBarViewBase::State::kExpanded, desks_bar_view->state());
  }
}

// Tests the behavior of dragging a window in portrait tablet mode with virtual
// desks enabled.
class OverviewWindowDragControllerDesksPortraitTabletTest
    : public OverviewWindowDragControllerTest {
 public:
  OverviewWindowDragControllerDesksPortraitTabletTest() = default;

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

  ~OverviewWindowDragControllerDesksPortraitTabletTest() override = default;

  // OverviewWindowDragControllerTest:
  void SetUp() override {
    OverviewWindowDragControllerTest::SetUp();

    // Setup a portrait internal display in tablet mode.
    UpdateDisplay("800x700");
    const int64_t display_id =
        display::Screen::GetScreen()->GetPrimaryDisplay().id();
    set_internal_ = std::make_unique<display::test::ScopedSetInternalDisplayId>(
        display_manager(), display_id);
    ScreenOrientationControllerTestApi test_api(
        Shell::Get()->screen_orientation_controller());
    // Set the screen orientation to primary portrait.
    test_api.SetDisplayRotation(display::Display::ROTATE_270,
                                display::Display::RotationSource::ACTIVE);
    EXPECT_EQ(test_api.GetCurrentOrientation(),
              chromeos::OrientationType::kPortraitPrimary);
    // Enter tablet mode. Avoid TabletModeController::OnGetSwitchStates() from
    // disabling tablet mode.
    base::RunLoop().RunUntilIdle();
    Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);

    // Setup two desks.
    auto* desks_controller = DesksController::Get();
    desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
    ASSERT_EQ(2u, desks_controller->desks().size());

    // Give the second desk a name. The desk name gets exposed as the accessible
    // name. And the focusable views that are painted in these tests will fail
    // the accessibility paint checker checks if they lack an accessible name.
    desks_controller->GetDeskAtIndex(1)->SetName(u"Desk 2", false);
  }

 private:
  std::unique_ptr<display::test::ScopedSetInternalDisplayId> set_internal_;
};

TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest,
       DragAndDropInEmptyArea) {
  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  StartDraggingAndValidateDesksBarShifted(window.get());

  // Dropping the window any where outside the bounds of the desks widget or the
  // snap bounds should restore the desks widget to its correct position.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 400));
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  EXPECT_EQ(0, desks_bar_widget()->GetWindowBoundsInScreen().y());
}

TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest,
       DragAndDropInSnapAreas) {
  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  StartDraggingAndValidateDesksBarShifted(window.get());

  // Drag towards the area at the bottom of the display and note that the desks
  // bar widget is not shifted.
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 800));
  ASSERT_TRUE(drag_indicators());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapSecondary,
            drag_indicators()->current_window_dragging_state());
  EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
            desks_bar_widget()->GetWindowBoundsInScreen().y());

  // Drag back to the middle, the desks bar should be shifted again.
  event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 400));
  ASSERT_TRUE(drag_indicators());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
            drag_indicators()->current_window_dragging_state());
  EXPECT_EQ(GetExpectedDesksBarShiftAmount(),
            desks_bar_widget()->GetWindowBoundsInScreen().y());

  // Drag towards the area at the top of the display and note that the desks bar
  // widget is no longer shifted.
  event_generator->MoveMouseTo(GetScreenInPixelsPoint(300, 0));
  ASSERT_TRUE(drag_indicators());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
            drag_indicators()->current_window_dragging_state());
  EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
            desks_bar_widget()->GetWindowBoundsInScreen().y());

  // Drop it at this location and expect the window to snap. The desks bar
  // remains unshifted.
  event_generator->ReleaseLeftButton();
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  EXPECT_EQ(SplitViewController::State::kPrimarySnapped,
            split_view_controller()->state());
  EXPECT_EQ(window.get(), split_view_controller()->primary_window());
  EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
            desks_bar_widget()->GetWindowBoundsInScreen().y());
}

TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest, DragAndDropInDesk) {
  auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  StartDraggingAndValidateDesksBarShifted(window.get());

  // Drag the window to the second desk's mini_view. While dragging is in
  // progress, the desks bar remains shifted.
  const auto* desks_bar_view = overview_grid()->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);
  auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
  ASSERT_TRUE(desk_2_mini_view);
  const auto mini_view_location =
      desk_2_mini_view->GetBoundsInScreen().CenterPoint();
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      GetScreenInPixelsPoint(mini_view_location.x(), mini_view_location.y()));
  ASSERT_TRUE(drag_indicators());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview,
            drag_indicators()->current_window_dragging_state());
  EXPECT_EQ(GetExpectedDesksBarShiftAmount(),
            desks_bar_widget()->GetWindowBoundsInScreen().y());

  // Once the window is dropped on that desk, the desks bar should return to its
  // unshifted position, and the window should move to the second desk.
  EXPECT_TRUE(desks_util::BelongsToActiveDesk(window.get()));
  event_generator->ReleaseLeftButton();  // Drop.
  EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get()));
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
  EXPECT_EQ(OverviewGridTestApi(overview_grid()).bounds().y(),
            desks_bar_widget()->GetWindowBoundsInScreen().y());
  EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag,
            drag_indicators()->current_window_dragging_state());
}

// Tests that dragging window in tablet portrait mode won't cause overview items
// overlap with desks bar. Regression test for https://crbug.com/1275285.
TEST_F(OverviewWindowDragControllerDesksPortraitTabletTest,
       DragWindowInPortraitMode) {
  UpdateDisplay("700x1000");

  // Create 9 windows to make sure we can use tablet mode grid layout.
  std::vector<std::unique_ptr<aura::Window>> windows;
  for (int i = 0; i < 9; ++i) {
    windows.push_back(CreateAppWindow());
  }

  StartDraggingAndValidateDesksBarShifted(windows[4].get());

  // Delete desk2 in the overview mode. Note if we delete desk2 outside of the
  // overview mode, there's no desks bar after entering overview mode. Cause we
  // don't show desks bar for tablet mode when there's only one desk.
  auto* desks_controller = DesksController::Get();
  DesksController::Get()->RemoveDesk(desks_controller->GetDeskAtIndex(1),
                                     DesksCreationRemovalSource::kButton,
                                     DeskCloseType::kCombineDesks);
  EXPECT_TRUE(OverviewController::Get()->InOverviewSession());

  // Check desks bar still exists after desk2 gets removed.
  const auto* desks_bar_view = overview_grid()->desks_bar_view();
  ASSERT_TRUE(desks_bar_view);

  const gfx::Rect desk_bar_bounds = desks_bar_view->GetBoundsInScreen();
  const gfx::Rect first_item_bounds =
      gfx::ToEnclosedRect(overview_grid()->item_list()[0]->target_bounds());
  if (features::IsForestFeatureEnabled()) {
    // With forest, a little overlap is ok since the desk bar is transparent.
    // TODO(sammiequon|zxdan): Check if this gap is okay.
    EXPECT_NEAR(desk_bar_bounds.bottom(), first_item_bounds.y(), 20);
  } else {
    // Check there's no overlap between overview items and desks bar view.
    EXPECT_FALSE(desk_bar_bounds.Intersects(first_item_bounds));
  }
}

}  // namespace ash