blob: a03d8be4a00655c554fecb0226d9decdd0152c42 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "ash/public/cpp/ash_features.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/window_factory.h"
#include "ash/wm/desks/close_desk_button.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desks_bar_view.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/new_desk_button.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/splitview/split_view_drag_indicators.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/stl_util.h"
#include "base/test/scoped_feature_list.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/events/test/event_generator.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
std::unique_ptr<aura::Window> CreateTransientWindow(
aura::Window* transient_parent,
const gfx::Rect& bounds) {
std::unique_ptr<aura::Window> window =
window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_POPUP);
window->Init(ui::LAYER_NOT_DRAWN);
window->SetBounds(bounds);
::wm::AddTransientChild(transient_parent, window.get());
aura::client::ParentWindowWithContext(
window.get(), transient_parent->GetRootWindow(), bounds);
window->Show();
return window;
}
bool DoesActiveDeskContainWindow(aura::Window* window) {
return DesksController::Get()->active_desk()->windows().contains(window);
}
OverviewGrid* GetOverviewGridForRoot(aura::Window* root) {
DCHECK(root->IsRootWindow());
auto* overview_controller = Shell::Get()->overview_controller();
DCHECK(overview_controller->InOverviewSession());
return overview_controller->overview_session()->GetGridWithRootWindow(root);
}
void CloseDeskFromMiniView(const DeskMiniView* desk_mini_view,
ui::test::EventGenerator* event_generator) {
DCHECK(desk_mini_view);
// Move to the center of the mini view so that the close button shows up.
const gfx::Point mini_view_center =
desk_mini_view->GetBoundsInScreen().CenterPoint();
event_generator->MoveMouseTo(mini_view_center);
EXPECT_TRUE(desk_mini_view->close_desk_button()->GetVisible());
// Move to the center of the close button and click.
event_generator->MoveMouseTo(
desk_mini_view->close_desk_button()->GetBoundsInScreen().CenterPoint());
event_generator->ClickLeftButton();
}
void ClickOnMiniView(const DeskMiniView* desk_mini_view,
ui::test::EventGenerator* event_generator) {
DCHECK(desk_mini_view);
const gfx::Point mini_view_center =
desk_mini_view->GetBoundsInScreen().CenterPoint();
event_generator->MoveMouseTo(mini_view_center);
event_generator->ClickLeftButton();
}
// If |drop| is false, the dragged |item| won't be dropped; giving the caller
// a chance to do some validations before the item is dropped.
void DragItemToPoint(OverviewItem* item,
const gfx::Point& screen_location,
ui::test::EventGenerator* event_generator,
bool drop = true) {
DCHECK(item);
const gfx::Point item_center =
gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
event_generator->MoveMouseTo(item_center);
event_generator->PressLeftButton();
// Move the mouse by an enough amount in X to engage in the normal drag mode
// rather than the drag to close mode.
event_generator->MoveMouseBy(50, 0);
event_generator->MoveMouseTo(screen_location);
if (drop)
event_generator->ReleaseLeftButton();
}
// Defines an observer to test DesksController notifications.
class TestObserver : public DesksController::Observer {
public:
TestObserver() = default;
~TestObserver() override = default;
const std::vector<const Desk*>& desks() const { return desks_; }
// DesksController::Observer:
void OnDeskAdded(const Desk* desk) override {
desks_.emplace_back(desk);
EXPECT_TRUE(DesksController::Get()->AreDesksBeingModified());
}
void OnDeskRemoved(const Desk* desk) override {
base::Erase(desks_, desk);
EXPECT_TRUE(DesksController::Get()->AreDesksBeingModified());
}
void OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) override {
EXPECT_TRUE(DesksController::Get()->AreDesksBeingModified());
}
void OnDeskSwitchAnimationFinished() override {
EXPECT_FALSE(DesksController::Get()->AreDesksBeingModified());
}
private:
std::vector<const Desk*> desks_;
DISALLOW_COPY_AND_ASSIGN(TestObserver);
};
class TestDeskObserver : public Desk::Observer {
public:
TestDeskObserver() = default;
~TestDeskObserver() override = default;
int notify_counts() const { return notify_counts_; }
// Desk::Observer:
void OnContentChanged() override { ++notify_counts_; }
void OnDeskDestroyed(const Desk* desk) override {}
private:
int notify_counts_ = 0;
DISALLOW_COPY_AND_ASSIGN(TestDeskObserver);
};
// Used for waiting for the desk switch animations on all root windows to
// complete.
class DeskSwitchAnimationWaiter : public DesksController::Observer {
public:
DeskSwitchAnimationWaiter() { DesksController::Get()->AddObserver(this); }
~DeskSwitchAnimationWaiter() override {
DesksController::Get()->RemoveObserver(this);
}
void Wait() {
auto* controller = DesksController::Get();
EXPECT_TRUE(controller->AreDesksBeingModified());
run_loop_.Run();
EXPECT_FALSE(controller->AreDesksBeingModified());
}
// DesksController::Observer:
void OnDeskAdded(const Desk* desk) override {}
void OnDeskRemoved(const Desk* desk) override {}
void OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) override {}
void OnDeskSwitchAnimationFinished() override { run_loop_.Quit(); }
private:
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(DeskSwitchAnimationWaiter);
};
class DesksTest : public AshTestBase {
public:
DesksTest() = default;
~DesksTest() override = default;
// AshTestBase:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(features::kVirtualDesks);
AshTestBase::SetUp();
}
void ActivateDesk(const Desk* desk) {
ASSERT_FALSE(desk->is_active());
DeskSwitchAnimationWaiter waiter;
DesksController::Get()->ActivateDesk(desk);
waiter.Wait();
ASSERT_TRUE(desk->is_active());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(DesksTest);
};
TEST_F(DesksTest, DesksCreationAndRemoval) {
TestObserver observer;
auto* controller = DesksController::Get();
controller->AddObserver(&observer);
// There's always a default pre-existing desk that cannot be removed.
EXPECT_EQ(1u, controller->desks().size());
EXPECT_FALSE(controller->CanRemoveDesks());
EXPECT_TRUE(controller->CanCreateDesks());
// Add desks until no longer possible.
while (controller->CanCreateDesks())
controller->NewDesk();
// Expect we've reached the max number of desks, and we've been notified only
// with the newly created desks.
EXPECT_EQ(desks_util::kMaxNumberOfDesks, controller->desks().size());
EXPECT_EQ(desks_util::kMaxNumberOfDesks - 1, observer.desks().size());
EXPECT_TRUE(controller->CanRemoveDesks());
// Remove all desks until no longer possible, and expect that there's always
// one default desk remaining.
while (controller->CanRemoveDesks())
controller->RemoveDesk(observer.desks().back());
EXPECT_EQ(1u, controller->desks().size());
EXPECT_FALSE(controller->CanRemoveDesks());
EXPECT_TRUE(controller->CanCreateDesks());
EXPECT_TRUE(observer.desks().empty());
controller->RemoveObserver(&observer);
}
TEST_F(DesksTest, DesksBarViewDeskCreation) {
TestObserver observer;
auto* controller = DesksController::Get();
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
// Initially the grid is not offset down when there are no desk mini_views
// once animations are added.
EXPECT_FALSE(overview_grid->IsDesksBarViewActive());
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
// Since we have a single default desk, there should be no mini_views, and the
// new desk button is enabled.
DCHECK(desks_bar_view);
EXPECT_TRUE(desks_bar_view->mini_views().empty());
EXPECT_TRUE(desks_bar_view->new_desk_button()->GetEnabled());
// Click many times on the new desk button and expect only the max number of
// desks will be created, and the button is no longer enabled.
const gfx::Point button_center =
desks_bar_view->new_desk_button()->GetBoundsInScreen().CenterPoint();
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(button_center);
for (size_t i = 0; i < desks_util::kMaxNumberOfDesks + 2; ++i)
event_generator->ClickLeftButton();
EXPECT_TRUE(overview_grid->IsDesksBarViewActive());
EXPECT_EQ(desks_util::kMaxNumberOfDesks, controller->desks().size());
EXPECT_EQ(controller->desks().size(), desks_bar_view->mini_views().size());
EXPECT_FALSE(controller->CanCreateDesks());
EXPECT_TRUE(controller->CanRemoveDesks());
EXPECT_FALSE(desks_bar_view->new_desk_button()->GetEnabled());
// Hover over one of the mini_views, and expect that the close button becomes
// visible.
const auto* mini_view = desks_bar_view->mini_views().back().get();
EXPECT_FALSE(mini_view->close_desk_button()->GetVisible());
const gfx::Point mini_view_center =
mini_view->GetBoundsInScreen().CenterPoint();
event_generator->MoveMouseTo(mini_view_center);
EXPECT_TRUE(mini_view->close_desk_button()->GetVisible());
// Use the close button to close the desk.
event_generator->MoveMouseTo(
mini_view->close_desk_button()->GetBoundsInScreen().CenterPoint());
event_generator->ClickLeftButton();
// The new desk button is now enabled again.
EXPECT_EQ(desks_util::kMaxNumberOfDesks - 1, controller->desks().size());
EXPECT_EQ(controller->desks().size(), desks_bar_view->mini_views().size());
EXPECT_TRUE(controller->CanCreateDesks());
EXPECT_TRUE(desks_bar_view->new_desk_button()->GetEnabled());
// Exit overview mode and re-enter. Since we have more than one pre-existing
// desks, their mini_views should be created upon construction of the desks
// bar.
overview_controller->ToggleOverview();
EXPECT_FALSE(overview_controller->InOverviewSession());
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
// Get the new grid and the new desk_bar_view.
overview_grid =
overview_controller->overview_session()->GetGridWithRootWindow(
Shell::GetPrimaryRootWindow());
EXPECT_TRUE(overview_grid->IsDesksBarViewActive());
desks_bar_view = overview_grid->GetDesksBarViewForTesting();
DCHECK(desks_bar_view);
EXPECT_EQ(controller->desks().size(), desks_bar_view->mini_views().size());
EXPECT_TRUE(desks_bar_view->new_desk_button()->GetEnabled());
}
TEST_F(DesksTest, DeskActivation) {
auto* controller = DesksController::Get();
ASSERT_EQ(1u, controller->desks().size());
const Desk* desk_1 = controller->desks()[0].get();
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_TRUE(desk_1->is_active());
auto* root = Shell::GetPrimaryRootWindow();
EXPECT_TRUE(desk_1->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_EQ(desks_util::GetActiveDeskContainerForRoot(root),
desk_1->GetDeskContainerForRoot(root));
// Create three new desks, and activate one of the middle ones.
controller->NewDesk();
controller->NewDesk();
controller->NewDesk();
ASSERT_EQ(4u, controller->desks().size());
const Desk* desk_2 = controller->desks()[1].get();
const Desk* desk_3 = controller->desks()[2].get();
const Desk* desk_4 = controller->desks()[3].get();
EXPECT_FALSE(controller->AreDesksBeingModified());
ActivateDesk(desk_2);
EXPECT_FALSE(controller->AreDesksBeingModified());
EXPECT_EQ(desk_2, controller->active_desk());
EXPECT_FALSE(desk_1->is_active());
EXPECT_TRUE(desk_2->is_active());
EXPECT_FALSE(desk_3->is_active());
EXPECT_FALSE(desk_4->is_active());
EXPECT_FALSE(desk_1->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_TRUE(desk_2->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_FALSE(desk_3->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible());
// Remove the active desk, which is in the middle, activation should move to
// the left, so desk 1 should be activated.
EXPECT_FALSE(controller->AreDesksBeingModified());
controller->RemoveDesk(desk_2);
EXPECT_FALSE(controller->AreDesksBeingModified());
ASSERT_EQ(3u, controller->desks().size());
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_TRUE(desk_1->is_active());
EXPECT_FALSE(desk_3->is_active());
EXPECT_FALSE(desk_4->is_active());
EXPECT_TRUE(desk_1->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_FALSE(desk_3->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible());
// Remove the active desk, it's the first one on the left, so desk_3 (on the
// right) will be activated.
EXPECT_FALSE(controller->AreDesksBeingModified());
controller->RemoveDesk(desk_1);
EXPECT_FALSE(controller->AreDesksBeingModified());
ASSERT_EQ(2u, controller->desks().size());
EXPECT_EQ(desk_3, controller->active_desk());
EXPECT_TRUE(desk_3->is_active());
EXPECT_FALSE(desk_4->is_active());
EXPECT_TRUE(desk_3->GetDeskContainerForRoot(root)->IsVisible());
EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible());
}
// This test makes sure we have coverage for that desk switch animation when run
// with multiple displays.
TEST_F(DesksTest, DeskActivationDualDisplay) {
UpdateDisplay("600x600,400x500");
auto* controller = DesksController::Get();
ASSERT_EQ(1u, controller->desks().size());
const Desk* desk_1 = controller->desks()[0].get();
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_TRUE(desk_1->is_active());
// Create three new desks, and activate one of the middle ones.
controller->NewDesk();
controller->NewDesk();
controller->NewDesk();
ASSERT_EQ(4u, controller->desks().size());
const Desk* desk_2 = controller->desks()[1].get();
const Desk* desk_3 = controller->desks()[2].get();
const Desk* desk_4 = controller->desks()[3].get();
EXPECT_FALSE(controller->AreDesksBeingModified());
ActivateDesk(desk_2);
EXPECT_FALSE(controller->AreDesksBeingModified());
EXPECT_EQ(desk_2, controller->active_desk());
EXPECT_FALSE(desk_1->is_active());
EXPECT_TRUE(desk_2->is_active());
EXPECT_FALSE(desk_3->is_active());
EXPECT_FALSE(desk_4->is_active());
auto roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
EXPECT_FALSE(desk_1->GetDeskContainerForRoot(roots[0])->IsVisible());
EXPECT_FALSE(desk_1->GetDeskContainerForRoot(roots[1])->IsVisible());
EXPECT_TRUE(desk_2->GetDeskContainerForRoot(roots[0])->IsVisible());
EXPECT_TRUE(desk_2->GetDeskContainerForRoot(roots[1])->IsVisible());
EXPECT_FALSE(desk_3->GetDeskContainerForRoot(roots[0])->IsVisible());
EXPECT_FALSE(desk_3->GetDeskContainerForRoot(roots[1])->IsVisible());
EXPECT_FALSE(desk_4->GetDeskContainerForRoot(roots[0])->IsVisible());
EXPECT_FALSE(desk_4->GetDeskContainerForRoot(roots[1])->IsVisible());
}
TEST_F(DesksTest, TransientWindows) {
auto* controller = DesksController::Get();
ASSERT_EQ(1u, controller->desks().size());
const Desk* desk_1 = controller->desks()[0].get();
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_TRUE(desk_1->is_active());
// Create two windows, one is a transient child of the other.
auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
auto win1 = CreateTransientWindow(win0.get(), gfx::Rect(100, 100, 100, 100));
EXPECT_EQ(2u, desk_1->windows().size());
EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get()));
auto* root = Shell::GetPrimaryRootWindow();
EXPECT_EQ(desks_util::GetActiveDeskContainerForRoot(root),
desks_util::GetDeskContainerForContext(win0.get()));
EXPECT_EQ(desks_util::GetActiveDeskContainerForRoot(root),
desks_util::GetDeskContainerForContext(win1.get()));
// Create a new desk and activate it.
controller->NewDesk();
const Desk* desk_2 = controller->desks()[1].get();
EXPECT_TRUE(desk_2->windows().empty());
ActivateDesk(desk_2);
EXPECT_FALSE(desk_1->is_active());
EXPECT_TRUE(desk_2->is_active());
// Create another transient child of the earlier transient child, and confirm
// it's tracked in desk_1 (even though desk_2 is the currently active one).
// This is because the transient parent exists in desk_1.
auto win2 = CreateTransientWindow(win1.get(), gfx::Rect(100, 100, 50, 50));
::wm::AddTransientChild(win1.get(), win2.get());
EXPECT_EQ(3u, desk_1->windows().size());
EXPECT_FALSE(DoesActiveDeskContainWindow(win2.get()));
// Remove the inactive desk 1, and expect that its windows, including
// transient will move to desk 2.
controller->RemoveDesk(desk_1);
EXPECT_EQ(1u, controller->desks().size());
EXPECT_EQ(desk_2, controller->active_desk());
EXPECT_EQ(3u, desk_2->windows().size());
EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win2.get()));
}
TEST_F(DesksTest, WindowActivation) {
// Create three windows.
auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
auto win1 = CreateTestWindow(gfx::Rect(50, 50, 200, 200));
auto win2 = CreateTestWindow(gfx::Rect(100, 100, 100, 100));
EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win2.get()));
// Activate win0 and expects that it remains activated until we switch desks.
wm::ActivateWindow(win0.get());
// Create a new desk and activate it. Expect it's not tracking any windows
// yet.
auto* controller = DesksController::Get();
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
Desk* desk_1 = controller->desks()[0].get();
const Desk* desk_2 = controller->desks()[1].get();
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_EQ(3u, desk_1->windows().size());
EXPECT_TRUE(desk_2->windows().empty());
EXPECT_EQ(win0.get(), wm::GetActiveWindow());
// Activate the newly-added desk. Expect that the tracked windows per each
// desk will remain the same.
ActivateDesk(desk_2);
EXPECT_EQ(desk_2, controller->active_desk());
EXPECT_EQ(3u, desk_1->windows().size());
EXPECT_TRUE(desk_2->windows().empty());
// `desk_2` has no windows, so now no window should be active, and windows on
// `desk_1` cannot be activated.
EXPECT_EQ(nullptr, wm::GetActiveWindow());
EXPECT_FALSE(wm::CanActivateWindow(win0.get()));
EXPECT_FALSE(wm::CanActivateWindow(win1.get()));
EXPECT_FALSE(wm::CanActivateWindow(win2.get()));
// Create two new windows, they should now go to desk_2.
auto win3 = CreateTestWindow(gfx::Rect(0, 0, 300, 200));
auto win4 = CreateTestWindow(gfx::Rect(10, 30, 400, 200));
wm::ActivateWindow(win3.get());
EXPECT_EQ(2u, desk_2->windows().size());
EXPECT_TRUE(DoesActiveDeskContainWindow(win3.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win4.get()));
EXPECT_FALSE(DoesActiveDeskContainWindow(win0.get()));
EXPECT_FALSE(DoesActiveDeskContainWindow(win1.get()));
EXPECT_FALSE(DoesActiveDeskContainWindow(win2.get()));
EXPECT_EQ(win3.get(), wm::GetActiveWindow());
// Delete `win0` and expect that `desk_1`'s windows will be updated.
win0.reset();
EXPECT_EQ(2u, desk_1->windows().size());
EXPECT_EQ(2u, desk_2->windows().size());
// No change in the activation.
EXPECT_EQ(win3.get(), wm::GetActiveWindow());
// Switch back to `desk_1`. Now we can activate its windows.
ActivateDesk(desk_1);
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_TRUE(wm::CanActivateWindow(win1.get()));
EXPECT_TRUE(wm::CanActivateWindow(win2.get()));
EXPECT_FALSE(wm::CanActivateWindow(win3.get()));
EXPECT_FALSE(wm::CanActivateWindow(win4.get()));
// After `win0` has been deleted, `win2` is next on the MRU list.
EXPECT_EQ(win2.get(), wm::GetActiveWindow());
// Remove `desk_2` and expect that its windows will be moved to the active
// desk.
TestDeskObserver observer;
desk_1->AddObserver(&observer);
controller->RemoveDesk(desk_2);
EXPECT_EQ(1u, controller->desks().size());
EXPECT_EQ(desk_1, controller->active_desk());
EXPECT_EQ(4u, desk_1->windows().size());
// Even though two new windows have been added to desk_1, observers should be
// notified only once.
EXPECT_EQ(1, observer.notify_counts());
desk_1->RemoveObserver(&observer);
EXPECT_TRUE(DoesActiveDeskContainWindow(win3.get()));
EXPECT_TRUE(DoesActiveDeskContainWindow(win4.get()));
// `desk_2`'s windows moved to `desk_1`, but that should not change the
// already active window.
EXPECT_EQ(win2.get(), wm::GetActiveWindow());
// Moved windows can now be activated.
EXPECT_TRUE(wm::CanActivateWindow(win3.get()));
EXPECT_TRUE(wm::CanActivateWindow(win4.get()));
}
TEST_F(DesksTest, ActivateDeskFromOverview) {
auto* controller = DesksController::Get();
// Create three desks other than the default initial desk.
controller->NewDesk();
controller->NewDesk();
controller->NewDesk();
ASSERT_EQ(4u, controller->desks().size());
// Create two windows on desk_1.
auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
auto win1 = CreateTestWindow(gfx::Rect(50, 50, 200, 200));
wm::ActivateWindow(win1.get());
EXPECT_EQ(win1.get(), wm::GetActiveWindow());
// Enter overview mode, and expect the desk bar is shown with exactly four
// desks mini views, and there are exactly two windows in the overview mode
// grid.
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(4u, desks_bar_view->mini_views().size());
EXPECT_EQ(2u, overview_grid->window_list().size());
// Activate desk_4 (last one on the right) by clicking on its mini view.
const Desk* desk_4 = controller->desks()[3].get();
EXPECT_FALSE(desk_4->is_active());
auto* mini_view = desks_bar_view->mini_views().back().get();
EXPECT_EQ(desk_4, mini_view->desk());
EXPECT_FALSE(mini_view->close_desk_button()->GetVisible());
const gfx::Point mini_view_center =
mini_view->GetBoundsInScreen().CenterPoint();
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(mini_view_center);
DeskSwitchAnimationWaiter waiter;
event_generator->ClickLeftButton();
waiter.Wait();
// Expect that desk_4 is now active, and overview mode exited.
EXPECT_TRUE(desk_4->is_active());
EXPECT_FALSE(overview_controller->InOverviewSession());
// Exiting overview mode should not restore focus to a window on a
// now-inactive desk. Run a loop since the overview session is destroyed async
// and until that happens, focus will be on the dummy
// "OverviewModeFocusedWidget".
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, wm::GetActiveWindow());
// Create one window in desk_4 and enter overview mode. Expect the grid is
// showing exactly one window.
auto win2 = CreateTestWindow(gfx::Rect(50, 50, 200, 200));
wm::ActivateWindow(win2.get());
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
EXPECT_EQ(1u, overview_grid->window_list().size());
// When exiting overview mode without changing desks, the focus should be
// restored to the same window.
overview_controller->ToggleOverview();
EXPECT_FALSE(overview_controller->InOverviewSession());
// Run a loop since the overview session is destroyed async and until that
// happens, focus will be on the dummy "OverviewModeFocusedWidget".
base::RunLoop().RunUntilIdle();
EXPECT_EQ(win2.get(), wm::GetActiveWindow());
}
// This test makes sure we have coverage for that desk switch animation when run
// with multiple displays while overview mode is active.
TEST_F(DesksTest, ActivateDeskFromOverviewDualDisplay) {
UpdateDisplay("600x600,400x500");
auto* controller = DesksController::Get();
// Create three desks other than the default initial desk.
controller->NewDesk();
controller->NewDesk();
controller->NewDesk();
ASSERT_EQ(4u, controller->desks().size());
// Enter overview mode.
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
auto roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
// Use secondary display grid.
const auto* overview_grid = GetOverviewGridForRoot(roots[1]);
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(4u, desks_bar_view->mini_views().size());
// Activate desk_4 (last one on the right) by clicking on its mini view.
const Desk* desk_4 = controller->desks()[3].get();
EXPECT_FALSE(desk_4->is_active());
const auto* mini_view = desks_bar_view->mini_views().back().get();
const gfx::Point mini_view_center =
mini_view->GetBoundsInScreen().CenterPoint();
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(mini_view_center);
DeskSwitchAnimationWaiter waiter;
event_generator->ClickLeftButton();
waiter.Wait();
// Expect that desk_4 is now active, and overview mode exited.
EXPECT_TRUE(desk_4->is_active());
EXPECT_FALSE(overview_controller->InOverviewSession());
}
TEST_F(DesksTest, RemoveInactiveDeskFromOverview) {
auto* controller = DesksController::Get();
// Create three desks other than the default initial desk.
controller->NewDesk();
controller->NewDesk();
controller->NewDesk();
ASSERT_EQ(4u, controller->desks().size());
// Create two windows on desk_1.
auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
auto win1 = CreateTestWindow(gfx::Rect(50, 50, 200, 200));
wm::ActivateWindow(win0.get());
EXPECT_EQ(win0.get(), wm::GetActiveWindow());
// Active desk_4 and enter overview mode. Expect that the grid is currently
// empty.
Desk* desk_4 = controller->desks()[3].get();
ActivateDesk(desk_4);
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
EXPECT_TRUE(overview_grid->window_list().empty());
// Remove desk_1 using the close button on its mini view. desk_1 is currently
// inactive. Its windows should be moved to desk_4 and added to the overview
// grid in the MRU order (win0, and win1).
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(4u, desks_bar_view->mini_views().size());
Desk* desk_1 = controller->desks()[0].get();
auto* mini_view = desks_bar_view->mini_views().front().get();
EXPECT_EQ(desk_1, mini_view->desk());
// Setup observers of both the active and inactive desks to make sure
// refreshing the mini_view is requested only *once* for the active desk, and
// never for the to-be-removed inactive desk.
TestDeskObserver desk_4_observer;
desk_4->AddObserver(&desk_4_observer);
TestDeskObserver desk_1_observer;
desk_1->AddObserver(&desk_1_observer);
CloseDeskFromMiniView(mini_view, GetEventGenerator());
EXPECT_EQ(0, desk_1_observer.notify_counts());
EXPECT_EQ(1, desk_4_observer.notify_counts());
ASSERT_EQ(3u, desks_bar_view->mini_views().size());
EXPECT_TRUE(overview_controller->InOverviewSession());
ASSERT_EQ(2u, overview_grid->window_list().size());
EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win0.get()));
EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win1.get()));
EXPECT_EQ(overview_grid->GetOverviewItemContaining(win0.get()),
overview_grid->window_list()[0].get());
EXPECT_EQ(overview_grid->GetOverviewItemContaining(win1.get()),
overview_grid->window_list()[1].get());
// Make sure overview mode remains active.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(overview_controller->InOverviewSession());
// Removing a desk with no windows should not result in any new mini_views
// updates.
mini_view = desks_bar_view->mini_views().front().get();
EXPECT_TRUE(mini_view->desk()->windows().empty());
CloseDeskFromMiniView(mini_view, GetEventGenerator());
EXPECT_EQ(1, desk_4_observer.notify_counts());
// Exiting overview mode should not cause any mini_views refreshes, since the
// destroyed overview-specific windows do not show up in the mini_view.
overview_controller->ToggleOverview();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(1, desk_4_observer.notify_counts());
desk_4->RemoveObserver(&desk_4_observer);
}
TEST_F(DesksTest, RemoveActiveDeskFromOverview) {
auto* controller = DesksController::Get();
// Create one desk other than the default initial desk.
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
// Create two windows on desk_1.
Desk* desk_1 = controller->desks()[0].get();
auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
auto win1 = CreateTestWindow(gfx::Rect(50, 50, 200, 200));
wm::ActivateWindow(win0.get());
EXPECT_EQ(win0.get(), wm::GetActiveWindow());
// Activate desk_2 and create one more window.
Desk* desk_2 = controller->desks()[1].get();
ActivateDesk(desk_2);
auto win2 = CreateTestWindow(gfx::Rect(50, 50, 200, 200));
wm::ActivateWindow(win2.get());
EXPECT_EQ(win2.get(), wm::GetActiveWindow());
// Enter overview mode, and remove desk_2 from its mini-view close button.
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
EXPECT_EQ(1u, overview_grid->window_list().size());
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(2u, desks_bar_view->mini_views().size());
auto* mini_view = desks_bar_view->mini_views().back().get();
EXPECT_EQ(desk_2, mini_view->desk());
// Setup observers of both the active and inactive desks to make sure
// refreshing the mini_view is requested only *once* for the soon-to-be active
// desk_1, and never for the to-be-removed currently active desk_2.
TestDeskObserver desk_1_observer;
desk_1->AddObserver(&desk_1_observer);
TestDeskObserver desk_2_observer;
desk_2->AddObserver(&desk_2_observer);
CloseDeskFromMiniView(mini_view, GetEventGenerator());
EXPECT_EQ(1, desk_1_observer.notify_counts());
EXPECT_EQ(0, desk_2_observer.notify_counts());
// desk_1 will become active, and windows from desk_2 and desk_1 will merge
// and added in the overview grid in the order of MRU.
ASSERT_EQ(1u, controller->desks().size());
ASSERT_EQ(1u, desks_bar_view->mini_views().size());
EXPECT_TRUE(desk_1->is_active());
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(3u, overview_grid->window_list().size());
EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win0.get()));
EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win1.get()));
EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win2.get()));
// The MRU order is {win2, win0, win1}.
EXPECT_EQ(overview_grid->GetOverviewItemContaining(win2.get()),
overview_grid->window_list()[0].get());
EXPECT_EQ(overview_grid->GetOverviewItemContaining(win0.get()),
overview_grid->window_list()[1].get());
EXPECT_EQ(overview_grid->GetOverviewItemContaining(win1.get()),
overview_grid->window_list()[2].get());
// Make sure overview mode remains active.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(overview_controller->InOverviewSession());
// Exiting overview mode should not cause any mini_views refreshes, since the
// destroyed overview-specific windows do not show up in the mini_view.
overview_controller->ToggleOverview();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(1, desk_1_observer.notify_counts());
desk_1->RemoveObserver(&desk_1_observer);
}
TEST_F(DesksTest, ActivateActiveDeskFromOverview) {
auto* controller = DesksController::Get();
// Create one more desk other than the default initial desk, so the desks bar
// shows up in overview mode.
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
// Enter overview mode, and click on `desk_1`'s mini_view, and expect that
// overview mode exits since this is the already active desk.
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
const Desk* desk_1 = controller->desks()[0].get();
const auto* mini_view = desks_bar_view->mini_views().front().get();
ClickOnMiniView(mini_view, GetEventGenerator());
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(desk_1->is_active());
EXPECT_EQ(desk_1, controller->active_desk());
}
TEST_F(DesksTest, MinimizedWindow) {
auto* controller = DesksController::Get();
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
const Desk* desk_1 = controller->desks()[0].get();
const Desk* desk_2 = controller->desks()[1].get();
auto win0 = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(win0.get());
EXPECT_EQ(win0.get(), wm::GetActiveWindow());
auto* window_state = wm::GetWindowState(win0.get());
window_state->Minimize();
EXPECT_TRUE(window_state->IsMinimized());
// Minimized windows on the active desk show up in the MRU list...
EXPECT_EQ(1u,
Shell::Get()->mru_window_tracker()->BuildMruWindowList().size());
ActivateDesk(desk_2);
// ... But they don't once their desk is inactive.
EXPECT_TRUE(Shell::Get()->mru_window_tracker()->BuildMruWindowList().empty());
// Switching back to their desk should neither activate them nor unminimize
// them.
ActivateDesk(desk_1);
EXPECT_TRUE(window_state->IsMinimized());
EXPECT_NE(win0.get(), wm::GetActiveWindow());
}
TEST_F(DesksTest, DragWindowToDesk) {
auto* controller = DesksController::Get();
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
const Desk* desk_1 = controller->desks()[0].get();
const Desk* desk_2 = controller->desks()[1].get();
auto window = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), wm::GetActiveWindow());
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
EXPECT_EQ(1u, overview_grid->size());
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();
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(2u, desks_bar_view->mini_views().size());
auto* desk_1_mini_view = desks_bar_view->mini_views()[0].get();
EXPECT_EQ(desk_1, desk_1_mini_view->desk());
// Drag it and drop it on its same desk's mini_view. Nothing happens, it
// should be returned back to its original target bounds.
DragItemToPoint(overview_item,
desk_1_mini_view->GetBoundsInScreen().CenterPoint(),
GetEventGenerator());
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(1u, overview_grid->size());
EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
// Now drag it to desk_2's mini_view. The overview grid should now show the
// "no-windows" widget, and the window should move to desk_2.
auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
EXPECT_EQ(desk_2, desk_2_mini_view->desk());
DragItemToPoint(overview_item,
desk_2_mini_view->GetBoundsInScreen().CenterPoint(),
GetEventGenerator());
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_grid->empty());
EXPECT_FALSE(DoesActiveDeskContainWindow(window.get()));
EXPECT_TRUE(overview_session->no_windows_widget_for_testing());
EXPECT_TRUE(desk_2->windows().contains(window.get()));
EXPECT_FALSE(overview_grid->drop_target_widget());
}
TEST_F(DesksTest, DragWindowToNonMiniViewPoints) {
auto* controller = DesksController::Get();
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
auto window = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), wm::GetActiveWindow());
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
EXPECT_EQ(1u, overview_grid->size());
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();
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
// Drag it and drop it on the new desk button. Nothing happens, it should be
// returned back to its original target bounds.
DragItemToPoint(
overview_item,
desks_bar_view->new_desk_button()->GetBoundsInScreen().CenterPoint(),
GetEventGenerator());
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(1u, overview_grid->size());
EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
// Drag it and drop it on the bottom right corner of the display. Also,
// nothing should happen.
DragItemToPoint(overview_item,
window->GetRootWindow()->GetBoundsInScreen().bottom_right(),
GetEventGenerator());
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(1u, overview_grid->size());
EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds());
EXPECT_TRUE(DoesActiveDeskContainWindow(window.get()));
}
class DesksWithSplitViewTest : public AshTestBase {
public:
DesksWithSplitViewTest() = default;
~DesksWithSplitViewTest() override = default;
// AshTestBase:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{features::kVirtualDesks,
features::kDragToSnapInClamshellMode},
/*disabled_features=*/{});
AshTestBase::SetUp();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(DesksWithSplitViewTest);
};
TEST_F(DesksWithSplitViewTest, SuccessfulDragToDeskRemovesSplitViewIndicators) {
auto* controller = DesksController::Get();
controller->NewDesk();
ASSERT_EQ(2u, controller->desks().size());
auto window = CreateTestWindow(gfx::Rect(0, 0, 250, 100));
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), wm::GetActiveWindow());
auto* overview_controller = Shell::Get()->overview_controller();
overview_controller->ToggleOverview();
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
auto* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window.get());
ASSERT_TRUE(overview_item);
const auto* desks_bar_view = overview_grid->GetDesksBarViewForTesting();
ASSERT_TRUE(desks_bar_view);
ASSERT_EQ(2u, desks_bar_view->mini_views().size());
// Drag it to desk_2's mini_view. The overview grid should now show the
// "no-windows" widget, and the window should move to desk_2.
auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
DragItemToPoint(overview_item,
desk_2_mini_view->GetBoundsInScreen().CenterPoint(),
GetEventGenerator(), /*drop=*/false);
// Validate that before dropping, the SplitView indicators and the drop target
// widget are created.
EXPECT_TRUE(overview_grid->drop_target_widget());
EXPECT_EQ(IndicatorState::kDragArea,
overview_session->split_view_drag_indicators()
->current_indicator_state());
// Now drop the window, and validate the indicators and the drop target were
// removed.
GetEventGenerator()->ReleaseLeftButton();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_grid->empty());
EXPECT_FALSE(DoesActiveDeskContainWindow(window.get()));
EXPECT_TRUE(overview_session->no_windows_widget_for_testing());
EXPECT_FALSE(overview_grid->drop_target_widget());
EXPECT_EQ(IndicatorState::kNone,
overview_session->split_view_drag_indicators()
->current_indicator_state());
}
// TODO(afakhry): Add more tests:
// - Always on top windows are not tracked by any desk.
// - Reusing containers when desks are removed and created.
} // namespace
} // namespace ash