#include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/display/display_move_window_util.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/multitask_menu_nudge_delegate_ash.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
#include "ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.h"
#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/caption_buttons/frame_size_button.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
#include "chromeos/ui/frame/multitask_menu/multitask_button.h"
#include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
#include "chromeos/ui/wm/features.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
chromeos::MultitaskMenuNudgeController* GetNudgeControllerForWindow(
aura::Window* window) {
if (Shell::Get()->tablet_mode_controller()->InTabletMode()) {
return TabletModeControllerTestApi()
.tablet_mode_window_manager()
->tablet_mode_multitask_menu_event_handler()
->multitask_cue()
->nudge_controller();
}
if (auto* frame = NonClientFrameViewAsh::Get(window)) {
return chromeos::FrameCaptionButtonContainerView::TestApi(
frame->GetHeaderView()->caption_button_container())
.nudge_controller();
}
return nullptr;
}
}
class MultitaskMenuNudgeControllerTest : public AshTestBase {
public:
MultitaskMenuNudgeControllerTest()
: scoped_feature_list_(chromeos::wm::features::kWindowLayoutMenu) {}
MultitaskMenuNudgeControllerTest(const MultitaskMenuNudgeControllerTest&) =
delete;
MultitaskMenuNudgeControllerTest& operator=(
const MultitaskMenuNudgeControllerTest&) = delete;
~MultitaskMenuNudgeControllerTest() override = default;
views::Widget* GetNudgeWidgetForWindow(aura::Window* window) {
chromeos::MultitaskMenuNudgeController* controller =
GetNudgeControllerForWindow(window);
return controller ? controller->nudge_widget_.get() : nullptr;
}
void FireDismissNudgeTimer(aura::Window* window) {
if (chromeos::MultitaskMenuNudgeController* controller =
GetNudgeControllerForWindow(window)) {
controller->clamshell_nudge_dismiss_timer_.FireNow();
}
}
void SetUp() override {
AshTestBase::SetUp();
chromeos::MultitaskMenuNudgeController::SetSuppressNudgeForTesting(false);
chromeos::MultitaskMenuNudgeController::SetOverrideClockForTesting(
&test_clock_);
test_clock_.Advance(base::Hours(50));
}
void TearDown() override {
chromeos::MultitaskMenuNudgeController::SetOverrideClockForTesting(nullptr);
AshTestBase::TearDown();
}
protected:
void ExpectCorrectTabletNudgeBounds(aura::Window* window) {
const gfx::Size size =
GetNudgeWidgetForWindow(window)->GetContentsView()->GetPreferredSize();
const auto window_screen_bounds = window->GetBoundsInScreen();
const int tablet_nudge_y_offset =
MultitaskMenuNudgeDelegateAsh::kTabletNudgeAdditionalYOffset +
TabletModeMultitaskCue::kCueHeight +
TabletModeMultitaskCue::kCueYOffset;
const gfx::Rect expected_bounds(
(window_screen_bounds.width() - size.width()) / 2 +
window_screen_bounds.x(),
tablet_nudge_y_offset + window_screen_bounds.y(), size.width(),
size.height());
EXPECT_EQ(expected_bounds,
GetNudgeWidgetForWindow(window)->GetWindowBoundsInScreen());
}
base::SimpleTestClock test_clock_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(MultitaskMenuNudgeControllerTest, NoCrashAfterFullscreening) {
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
views::Widget::GetWidgetForNativeView(window.get()));
chromeos::ImmersiveFullscreenControllerTestApi(immersive_controller)
.SetupForTest();
const WMEvent event(WM_EVENT_TOGGLE_FULLSCREEN);
WindowState::Get(window.get())->OnWMEvent(&event);
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
ASSERT_TRUE(immersive_controller->IsEnabled());
ASSERT_FALSE(immersive_controller->IsRevealed());
WindowState::Get(window.get())->OnWMEvent(&event);
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
TEST_F(MultitaskMenuNudgeControllerTest,
NoCrashAfterFloatingFromMultitaskMenu) {
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
views::NamedWidgetShownWaiter waiter(
views::test::AnyWidgetTestPasskey{},
std::string("MultitaskMenuBubbleWidget"));
auto* size_button = static_cast<chromeos::FrameSizeButton*>(
NonClientFrameViewAsh::Get(window.get())
->GetHeaderView()
->caption_button_container()
->size_button());
size_button->ShowMultitaskMenu(
chromeos::MultitaskMenuEntryType::kFrameSizeButtonHover);
views::WidgetDelegate* delegate =
waiter.WaitIfNeededAndGet()->widget_delegate();
auto* multitask_menu =
static_cast<chromeos::MultitaskMenu*>(delegate->AsDialogDelegate());
GetEventGenerator()->MoveMouseTo(
multitask_menu->multitask_menu_view_for_testing()
->float_button_for_testing()
->GetBoundsInScreen()
.CenterPoint());
GetEventGenerator()->ClickLeftButton();
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(MultitaskMenuNudgeControllerTest, NudgeTimeout) {
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
FireDismissNudgeTimer(window.get());
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
TEST_F(MultitaskMenuNudgeControllerTest, ClamshellNudgeBounds) {
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
WindowState::Get(window.get())->Maximize();
auto* nudge_widget = GetNudgeWidgetForWindow(window.get());
ASSERT_TRUE(nudge_widget);
EXPECT_TRUE(display::Screen::GetScreen()
->GetDisplayNearestView(window.get())
.work_area()
.Contains(nudge_widget->GetWindowBoundsInScreen()));
FireDismissNudgeTimer(window.get());
window.reset();
test_clock_.Advance(base::Hours(26));
base::i18n::SetRTLForTesting(true);
window = CreateAppWindow(gfx::Rect(300, 300));
WindowState::Get(window.get())->Maximize();
nudge_widget = GetNudgeWidgetForWindow(window.get());
ASSERT_TRUE(nudge_widget);
EXPECT_TRUE(display::Screen::GetScreen()
->GetDisplayNearestView(window.get())
.work_area()
.Contains(nudge_widget->GetWindowBoundsInScreen()));
}
TEST_F(MultitaskMenuNudgeControllerTest, NudgeMultiDisplay) {
UpdateDisplay("800x700,801+0-800x700");
ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
EXPECT_EQ(Shell::GetAllRootWindows()[1], GetNudgeWidgetForWindow(window.get())
->GetNativeWindow()
->GetRootWindow());
display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
auto* event_generator = GetEventGenerator();
event_generator->set_current_screen_location(gfx::Point(150, 10));
event_generator->PressLeftButton();
event_generator->MoveMouseTo(gfx::Point(1200, 0));
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
TEST_F(MultitaskMenuNudgeControllerTest, NudgePreferences) {
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
FireDismissNudgeTimer(window.get());
ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
window.reset();
window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
test_clock_.Advance(base::Hours(25));
window.reset();
window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
FireDismissNudgeTimer(window.get());
ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
test_clock_.Advance(base::Hours(25));
window.reset();
window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
FireDismissNudgeTimer(window.get());
ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
test_clock_.Advance(base::Hours(25));
window.reset();
window = CreateAppWindow(gfx::Rect(300, 300));
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
TEST_F(MultitaskMenuNudgeControllerTest, MenuShown) {
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
views::NamedWidgetShownWaiter waiter(
views::test::AnyWidgetTestPasskey{},
std::string("MultitaskMenuBubbleWidget"));
auto* size_button = static_cast<chromeos::FrameSizeButton*>(
NonClientFrameViewAsh::Get(window.get())
->GetHeaderView()
->caption_button_container()
->size_button());
size_button->ShowMultitaskMenu(
chromeos::MultitaskMenuEntryType::kFrameSizeButtonHover);
waiter.WaitIfNeededAndGet();
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
test_clock_.Advance(base::Hours(25));
window.reset();
window = CreateAppWindow(gfx::Rect(300, 300));
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
TEST_F(MultitaskMenuNudgeControllerTest, FloatedWindowNudge) {
NewDesk();
ASSERT_TRUE(DesksController::Get()->desks()[0]->is_active());
auto window = CreateAppWindow(gfx::Rect(300, 300));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
ActivateDesk(DesksController::Get()->desks()[1].get());
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
TEST_F(MultitaskMenuNudgeControllerTest, TabletNudgeBounds) {
TabletModeControllerTestApi().EnterTabletMode();
auto window = CreateAppWindow();
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
ExpectCorrectTabletNudgeBounds(window.get());
auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
split_view_controller->SnapWindow(
window.get(), SplitViewController::SnapPosition::kPrimary);
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
ExpectCorrectTabletNudgeBounds(window.get());
split_view_controller->SnapWindow(
window.get(), SplitViewController::SnapPosition::kSecondary);
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
ExpectCorrectTabletNudgeBounds(window.get());
}
TEST_F(MultitaskMenuNudgeControllerTest, TabletWindowDestroyedWhileNudgeShown) {
TabletModeControllerTestApi().EnterTabletMode();
auto window = CreateAppWindow(gfx::Rect(300, 300));
ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
window.reset();
EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
}
}