chromium/ash/drag_drop/scoped_drag_drop_observer_unittest.cc

// Copyright 2023 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/drag_drop/scoped_drag_drop_observer.h"

#include "ash/drag_drop/drag_drop_controller.h"
#include "ash/drag_drop/draggable_test_view.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/test/ash_test_base.h"
#include "base/test/mock_callback.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/window.h"
#include "ui/views/widget/widget.h"

namespace ash {

// Aliases.
using EventCallback = ScopedDragDropObserver::EventCallback;
using EventType = ScopedDragDropObserver::EventType;
using testing::AtLeast;
using testing::Conditional;
using testing::Eq;
using testing::IsNull;
using testing::NotNull;

// ScopedDragDropObserverTest --------------------------------------------------

// Base class for tests of `ScopedDragDropObserver` parameterized by whether to
// test (a) `EventType::kDragCompleted` or (b) `EventType::kDragCancelled`.
class ScopedDragDropObserverTest
    : public AshTestBase,
      public testing::WithParamInterface</*test_scenario=*/EventType> {
 public:
  ScopedDragDropObserverTest() {
    // Sanity check parameterization.
    EXPECT_TRUE(GetTestScenario() == EventType::kDragCompleted ||
                GetTestScenario() == EventType::kDragCancelled);
  }

  // Returns (a) `EventType::kDragCompleted` or (b) `EventType::kDragCancelled`
  // depending on test parameterization.
  EventType GetTestScenario() const { return GetParam(); }

  // Returns a `ScopedDragDropObserver` with the specified `event_callback`.
  std::unique_ptr<ScopedDragDropObserver> CreateScopedDragDropObserver(
      EventCallback event_callback) {
    return std::make_unique<ScopedDragDropObserver>(
        aura::client::GetDragDropClient(
            widget_->GetNativeWindow()->GetRootWindow()),
        std::move(event_callback));
  }

  // Moves the mouse to the center of the specified `widget`.
  void MoveMouseTo(views::Widget* widget) {
    GetEventGenerator()->MoveMouseTo(
        widget->GetWindowBoundsInScreen().CenterPoint());
  }

  // Moves the mouse by the specified `x` and `y` offsets.
  void MoveMouseBy(int x, int y) {
    auto* event_generator = GetEventGenerator();
    event_generator->MoveMouseTo(
        event_generator->current_screen_location() + gfx::Vector2d(x, y),
        /*count=*/10);
  }

  // Presses/releases the left button.
  void PressLeftButton() { GetEventGenerator()->PressLeftButton(); }
  void ReleaseLeftButton() { GetEventGenerator()->ReleaseLeftButton(); }

  // Returns the `widget_` with draggable contents.
  views::Widget* widget() { return widget_.get(); }

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

    // Prevent blocking during drag-and-drop sequences.
    ShellTestApi().drag_drop_controller()->SetDisableNestedLoopForTesting(true);

    // Create and show a `widget_` with draggable contents.
    widget_ = CreateFramelessTestWidget();
    widget_->SetContentsView(std::make_unique<DraggableTestView>());
    widget_->CenterWindow(gfx::Size(100, 100));
    widget_->Show();
  }

  // Widget with draggable contents.
  std::unique_ptr<views::Widget> widget_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         ScopedDragDropObserverTest,
                         /*test_scenario=*/
                         testing::Values(EventType::kDragCompleted,
                                         EventType::kDragCancelled));

// Tests -----------------------------------------------------------------------

// Verifies that `EventCallback`s are run as expected.
TEST_P(ScopedDragDropObserverTest, EventCallback) {
  base::MockCallback<EventCallback> event_callback;

  // Create observer.
  auto scoped_drag_drop_observer =
      CreateScopedDragDropObserver(event_callback.Get());

  {
    testing::InSequence sequence;

    // Expect one or more drag update events...
    EXPECT_CALL(event_callback,
                Run(Eq(EventType::kDragUpdated), /*event=*/NotNull()))
        .Times(AtLeast(1));

    // ...followed by a single drag completed or cancelled event.
    EXPECT_CALL(event_callback,
                Run(Eq(GetTestScenario()),
                    Conditional(GetTestScenario() == EventType::kDragCompleted,
                                /*event=*/NotNull(), /*event=*/IsNull())));
  }

  // Drag contents from `widget()`...
  MoveMouseTo(widget());
  PressLeftButton();
  MoveMouseBy(/*x=*/100, /*y=*/100);

  // ...then cancel drag...
  if (GetTestScenario() == EventType::kDragCancelled) {
    PressAndReleaseKey(ui::VKEY_ESCAPE);
  }

  // ...or complete drag.
  ReleaseLeftButton();
}

}  // namespace ash