[go: nahoru, domu]

blob: 2bee80f4ad464dee5c82da3934805b06d6b458a9 [file] [log] [blame]
// 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/wm/snap_group/snap_group.h"
#include <memory>
#include <vector>
#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/display/window_tree_host_manager.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/style/close_button.h"
#include "ash/system/toast/toast_manager_impl.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_util.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/legacy_desk_bar_view.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_drop_target.h"
#include "ash/wm/overview/overview_focus_cycler.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_group_item.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_item_view.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_test_base.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/overview_window_drag_controller.h"
#include "ash/wm/overview/scoped_overview_transform_window.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_divider.h"
#include "ash/wm/splitview/split_view_divider_view.h"
#include "ash/wm/splitview/split_view_overview_session.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_cycle/window_cycle_controller.h"
#include "ash/wm/window_cycle/window_cycle_list.h"
#include "ash/wm/window_cycle/window_cycle_view.h"
#include "ash/wm/window_mini_view.h"
#include "ash/wm/window_resizer.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_constants.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/wm_metrics.h"
#include "ash/wm/workspace/multi_window_resize_controller.h"
#include "ash/wm/workspace/workspace_event_handler.h"
#include "ash/wm/workspace_controller_test_api.h"
#include "base/check_op.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/hit_test.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_modality_controller.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/window_move_client.h"
namespace ash {
namespace {
using ui::mojom::CursorType;
using WindowCyclingDirection = WindowCycleController::WindowCyclingDirection;
SplitViewController* split_view_controller() {
return SplitViewController::Get(Shell::GetPrimaryRootWindow());
}
SplitViewDivider* split_view_divider() {
return split_view_controller()->split_view_divider();
}
gfx::Rect split_view_divider_bounds_in_screen() {
return split_view_divider()->GetDividerBoundsInScreen(
/*is_dragging=*/false);
}
const gfx::Rect work_area_bounds() {
return display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
}
void SwitchToTabletMode() {
TabletModeControllerTestApi test_api;
test_api.DetachAllMice();
test_api.EnterTabletMode();
}
void ExitTabletMode() {
TabletModeControllerTestApi().LeaveTabletMode();
}
gfx::Rect GetOverviewGridBounds() {
OverviewSession* overview_session = GetOverviewSession();
if (!overview_session) {
return gfx::Rect();
}
return overview_session->grid_list()[0]->bounds_for_testing();
}
void SnapOneTestWindow(
aura::Window* window,
chromeos::WindowStateType state_type,
WindowSnapActionSource snap_action_source = WindowSnapActionSource::kTest) {
WindowState* window_state = WindowState::Get(window);
const WindowSnapWMEvent snap_event(
state_type == chromeos::WindowStateType::kPrimarySnapped
? WM_EVENT_SNAP_PRIMARY
: WM_EVENT_SNAP_SECONDARY,
snap_action_source);
window_state->OnWMEvent(&snap_event);
EXPECT_EQ(state_type, window_state->GetStateType());
}
// Verifies that `window` is in split view overview, where `window` is
// excluded from overview, and overview occupies the work area opposite of
// `window`. Returns the corresponding `SplitViewOverviewSession` if exits and
// nullptr otherwise. `faster_split_screen_setup` specifies whether the
// `SplitViewOverviewSession` is initiated by faster split screen set up or not,
// where behaviors differ such as overview widget.
SplitViewOverviewSession* VerifySplitViewOverviewSession(
aura::Window* window,
bool faster_split_screen_setup = true) {
auto* overview_controller = Shell::Get()->overview_controller();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_FALSE(
overview_controller->overview_session()->IsWindowInOverview(window));
SplitViewOverviewSession* split_view_overview_session =
RootWindowController::ForWindow(window)->split_view_overview_session();
EXPECT_TRUE(split_view_overview_session);
gfx::Rect expected_grid_bounds = work_area_bounds();
expected_grid_bounds.Subtract(window->GetBoundsInScreen());
if (split_view_divider()) {
expected_grid_bounds.Subtract(split_view_divider_bounds_in_screen());
}
if (!Shell::Get()->IsInTabletMode()) {
EXPECT_EQ(expected_grid_bounds, GetOverviewGridBounds());
}
EXPECT_TRUE(expected_grid_bounds.Contains(GetOverviewGridBounds()));
// Hotseat may be on the bottom of the work area.
EXPECT_TRUE(work_area_bounds().Contains(expected_grid_bounds));
if (!Shell::Get()->IsInTabletMode() && faster_split_screen_setup) {
EXPECT_TRUE(
GetOverviewGridForRoot(window->GetRootWindow())->no_windows_widget());
}
return split_view_overview_session;
}
// Maximize the snapped window which will exit the split view session. This is
// used in preparation for the next round of testing.
void MaximizeToClearTheSession(aura::Window* window) {
WindowState* window_state = WindowState::Get(window);
window_state->Maximize();
SplitViewOverviewSession* split_view_overview_session =
RootWindowController::ForWindow(window)->split_view_overview_session();
EXPECT_FALSE(split_view_overview_session);
}
// Drag the given group `item` to the `screen_location`. This is added before
// the event handling of the middle seam is done.
void DragGroupItemToPoint(OverviewItemBase* item,
const gfx::Point& screen_location,
ui::test::EventGenerator* event_generator,
bool by_touch_gestures,
bool drop) {
DCHECK(item);
gfx::Point location =
gfx::ToRoundedPoint(item->target_bounds().CenterPoint());
// TODO(michelefan): Use the center point of the `overview_item` after
// implementing or defining the event handling for the middle seam area.
location.Offset(/*delta_x=*/5, /*delta_y=*/5);
event_generator->set_current_screen_location(location);
if (by_touch_gestures) {
event_generator->PressTouch();
event_generator->MoveTouchBy(50, 0);
event_generator->MoveTouch(screen_location);
if (drop) {
event_generator->ReleaseTouch();
}
} else {
event_generator->PressLeftButton();
Shell::Get()->cursor_manager()->SetDisplay(
display::Screen::GetScreen()->GetDisplayNearestPoint(screen_location));
event_generator->MoveMouseTo(screen_location);
if (drop) {
event_generator->ReleaseLeftButton();
}
}
}
// Returns true if the union bounds of `w1`, `w2` and the split view
// divider (if exists) equal to the bounds of the work area and false
// otherwise.
bool UnionBoundsEqualToWorkAreaBounds(aura::Window* w1, aura::Window* w2) {
gfx::Rect union_bounds;
union_bounds.Union(w1->GetBoundsInScreen());
union_bounds.Union(w2->GetBoundsInScreen());
const auto divider_bounds = split_view_divider()
? split_view_divider_bounds_in_screen()
: gfx::Rect();
union_bounds.Union(divider_bounds);
return union_bounds == work_area_bounds();
}
} // namespace
// -----------------------------------------------------------------------------
// FasterSplitScreenTest:
// Test fixture to verify faster split screen feature.
class FasterSplitScreenTest : public AshTestBase {
public:
FasterSplitScreenTest()
: scoped_feature_list_(features::kFasterSplitScreenSetup) {}
FasterSplitScreenTest(const FasterSplitScreenTest&) = delete;
FasterSplitScreenTest& operator=(const FasterSplitScreenTest&) = delete;
~FasterSplitScreenTest() override = default;
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
WindowCycleList::SetDisableInitialDelayForTesting(true);
}
protected:
base::HistogramTester histogram_tester_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that if the user disables the pref for snap window suggestions, we
// don't start partial overview.
TEST_F(FasterSplitScreenTest, DisableSnapWindowSuggestionsPref) {
PrefService* pref =
Shell::Get()->session_controller()->GetActivePrefService();
pref->SetBoolean(prefs::kSnapWindowSuggestions, false);
ASSERT_FALSE(pref->GetBoolean(prefs::kSnapWindowSuggestions));
// Snap a window. Test we don't start overview.
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, Basic) {
// Create two test windows, snap `w1`. Test `w1` is snapped and excluded from
// overview while `w2` is in overview.
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
auto* overview_controller = Shell::Get()->overview_controller();
EXPECT_TRUE(
overview_controller->overview_session()->IsWindowInOverview(w2.get()));
// Select `w2` from overview. Test `w2` auto snaps.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(GetOverviewItemForWindow(w2.get())
->GetTransformedBounds()
.CenterPoint()));
event_generator->ClickLeftButton();
WaitForOverviewExitAnimation();
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
EXPECT_FALSE(overview_controller->InOverviewSession());
// Create a new `w3` and snap it to the left. Test it doesn't start overview.
std::unique_ptr<aura::Window> w3(CreateTestWindow());
SnapOneTestWindow(w3.get(), chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(overview_controller->InOverviewSession());
// Create a new `w4` and snap it to the right. Test it doesn't start overview.
std::unique_ptr<aura::Window> w4(CreateTestWindow());
SnapOneTestWindow(w4.get(), chromeos::WindowStateType::kSecondarySnapped);
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w4.get())->GetStateType());
// Test all the other window states remain the same.
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
WindowState::Get(w1.get())->GetStateType());
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
WindowState::Get(w3.get())->GetStateType());
// Enter overview normally. Test no widget.
ToggleOverview();
EXPECT_FALSE(
GetOverviewGridForRoot(w1->GetRootWindow())->no_windows_widget());
}
TEST_F(FasterSplitScreenTest, CycleSnap) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
auto* window_state = WindowState::Get(w1.get());
// Cycle snap to the left.
const WindowSnapWMEvent cycle_snap_primary(WM_EVENT_CYCLE_SNAP_PRIMARY);
window_state->OnWMEvent(&cycle_snap_primary);
auto* overview_controller = Shell::Get()->overview_controller();
EXPECT_FALSE(overview_controller->InOverviewSession());
// Cycle snap to the right.
const WindowSnapWMEvent cycle_snap_secondary(WM_EVENT_CYCLE_SNAP_SECONDARY);
window_state->OnWMEvent(&cycle_snap_secondary);
EXPECT_FALSE(overview_controller->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, EndSplitViewOverviewSession) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kSecondarySnapped);
VerifySplitViewOverviewSession(w1.get());
// Drag `w1` out of split view. Test it ends overview.
const gfx::Rect window_bounds(w1->GetBoundsInScreen());
const gfx::Point drag_point(window_bounds.CenterPoint().x(),
window_bounds.y() + 10);
auto* event_generator = GetEventGenerator();
event_generator->set_current_screen_location(drag_point);
event_generator->DragMouseBy(10, 10);
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
// Snap then minimize the window. Test it ends overview.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
WMEvent minimize_event(WM_EVENT_MINIMIZE);
WindowState::Get(w1.get())->OnWMEvent(&minimize_event);
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
// Snap then close the window. Test it ends overview.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
w1.reset();
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, ResizeSplitViewOverviewAndWindow) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
const gfx::Rect initial_bounds(w1->GetBoundsInScreen());
// Drag the right edge of the window to resize the window and overview at the
// same time. Test that the bounds are updated.
const gfx::Point start_point(w1->GetBoundsInScreen().right_center());
auto* generator = GetEventGenerator();
generator->set_current_screen_location(start_point);
// Resize to less than 1/3. Test we don't end overview.
int x = 200;
ASSERT_LT(x, work_area_bounds().width() * chromeos::kOneThirdSnapRatio);
generator->DragMouseTo(gfx::Point(x, start_point.y()));
gfx::Rect expected_window_bounds(initial_bounds);
expected_window_bounds.set_width(x);
EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
VerifySplitViewOverviewSession(w1.get());
// Resize to greater than 2/3. Test we don't end overview.
x = 600;
ASSERT_GT(x, work_area_bounds().width() * chromeos::kTwoThirdSnapRatio);
generator->DragMouseTo(gfx::Point(x, start_point.y()));
expected_window_bounds.set_width(x);
EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
VerifySplitViewOverviewSession(w1.get());
}
TEST_F(FasterSplitScreenTest, ResizeAndAutoSnap) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
const gfx::Rect initial_bounds(w1->GetBoundsInScreen());
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
auto* generator = GetEventGenerator();
generator->set_current_screen_location(
w1->GetBoundsInScreen().right_center());
const int drag_x = 100;
generator->DragMouseBy(drag_x, 0);
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
gfx::Rect expected_window_bounds(initial_bounds);
expected_window_bounds.set_width(initial_bounds.width() + drag_x);
EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
gfx::Rect expected_grid_bounds(work_area_bounds());
expected_grid_bounds.Subtract(w1->GetBoundsInScreen());
EXPECT_EQ(expected_grid_bounds, GetOverviewGridBounds());
// Select a window to auto snap. Test it snaps to the correct ratio.
std::unique_ptr<aura::Window> w2(CreateTestWindow());
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
EXPECT_EQ(expected_grid_bounds, w2->GetBoundsInScreen());
}
TEST_F(FasterSplitScreenTest, DragToPartialOverview) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
ToggleOverview();
OverviewSession* overview_session =
OverviewController::Get()->overview_session();
ASSERT_TRUE(overview_session);
EXPECT_TRUE(overview_session->IsWindowInOverview(w1.get()));
EXPECT_TRUE(overview_session->IsWindowInOverview(w2.get()));
// Drag `w1` to enter partial overview.
DragGroupItemToPoint(GetOverviewItemForWindow(w1.get()), gfx::Point(0, 0),
GetEventGenerator(),
/*by_touch_gestures=*/false, /*drop=*/true);
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
WindowState::Get(w1.get())->GetStateType());
VerifySplitViewOverviewSession(w1.get(), /*faster_split_screen_setup=*/false);
EXPECT_TRUE(overview_session->IsWindowInOverview(w2.get()));
// Select `w2`. Test it snaps and we end overview.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(GetOverviewItemForWindow(w2.get())
->GetTransformedBounds()
.CenterPoint()));
event_generator->ClickLeftButton();
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
WindowState::Get(w1.get())->GetStateType());
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, SkipPairingInOverviewOnMouseEvent) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
ASSERT_EQ(1u, GetOverviewSession()->grid_list().size());
auto* w2_overview_item = GetOverviewItemForWindow(w2.get());
EXPECT_TRUE(w2_overview_item);
const gfx::Point outside_point =
gfx::ToRoundedPoint(
w2_overview_item->GetTransformedBounds().bottom_right()) +
gfx::Vector2d(20, 20);
// Verify that clicking on an empty area in overview will exit the paring.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(outside_point);
event_generator->ClickLeftButton();
OverviewController* overview_controller = OverviewController::Get();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
// Snap `w1`. Test that clicking on `w1` again exits overview.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
// Moving the mouse around won't end overview.
event_generator->MoveMouseTo(w1->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(overview_controller->InOverviewSession());
// Clicking on `w1` again exits overview.
event_generator->ClickLeftButton();
EXPECT_FALSE(overview_controller->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, SkipPairingInOverviewOnKeyEvent) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
ASSERT_EQ(1u, GetOverviewSession()->grid_list().size());
// Test that Esc key exits overview.
PressAndReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
// Test that Alt + Tab exits overview.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
EXPECT_TRUE(Shell::Get()->window_cycle_controller()->IsCycling());
}
TEST_F(FasterSplitScreenTest, DontStartPartialOverviewAfterSkippingPairing) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
// Press Esc key to skip pairing.
PressAndReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_FALSE(overview_controller->InOverviewSession());
// Snap `w2`. Since `w1` is snapped to primary, it doesn't start partial
// overview. wm::ActivateWindow(w2.get());
SnapOneTestWindow(w2.get(), chromeos::WindowStateType::kSecondarySnapped);
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
EXPECT_EQ(WindowState::Get(w2.get())->GetStateType(),
chromeos::WindowStateType::kSecondarySnapped);
}
TEST_F(FasterSplitScreenTest, DontStartPartialOverviewAfterClosingWindow) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
// Select `w2` to auto-snap it.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(GetOverviewItemForWindow(w2.get())
->GetTransformedBounds()
.CenterPoint()));
event_generator->ClickLeftButton();
// Close `w2`, then open and snap a new `w3`. Test we don't start partial
// overview.
w2.reset();
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapOneTestWindow(w3.get(), chromeos::WindowStateType::kSecondarySnapped);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, StartPartialOverviewForMinimizedWindow) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
// Now minimize `w1`, so visually there is no primary snapped window.
WindowState::Get(w1.get())->Minimize();
// Now snap `w2` to secondary. Since `w1` is minimized, it starts partial
// overview.
SnapOneTestWindow(w2.get(), chromeos::WindowStateType::kSecondarySnapped);
VerifySplitViewOverviewSession(w2.get());
}
// Tests that when activating an already snapped window, cannot snap toast will
// not show by mistake. See b/323391799 for details.
TEST_F(FasterSplitScreenTest,
DoNotShowCannotSnapToastWhenActivatingTheSnappedWindow) {
UpdateDisplay("800x600");
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
WindowSnapActionSource::kKeyboardShortcutToSnap);
ASSERT_TRUE(WindowState::Get(w1.get())->IsSnapped());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapOneTestWindow(w2.get(), chromeos::WindowStateType::kSecondarySnapped,
WindowSnapActionSource::kDragWindowToEdgeToSnap);
EXPECT_FALSE(IsInOverviewSession());
wm::ActivateWindow(w1.get());
EXPECT_FALSE(ToastManager::Get()->IsToastShown(kAppCannotSnapToastId));
}
TEST_F(FasterSplitScreenTest, DontStartPartialOverviewForFloatedWindow) {
// Snap 2 test windows in place.
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
// To simulate the CUJ when a user selects a window from overview, activate
// and snap `w2`.
wm::ActivateWindow(w2.get());
SnapOneTestWindow(w2.get(), chromeos::WindowStateType::kSecondarySnapped);
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
// Create a 3rd floated window on top of `w2`.
std::unique_ptr<aura::Window> floated_window = CreateAppWindow();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
EXPECT_TRUE(
w2->GetBoundsInScreen().Contains(floated_window->GetBoundsInScreen()));
// Open a 4th window and snap it on top of `w1`. Test we don't start partial
// overview.
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapOneTestWindow(w3.get(), chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
}
TEST_F(FasterSplitScreenTest, MultiDisplay) {
UpdateDisplay("800x600,1000x600");
display::test::DisplayManagerTestApi display_manager_test(display_manager());
// Snap `window` on the second display. Test its bounds are updated.
std::unique_ptr<aura::Window> window(
CreateTestWindowInShellWithBounds(gfx::Rect(900, 0, 100, 100)));
SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped);
ASSERT_EQ(
display_manager_test.GetSecondaryDisplay().id(),
display::Screen::GetScreen()->GetDisplayNearestWindow(window.get()).id());
const gfx::Rect work_area(
display_manager_test.GetSecondaryDisplay().work_area());
EXPECT_EQ(gfx::Rect(800, 0, work_area.width() / 2, work_area.height()),
window->GetBoundsInScreen());
VerifySplitViewOverviewSession(window.get());
// Disconnect the second display. Test no crash.
UpdateDisplay("800x600");
base::RunLoop().RunUntilIdle();
}
// Verifies that there will be no crash when transitioning the
// `SplitViewOverviewSession` between clamshell and tablet mode.
TEST_F(FasterSplitScreenTest, ClamshellTabletTransitionOneSnappedWindow) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
SwitchToTabletMode();
EXPECT_TRUE(split_view_divider());
auto observed_windows = split_view_divider()->observed_windows();
EXPECT_EQ(1u, observed_windows.size());
EXPECT_EQ(w1.get(), observed_windows.front());
TabletModeControllerTestApi().LeaveTabletMode();
}
TEST_F(FasterSplitScreenTest, ClamshellTabletTransitionTwoSnappedWindows) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
// Select the second window from overview to snap it.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(GetOverviewItemForWindow(w2.get())
->GetTransformedBounds()
.CenterPoint()));
event_generator->ClickLeftButton();
EXPECT_FALSE(split_view_divider());
SwitchToTabletMode();
EXPECT_TRUE(split_view_divider());
auto observed_windows = split_view_divider()->observed_windows();
EXPECT_EQ(2u, observed_windows.size());
// TODO(b/312229933): Determine whether the order of `observed_windows_`
// matters.
EXPECT_TRUE(UnionBoundsEqualToWorkAreaBounds(w1.get(), w2.get()));
TabletModeControllerTestApi().LeaveTabletMode();
}
// Tests that double tap to swap windows doesn't crash after transition to
// tablet mode (b/308216746).
TEST_F(FasterSplitScreenTest, NoCrashWhenDoubleTapAfterTransition) {
// Use non-zero to start an animation, which will notify
// `SplitViewOverviewSession::OnWindowBoundsChanged()`.
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
SwitchToTabletMode();
EXPECT_TRUE(split_view_divider());
// Double tap on the divider. This will start a drag and notify
// SplitViewOverviewSession.
const gfx::Point divider_center =
split_view_divider()
->GetDividerBoundsInScreen(/*is_dragging=*/false)
.CenterPoint();
GetEventGenerator()->GestureTapAt(divider_center);
GetEventGenerator()->GestureTapAt(divider_center);
}
// Tests the histograms for the split view overview session exit points are
// recorded correctly in clamshell.
TEST_F(FasterSplitScreenTest,
SplitViewOverviewSessionExitPointClamshellHistograms) {
const auto kWindowLayoutCompleteOnSessionExit =
BuildWindowLayoutCompleteOnSessionExitHistogram();
const auto kSplitViewOverviewSessionExitPoint =
BuildSplitViewOverviewExitPointHistogramName(
WindowSnapActionSource::kDragWindowToEdgeToSnap);
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
// Verify the initial count for the histogram.
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/0);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/0);
// Set up the splitview overview session and select a window in the partial
// overview to complete the window layout.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
WindowSnapActionSource::kDragWindowToEdgeToSnap);
VerifySplitViewOverviewSession(w1.get());
auto* item2 = GetOverviewItemForWindow(w2.get());
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
event_generator->ClickLeftButton();
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/1);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/0);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w2.get());
// Set up the splitview overview session and click an empty area to skip the
// pairing.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
WindowSnapActionSource::kDragWindowToEdgeToSnap);
VerifySplitViewOverviewSession(w1.get());
item2 = GetOverviewItemForWindow(w2.get());
gfx::Point outside_point =
gfx::ToRoundedPoint(item2->target_bounds().bottom_right());
outside_point.Offset(/*delta_x=*/5, /*delta_y=*/5);
event_generator->MoveMouseTo(outside_point);
event_generator->ClickLeftButton();
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/1);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/1);
histogram_tester_.ExpectBucketCount(kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kSkip,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w2.get());
// Set up the splitview overview session, create a 3rd window to be
// auto-snapped and complete the window layout.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
WindowSnapActionSource::kDragWindowToEdgeToSnap);
VerifySplitViewOverviewSession(w1.get());
std::unique_ptr<aura::Window> w3(CreateAppWindow());
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/2);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/1);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/2);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w3.get());
// Set up the splitview overview session and press escape key to skip pairing.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
WindowSnapActionSource::kDragWindowToEdgeToSnap);
VerifySplitViewOverviewSession(w1.get());
event_generator->PressAndReleaseKey(ui::VKEY_ESCAPE);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/2);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/2);
histogram_tester_.ExpectBucketCount(kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kSkip,
/*expected_count=*/2);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w2.get());
// Set up the splitview overview session and close the snapped window to exit
// the session.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped,
WindowSnapActionSource::kDragWindowToEdgeToSnap);
VerifySplitViewOverviewSession(w1.get());
w1.reset();
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/2);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/2);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kWindowDestroy,
/*expected_count=*/1);
}
// Tests the histograms for the split view overview session exit points are
// recorded correctly in tablet mode.
// SplitViewOverviewSession should not support tablet mode.
// TODO(sophiewen): Re-enable or delete this.
TEST_F(FasterSplitScreenTest,
DISABLED_SplitViewOverviewSessionExitPointTabletHistograms) {
UpdateDisplay("800x600");
SwitchToTabletMode();
EXPECT_TRUE(Shell::Get()->IsInTabletMode());
const auto kWindowLayoutCompleteOnSessionExit =
BuildWindowLayoutCompleteOnSessionExitHistogram();
const auto kSplitViewOverviewSessionExitPoint =
BuildSplitViewOverviewExitPointHistogramName(
WindowSnapActionSource::kNotSpecified);
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
// Verify the initial count for the histogram.
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/0);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/0);
// Set up the splitview overview session and select a window in the partial
// overview to complete the window layout.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
auto* item2 = GetOverviewItemForWindow(w2.get());
auto* event_generator = GetEventGenerator();
event_generator->PressTouch(
gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
event_generator->ReleaseTouch();
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/1);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/0);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w2.get());
// Set up the splitview overview session, create a 3rd window to be
// auto-snapped and complete the window layout.
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
std::unique_ptr<aura::Window> w3(CreateAppWindow());
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/true,
/*expected_count=*/2);
histogram_tester_.ExpectBucketCount(kWindowLayoutCompleteOnSessionExit,
/*sample=*/false,
/*expected_count=*/0);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/2);
}
// Integration test of the `SplitViewOverviewSession` exit point with drag to
// snap action source. Verify that the end-to-end metric is recorded correctly.
TEST_F(FasterSplitScreenTest, KeyMetricsIntegrationTest_DragToSnap) {
UpdateDisplay("800x600");
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
const auto kSplitViewOverviewSessionExitPoint =
BuildSplitViewOverviewExitPointHistogramName(
WindowSnapActionSource::kDragWindowToEdgeToSnap);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/0);
// Drag a window to snap on the primary snapped position and verify the
// metrics.
std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
w1.get(), gfx::PointF(), HTCAPTION, wm::WINDOW_MOVE_SOURCE_MOUSE));
resizer->Drag(gfx::PointF(0, 400), /*event_flags=*/0);
resizer->CompleteDrag();
resizer.reset();
SplitViewOverviewSession* split_view_overview_session =
VerifySplitViewOverviewSession(w1.get());
EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
WindowSnapActionSource::kDragWindowToEdgeToSnap);
auto* event_generator = GetEventGenerator();
auto* item2 = GetOverviewItemForWindow(w2.get());
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
event_generator->ClickLeftButton();
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w2.get());
// Drag a window to snap on the secondary snapped position and verify the
// metrics.
resizer = CreateWindowResizer(w1.get(), gfx::PointF(), HTCAPTION,
wm::WINDOW_MOVE_SOURCE_MOUSE);
resizer->Drag(gfx::PointF(800, 0), /*event_flags=*/0);
resizer->CompleteDrag();
resizer.reset();
split_view_overview_session = VerifySplitViewOverviewSession(w1.get());
EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
WindowSnapActionSource::kDragWindowToEdgeToSnap);
item2 = GetOverviewItemForWindow(w2.get());
gfx::Point outside_point =
gfx::ToRoundedPoint(item2->target_bounds().bottom_right());
outside_point.Offset(/*delta_x=*/5, /*delta_y=*/5);
event_generator->MoveMouseTo(outside_point);
event_generator->ClickLeftButton();
histogram_tester_.ExpectBucketCount(kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kSkip,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
}
// Integration test of the `SplitViewOverviewSession` exit point with window
// size button as the snap action source. Verify that the end-to-end metric is
// recorded correctly.
TEST_F(FasterSplitScreenTest, KeyMetricsIntegrationTest_WindowSizeButton) {
UpdateDisplay("800x600");
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
struct SnapRequestWithActionSource {
chromeos::SnapController::SnapRequestSource request_source;
WindowSnapActionSource snap_action_source;
} kTestCases[]{
{chromeos::SnapController::SnapRequestSource::kWindowLayoutMenu,
WindowSnapActionSource::kSnapByWindowLayoutMenu},
{chromeos::SnapController::SnapRequestSource::kSnapButton,
WindowSnapActionSource::kLongPressCaptionButtonToSnap},
};
for (const auto test_case : kTestCases) {
const auto kSplitViewOverviewSessionExitPoint =
BuildSplitViewOverviewExitPointHistogramName(
test_case.snap_action_source);
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/0);
auto commit_snap = [&]() {
chromeos::SnapController::Get()->CommitSnap(
w1.get(), chromeos::SnapDirection::kSecondary,
chromeos::kDefaultSnapRatio, test_case.request_source);
SplitViewOverviewSession* split_view_overview_session =
VerifySplitViewOverviewSession(w1.get());
EXPECT_TRUE(split_view_overview_session);
EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
test_case.snap_action_source);
};
commit_snap();
auto* event_generator = GetEventGenerator();
auto* item2 = GetOverviewItemForWindow(w2.get());
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(item2->target_bounds().CenterPoint()));
event_generator->ClickLeftButton();
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kCompleteByActivating,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
MaximizeToClearTheSession(w2.get());
commit_snap();
item2 = GetOverviewItemForWindow(w2.get());
gfx::Point outside_point =
gfx::ToRoundedPoint(item2->target_bounds().bottom_right());
outside_point.Offset(/*delta_x=*/5, /*delta_y=*/5);
event_generator->MoveMouseTo(outside_point);
event_generator->ClickLeftButton();
histogram_tester_.ExpectBucketCount(
kSplitViewOverviewSessionExitPoint,
SplitViewOverviewSessionExitPoint::kSkip,
/*expected_count=*/1);
MaximizeToClearTheSession(w1.get());
}
}
// Tests that the `OverviewStartAction` will be recorded correctly in uma for
// the faster split screen setup.
TEST_F(FasterSplitScreenTest, OverviewStartActionHistogramTest) {
constexpr char kOverviewStartActionHistogram[] = "Ash.Overview.StartAction";
// Verify the initial count for the histogram.
histogram_tester_.ExpectBucketCount(
kOverviewStartActionHistogram,
OverviewStartAction::kFasterSplitScreenSetup,
/*expected_count=*/0);
std::unique_ptr<aura::Window> window(CreateAppWindow());
SnapOneTestWindow(window.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(window.get());
histogram_tester_.ExpectBucketCount(
kOverviewStartActionHistogram,
OverviewStartAction::kFasterSplitScreenSetup,
/*expected_count=*/1);
}
// -----------------------------------------------------------------------------
// SnapGroupTest:
// A test fixture to test the snap group feature.
class SnapGroupTest : public FasterSplitScreenTest {
public:
SnapGroupTest() {
scoped_feature_list_.InitWithFeatures(/*enabled_features=*/
{features::kSnapGroup,
features::kSameAppWindowCycle},
/*disabled_features=*/{});
}
SnapGroupTest(const SnapGroupTest&) = delete;
SnapGroupTest& operator=(const SnapGroupTest&) = delete;
~SnapGroupTest() override = default;
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
WindowCycleList::SetDisableInitialDelayForTesting(true);
}
void SnapOneTestWindow(aura::Window* window,
chromeos::WindowStateType state_type) {
UpdateDisplay("800x600");
WindowState* window_state = WindowState::Get(window);
const WindowSnapWMEvent snap_type(
state_type == chromeos::WindowStateType::kPrimarySnapped
? WM_EVENT_SNAP_PRIMARY
: WM_EVENT_SNAP_SECONDARY,
/*snap_action_source=*/WindowSnapActionSource::kTest);
window_state->OnWMEvent(&snap_type);
EXPECT_EQ(state_type, window_state->GetStateType());
}
void SnapTwoTestWindows(aura::Window* window1,
aura::Window* window2,
bool horizontal = true) {
CHECK_NE(window1, window2);
// Snap `window1` to trigger the overview session shown on the other side of
// the screen.
SnapOneTestWindow(
window1,
/*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
EXPECT_TRUE(split_view_controller()->InClamshellSplitViewMode());
EXPECT_EQ(split_view_controller()->state(),
SplitViewController::State::kPrimarySnapped);
EXPECT_EQ(split_view_controller()->primary_window(), window1);
WaitForOverviewEnterAnimation();
VerifySplitViewOverviewSession(window1);
// When the first window is snapped, it takes exactly half the width.
gfx::Rect expected_bounds(work_area_bounds());
gfx::Rect left_bounds, right_bounds;
expected_bounds.SplitVertically(&left_bounds, &right_bounds);
EXPECT_EQ(left_bounds, window1->GetBoundsInScreen());
// The `window2` gets selected in the overview will be snapped to the
// non-occupied snap position and the overview session will end.
auto* item2 = GetOverviewItemForWindow(window2);
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(item2->GetTransformedBounds().CenterPoint()));
event_generator->ClickLeftButton();
WaitForOverviewExitAnimation();
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
EXPECT_FALSE(RootWindowController::ForWindow(window1)
->split_view_overview_session());
auto* snap_group_controller = SnapGroupController::Get();
ASSERT_TRUE(snap_group_controller);
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(window1, window2));
// The split view divider will show on two windows snapped.
EXPECT_TRUE(split_view_divider());
EXPECT_EQ(0.5f, *WindowState::Get(window1)->snap_ratio());
EXPECT_EQ(0.5f, *WindowState::Get(window2)->snap_ratio());
// Now that two windows are snapped, the divider is between them.
gfx::Rect divider_bounds(
split_view_divider()->GetDividerBoundsInScreen(/*is_dragging=*/false));
left_bounds.set_width(left_bounds.width() - divider_bounds.width() / 2);
right_bounds.set_x(right_bounds.x() + divider_bounds.width() / 2);
right_bounds.set_width(right_bounds.width() - divider_bounds.width() / 2);
EXPECT_EQ(left_bounds, window1->GetBoundsInScreen());
EXPECT_EQ(right_bounds, window2->GetBoundsInScreen());
}
void CompleteWindowCycling() {
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
window_cycle_controller->CompleteCycling();
EXPECT_FALSE(window_cycle_controller->IsCycling());
}
void CycleWindow(WindowCyclingDirection direction, int steps) {
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
for (int i = 0; i < steps; i++) {
window_cycle_controller->HandleCycleWindow(direction);
EXPECT_TRUE(window_cycle_controller->IsCycling());
}
}
// TODO(michelefan): Consider put this test util in a base class or test file.
std::unique_ptr<aura::Window> CreateTestWindowWithAppID(
std::string app_id_key) {
std::unique_ptr<aura::Window> window = CreateTestWindow();
window->SetProperty(kAppIDKey, std::move(app_id_key));
return window;
}
std::unique_ptr<aura::Window> CreateTransientChildWindow(
gfx::Rect child_window_bounds,
aura::Window* transient_parent) {
auto child = CreateTestWindow(child_window_bounds);
wm::AddTransientChild(transient_parent, child.get());
return child;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests that the creation and removal of snap group.
TEST_F(SnapGroupTest, AddAndRemoveSnapGroupTest) {
auto* snap_group_controller = SnapGroupController::Get();
const auto& snap_groups = snap_group_controller->snap_groups_for_testing();
const auto& window_to_snap_group_map =
snap_group_controller->window_to_snap_group_map_for_testing();
EXPECT_EQ(snap_groups.size(), 0u);
EXPECT_EQ(window_to_snap_group_map.size(), 0u);
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
std::unique_ptr<aura::Window> w3(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
EXPECT_FALSE(snap_group_controller->AddSnapGroup(w1.get(), w3.get()));
EXPECT_EQ(snap_groups.size(), 1u);
EXPECT_EQ(window_to_snap_group_map.size(), 2u);
const auto iter1 = window_to_snap_group_map.find(w1.get());
ASSERT_TRUE(iter1 != window_to_snap_group_map.end());
const auto iter2 = window_to_snap_group_map.find(w2.get());
ASSERT_TRUE(iter2 != window_to_snap_group_map.end());
auto* snap_group = snap_groups.back().get();
EXPECT_EQ(iter1->second, snap_group);
EXPECT_EQ(iter2->second, snap_group);
ASSERT_TRUE(snap_group_controller->RemoveSnapGroup(snap_group));
ASSERT_TRUE(snap_groups.empty());
ASSERT_TRUE(window_to_snap_group_map.empty());
}
// Tests that the corresponding snap group will be removed when one of the
// windows in the snap group gets destroyed.
TEST_F(SnapGroupTest, WindowDestroyTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
SnapGroupController* snap_group_controller = SnapGroupController::Get();
ASSERT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
const auto& snap_groups = snap_group_controller->snap_groups_for_testing();
const auto& window_to_snap_group_map =
snap_group_controller->window_to_snap_group_map_for_testing();
EXPECT_EQ(snap_groups.size(), 1u);
EXPECT_EQ(window_to_snap_group_map.size(), 2u);
// Destroy one window in the snap group and the entire snap group will be
// removed.
w1.reset();
EXPECT_TRUE(snap_groups.empty());
EXPECT_TRUE(window_to_snap_group_map.empty());
}
// Tests that if one window in the snap group is actiaved, the stacking order of
// the other window in the snap group will be updated to be right below the
// activated window i.e. the two windows in the snap group will be placed on
// top.
TEST_F(SnapGroupTest, WindowStackingOrderTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
std::unique_ptr<aura::Window> w3(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
ASSERT_TRUE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
wm::ActivateWindow(w3.get());
// Actiave one of the windows in the snap group.
wm::ActivateWindow(w1.get());
MruWindowTracker::WindowList window_list =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
EXPECT_EQ(std::vector<vector_experimental_raw_ptr<aura::Window>>(
{w1.get(), w3.get(), w2.get()}),
window_list);
// `w3` is stacked below `w2` even though the activation order of `w3` is
// before `w2`.
EXPECT_TRUE(window_util::IsStackedBelow(w3.get(), w2.get()));
}
// Tests that on one window snapped in clamshell mode, the overview will be
// shown on the other side of the screen. When activating a window in overview,
// the window gets activated will be auto-snapped and the overview session will
// end. Close one window will end the split view mode.
TEST_F(SnapGroupTest, ClamshellSplitViewBasicFunctionalities) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
w1.reset();
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
}
// Tests that on one window snapped, `SnapGroupController` starts
// `SplitViewOverviewSession` (snap group creation session).
TEST_F(SnapGroupTest, SnapOneTestWindowStartsOverview) {
std::unique_ptr<aura::Window> w(CreateAppWindow());
// Snap `w` to the left. Test that we are in split view overview, excluding
// `w` and taking half the screen.
SnapOneTestWindow(w.get(),
/*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w.get());
// Snap `w` to the left again. Test we are still in split view overview.
SnapOneTestWindow(w.get(),
/*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w.get());
// Snap `w` to the right. Test we are still in split view overview.
SnapOneTestWindow(
w.get(),
/*state_type=*/chromeos::WindowStateType::kSecondarySnapped);
VerifySplitViewOverviewSession(w.get());
// Close `w`. Test that we end overview.
w.reset();
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
}
// Tests that when there is one snapped window and overview open, creating a new
// window, i.e. by clicking the shelf icon, will auto-snap it.
// TODO(michelefan): Re-enable this test after the divider refactor work is
// done.
TEST_F(SnapGroupTest, DISABLED_AutoSnapNewWindow) {
// Snap `w1` to start split view overview session.
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(),
/*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
// Create a new `w2`. Test it auto-snaps and forms a snap group with `w1`.
std::unique_ptr<aura::Window> w2(CreateAppWindow());
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
EXPECT_TRUE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
// Create a new `w3` and snap it on top of `w1` and `w2`'s group. Test it
// starts overview.
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapOneTestWindow(w3.get(),
/*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_TRUE(
RootWindowController::ForWindow(w3.get())->split_view_overview_session());
// TODO(b/296935443): Currently SplitViewController calculates the snap bounds
// based on `split_view_divider_`, which may be created for the snap group
// underneath `w3`'s split view overview session, so we won't verify
// overview is exactly the remaining work area of `w3` yet.
EXPECT_TRUE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
}
TEST_F(SnapGroupTest, DoubleTapDivider) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
auto* snap_group = SnapGroupController::Get()->GetTopmostSnapGroup();
EXPECT_TRUE(snap_group);
const auto* cached_primary_window = snap_group->window1();
const auto* cached_secondary_window = snap_group->window2();
// Test that double click on the divider swaps the windows.
const gfx::Point divider_center =
split_view_divider()
->GetDividerBoundsInScreen(/*is_dragging=*/false)
.CenterPoint();
GetEventGenerator()->set_current_screen_location(divider_center);
GetEventGenerator()->DoubleClickLeftButton();
auto* new_primary_window = snap_group->window1();
auto* new_secondary_window = snap_group->window2();
EXPECT_TRUE(SnapGroupController::Get()->AreWindowsInSnapGroup(
new_primary_window, new_secondary_window));
EXPECT_EQ(new_primary_window, cached_secondary_window);
EXPECT_EQ(new_secondary_window, cached_primary_window);
// Switch to tablet mode. Test that double tap on the divider swaps the
// windows.
SwitchToTabletMode();
EXPECT_EQ(new_primary_window, split_view_controller()->primary_window());
EXPECT_EQ(new_secondary_window, split_view_controller()->secondary_window());
GetEventGenerator()->GestureTapAt(divider_center);
GetEventGenerator()->GestureTapAt(divider_center);
EXPECT_EQ(new_secondary_window, split_view_controller()->primary_window());
EXPECT_EQ(new_primary_window, split_view_controller()->secondary_window());
}
TEST_F(SnapGroupTest, DontAutoSnapNewWindowOutsideSplitViewOverview) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
EXPECT_TRUE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
EXPECT_FALSE(
RootWindowController::ForWindow(w1.get())->split_view_overview_session());
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
// Open a third window. Test it does *not* snap.
std::unique_ptr<aura::Window> w3(CreateAppWindow());
EXPECT_FALSE(WindowState::Get(w3.get())->IsSnapped());
EXPECT_TRUE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
EXPECT_TRUE(split_view_divider());
}
// Tests that removing a display during split view overview session doesn't
// crash.
TEST_F(SnapGroupTest, RemoveDisplay) {
UpdateDisplay("800x600,800x600");
display::test::DisplayManagerTestApi display_manager_test(display_manager());
// Snap `window` on the second display to start split view overview session.
std::unique_ptr<aura::Window> window(
CreateTestWindowInShellWithBounds(gfx::Rect(900, 0, 100, 100)));
WindowState* window_state = WindowState::Get(window.get());
const WindowSnapWMEvent snap_type(
WM_EVENT_SNAP_PRIMARY,
/*snap_action_source=*/WindowSnapActionSource::kTest);
window_state->OnWMEvent(&snap_type);
ASSERT_EQ(
display_manager_test.GetSecondaryDisplay().id(),
display::Screen::GetScreen()->GetDisplayNearestWindow(window.get()).id());
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
window_state->GetStateType());
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_TRUE(RootWindowController::ForWindow(window.get())
->split_view_overview_session());
// Disconnect the second display. Test no crash.
UpdateDisplay("800x600");
base::RunLoop().RunUntilIdle();
}
// Tests the snap ratio is updated correctly when resizing the windows in a snap
// group with the split view divider.
TEST_F(SnapGroupTest, SnapRatioTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
const gfx::Point hover_location =
split_view_divider_bounds_in_screen().CenterPoint();
split_view_divider()->StartResizeWithDivider(hover_location);
const auto end_point =
hover_location + gfx::Vector2d(-work_area_bounds().width() / 6, 0);
split_view_divider()->ResizeWithDivider(end_point);
split_view_divider()->EndResizeWithDivider(end_point);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_NEAR(0.33f, WindowState::Get(w1.get())->snap_ratio().value(),
/*abs_error=*/0.1);
EXPECT_NEAR(0.67f, WindowState::Get(w2.get())->snap_ratio().value(),
/*abs_error=*/0.1);
}
// Tests that the windows in a snap group can be resized to an arbitrary
// location with the split view divider.
TEST_F(SnapGroupTest, ResizeWithSplitViewDividerToArbitraryLocations) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
for (const int distance_delta : {-10, 6, -15}) {
const auto w1_cached_bounds = w1.get()->GetBoundsInScreen();
const auto w2_cached_bounds = w2.get()->GetBoundsInScreen();
const gfx::Point hover_location =
split_view_divider_bounds_in_screen().CenterPoint();
split_view_divider()->StartResizeWithDivider(hover_location);
split_view_divider()->ResizeWithDivider(hover_location +
gfx::Vector2d(distance_delta, 0));
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
// TODO(michelefan): Consolidate the bounds update / calculation with the
// existence of divider between clamshell and tablet mode. Change
// `EXPECT_NEAR` back to `EXPECT_EQ`.
const int abs_error = kSplitviewDividerShortSideLength / 2;
EXPECT_NEAR(w1_cached_bounds.width() + distance_delta,
w1.get()->GetBoundsInScreen().width(), abs_error);
EXPECT_NEAR(w2_cached_bounds.width() - distance_delta,
w2.get()->GetBoundsInScreen().width(), abs_error);
EXPECT_NEAR(w1.get()->GetBoundsInScreen().width() +
w2.get()->GetBoundsInScreen().width() +
kSplitviewDividerShortSideLength,
work_area_bounds().width(), abs_error);
}
}
// Tests that when snapping a snapped window to the same snapped state, the
// overview session will not be triggered. The Overview session will be
// triggered when the snapped window is being snapped to the other snapped
// state.
TEST_F(SnapGroupTest, TwoWindowsSnappedTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
// Snap the primary window again as the primary window, the overview session
// won't be triggered.
SnapOneTestWindow(w1.get(),
/*state_type=*/chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
auto* snap_group_controller = SnapGroupController::Get();
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
// Snap the current primary window as the secondary window, the overview
// session will be triggered.
SnapOneTestWindow(
w1.get(),
/*state_type=*/chromeos::WindowStateType::kSecondarySnapped);
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
EXPECT_FALSE(
snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
// Select the other window in overview to form a snap group and exit overview.
auto* item2 = GetOverviewItemForWindow(w2.get());
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(
gfx::ToRoundedPoint(item2->GetTransformedBounds().CenterPoint()));
event_generator->ClickLeftButton();
WaitForOverviewExitAnimation();
}
// Tests that there is no crash when work area changed after snapping two
// windows. Docked mananifier is used as an example to trigger the work area
// change.
TEST_F(SnapGroupTest, WorkAreaChangeTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
auto* docked_mangnifier_controller =
Shell::Get()->docked_magnifier_controller();
docked_mangnifier_controller->SetEnabled(/*enabled=*/true);
}
// Tests that a snap group and the split view divider will be will be
// automatically created on two windows snapped in the clamshell mode. The snap
// group will be removed together with the split view divider on destroying of
// one window in the snap group.
TEST_F(SnapGroupTest, AutomaticallyCreateGroupOnTwoWindowsSnappedInClamshell) {
auto* snap_group_controller = SnapGroupController::Get();
ASSERT_TRUE(snap_group_controller);
const auto& snap_groups = snap_group_controller->snap_groups_for_testing();
const auto& window_to_snap_group_map =
snap_group_controller->window_to_snap_group_map_for_testing();
EXPECT_TRUE(snap_groups.empty());
EXPECT_TRUE(window_to_snap_group_map.empty());
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
EXPECT_EQ(snap_groups.size(), 1u);
EXPECT_EQ(window_to_snap_group_map.size(), 2u);
std::unique_ptr<aura::Window> w3(CreateTestWindow());
wm::ActivateWindow(w2.get());
EXPECT_TRUE(window_util::IsStackedBelow(w3.get(), w1.get()));
w1.reset();
EXPECT_FALSE(split_view_divider());
EXPECT_TRUE(snap_groups.empty());
EXPECT_TRUE(window_to_snap_group_map.empty());
}
// Tests that, after a window is snapped with overview on the other side,
// resizing overview works as expected.
// TODO(b/308170967): Combine this with FasterSplitScreenTest.
TEST_F(SnapGroupTest, ResizeSplitViewOverviewAndWindow) {
auto* snap_group_controller = SnapGroupController::Get();
// TODO(sophiewen): Make this the default for SnapGroupTest.
snap_group_controller->set_can_enter_overview_for_testing(
/*can_enter_overview=*/true);
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
VerifySplitViewOverviewSession(w1.get());
const gfx::Rect initial_bounds(w1->GetBoundsInScreen());
// Drag the right edge of the window to resize the window and overview at the
// same time. Test that the bounds are updated.
const gfx::Point start_point(w1->GetBoundsInScreen().right_center());
auto* generator = GetEventGenerator();
generator->set_current_screen_location(start_point);
// Resize to less than 1/3. Test we don't end overview.
int x = 200;
ASSERT_LT(x, work_area_bounds().width() * chromeos::kOneThirdSnapRatio);
generator->DragMouseTo(gfx::Point(x, start_point.y()));
gfx::Rect expected_window_bounds(initial_bounds);
expected_window_bounds.set_width(x);
EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
VerifySplitViewOverviewSession(w1.get());
// Resize to greater than 2/3. Test we don't end overview.
x = 600;
ASSERT_GT(x, work_area_bounds().width() * chromeos::kTwoThirdSnapRatio);
generator->DragMouseTo(gfx::Point(x, start_point.y()));
expected_window_bounds.set_width(x);
EXPECT_EQ(expected_window_bounds, w1->GetBoundsInScreen());
VerifySplitViewOverviewSession(w1.get());
}
// Tests that the split view divider will be stacked on top of both windows in
// the snap group and that on a third window activated the split view divider
// will be stacked below the newly activated window.
TEST_F(SnapGroupTest, DividerStackingOrderTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
wm::ActivateWindow(w1.get());
SplitViewDivider* divider = split_view_divider();
auto* divider_widget = divider->divider_widget();
aura::Window* divider_window = divider_widget->GetNativeWindow();
EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), w1.get()));
EXPECT_TRUE(window_util::IsStackedBelow(w1.get(), divider_window));
EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), divider_window));
std::unique_ptr<aura::Window> w3(
CreateTestWindow(gfx::Rect(100, 200, 300, 400)));
EXPECT_TRUE(window_util::IsStackedBelow(divider_window, w3.get()));
EXPECT_TRUE(window_util::IsStackedBelow(w1.get(), divider_window));
EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), w1.get()));
wm::ActivateWindow(w2.get());
EXPECT_TRUE(window_util::IsStackedBelow(w3.get(), w1.get()));
EXPECT_TRUE(window_util::IsStackedBelow(w1.get(), w2.get()));
EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), divider_window));
}
// Tests that divider will be closely tied to the windows in a snap group, which
// will also apply on transient window added.
TEST_F(SnapGroupTest, DividerStackingOrderWithTransientWindow) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
wm::ActivateWindow(w1.get());
SplitViewDivider* divider = split_view_divider();
ASSERT_TRUE(divider);
auto* divider_widget = divider->divider_widget();
aura::Window* divider_window = divider_widget->GetNativeWindow();
EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), w1.get()));
EXPECT_TRUE(window_util::IsStackedBelow(w1.get(), divider_window));
EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), divider_window));
auto w1_transient =
CreateTransientChildWindow(gfx::Rect(100, 200, 200, 200), w1.get());
w1_transient->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
wm::SetModalParent(w1_transient.get(), w1.get());
EXPECT_TRUE(window_util::IsStackedBelow(divider_window, w1_transient.get()));
}
// Tests the overall stacking order with two transient windows each of which
// belongs to a window in snap group is expected. The tests is to verify the
// transient windows issue showed in http://b/297448600#comment2.
TEST_F(SnapGroupTest, DividerStackingOrderWithTwoTransientWindows) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get());
SplitViewDivider* divider = split_view_divider();
ASSERT_TRUE(divider);
auto* divider_widget = divider->divider_widget();
aura::Window* divider_window = divider_widget->GetNativeWindow();
ASSERT_TRUE(window_util::IsStackedBelow(w1.get(), w2.get()));
ASSERT_TRUE(window_util::IsStackedBelow(w1.get(), divider_window));
ASSERT_TRUE(window_util::IsStackedBelow(w2.get(), divider_window));
// By default `w1_transient` is `MODAL_TYPE_NONE`, meaning that the associated
// `w1` interactable.
std::unique_ptr<aura::Window> w1_transient(
CreateTransientChildWindow(gfx::Rect(10, 20, 20, 30), w1.get()));
// Add transient window for `w2` and making it not interactable by setting it
// with the type of `ui::MODAL_TYPE_WINDOW`.
std::unique_ptr<aura::Window> w2_transient(
CreateTransientChildWindow(gfx::Rect(200, 20, 20, 30), w2.get()));
w2_transient->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
wm::SetModalParent(w2_transient.get(), w2.get());
// The expected stacking order is as follows:
// TOP
// `w2_transient` |
// | |
// divider |
// | |
// `w2` |
// | |
// `w1_transient` |
// | |
// `w1` |
// BOTTOM
EXPECT_TRUE(window_util::IsStackedBelow(divider_window, w2_transient.get()));
EXPECT_TRUE(
window_util::IsStackedBelow(w1_transient.get(), w2_transient.get()));
EXPECT_TRUE(window_util::IsStackedBelow(w1_transient.get(), divider_window));
}
// Tests that the union bounds of the primary window, secondary window in a snap
// group and the split view divider will be equal to the work area bounds both
// in horizontal and vertical split view mode.
TEST_F(SnapGroupTest, SplitViewDividerBoundsTest) {
for (const auto is_display_horizontal_layout : {true, false}) {
// Need to explicitly create two windows otherwise to snap a snapped window
// on the same position won't trigger the overview session.
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get(), is_display_horizontal_layout);
EXPECT_TRUE(UnionBoundsEqualToWorkAreaBounds(w1.get(), w2.get()));
}
}
TEST_F(SnapGroupTest, OverviewEnterExitBasic) {
UpdateDisplay("800x600");
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true);
// Verify that full overview session is expected when starting overview from
// accelerator and that split view divider will not be available.
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
WaitForOverviewEnterAnimation();
EXPECT_TRUE(overview_controller->overview_session());
EXPECT_EQ(GetOverviewGridBounds(), work_area_bounds());
EXPECT_FALSE(split_view_divider());
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
WindowState::Get(w1.get())->GetStateType());
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
// Verify that the snap group is restored with two windows snapped and that
// the split view divider becomes available on overview exit.
ToggleOverview();
EXPECT_FALSE(overview_controller->overview_session());
SnapGroupController* snap_group_controller = SnapGroupController::Get();
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
EXPECT_EQ(chromeos::WindowStateType::kPrimarySnapped,
WindowState::Get(w1.get())->GetStateType());
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(w2.get())->GetStateType());
EXPECT_TRUE(split_view_divider());
}
// Tests that partial overview is shown on the other side of the screen on one
// window snapped.
TEST_F(SnapGroupTest, PartialOverview) {
UpdateDisplay("800x600");
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
for (const auto& snap_state :
{chromeos::WindowStateType::kPrimarySnapped,
chromeos::WindowStateType::kSecondarySnapped}) {
SnapOneTestWindow(w1.get(), snap_state);
WaitForOverviewEnterAnimation();
EXPECT_TRUE(OverviewController::Get()->overview_session());
EXPECT_NE(GetOverviewGridBounds(), work_area_bounds());
EXPECT_NEAR(GetOverviewGridBounds().width(),
work_area_bounds().width() / 2.f,
kSplitviewDividerShortSideLength / 2.f);
}
}
// Tests that the group item will be created properly and that the snap group
// will be represented as one group item in overview.
TEST_F(SnapGroupTest, OverviewGroupItemCreationBasic) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
WaitForOverviewEnterAnimation();
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
EXPECT_EQ(overview_grid->window_list().size(), 2u);
}
// Tests that if one of the windows in a snap group gets destroyed in overview,
// the overview group item will only host the other window. If both of the
// windows get destroyed, the corresponding overview group item will be removed
// from the overview grid.
TEST_F(SnapGroupTest, WindowDestructionInOverview) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
WaitForOverviewEnterAnimation();
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
ASSERT_EQ(overview_grid->window_list().size(), 2u);
// On one window in snap group destroying, the group item will host the other
// window.
w2.reset();
ASSERT_TRUE(overview_grid);
EXPECT_EQ(overview_grid->window_list().size(), 2u);
// On the only remaining window in snap group destroying, the group item will
// be removed from the overview grid.
w1.reset();
ASSERT_TRUE(overview_grid);
EXPECT_EQ(overview_grid->window_list().size(), 1u);
}
// Tests that the rounded corners of the remaining item in the snap group on
// window destruction will be refreshed so that the exposed corners will be
// rounded corners.
TEST_F(SnapGroupTest, RefreshVisualsOnWindowDestructionInOverview) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& overview_items = overview_grid->window_list();
ASSERT_EQ(overview_items.size(), 2u);
w2.reset();
ASSERT_TRUE(overview_grid);
EXPECT_EQ(overview_grid->window_list().size(), 2u);
for (const auto& overview_item : overview_items) {
const gfx::RoundedCornersF rounded_corners =
overview_item->GetRoundedCorners();
EXPECT_NEAR(rounded_corners.upper_left(), kWindowMiniViewCornerRadius,
/*abs_error=*/0.01);
EXPECT_NEAR(rounded_corners.upper_right(), kWindowMiniViewCornerRadius,
/*abs_error=*/0.01);
EXPECT_NEAR(rounded_corners.lower_right(), kWindowMiniViewCornerRadius,
/*abs_error=*/0.01);
EXPECT_NEAR(rounded_corners.lower_left(), kWindowMiniViewCornerRadius,
/*abs_error=*/0.01);
}
}
// Tests that when one of the window in snap group gets destroyed in overview,
// the other window will restore its bounds properly when been activated to exit
// overview.
TEST_F(SnapGroupTest, RemainingWindowBoundsRestoreAfterDestructionInOverview) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
std::unique_ptr<aura::Window> w3(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
ASSERT_TRUE(split_view_divider());
const gfx::Size w1_size_before_overview = w1->GetBoundsInScreen().size();
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
ASSERT_TRUE(overview_controller->InOverviewSession());
EXPECT_FALSE(w1->transform().IsIdentity());
EXPECT_FALSE(w2->transform().IsIdentity());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
ASSERT_EQ(overview_grid->window_list().size(), 2u);
// On one window in snap group destroying, the group item will host the other
// window.
w2.reset();
ASSERT_TRUE(overview_grid);
EXPECT_EQ(overview_grid->window_list().size(), 2u);
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(gfx::ToRoundedPoint(
GetOverviewItemForWindow(w1.get())->target_bounds().CenterPoint()));
event_generator->ClickLeftButton();
EXPECT_FALSE(overview_controller->InOverviewSession());
const gfx::Size w1_size_after_overview = w1->GetBoundsInScreen().size();
// Verify that the size of `w1` on overview exit is equal to that of before
// entering overview plus `kSplitviewDividerShortSideLength / 2`.
EXPECT_EQ(
w1_size_before_overview.width() + kSplitviewDividerShortSideLength / 2,
w1_size_after_overview.width());
EXPECT_EQ(w1_size_before_overview.height(), w1_size_after_overview.height());
// Verify that the transform is identity.
EXPECT_TRUE(w1->transform().IsIdentity());
}
// Tests that the individual items within the same group will be hosted by the
// same overview group item.
TEST_F(SnapGroupTest, OverviewItemTest) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
OverviewSession* overview_session = overview_controller->overview_session();
ASSERT_TRUE(overview_session);
EXPECT_EQ(overview_session->GetOverviewItemForWindow(w1.get()),
overview_session->GetOverviewItemForWindow(w2.get()));
}
// Tests that the size of the `OverviewItem`s hosted by the `OverviewGroupItem`
// will correspond to the actual window layout.
TEST_F(SnapGroupTest, ReflectSnapRatioInOverviewGroupItem) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
ASSERT_TRUE(split_view_divider());
const gfx::Point hover_location =
split_view_divider_bounds_in_screen().CenterPoint();
split_view_divider()->StartResizeWithDivider(hover_location);
const gfx::Vector2d drag_delta(-work_area_bounds().width() / 6, 0);
const auto end_point = hover_location + drag_delta;
split_view_divider()->ResizeWithDivider(end_point);
split_view_divider()->EndResizeWithDivider(end_point);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_NEAR(0.33f, WindowState::Get(w1.get())->snap_ratio().value(),
/*abs_error=*/0.01);
EXPECT_NEAR(0.67f, WindowState::Get(w2.get())->snap_ratio().value(),
/*abs_error=*/0.01);
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
OverviewSession* overview_session = overview_controller->overview_session();
ASSERT_TRUE(overview_session);
OverviewGroupItem* overview_group_item =
static_cast<OverviewGroupItem*>(GetOverviewItemForWindow(w1.get()));
ASSERT_TRUE(overview_group_item);
const auto& overview_items =
overview_group_item->overview_items_for_testing();
ASSERT_EQ(overview_items.size(), 2u);
// Since `w1` is roughly half the width of `w2`, verify that `item1_bounds` is
// also half the width of `item2_bounds`.
const gfx::Rect item1_bounds =
overview_items[0]->item_widget()->GetWindowBoundsInScreen();
const gfx::Rect item2_bounds =
overview_items[1]->item_widget()->GetWindowBoundsInScreen();
const float size_ratio =
static_cast<float>(item1_bounds.width()) / item2_bounds.width();
EXPECT_NEAR(size_ratio, 0.5, /*abs_error=*/0.01);
}
// Tests the individual close functionality of the `OverviewGroupItem` by
// clicking on the close button of each overview item.
TEST_F(SnapGroupTest, CloseIndividualWindowByCloseButton) {
ScopedOverviewTransformWindow::SetImmediateCloseForTests(/*immediate=*/true);
std::unique_ptr<aura::Window> w0(CreateAppWindow());
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapTwoTestWindows(w0.get(), w1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
OverviewGroupItem* overview_group_item =
static_cast<OverviewGroupItem*>(GetOverviewItemForWindow(w0.get()));
ASSERT_TRUE(overview_group_item);
const auto& overview_items =
overview_group_item->overview_items_for_testing();
ASSERT_EQ(overview_items.size(), 2u);
// Since the window will be deleted in overview, release the ownership to
// avoid double deletion.
w0.release();
auto* event_generator = GetEventGenerator();
const CloseButton* w0_close_button =
overview_items[0]->overview_item_view()->close_button();
event_generator->MoveMouseTo(
w0_close_button->GetBoundsInScreen().CenterPoint());
event_generator->ClickLeftButton();
// Use the run loop so that to wait until the window is closed.
base::RunLoop().RunUntilIdle();
// Verify that only one item remains to be hosted by the group item.
ASSERT_EQ(overview_items.size(), 1u);
// Verify that the visuals of the remaining item will be refreshed with four
// rounded corners applied.
const gfx::RoundedCornersF rounded_corners =
GetOverviewItemForWindow(w1.get())->GetRoundedCorners();
EXPECT_NEAR(rounded_corners.upper_left(), kWindowMiniViewCornerRadius,
/*abs_error=*/1);
EXPECT_NEAR(rounded_corners.upper_right(), kWindowMiniViewCornerRadius,
/*abs_error=*/1);
EXPECT_NEAR(rounded_corners.lower_right(), kWindowMiniViewCornerRadius,
/*abs_error=*/1);
EXPECT_NEAR(rounded_corners.lower_left(), kWindowMiniViewCornerRadius,
/*abs_error=*/1);
}
// Tests that the overview group item will be closed when focused in overview
// with `Ctrl + W`.
// TODO(michelefan@): Re-purpose this test. Currently disabled due to product
// decision change.
TEST_F(SnapGroupTest, DISABLED_CtrlPlusWToCloseFocusedGroupInOverview) {
// Explicitly enable immediate close so that we can directly close the
// window(s) without waiting the delayed task to be completed in
// `ScopedOverviewTransformWindow::Close()`.
ScopedOverviewTransformWindow::SetImmediateCloseForTests(/*immediate=*/true);
std::unique_ptr<aura::Window> w0(CreateAppWindow());
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapTwoTestWindows(w0.get(), w1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
OverviewSession* overview_session = overview_controller->overview_session();
ASSERT_TRUE(GetOverviewItemForWindow(w0.get()));
SendKeyUntilOverviewItemIsFocused(ui::VKEY_TAB);
EXPECT_TRUE(overview_session->focus_cycler()->GetFocusedItem());
// Since the window will be deleted in overview, release the ownership to
// avoid double deletion.
w0.release();
w1.release();
SendKey(ui::VKEY_TAB, GetEventGenerator(), ui::EF_CONTROL_DOWN);
// Verify that both windows in the snap group will be deleted.
EXPECT_FALSE(w0.get());
EXPECT_FALSE(w1.get());
}
// Tests that the minimized windows in a snap group will be shown as a single
// group item in overview.
TEST_F(SnapGroupTest, MinimizedSnapGroupInOverview) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
SnapGroupController::Get()->MinimizeTopMostSnapGroup();
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
EXPECT_EQ(overview_grid->window_list().size(), 1u);
}
// Tests that the bounds on the overview group item as well as the individual
// overview item hosted by the group item will be set correctly.
TEST_F(SnapGroupTest, OverviewItemBoundsTest) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get());
ASSERT_TRUE(wm::IsActiveWindow(w2.get()));
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests);
OverviewSession* overview_session = overview_controller->overview_session();
ASSERT_TRUE(overview_session);
// The cumulative sum of the bounds while iterating through the individual
// items hosted by the overview item should always be inside the group item
// widget target bounds.
auto* overview_group_item =
overview_session->GetOverviewItemForWindow(w1.get());
const gfx::RectF& group_item_bounds = overview_group_item->target_bounds();
gfx::RectF cumulative_bounds;
for (aura::Window* window : overview_group_item->GetWindows()) {
auto* overview_item = overview_session->GetOverviewItemForWindow(window);
cumulative_bounds.Union(overview_item->target_bounds());
EXPECT_GT(cumulative_bounds.width(), 0u);
EXPECT_TRUE(group_item_bounds.Contains(cumulative_bounds));
}
}
// Tests the rounded corners will be applied to the exposed corners of the
// overview group item.
TEST_F(SnapGroupTest, OverviewGroupItemRoundedCorners) {
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
std::unique_ptr<aura::Window> window2 = CreateAppWindow(gfx::Rect(100, 100));
SnapTwoTestWindows(window0.get(), window1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 2u);
for (const auto& overview_item : window_list) {
EXPECT_EQ(overview_item->GetRoundedCorners(),
gfx::RoundedCornersF(kWindowMiniViewCornerRadius));
}
}
// Tests the rounded corners will be applied to the exposed corners of the
// overview group item if the corresponding snap group is minimized.
TEST_F(SnapGroupTest, MinimizedSnapGroupRoundedCornersInOverview) {
std::unique_ptr<aura::Window> w0(CreateAppWindow());
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow(gfx::Rect(100, 100)));
SnapTwoTestWindows(w0.get(), w1.get());
SnapGroupController::Get()->MinimizeTopMostSnapGroup();
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 2u);
for (const auto& overview_item : window_list) {
EXPECT_EQ(overview_item->GetRoundedCorners(),
gfx::RoundedCornersF(kWindowMiniViewCornerRadius));
}
}
// Tests that the shadow for the group item in overview will be applied on the
// group-level.
TEST_F(SnapGroupTest, OverviewGroupItemShadow) {
std::unique_ptr<aura::Window> w0(CreateAppWindow());
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow(gfx::Rect(100, 100)));
SnapTwoTestWindows(w0.get(), w1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 2u);
// Wait until the post task to `UpdateRoundedCornersAndShadow()` triggered in
// `OverviewController::DelayedUpdateRoundedCornersAndShadow()` is finished.
ShellTestApi().WaitForOverviewAnimationState(
OverviewAnimationState::kEnterAnimationComplete);
base::RunLoop().RunUntilIdle();
for (const auto& overview_item : window_list) {
const auto shadow_content_bounds =
overview_item->get_shadow_content_bounds_for_testing();
ASSERT_TRUE(!shadow_content_bounds.IsEmpty());
EXPECT_EQ(shadow_content_bounds.size(),
gfx::ToRoundedSize(overview_item->target_bounds().size()));
}
}
// Tests that when one of the windows in the snap group gets destroyed in
// overview the shadow contents bounds on the remaining item get updated
// correctly.
TEST_F(SnapGroupTest, CorrectShadowBoundsOnRemainingItemInOverview) {
std::unique_ptr<aura::Window> w0(CreateAppWindow());
std::unique_ptr<aura::Window> w1(CreateAppWindow());
SnapTwoTestWindows(w0.get(), w1.get());
OverviewController* overview_controller = Shell::Get()->overview_controller();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->overview_session());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 1u);
w0.reset();
EXPECT_EQ(window_list.size(), 1u);
// Verify that the shadow bounds will be refreshed to fit with the remaining
// item.
auto& overview_item = window_list[0];
const auto shadow_content_bounds =
overview_item->get_shadow_content_bounds_for_testing();
EXPECT_EQ(shadow_content_bounds.size(),
gfx::ToRoundedSize(overview_item->target_bounds().size()));
}
// Tests the basic functionality of focus cycling in overview through tabbing,
// the overview group item will be focused and activated as a group
TEST_F(SnapGroupTest, DISABLED_OverviewGroupItemFocusCycling) {
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
std::unique_ptr<aura::Window> window2 = CreateAppWindow(gfx::Rect(100, 100));
SnapTwoTestWindows(window0.get(), window1.get());
EXPECT_TRUE(window_util::IsStackedBelow(window0.get(), window1.get()));
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 2u);
// Overview items to be cycled:
// [window0, window1], window2
SendKeyUntilOverviewItemIsFocused(ui::VKEY_TAB);
auto* event_generator = GetEventGenerator();
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
SendKey(ui::VKEY_RETURN, event_generator);
EXPECT_FALSE(overview_controller->InOverviewSession());
MruWindowTracker* mru_window_tracker = Shell::Get()->mru_window_tracker();
aura::Window* top_most_window = window_util::GetTopMostWindow(
mru_window_tracker->BuildMruWindowList(DesksMruType::kActiveDesk));
EXPECT_EQ(top_most_window, window2.get());
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
EXPECT_TRUE(overview_controller->InOverviewSession());
// Overview items to be cycled:
// window2, [window0, window1]
SendKeyUntilOverviewItemIsFocused(ui::VKEY_TAB);
SendKey(ui::VKEY_TAB, event_generator, ui::EF_NONE, /*count=*/2);
SendKey(ui::VKEY_RETURN, event_generator);
EXPECT_FALSE(overview_controller->InOverviewSession());
top_most_window = window_util::GetTopMostWindow(
mru_window_tracker->BuildMruWindowList(DesksMruType::kActiveDesk));
EXPECT_EQ(top_most_window, window1.get());
}
// Tests the basic functionality of activating a group item in overview with
// mouse or touch. Overview will exit upon mouse/touch release and the overview
// item that directly handles the event will be activated.
TEST_F(SnapGroupTest, GroupItemActivation) {
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
SnapTwoTestWindows(window0.get(), window1.get());
// Pre-check that `window1` is the active window between the windows in the
// snap group.
ASSERT_TRUE(wm::IsActiveWindow(window1.get()));
std::unique_ptr<aura::Window> window2 = CreateAppWindow(gfx::Rect(100, 100));
ASSERT_TRUE(wm::IsActiveWindow(window2.get()));
struct {
bool use_touch;
gfx::Vector2d offset;
raw_ptr<aura::Window> expected_activated_window;
} kTestCases[]{
{false, gfx::Vector2d(-5, -5), window0.get()},
{true, gfx::Vector2d(-5, -5), window0.get()},
{false, gfx::Vector2d(5, 5), window1.get()},
{true, gfx::Vector2d(5, 5), window1.get()},
};
OverviewController* overview_controller = OverviewController::Get();
auto* event_generator = GetEventGenerator();
for (const auto& test : kTestCases) {
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 2u);
OverviewSession* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window0.get());
const auto hover_point =
gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint()) +
test.offset;
event_generator->set_current_screen_location(hover_point);
if (test.use_touch) {
event_generator->PressTouch();
event_generator->ReleaseTouch();
} else {
event_generator->ClickLeftButton();
}
EXPECT_FALSE(overview_controller->InOverviewSession());
// Verify that upon mouse/touch release, the snap group will be brought to
// the front with the expected activated.
EXPECT_TRUE(wm::IsActiveWindow(test.expected_activated_window));
}
}
// Tests the basic drag and drop functionality for overview group item with both
// mouse and touch events. The group item will be dropped to its original
// position before drag started.
TEST_F(SnapGroupTest, DragAndDropBasic) {
// Explicitly create another desk so that the virtual desk bar won't expand
// from zero-state to expanded-state when dragging starts.
auto* desks_controller = DesksController::Get();
desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
SnapTwoTestWindows(window0.get(), window1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 1u);
OverviewSession* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window0.get());
auto* event_generator = GetEventGenerator();
const auto target_bounds_before_dragging = overview_item->target_bounds();
for (const bool by_touch : {false, true}) {
DragGroupItemToPoint(
overview_item,
Shell::GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint(),
event_generator, by_touch, /*drop=*/false);
EXPECT_NE(overview_item->target_bounds(), target_bounds_before_dragging);
if (by_touch) {
event_generator->ReleaseTouch();
} else {
event_generator->ReleaseLeftButton();
}
EXPECT_TRUE(overview_controller->InOverviewSession());
// Verify that `overview_item` is dropped to its old position before
// dragging.
EXPECT_EQ(overview_item->target_bounds(), target_bounds_before_dragging);
}
}
// Tests that the bounds of the drop target for `OverviewGroupItem` will match
// that of the corresponding item which the drop target is a placeholder for.
TEST_F(SnapGroupTest, DropTargetBoundsForGroupItem) {
auto* desks_controller = DesksController::Get();
desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
SnapTwoTestWindows(window0.get(), window1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
aura::Window* primary_root_window = Shell::GetPrimaryRootWindow();
auto* overview_grid = GetOverviewGridForRoot(primary_root_window);
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 1u);
OverviewSession* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window0.get());
auto* event_generator = GetEventGenerator();
const gfx::RectF target_bounds_before_dragging =
overview_item->target_bounds();
for (const bool by_touch : {true}) {
DragGroupItemToPoint(
overview_item,
Shell::GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint(),
event_generator, by_touch, /*drop=*/false);
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* drop_target = overview_grid->drop_target();
EXPECT_TRUE(drop_target);
// Verify that the bounds of the `drop_target` will be the same as the
// `target_bounds_before_dragging`.
EXPECT_EQ(gfx::RectF(drop_target->item_widget()->GetWindowBoundsInScreen()),
target_bounds_before_dragging);
if (by_touch) {
event_generator->ReleaseTouch();
} else {
event_generator->ReleaseLeftButton();
}
}
}
// Tests the stacking order of the overview group item should be above other
// overview items while being dragged.
TEST_F(SnapGroupTest, StackingOrderWhileDraggingInOverview) {
auto* desks_controller = DesksController::Get();
desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> w0 = CreateAppWindow();
std::unique_ptr<aura::Window> w1 = CreateAppWindow();
std::unique_ptr<aura::Window> w2 = CreateAppWindow(gfx::Rect(100, 100));
SnapTwoTestWindows(w0.get(), w1.get());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kTests,
OverviewEnterExitType::kImmediateEnter);
ASSERT_TRUE(overview_controller->InOverviewSession());
const auto* overview_grid =
GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
ASSERT_TRUE(overview_grid);
const auto& window_list = overview_grid->window_list();
ASSERT_EQ(window_list.size(), 2u);
OverviewSession* overview_session = overview_controller->overview_session();
auto* group_item = overview_session->GetOverviewItemForWindow(w0.get());
auto* group_item_widget = group_item->item_widget();
auto* w2_item_pre_drag = GetOverviewItemForWindow(w2.get());
EXPECT_TRUE(window_util::IsStackedBelow(
w2_item_pre_drag->item_widget()->GetNativeWindow(),
group_item_widget->GetNativeWindow()));
// Initiate the first drag.
auto* event_generator = GetEventGenerator();
DragGroupItemToPoint(
group_item,
Shell::GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint(),
event_generator, /*by_touch_gestures=*/false, /*drop=*/false);
EXPECT_TRUE(overview_controller->InOverviewSession());
auto* w2_item_during_drag = GetOverviewItemForWindow(w2.get());
auto* w2_item_window_during_drag =
w2_item_during_drag->item_widget()->GetNativeWindow();
// Verify that the two windows together with the group item widget will be
// stacked above the other overview item.
EXPECT_TRUE(window_util::IsStackedBelow(
w2_item_window_during_drag, group_item_widget->GetNativeWindow()));
EXPECT_TRUE(
window_util::IsStackedBelow(w2_item_window_during_drag, w0.get()));
EXPECT_TRUE(
window_util::IsStackedBelow(w2_item_window_during_drag, w1.get()));
event_generator->ReleaseLeftButton();
// Verify that the group item can be dragged again after completing the first
// drag.
DragGroupItemToPoint(
group_item,
Shell::GetPrimaryRootWindow()->GetBoundsInScreen().CenterPoint(),
event_generator, /*by_touch_gestures=*/false, /*drop=*/true);
EXPECT_TRUE(overview_controller->InOverviewSession());
}
// Tests that `OverviewGroupItem` is not snappable in overview when there are
// two windows hosted by it however when one of the windows gets destroyed in
// overview, the remaining item becomes snappable.
TEST_F(SnapGroupTest, GroupItemSnapBehaviorInOverview) {
auto* desks_controller = DesksController::Get();
desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
SnapTwoTestWindows(window0.get(), window1.get());
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->window_list();
ASSERT_EQ(window_list.size(), 1u);
OverviewSession* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window0.get());
auto* event_generator = GetEventGenerator();
const auto target_bounds_before_dragging = overview_item->target_bounds();
const auto drag_point =
Shell::GetPrimaryRootWindow()->GetBoundsInScreen().left_center();
DragGroupItemToPoint(overview_item, drag_point, event_generator,
/*by_touch_gestures=*/false, /*drop=*/true);
DragGroupItemToPoint(overview_item, drag_point, event_generator,
/*by_touch_gestures=*/false, /*drop=*/true);
EXPECT_FALSE(overview_item->get_cannot_snap_widget_for_testing());
EXPECT_TRUE(overview_controller->InOverviewSession());
// Verify that `overview_item` is dropped to its old position before
// dragging.
EXPECT_EQ(overview_item->target_bounds(), target_bounds_before_dragging);
// Reset `window0` and verify that the remaining item becomes snappable.
window0.reset();
DragGroupItemToPoint(
overview_session->GetOverviewItemForWindow(window1.get()), drag_point,
event_generator, /*by_touch_gestures=*/false, /*drop=*/true);
EXPECT_EQ(WindowState::Get(window1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
}
// Tests that the two windows contained in the overview group item will be moved
// from the original desk to another desk on drag complete and that the two
// windows will still be in a snap group. The divider will show up in the
// destination desk on target desk activated.
TEST_F(SnapGroupTest, DragOverviewGroupItemToAnotherDesk) {
auto* desks_controller = DesksController::Get();
desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window0 = CreateAppWindow();
std::unique_ptr<aura::Window> window1 = CreateAppWindow();
SnapTwoTestWindows(window0.get(), window1.get());
OverviewController* overview_controller = Shell::Get()->overview_controller();
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->window_list();
ASSERT_EQ(window_list.size(), 1u);
const auto* desks_bar_view = overview_grid->desks_bar_view();
ASSERT_TRUE(desks_bar_view);
const auto& mini_views = desks_bar_view->mini_views();
ASSERT_EQ(mini_views.size(), 2u);
const Desk* desk0 = desks_controller->GetDeskAtIndex(0);
const Desk* desk1 = desks_controller->GetDeskAtIndex(1);
// Verify the initial conditions before dragging the item to another desk.
ASSERT_EQ(desks_util::GetDeskForContext(window0.get()), desk0);
ASSERT_EQ(desks_util::GetDeskForContext(window1.get()), desk0);
// Test that both windows contained in the overview group item will be moved
// to the another desk.
DragGroupItemToPoint(
overview_controller->overview_session()->GetOverviewItemForWindow(
window0.get()),
mini_views[1]->GetBoundsInScreen().CenterPoint(), GetEventGenerator(),
/*by_touch_gestures=*/false,
/*drop=*/true);
EXPECT_TRUE(overview_controller->InOverviewSession());
ASSERT_EQ(desks_util::GetDeskForContext(window0.get()), desk1);
ASSERT_EQ(desks_util::GetDeskForContext(window1.get()), desk1);
EXPECT_TRUE(SnapGroupController::Get()->AreWindowsInSnapGroup(window0.get(),
window1.get()));
ActivateDesk(desk1);
EXPECT_TRUE(split_view_divider());
EXPECT_EQ(desks_util::GetDeskForContext(
split_view_divider()->divider_widget()->GetNativeWindow()),
desk1);
}
// Tests that the hit area of the split view divider can be outside of its
// bounds with the extra insets whose value is `kSplitViewDividerExtraInset`.
TEST_F(SnapGroupTest, SplitViewDividerEnlargedHitArea) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true);
const gfx::Point cached_divider_center_point =
split_view_divider_bounds_in_screen().CenterPoint();
auto* event_generator = GetEventGenerator();
gfx::Point hover_location =
cached_divider_center_point -
gfx::Vector2d(kSplitviewDividerShortSideLength / 2 +
kSplitViewDividerExtraInset / 2,
0);
event_generator->MoveMouseTo(hover_location);
event_generator->PressLeftButton();
const auto move_vector = -gfx::Vector2d(50, 0);
event_generator->MoveMouseTo(hover_location + move_vector);
event_generator->ReleaseLeftButton();
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_EQ(split_view_divider_bounds_in_screen().CenterPoint(),
cached_divider_center_point + move_vector);
}
// Tests that by toggling the keyboard shortcut 'Search + Shift + G', the two
// snapped windows can be grouped or ungrouped.
TEST_F(SnapGroupTest, UseShortcutToGroupUnGroupWindows) {
std::unique_ptr<aura::Window> w1(CreateAppWindow());
std::unique_ptr<aura::Window> w2(CreateAppWindow());
SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true);
SnapGroupController* snap_group_controller = SnapGroupController::Get();
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
// Press the shortcut and the windows will be ungrouped.
auto* event_generator = GetEventGenerator();
event_generator->PressAndReleaseKey(ui::VKEY_G,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(
snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
// Press the shortcut again and the windows will be grouped.
event_generator->PressAndReleaseKey(ui::VKEY_G,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
EXPECT_TRUE(split_view_divider());
}
// Tests that the windows in snap group can be toggled between been minimized
// and restored with the keyboard shortcut 'Search + Shift + D', the windows
// will be remained in a snap group through these operations.
TEST_F(SnapGroupTest, UseShortcutToMinimizeWindows) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true);
SnapGroupController* snap_group_controller = SnapGroupController::Get();
// Press the shortcut first time and the windows will be minimized.
auto* event_generator = GetEventGenerator();
event_generator->PressAndReleaseKey(ui::VKEY_D,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(WindowState::Get(w1.get())->IsMinimized());
EXPECT_TRUE(WindowState::Get(w2.get())->IsMinimized());
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
// Press the shortcut again and the windows will be unminimized.
event_generator->PressAndReleaseKey(ui::VKEY_D,
ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(WindowState::Get(w1.get())->IsMinimized());
EXPECT_FALSE(WindowState::Get(w2.get())->IsMinimized());
EXPECT_TRUE(snap_group_controller->AreWindowsInSnapGroup(w1.get(), w2.get()));
EXPECT_TRUE(split_view_divider());
}
TEST_F(SnapGroupTest, SkipPairingInOverviewWhenClickingEmptyArea) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
WaitForOverviewEnterAnimation();
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
ASSERT_EQ(1u, GetOverviewSession()->grid_list().size());
auto* w2_overview_item = GetOverviewItemForWindow(w2.get());
EXPECT_TRUE(w2_overview_item);
const gfx::Point outside_point =
gfx::ToRoundedPoint(
w2_overview_item->GetTransformedBounds().bottom_right()) +
gfx::Vector2d(20, 20);
// Verify that clicking on an empty area in overview will exit the paring.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(outside_point);
event_generator->ClickLeftButton();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
}
TEST_F(SnapGroupTest, SkipPairingInOverviewWithEscapeKey) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(GetOverviewSession()->IsWindowInOverview(w2.get()));
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
ASSERT_EQ(1u, GetOverviewSession()->grid_list().size());
GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE);
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(WindowState::Get(w1.get())->GetStateType(),
chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(
SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w2.get()));
}
// Tests that when disallowing showing overview in clamshell with `kSnapGroup`
// enabled, the overview will not show on one window snapped. The overview will
// show when re-enabling showing overview.
TEST_F(SnapGroupTest, SnapWithoutShowingOverview) {
SnapGroupController* snap_group_controller = SnapGroupController::Get();
snap_group_controller->set_can_enter_overview_for_testing(
/*can_enter_overview=*/false);
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
std::unique_ptr<aura::Window> w3(CreateTestWindow());
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kPrimarySnapped);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
SnapOneTestWindow(w2.get(), chromeos::WindowStateType::kSecondarySnapped);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
w2.reset();
snap_group_controller->set_can_enter_overview_for_testing(
/*can_enter_overview=*/true);
SnapOneTestWindow(w1.get(), chromeos::WindowStateType::kSecondarySnapped);
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
}
// Tests that the window list is reordered when there is snap group. The two
// windows will be adjacent with each other with primary snapped window put
// before secondary snapped window.
TEST_F(SnapGroupTest, WindowReorderInAltTab) {
std::unique_ptr<aura::Window> window0(CreateTestWindowInShellWithId(0));
std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
SnapTwoTestWindows(window0.get(), window1.get());
wm::ActivateWindow(window2.get());
// Initial window activation order: window2, [window1, window0].
ASSERT_TRUE(wm::IsActiveWindow(window2.get()));
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/1);
const auto& windows =
window_cycle_controller->window_cycle_list()->windows_for_testing();
// Test that the two windows in a snap group are reordered to be adjacent
// with each other to reflect the window layout with the revised order as :
// window2, [window0, window1].
ASSERT_EQ(windows.size(), 3u);
EXPECT_EQ(windows.at(0), window2.get());
EXPECT_EQ(windows.at(1), window0.get());
EXPECT_EQ(windows.at(2), window1.get());
CompleteWindowCycling();
EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
// With the activation of `window1`, `window0` will be inserted right before
// `window1`.
// The new window cycle list order as: [window0, window1], window2. Cycle
// twice to focus on `window2`.
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/2);
CompleteWindowCycling();
EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
}
// Tests that the number of views to be cycled through inside the mirror
// container view of window cycle view will be the number of free-form windows
// plus snap groups.
TEST_F(SnapGroupTest, WindowCycleViewTest) {
std::unique_ptr<aura::Window> window0(CreateTestWindowInShellWithId(0));
std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
SnapTwoTestWindows(window0.get(), window1.get());
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/3);
const auto* window_cycle_list = window_cycle_controller->window_cycle_list();
const auto& windows = window_cycle_list->windows_for_testing();
EXPECT_EQ(windows.size(), 3u);
const WindowCycleView* cycle_view = window_cycle_list->cycle_view();
ASSERT_TRUE(cycle_view);
EXPECT_EQ(cycle_view->mirror_container_for_testing()->children().size(), 2u);
CompleteWindowCycling();
}
// Tests that on window that belongs to a snap group destroying while cycling
// the window list with Alt + Tab, there will be no crash. The corresponding
// child mini view hosted by the group container view will be destroyed, the
// group container view will host the other child mini view.
TEST_F(SnapGroupTest, WindowInSnapGroupDestructionInAltTab) {
std::unique_ptr<aura::Window> window0(CreateTestWindowInShellWithId(0));
std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2));
SnapTwoTestWindows(window0.get(), window1.get());
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/3);
const auto* window_cycle_list = window_cycle_controller->window_cycle_list();
const auto& windows = window_cycle_list->windows_for_testing();
EXPECT_EQ(windows.size(), 3u);
const WindowCycleView* cycle_view = window_cycle_list->cycle_view();
ASSERT_TRUE(cycle_view);
// Verify that the number of child views hosted by mirror container is two at
// the beginning.
EXPECT_EQ(cycle_view->mirror_container_for_testing()->children().size(), 2u);
// Destroy `window0` which belongs to a snap group.
window0.reset();
// Verify that we should still be cycling.
EXPECT_TRUE(window_cycle_controller->IsCycling());
const auto* updated_window_cycle_list =
window_cycle_controller->window_cycle_list();
const auto& updated_windows =
updated_window_cycle_list->windows_for_testing();
// Verify that the updated windows list size decreased.
EXPECT_EQ(updated_windows.size(), 2u);
// Verify that the number of child views hosted by mirror container will still
// be two.
EXPECT_EQ(cycle_view->mirror_container_for_testing()->children().size(), 2u);
}
// Tests and verifies the steps it takes to focus on a window cycle item by
// tabbing and reverse tabbing. The focused item will be activated upon
// completion of window cycling.
TEST_F(SnapGroupTest, SteppingInWindowCycleView) {
std::unique_ptr<aura::Window> window3 =
CreateAppWindow(gfx::Rect(300, 300), AppType::CHROME_APP);
std::unique_ptr<aura::Window> window2 =
CreateAppWindow(gfx::Rect(200, 200), AppType::CHROME_APP);
std::unique_ptr<aura::Window> window1 =
CreateAppWindow(gfx::Rect(100, 100), AppType::BROWSER);
std::unique_ptr<aura::Window> window0 =
CreateAppWindow(gfx::Rect(10, 10), AppType::BROWSER);
SnapTwoTestWindows(window0.get(), window1.get());
EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
WindowState::Get(window3.get())->Activate();
EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
// Window cycle list:
// window3, [window0, window1], window2
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/2);
CompleteWindowCycling();
EXPECT_TRUE(wm::IsActiveWindow(window1.get()));
// Window cycle list:
// [window0, window1], window3, window2
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/1);
CompleteWindowCycling();
EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
// Window cycle list:
// [window0, window1], window3, window2
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/3);
CompleteWindowCycling();
EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
// Window cycle list:
// window2, [window0, window1], window3
CycleWindow(WindowCyclingDirection::kBackward, /*steps=*/1);
CompleteWindowCycling();
EXPECT_TRUE(wm::IsActiveWindow(window3.get()));
}
// Tests that the exposed rounded corners of the cycling items are rounded
// corners. The visuals will be refreshed on window destruction that belongs to
// a snap group.
TEST_F(SnapGroupTest, WindowCycleItemRoundedCorners) {
std::unique_ptr<aura::Window> window0 =
CreateAppWindow(gfx::Rect(100, 200), AppType::BROWSER);
std::unique_ptr<aura::Window> window1 =
CreateAppWindow(gfx::Rect(200, 300), AppType::BROWSER);
std::unique_ptr<aura::Window> window2 =
CreateAppWindow(gfx::Rect(300, 400), AppType::BROWSER);
SnapTwoTestWindows(window0.get(), window1.get());
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
CycleWindow(WindowCyclingDirection::kForward, /*steps=*/3);
EXPECT_TRUE(window_cycle_controller->IsCycling());
const auto* window_cycle_list = window_cycle_controller->window_cycle_list();
const auto* cycle_view = window_cycle_list->cycle_view();
auto& cycle_item_views = cycle_view->cycle_views_for_testing();
ASSERT_EQ(cycle_item_views.size(), 2u);
for (ash::WindowMiniViewBase* cycle_item_view : cycle_item_views) {
EXPECT_EQ(cycle_item_view->GetRoundedCorners(),
gfx::RoundedCornersF(kWindowMiniViewCornerRadius));
}
// Destroy `window0` which belongs to a snap group while cycling.
window0.reset();
auto& new_cycle_item_views = cycle_view->cycle_views_for_testing();
EXPECT_EQ(new_cycle_item_views.size(), 2u);
// Verify that the visuals of the cycling items will be refreshed so that the
// exposed corners will be rounded corners.
for (ash::WindowMiniViewBase* cycle_item_view : new_cycle_item_views) {
EXPECT_EQ(cycle_item_view->GetRoundedCorners(),
gfx::RoundedCornersF(kWindowMiniViewCornerRadius));
}
CompleteWindowCycling();
}
// Tests that two windows in a snap group is allowed to be shown as group item
// view only if both of them belong to the same app as the mru window. If only
// one window belongs to the app, the representation of the window will be shown
// as the individual window cycle item view.
TEST_F(SnapGroupTest, SameAppWindowCycle) {
struct app_id_pair {
const char* trace_message;
const std::string app_id_2;
const std::string app_id_3;
const size_t windows_size;
const size_t cycle_views_count;
} kTestCases[]{
{/*trace_message=*/"Windows in snap group with same app id",
/*app_id_2=*/"A", /*app_id_3=*/"A", /*windows_size=*/4u,
/*cycle_views_count=*/3u},
{/*trace_message=*/"Windows in snap group with different app ids",
/*app_id_2=*/"A", /*app_id_3=*/"B", /*windows_size=*/3u,
/*cycle_views_count=*/3u},
};
std::unique_ptr<aura::Window> w0(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w1(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w2(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w3(CreateTestWindowWithAppID(std::string("A")));
SnapTwoTestWindows(w2.get(), w3.get());
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
for (const auto& test_case : kTestCases) {
w2->SetProperty(kAppIDKey, std::move(test_case.app_id_2));
w3->SetProperty(kAppIDKey, std::move(test_case.app_id_3));
wm::ActivateWindow(w2.get());
ASSERT_TRUE(wm::IsActiveWindow(w2.get()));
// Simulate pressing Alt + Backtick to trigger the same app cycling.
auto* event_generator = GetEventGenerator();
event_generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
event_generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
const auto* window_cycle_list =
window_cycle_controller->window_cycle_list();
ASSERT_TRUE(window_cycle_list->same_app_only());
// Verify the number of windows for the cycling.
const auto& windows = window_cycle_list->windows_for_testing();
EXPECT_EQ(windows.size(), test_case.windows_size);
EXPECT_TRUE(window_cycle_controller->IsCycling());
const auto* cycle_view = window_cycle_list->cycle_view();
ASSERT_TRUE(cycle_view);
// Verify the number of cycle views.
auto& cycle_item_views = cycle_view->cycle_views_for_testing();
EXPECT_EQ(cycle_item_views.size(), test_case.cycle_views_count);
event_generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
}
}
// Tests and verifies that if one of the window in a snap group gets destroyed
// while doing same app window cycling the corresponding window cycle item view
// will be properly removed and re-configured with no crash.
TEST_F(SnapGroupTest, WindowDestructionDuringSameAppWindowCycle) {
std::unique_ptr<aura::Window> w0(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w1(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w2(CreateTestWindowWithAppID(std::string("A")));
SnapTwoTestWindows(w0.get(), w1.get());
// Simulate pressing Alt + Backtick to trigger the same app cycling.
auto* event_generator = GetEventGenerator();
event_generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
event_generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
const auto* window_cycle_list = window_cycle_controller->window_cycle_list();
ASSERT_TRUE(window_cycle_list->same_app_only());
const auto* cycle_view = window_cycle_list->cycle_view();
ASSERT_TRUE(cycle_view);
const auto& windows = window_cycle_list->windows_for_testing();
EXPECT_EQ(windows.size(), 3u);
w0.reset();
// After the window destruction, the window cycle view is still available.
ASSERT_TRUE(cycle_view);
const auto& updated_windows = window_cycle_list->windows_for_testing();
EXPECT_EQ(updated_windows.size(), 2u);
CompleteWindowCycling();
}
// Tests that if a snap group is at the beginning of a window cycling list, the
// mru window will depend on the mru window between the two windows in the snap
// group, since the windows are reordered so that it reflects the actual window
// layout.
TEST_F(SnapGroupTest, MruWindowForSameApp) {
// Generate 5 windows with 3 of them from app A and 2 of them from app B.
std::unique_ptr<aura::Window> w0(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w1(CreateTestWindowWithAppID(std::string("B")));
std::unique_ptr<aura::Window> w2(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w3(CreateTestWindowWithAppID(std::string("A")));
std::unique_ptr<aura::Window> w4(CreateTestWindowWithAppID(std::string("B")));
SnapTwoTestWindows(w0.get(), w1.get());
// Specifically activate the secondary snapped window with app type B.
wm::ActivateWindow(w1.get());
// Simulate pressing Alt + Backtick to trigger the same app cycling.
auto* event_generator = GetEventGenerator();
event_generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
event_generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
WindowCycleController* window_cycle_controller =
Shell::Get()->window_cycle_controller();
const auto* window_cycle_list = window_cycle_controller->window_cycle_list();
ASSERT_TRUE(window_cycle_list->same_app_only());
const auto& windows = window_cycle_list->windows_for_testing();
// Verify that the windows in the list that are been cycled all belong to app
// B.
EXPECT_EQ(windows.size(), 2u);
CompleteWindowCycling();
}
// Tests that after creating a snap group in clamshell, transition to tablet
// mode won't crash (b/288179725).
TEST_F(SnapGroupTest, NoCrashWhenRemovingGroupInTabletMode) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true);
SwitchToTabletMode();
// Close w2. Test that the group is destroyed but we are still in split view.
w2.reset();
SnapGroupController* snap_group_controller =
Shell::Get()->snap_group_controller();
EXPECT_FALSE(snap_group_controller->GetSnapGroupForGivenWindow(w1.get()));
EXPECT_FALSE(snap_group_controller->GetSnapGroupForGivenWindow(w2.get()));
EXPECT_EQ(split_view_controller()->primary_window(), w1.get());
EXPECT_TRUE(OverviewController::Get()->InOverviewSession());
}
// Tests that one snap group in clamshell will be converted to windows in tablet
// split view. When converted back to clamshell, the snap group will be
// restored.
TEST_F(SnapGroupTest, ClamshellTabletTransitionWithOneSnapGroup) {
std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
SnapTwoTestWindows(window1.get(), window2.get(), /*horizontal=*/true);
EXPECT_TRUE(split_view_divider());
SwitchToTabletMode();
EXPECT_TRUE(split_view_divider());
auto observed_windows = split_view_divider()->observed_windows();
EXPECT_EQ(window1.get(), observed_windows.front());
EXPECT_EQ(window2.get(), observed_windows.back());
EXPECT_EQ(0.5f, *WindowState::Get(window1.get())->snap_ratio());
EXPECT_EQ(0.5f, *WindowState::Get(window2.get())->snap_ratio());
ExitTabletMode();
EXPECT_TRUE(SnapGroupController::Get()->AreWindowsInSnapGroup(window1.get(),
window2.get()));
// TODO(b/322576687): Consolidate the bounds update / calculation with the
// existence of divider between clamshell and tablet mode. Change
// `EXPECT_NEAR` back to `EXPECT_EQ`.
EXPECT_NEAR(0.5f, *WindowState::Get(window1.get())->snap_ratio(),
/*abs_error=*/0.01);
EXPECT_NEAR(0.5f, *WindowState::Get(window2.get())->snap_ratio(),
/*abs_error=*/0.01);
EXPECT_TRUE(split_view_divider());
}
// Tests that when converting to tablet mode with split view divider at an
// arbitrary location, the bounds of the two windows and the divider will be
// updated such that the snap ratio of the layout is one of the fixed snap
// ratios.
TEST_F(SnapGroupTest, ClamshellTabletTransitionGetClosestFixedRatio) {
UpdateDisplay("900x600");
std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
SnapTwoTestWindows(window1.get(), window2.get(), /*horizontal=*/true);
ASSERT_TRUE(split_view_divider());
EXPECT_EQ(*WindowState::Get(window1.get())->snap_ratio(),
chromeos::kDefaultSnapRatio);
// Build test cases to be used for divider dragging, with expected fixed ratio
// and corresponding pixels shown in the ASCII diagram below:
// ┌────────────────┬────────────────┐
// │ │ │
// │ │ │
// │ │ │
// │ │ │
// │ │ │
// │ │ │
// │ │ │
// └─────────┬──────┼───────┬────────┘
// │ │ │
// ratio: 1/3 1/2 2/3
// pixel: 300 450 600 900
struct {
int distance_delta;
float expected_snap_ratio;
} kTestCases[]{{/*distance_delta=*/-200, chromeos::kOneThirdSnapRatio},
{/*distance_delta=*/400, chromeos::kTwoThirdSnapRatio},
{/*distance_delta=*/-180, chromeos::kDefaultSnapRatio}};
auto* event_generator = GetEventGenerator();
const gfx::Rect work_area_bounds_in_screen =
screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
split_view_controller()->root_window()->GetChildById(
desks_util::GetActiveDeskContainerId()));
for (const auto test_case : kTestCases) {
event_generator->set_current_screen_location(
split_view_divider_bounds_in_screen().CenterPoint());
event_generator->DragMouseBy(test_case.distance_delta, 0);
split_view_divider()->EndResizeWithDivider(
event_generator->current_screen_location());
SwitchToTabletMode();
const auto current_divider_position =
split_view_divider()
->GetDividerBoundsInScreen(/*is_dragging=*/false)
.x();
// We need to take into consideration of the variation introduced by the
// divider shorter side length when calculating using snap ratio, i.e.
// `kSplitviewDividerShortSideLength / 2`.
const auto expected_divider_position = std::round(
work_area_bounds_in_screen.width() * test_case.expected_snap_ratio -
kSplitviewDividerShortSideLength / 2);
// Verifies that the bounds of the windows and divider are updated correctly
// such that snap ratio in the new window layout is expected.
EXPECT_NEAR(current_divider_position, expected_divider_position,
/*abs_error=*/1);
EXPECT_NEAR(float(window1->GetBoundsInScreen().width()) /
work_area_bounds_in_screen.width(),
test_case.expected_snap_ratio, /*abs_error=*/1);
ExitTabletMode();
}
}
// Tests that the cursor type gets updated to be resize cursor on mouse hovering
// on the split view divider.
TEST_F(SnapGroupTest, CursorUpdateTest) {
std::unique_ptr<aura::Window> w1(CreateTestWindow());
std::unique_ptr<aura::Window> w2(CreateTestWindow());
SnapTwoTestWindows(w1.get(), w2.get(), /*horizontal=*/true);
auto* divider = split_view_divider();
ASSERT_TRUE(divider);
auto divider_bounds = split_view_divider_bounds_in_screen();
auto outside_point = split_view_divider_bounds_in_screen().CenterPoint();
outside_point.Offset(-kSplitviewDividerShortSideLength * 5, 0);
EXPECT_FALSE(divider_bounds.Contains(outside_point));
auto* cursor_manager = Shell::Get()->cursor_manager();
cursor_manager->SetCursor(CursorType::kPointer);
// Test that the default cursor type when mouse is not hovered over the split
// view divider.
auto* event_generator = GetEventGenerator();
event_generator->MoveMouseTo(outside_point);
EXPECT_TRUE(cursor_manager->IsCursorVisible());
EXPECT_FALSE(cursor_manager->IsCursorLocked());
EXPECT_EQ(CursorType::kNull, cursor_manager->GetCursor().type());
// Test that the cursor changed to resize cursor while hovering over the split
// view divider.
const auto delta_vector = gfx::Vector2d(0, -10);
const gfx::Point cached_hover_point =
divider_bounds.CenterPoint() + delta_vector;
event_generator->MoveMouseTo(cached_hover_point);
EXPECT_EQ(CursorType::kColumnResize, cursor_manager->GetCursor().type());
// Test that after resizing, the cursor type is still the resize cursor.
event_generator->PressLeftButton();
const auto move_vector = gfx::Vector2d(20, 0);
event_generator->MoveMouseTo(cached_hover_point + move_vector);
event_generator->ReleaseLeftButton();
EXPECT_EQ(CursorType::kColumnResize, cursor_manager->GetCursor().type());
EXPECT_EQ(split_view_divider_bounds_in_screen().CenterPoint() + delta_vector,
cached_hover_point + move_vector);
}
// -----------------------------------------------------------------------------
// SnapGroupHistogramTest:
using SnapGroupHistogramTest = SnapGroupTest;
// Tests that the pipeline to get snap action source info all the way to be
// stored in the `SplitViewOverviewSession` is working. This test focuses on the
// snap action source with top-usage in clamshell.
TEST_F(SnapGroupHistogramTest, SnapActionSourcePipeline) {
UpdateDisplay("800x600");
std::unique_ptr<aura::Window> window(CreateAppWindow(gfx::Rect(100, 100)));
// Drag a window to snap and verify the snap action source info.
std::unique_ptr<WindowResizer> resizer(CreateWindowResizer(
window.get(), gfx::PointF(), HTCAPTION, wm::WINDOW_MOVE_SOURCE_MOUSE));
resizer->Drag(gfx::PointF(0, 400), /*event_flags=*/0);
resizer->CompleteDrag();
resizer.reset();
SplitViewOverviewSession* split_view_overview_session =
VerifySplitViewOverviewSession(window.get());
EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
WindowSnapActionSource::kDragWindowToEdgeToSnap);
MaximizeToClearTheSession(window.get());
// Mock snap from window layout menu and verify the snap action source info.
chromeos::SnapController::Get()->CommitSnap(
window.get(), chromeos::SnapDirection::kSecondary,
chromeos::kDefaultSnapRatio,
chromeos::SnapController::SnapRequestSource::kWindowLayoutMenu);
split_view_overview_session = VerifySplitViewOverviewSession(window.get());
EXPECT_TRUE(split_view_overview_session);
EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
WindowSnapActionSource::kSnapByWindowLayoutMenu);
MaximizeToClearTheSession(window.get());
// Mock snap from window snap button and verify the snap action source info.
chromeos::SnapController::Get()->CommitSnap(
window.get(), chromeos::SnapDirection::kPrimary,
chromeos::kDefaultSnapRatio,
chromeos::SnapController::SnapRequestSource::kSnapButton);
split_view_overview_session = VerifySplitViewOverviewSession(window.get());
EXPECT_TRUE(split_view_overview_session);
EXPECT_EQ(split_view_overview_session->snap_action_source_for_testing(),
WindowSnapActionSource::kLongPressCaptionButtonToSnap);
MaximizeToClearTheSession(window.get());
}
} // namespace ash