| // 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/wallpaper/wallpaper_drag_drop_delegate.h" |
| |
| #include <memory> |
| #include <set> |
| |
| #include "ash/drag_drop/drag_drop_controller.h" |
| #include "ash/drag_drop/draggable_test_view.h" |
| #include "ash/public/cpp/test/shell_test_api.h" |
| #include "ash/public/cpp/wallpaper/wallpaper_controller.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/strcat.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/dragdrop/os_exchange_data.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash { |
| namespace { |
| |
| // Aliases. |
| using testing::Invoke; |
| using testing::Return; |
| using testing::StrictMock; |
| using testing::WithArg; |
| |
| // MockWallpaperDragDropDelegate ----------------------------------------------- |
| |
| // A mock implementation of `WallpaperDragDropDelegate` for testing. |
| class MockWallpaperDragDropDelegate : public WallpaperDragDropDelegate { |
| public: |
| // WallpaperDragDropDelegate: |
| MOCK_METHOD(void, |
| GetDropFormats, |
| (int* formats, std::set<ui::ClipboardFormatType>* types), |
| (override)); |
| |
| MOCK_METHOD(bool, CanDrop, (const ui::OSExchangeData& data), (override)); |
| |
| MOCK_METHOD(void, |
| OnDragEntered, |
| (const ui::OSExchangeData& data, |
| const gfx::Point& location_in_screen), |
| (override)); |
| |
| MOCK_METHOD(ui::DragDropTypes::DragOperation, |
| OnDragUpdated, |
| (const ui::OSExchangeData& data, |
| const gfx::Point& location_in_screen), |
| (override)); |
| |
| MOCK_METHOD(void, OnDragExited, (), (override)); |
| |
| MOCK_METHOD(ui::mojom::DragOperation, |
| OnDrop, |
| (const ui::OSExchangeData& data, |
| const gfx::Point& location_in_screen), |
| (override)); |
| }; |
| |
| } // namespace |
| |
| // WallpaperDragDropDelegateTest ----------------------------------------------- |
| |
| enum class Scenario { |
| kWithoutDelegate, |
| kWithDelegateAndInterestingPayload, |
| kWithDelegateAndInterestingButRejectedPayload, |
| kWithDelegateAndUninterestingPayload, |
| }; |
| |
| // Base class for tests of `WallpaperDragDropDelegate`, parameterized by testing |
| // scenario and whether or not to drop data over the wallpaper. |
| class WallpaperDragDropDelegateTest |
| : public AshTestBase, |
| public testing::WithParamInterface< |
| std::tuple<Scenario, /*drop_over_wallpaper=*/bool>> { |
| public: |
| // Returns the `delegate_` for drag-and-drop events over the wallpaper. |
| // NOTE: May be `nullptr` depending on test parameterization. |
| StrictMock<MockWallpaperDragDropDelegate>* delegate() { return delegate_; } |
| |
| // Returns whether data should be dropped over the wallpaper given test |
| // parameterization. |
| bool drop_over_wallpaper() const { return std::get<1>(GetParam()); } |
| |
| // Returns the testing scenario given parameterization. |
| Scenario scenario() const { return std::get<0>(GetParam()); } |
| |
| // Returns the `widget_` from which data can be drag-and-dropped. |
| views::Widget* widget() { return widget_.get(); } |
| |
| // Moves the mouse to the center of the specified `widget`. |
| void MoveMouseTo(views::Widget* widget) { |
| GetEventGenerator()->MoveMouseTo( |
| widget->GetWindowBoundsInScreen().CenterPoint(), /*count=*/10); |
| } |
| |
| // 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); |
| } |
| |
| // Performs a press/release of the left mouse button. |
| void PressLeftButton() { GetEventGenerator()->PressLeftButton(); } |
| void ReleaseLeftButton() { GetEventGenerator()->ReleaseLeftButton(); } |
| |
| // Verifies that any previously set expectations of the drag-and-drop |
| // `delegate_` have been fulfilled, and resets expectations to their initial |
| // state. No-ops if `delegate_` does not exist. |
| void VerifyAndResetExpectations() { |
| if (!delegate_) { |
| return; |
| } |
| |
| testing::Mock::VerifyAndClearExpectations(delegate_); |
| |
| ON_CALL(*delegate_, CanDrop) |
| .WillByDefault(testing::Return( |
| scenario() != |
| Scenario::kWithDelegateAndInterestingButRejectedPayload)); |
| |
| ON_CALL(*delegate_, GetDropFormats) |
| .WillByDefault(testing::Invoke( |
| [&](int* formats, std::set<ui::ClipboardFormatType>* types) { |
| if (scenario() != |
| Scenario::kWithDelegateAndUninterestingPayload) { |
| *formats |= OSExchangeData::Format::STRING; |
| } |
| })); |
| |
| ON_CALL(*delegate_, OnDragUpdated) |
| .WillByDefault( |
| testing::Return(ui::DragDropTypes::DragOperation::DRAG_COPY)); |
| } |
| |
| private: |
| // AshTestBase: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| |
| // Prevent blocking during drag-and-drop sequences. |
| ShellTestApi().drag_drop_controller()->set_should_block_during_drag_drop( |
| false); |
| |
| // Create and show a `widget_` from which data can be drag-and-dropped. |
| widget_ = CreateFramelessTestWidget(); |
| widget_->SetContentsView(std::make_unique<DraggableTestView>()); |
| widget_->CenterWindow(gfx::Size(100, 100)); |
| widget_->Show(); |
| |
| if (scenario() == Scenario::kWithoutDelegate) { |
| return; |
| } |
| |
| // Initialize and set default expectations for the `delegate_` for |
| // drag-and-drop events over the wallpaper. |
| auto delegate = |
| std::make_unique<StrictMock<MockWallpaperDragDropDelegate>>(); |
| delegate_ = delegate.get(); |
| VerifyAndResetExpectations(); |
| WallpaperController::Get()->SetDragDropDelegate(std::move(delegate)); |
| } |
| |
| void TearDown() override { |
| widget_->CloseNow(); |
| widget_.reset(); |
| |
| if (delegate_) { |
| WallpaperController::Get()->SetDragDropDelegate(nullptr); |
| delegate_ = nullptr; |
| } |
| |
| AshTestBase::TearDown(); |
| } |
| |
| // The delegate, owned by the `WallpaperControllerImpl`, for drag-and-drop |
| // events over the wallpaper. May be `nullptr` depending on test |
| // parameterization. |
| raw_ptr<StrictMock<MockWallpaperDragDropDelegate>, DanglingUntriaged> |
| delegate_ = nullptr; |
| |
| // The widget from which data can be drag-and-dropped. |
| std::unique_ptr<views::Widget> widget_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| WallpaperDragDropDelegateTest, |
| testing::Combine( |
| testing::Values(Scenario::kWithoutDelegate, |
| Scenario::kWithDelegateAndInterestingPayload, |
| Scenario::kWithDelegateAndInterestingButRejectedPayload, |
| Scenario::kWithDelegateAndUninterestingPayload), |
| /*drop_over_wallpaper=*/testing::Bool()), |
| [](const auto& info) { |
| std::string scenario; |
| switch (/*scenario=*/std::get<0>(info.param)) { |
| case Scenario::kWithoutDelegate: |
| scenario = "WithoutDelegate"; |
| break; |
| case Scenario::kWithDelegateAndInterestingPayload: |
| scenario = "WithDelegateAndInterestingPayload"; |
| break; |
| case Scenario::kWithDelegateAndInterestingButRejectedPayload: |
| scenario = "WithDelegateAndInterestingButRejectedPayload"; |
| break; |
| case Scenario::kWithDelegateAndUninterestingPayload: |
| scenario = "WithDelegateAndUninterestingPayload"; |
| break; |
| } |
| return base::StrCat({/*drop_over_wallpaper=*/std::get<1>(info.param) |
| ? "OverWallpaper" |
| : "OverWidget", |
| scenario}); |
| }); |
| |
| // Tests ----------------------------------------------------------------------- |
| |
| // Verifies that drag-and-drop events propagate as expected given the presence/ |
| // absence of a delegate and various parameterized testing scenarios. |
| TEST_P(WallpaperDragDropDelegateTest, DragAndDrop) { |
| // Initiate a drag from `widget()`. |
| MoveMouseTo(widget()); |
| PressLeftButton(); |
| |
| // The data will be dragged from `widget()` over the wallpaper. The sequence |
| // of expected events depends on the presence/absence of the `delegate()` and |
| // the parameterized testing `scenario()`. |
| if (delegate()) { |
| testing::InSequence sequence; |
| EXPECT_CALL(*delegate(), GetDropFormats); |
| if (scenario() != Scenario::kWithDelegateAndUninterestingPayload) { |
| EXPECT_CALL(*delegate(), CanDrop); |
| if (scenario() != |
| Scenario::kWithDelegateAndInterestingButRejectedPayload) { |
| EXPECT_CALL(*delegate(), OnDragEntered); |
| EXPECT_CALL(*delegate(), OnDragUpdated).Times(testing::AtLeast(1)); |
| } |
| } |
| } |
| |
| // Drag the data from `widget()` over the wallpaper. |
| MoveMouseBy(/*x=*/widget()->GetWindowBoundsInScreen().width(), /*y=*/0); |
| VerifyAndResetExpectations(); |
| |
| // The data will be dropped over either the wallpaper or the `widget()`. The |
| // sequence of expected events depends on the presence/absence of the |
| // `delegate()` and the paramaterized testing `scenario()`. |
| if (delegate()) { |
| if (scenario() == Scenario::kWithDelegateAndInterestingPayload) { |
| if (drop_over_wallpaper()) { |
| EXPECT_CALL(*delegate(), OnDrop); |
| } else { |
| testing::InSequence sequence; |
| EXPECT_CALL(*delegate(), OnDragUpdated).Times(testing::AtLeast(1)); |
| EXPECT_CALL(*delegate(), OnDragExited); |
| } |
| } |
| } |
| |
| // If the data should not be dropped over the wallpaper, drag the data back |
| // over the `widget()`. |
| if (!drop_over_wallpaper()) { |
| MoveMouseTo(widget()); |
| } |
| |
| // Drop the drag data over either the wallpaper or the `widget()`. |
| ReleaseLeftButton(); |
| VerifyAndResetExpectations(); |
| } |
| |
| } // namespace ash |