chromium/ash/wm/window_cycle/window_cycle_controller_unittest.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/wm/window_cycle/window_cycle_controller.h"

#include <algorithm>
#include <memory>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accessibility/accessibility_controller.h"
#include "ash/accessibility/test_accessibility_controller_client.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/focus_cycler.h"
#include "ash/frame_throttler/frame_throttling_controller.h"
#include "ash/frame_throttler/mock_frame_throttling_observer.h"
#include "ash/multi_user/multi_user_window_manager_impl.h"
#include "ash/public/cpp/ash_prefs.h"
#include "ash/public/cpp/multi_user_window_manager.h"
#include "ash/public/cpp/multi_user_window_manager_delegate.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/tab_slider_button.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/raster_scale_change_tracker.h"
#include "ash/test_shell_delegate.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/gestures/wm_gesture_handler.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_cycle/window_cycle_event_filter.h"
#include "ash/wm/window_cycle/window_cycle_item_view.h"
#include "ash/wm/window_cycle/window_cycle_list.h"
#include "ash/wm/window_cycle/window_cycle_view.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_event.h"
#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/ranges.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 "components/prefs/pref_service.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/display_layout_builder.h"
#include "ui/display/manager/display_layout_store.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/scoped_display_for_new_windows.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/event_handler.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/accessibility/accessibility_paint_checks.h"
#include "ui/views/accessibility/view_accessibility.h"

namespace ash {

namespace {

constexpr int kNumFingersForMouseWheel = 2;
constexpr int kNumFingersForTrackpad = 3;

class EventCounter : public ui::EventHandler {
 public:
  EventCounter() : key_events_(0), mouse_events_(0) {}

  EventCounter(const EventCounter&) = delete;
  EventCounter& operator=(const EventCounter&) = delete;

  ~EventCounter() override = default;

  int GetKeyEventCountAndReset() {
    int count = key_events_;
    key_events_ = 0;
    return count;
  }

  int GetMouseEventCountAndReset() {
    int count = mouse_events_;
    mouse_events_ = 0;
    return count;
  }

  // ui::EventHandler:
  void OnKeyEvent(ui::KeyEvent* event) override { key_events_++; }
  void OnMouseEvent(ui::MouseEvent* event) override { mouse_events_++; }

 private:
  int key_events_;
  int mouse_events_;
};

bool InOverviewSession() {
  return OverviewController::Get()->InOverviewSession();
}

int GetOffsetY(int offset) {
  // Reverse the offset if natural scroll is enabled so that the unit tests test
  // the opposite direction.
  return window_util::IsNaturalScrollOn() ? -offset : offset;
}

const WindowCycleList* GetCycleList() {
  return Shell::Get()->window_cycle_controller()->window_cycle_list();
}

}  // namespace

// Wrapper for WindowCycleList that exposes internal state to test functions.
class WindowCycleListTestApi {
 public:
  explicit WindowCycleListTestApi(const WindowCycleList* cycle_list)
      : cycle_list_(cycle_list) {}
  WindowCycleListTestApi(const WindowCycleListTestApi&) = delete;
  WindowCycleListTestApi& operator=(const WindowCycleListTestApi&) = delete;
  ~WindowCycleListTestApi() = default;

  const aura::Window::Windows& windows() const { return cycle_list_->windows_; }

  const views::Widget* widget() const { return cycle_list_->cycle_ui_widget_; }

  WindowCycleView* cycle_view() const { return cycle_list_->cycle_view_; }

  const views::Label* no_recent_items_label() const {
    return cycle_view()->no_recent_items_label_;
  }

  const aura::Window* target_window() const {
    return cycle_view()->target_window_;
  }

  int current_index() const { return cycle_list_->current_index_; }

  const views::View::Views& GetWindowCycleItemViews() const {
    return cycle_view()->mirror_container_->children();
  }

  const std::vector<TabSliderButton*> GetTabSliderButtons() const {
    auto* tab_slider = cycle_view()->tab_slider_.get();
    if (!tab_slider) {
      static const std::vector<TabSliderButton*> empty;
      return empty;
    }
    std::vector<TabSliderButton*> buttons;
    buttons.push_back(cycle_view()->all_desks_tab_slider_button_);
    buttons.push_back(cycle_view()->current_desk_tab_slider_button_);
    return buttons;
  }

  bool IsCycleViewAnimating() const {
    return cycle_view()->layer()->GetAnimator()->is_animating();
  }

 private:
  const raw_ptr<const WindowCycleList> cycle_list_;
};

using aura::Window;
using aura::test::CreateTestWindowWithId;
using aura::test::TestWindowDelegate;

class WindowCycleControllerTest : public AshTestBase {
 public:
  WindowCycleControllerTest() = default;

  WindowCycleControllerTest(const WindowCycleControllerTest&) = delete;
  WindowCycleControllerTest& operator=(const WindowCycleControllerTest&) =
      delete;

  ~WindowCycleControllerTest() override = default;

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

    WindowCycleList::SetDisableInitialDelayForTesting(true);

    shelf_view_test_ = std::make_unique<ShelfViewTestAPI>(
        GetPrimaryShelf()->GetShelfViewForTesting());
    shelf_view_test_->SetAnimationDuration(base::Milliseconds(1));
  }

  const aura::Window::Windows GetWindows(WindowCycleController* controller) {
    return WindowCycleListTestApi(controller->window_cycle_list()).windows();
  }

  const views::Widget* GetWindowCycleListWidget() const {
    return WindowCycleListTestApi(GetCycleList()).widget();
  }

  const views::View::Views& GetWindowCycleItemViews() const {
    return WindowCycleListTestApi(GetCycleList()).GetWindowCycleItemViews();
  }

  const std::vector<TabSliderButton*> GetWindowCycleTabSliderButtons() const {
    return WindowCycleListTestApi(GetCycleList()).GetTabSliderButtons();
  }

  const views::Label* GetWindowCycleNoRecentItemsLabel() const {
    return WindowCycleListTestApi(GetCycleList()).no_recent_items_label();
  }

  const aura::Window* GetTargetWindow() const {
    return WindowCycleListTestApi(GetCycleList()).target_window();
  }

  bool CycleViewExists() const {
    return WindowCycleListTestApi(GetCycleList()).cycle_view();
  }

  int GetCurrentIndex() const {
    return WindowCycleListTestApi(GetCycleList()).current_index();
  }

  void CompleteCycling(WindowCycleController* controller) {
    controller->CompleteCycling();
    base::RunLoop().RunUntilIdle();
  }

  void CompleteCyclingAndDeskSwitching(WindowCycleController* controller) {
    DeskSwitchAnimationWaiter waiter;
    controller->CompleteCycling();
    base::RunLoop().RunUntilIdle();
    auto* desks_controller = Shell::Get()->desks_controller();
    if (desks_controller->AreDesksBeingModified())
      waiter.Wait();
  }

  void Scroll(float x_offset, float y_offset, int fingers) {
    GetEventGenerator()->ScrollSequence(gfx::Point(), base::Milliseconds(5),
                                        x_offset, GetOffsetY(y_offset),
                                        /*steps=*/100, fingers);
  }

  void MouseWheelScroll(int delta_x, int delta_y, int num_of_times) {
    auto* generator = GetEventGenerator();
    for (int i = 0; i < num_of_times; i++)
      generator->MoveMouseWheel(delta_x, delta_y);
  }

 private:
  std::unique_ptr<ShelfViewTestAPI> shelf_view_test_;
};

TEST_F(WindowCycleControllerTest, HandleCycleWindowBaseCases) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Cycling doesn't crash if there are no windows.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Create a single test window.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  wm::ActivateWindow(window0.get());
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));

  // Cycling works for a single window, even though nothing changes.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
}

// Verifies if there is only one window and it isn't active that cycling
// activates it.
TEST_F(WindowCycleControllerTest, SingleWindowNotActive) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Create a single test window.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  wm::ActivateWindow(window0.get());
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));

  // Rotate focus, this should move focus to another window that isn't part of
  // the default container.
  Shell::Get()->focus_cycler()->RotateFocus(FocusCycler::FORWARD);
  EXPECT_FALSE(wm::IsActiveWindow(window0.get()));

  // Cycling should activate the window.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
}

TEST_F(WindowCycleControllerTest, HandleCycleWindow) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Set up several windows to use to test cycling.  Create them in reverse
  // order so they are stacked 0 over 1 over 2.
  std::unique_ptr<Window> window2(CreateTestWindowInShellWithId(2));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  wm::ActivateWindow(window0.get());

  // Simulate pressing and releasing Alt-tab.
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Window lists should return the topmost window in front.
  ASSERT_TRUE(controller->window_cycle_list());
  ASSERT_EQ(3u, GetWindows(controller).size());
  ASSERT_EQ(window0.get(), GetWindows(controller)[0]);
  ASSERT_EQ(window1.get(), GetWindows(controller)[1]);
  ASSERT_EQ(window2.get(), GetWindows(controller)[2]);

  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Pressing and releasing Alt-tab again should cycle back to the most-
  // recently-used window in the current child order.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));

  // Cancelled cycling shouldn't move the active window.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  controller->CancelCycling();
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));

  // Pressing Alt-tab multiple times without releasing Alt should cycle through
  // all the windows and wrap around.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(controller->IsCycling());

  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(controller->IsCycling());

  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(controller->IsCycling());

  CompleteCycling(controller);
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));

  // Reset our stacking order.
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());
  wm::ActivateWindow(window0.get());

  // Likewise we can cycle backwards through the windows.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kBackward);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kBackward);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Reset our stacking order.
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window1.get());
  wm::ActivateWindow(window0.get());

  // When the screen is locked, cycling window does not take effect.
  GetSessionControllerClient()->LockScreen();
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_FALSE(controller->IsCycling());

  // Unlock, it works again.
  GetSessionControllerClient()->UnlockScreen();
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // When a modal window is active, cycling window does not take effect.
  aura::Window* modal_container = Shell::GetContainer(
      Shell::GetPrimaryRootWindow(), kShellWindowId_SystemModalContainer);
  std::unique_ptr<Window> modal_window(
      CreateTestWindowWithId(-2, modal_container));
  modal_window->SetProperty(aura::client::kModalKey,
                            ui::mojom::ModalType::kSystem);
  wm::ActivateWindow(modal_window.get());
  EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window2.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kBackward);
  EXPECT_TRUE(wm::IsActiveWindow(modal_window.get()));
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window2.get()));

  modal_window.reset();
  std::unique_ptr<Window> skip_overview_window(
      CreateTestWindowInShellWithId(-3));
  skip_overview_window->SetProperty(kHideInOverviewKey, true);
  wm::ActivateWindow(window0.get());
  wm::ActivateWindow(skip_overview_window.get());
  wm::ActivateWindow(window1.get());
  EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  EXPECT_FALSE(wm::IsActiveWindow(skip_overview_window.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));
}

// Cycles between a maximized and normal window.
TEST_F(WindowCycleControllerTest, MaximizedWindow) {
  // Create a couple of test windows.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
  WindowState* window1_state = WindowState::Get(window1.get());
  window1_state->Maximize();
  window1_state->Activate();
  EXPECT_TRUE(window1_state->IsActive());

  // Rotate focus, this should move focus to window0.
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(WindowState::Get(window0.get())->IsActive());
  EXPECT_FALSE(window1_state->IsActive());

  // One more time.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(window1_state->IsActive());
}

// Cycles to a minimized window.
TEST_F(WindowCycleControllerTest, Minimized) {
  // Create a couple of test windows.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
  WindowState* window0_state = WindowState::Get(window0.get());
  WindowState* window1_state = WindowState::Get(window1.get());

  window1_state->Minimize();
  window0_state->Activate();
  EXPECT_TRUE(window0_state->IsActive());

  // Rotate focus, this should move focus to window1 and unminimize it.
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_FALSE(window0_state->IsActive());
  EXPECT_FALSE(window1_state->IsMinimized());
  EXPECT_TRUE(window1_state->IsActive());

  // One more time back to w0.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(window0_state->IsActive());
}

// Tests that when all windows are minimized, cycling starts with the first one
// rather than the second.
TEST_F(WindowCycleControllerTest, AllAreMinimized) {
  // Create a couple of test windows.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
  WindowState* window0_state = WindowState::Get(window0.get());
  WindowState* window1_state = WindowState::Get(window1.get());

  window0_state->Minimize();
  window1_state->Minimize();

  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(window1_state->IsActive());
  EXPECT_FALSE(window1_state->IsMinimized());
  EXPECT_TRUE(window0_state->IsMinimized());

  // But it's business as usual when cycling backwards.
  window0_state->Minimize();
  window1_state->Minimize();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kBackward);
  CompleteCycling(controller);
  EXPECT_TRUE(window0_state->IsActive());
  EXPECT_FALSE(window0_state->IsMinimized());
  EXPECT_TRUE(window1_state->IsMinimized());
}

TEST_F(WindowCycleControllerTest, AlwaysOnTopWindow) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Set up several windows to use to test cycling.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));

  Window* top_container = Shell::GetContainer(
      Shell::GetPrimaryRootWindow(), kShellWindowId_AlwaysOnTopContainer);
  std::unique_ptr<Window> window2(CreateTestWindowWithId(2, top_container));
  wm::ActivateWindow(window0.get());

  // Simulate pressing and releasing Alt-tab.
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Window lists should return the topmost window in front.
  ASSERT_TRUE(controller->window_cycle_list());
  ASSERT_EQ(3u, GetWindows(controller).size());
  EXPECT_EQ(window0.get(), GetWindows(controller)[0]);
  EXPECT_EQ(window2.get(), GetWindows(controller)[1]);
  EXPECT_EQ(window1.get(), GetWindows(controller)[2]);

  CompleteCycling(controller);
}

TEST_F(WindowCycleControllerTest, AlwaysOnTopMultiWindow) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Set up several windows to use to test cycling.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));

  Window* top_container = Shell::GetContainer(
      Shell::GetPrimaryRootWindow(), kShellWindowId_AlwaysOnTopContainer);
  std::unique_ptr<Window> window2(CreateTestWindowWithId(2, top_container));
  std::unique_ptr<Window> window3(CreateTestWindowWithId(3, top_container));
  wm::ActivateWindow(window0.get());

  // Simulate pressing and releasing Alt-tab.
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Window lists should return the topmost window in front.
  ASSERT_TRUE(controller->window_cycle_list());
  ASSERT_EQ(4u, GetWindows(controller).size());
  EXPECT_EQ(window0.get(), GetWindows(controller)[0]);
  EXPECT_EQ(window3.get(), GetWindows(controller)[1]);
  EXPECT_EQ(window2.get(), GetWindows(controller)[2]);
  EXPECT_EQ(window1.get(), GetWindows(controller)[3]);

  CompleteCycling(controller);
}

TEST_F(WindowCycleControllerTest, AlwaysOnTopMultipleRootWindows) {
  // Set up a second root window
  UpdateDisplay("1000x600,600x400");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2U, root_windows.size());

  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Create two windows in the primary root.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  EXPECT_EQ(root_windows[0], window0->GetRootWindow());
  Window* top_container0 =
      Shell::GetContainer(root_windows[0], kShellWindowId_AlwaysOnTopContainer);
  std::unique_ptr<Window> window1(CreateTestWindowWithId(1, top_container0));
  EXPECT_EQ(root_windows[0], window1->GetRootWindow());

  // Move the active root window to the secondary root and create two windows.
  display::ScopedDisplayForNewWindows display_for_new_windows(root_windows[1]);
  std::unique_ptr<Window> window2(CreateTestWindowInShellWithId(2));
  EXPECT_EQ(root_windows[1], window2->GetRootWindow());

  Window* top_container1 =
      Shell::GetContainer(root_windows[1], kShellWindowId_AlwaysOnTopContainer);
  std::unique_ptr<Window> window3(CreateTestWindowWithId(3, top_container1));
  EXPECT_EQ(root_windows[1], window3->GetRootWindow());

  wm::ActivateWindow(window2.get());

  EXPECT_EQ(root_windows[0], window0->GetRootWindow());
  EXPECT_EQ(root_windows[0], window1->GetRootWindow());
  EXPECT_EQ(root_windows[1], window2->GetRootWindow());
  EXPECT_EQ(root_windows[1], window3->GetRootWindow());

  // Simulate pressing and releasing Alt-tab.
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Window lists should return the topmost window in front.
  ASSERT_TRUE(controller->window_cycle_list());
  ASSERT_EQ(4u, GetWindows(controller).size());
  EXPECT_EQ(window2.get(), GetWindows(controller)[0]);
  EXPECT_EQ(window3.get(), GetWindows(controller)[1]);
  EXPECT_EQ(window1.get(), GetWindows(controller)[2]);
  EXPECT_EQ(window0.get(), GetWindows(controller)[3]);

  CompleteCycling(controller);
}

TEST_F(WindowCycleControllerTest, MostRecentlyUsed) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Set up several windows to use to test cycling.
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
  std::unique_ptr<Window> window2(CreateTestWindowInShellWithId(2));

  wm::ActivateWindow(window0.get());

  // Simulate pressing and releasing Alt-tab.
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Window lists should return the topmost window in front.
  ASSERT_TRUE(controller->window_cycle_list());
  ASSERT_EQ(3u, GetWindows(controller).size());
  EXPECT_EQ(window0.get(), GetWindows(controller)[0]);
  EXPECT_EQ(window2.get(), GetWindows(controller)[1]);
  EXPECT_EQ(window1.get(), GetWindows(controller)[2]);

  // Cycling through then stopping the cycling will activate a window.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Cycling alone (without CompleteCycling()) doesn't activate.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_FALSE(wm::IsActiveWindow(window0.get()));

  CompleteCycling(controller);
}

// Tests that beginning window selection hides the app list.
TEST_F(WindowCycleControllerTest, SelectingHidesAppList) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  std::unique_ptr<aura::Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
  wm::ActivateWindow(window0.get());

  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplay().id());
  GetAppListTestHelper()->CheckVisibility(true);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);

  // Make sure that dismissing the app list this way doesn't pass activation
  // to a different window.
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  EXPECT_FALSE(wm::IsActiveWindow(window1.get()));

  CompleteCycling(controller);
}

// Tests that beginning window selection doesn't hide the app list in tablet
// mode.
TEST_F(WindowCycleControllerTest, SelectingDoesNotHideAppListInTabletMode) {
  TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EXPECT_TRUE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());

  std::unique_ptr<aura::Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
  wm::ActivateWindow(window0.get());

  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  window0->Hide();
  window1->Hide();
  EXPECT_TRUE(Shell::Get()->app_list_controller()->IsHomeScreenVisible());
}

// Tests that cycling through windows doesn't change their minimized state.
TEST_F(WindowCycleControllerTest, CyclePreservesMinimization) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  std::unique_ptr<aura::Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1));
  wm::ActivateWindow(window1.get());
  WindowState::Get(window1.get())->Minimize();
  wm::ActivateWindow(window0.get());
  EXPECT_TRUE(WindowState::Get(window1.get())->IsMinimized());

  // On window 2.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(WindowState::Get(window1.get())->IsMinimized());

  // Back on window 1.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(WindowState::Get(window1.get())->IsMinimized());

  CompleteCycling(controller);

  EXPECT_TRUE(WindowState::Get(window1.get())->IsMinimized());
}

// Tests that the tab key events are not sent to the window.
TEST_F(WindowCycleControllerTest, TabKeyNotLeaked) {
  std::unique_ptr<Window> w0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> w1(CreateTestWindowInShellWithId(1));
  EventCounter event_count;
  w0->AddPreTargetHandler(&event_count);
  w1->AddPreTargetHandler(&event_count);
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowState::Get(w0.get())->Activate();
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_EQ(1, event_count.GetKeyEventCountAndReset());
  generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_EQ(0, event_count.GetKeyEventCountAndReset());
  generator->ReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_EQ(0, event_count.GetKeyEventCountAndReset());
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(WindowState::Get(w1.get())->IsActive());
  EXPECT_EQ(0, event_count.GetKeyEventCountAndReset());
}

// Tests that we can cycle past fullscreen windows: https://crbug.com/622396.
// Fullscreen windows are special in that they are allowed to handle alt+tab
// keypresses, which means the window cycle event filter should not handle
// the tab press else it prevents cycling past that window.
TEST_F(WindowCycleControllerTest, TabPastFullscreenWindow) {
  std::unique_ptr<Window> w0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> w1(CreateTestWindowInShellWithId(1));
  WMEvent maximize_event(WM_EVENT_FULLSCREEN);

  // To make this test work with or without the new alt+tab selector we make
  // both the initial window and the second window fullscreen.
  WindowState::Get(w0.get())->OnWMEvent(&maximize_event);
  WindowState::Get(w1.get())->Activate();
  WindowState::Get(w1.get())->OnWMEvent(&maximize_event);
  EXPECT_TRUE(WindowState::Get(w0.get())->IsFullscreen());
  EXPECT_TRUE(WindowState::Get(w1.get())->IsFullscreen());
  WindowState::Get(w0.get())->Activate();
  EXPECT_TRUE(WindowState::Get(w0.get())->IsActive());

  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);

  generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);

  // Because w0 and w1 are full-screen, the event should be passed to the
  // browser window to handle it (which if the browser doesn't handle it will
  // pass on the alt+tab to continue cycling). To make this test work with or
  // without the new alt+tab selector we check for the event on either
  // fullscreen window.
  EventCounter event_count;
  w0->AddPreTargetHandler(&event_count);
  w1->AddPreTargetHandler(&event_count);
  generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_EQ(1, event_count.GetKeyEventCountAndReset());
}

// Tests that the Alt+Tab UI's position isn't affected by the origin of the
// display it's on. See crbug.com/675718
TEST_F(WindowCycleControllerTest, MultiDisplayPositioning) {
  int64_t primary_id = GetPrimaryDisplay().id();
  display::DisplayIdList list =
      display::test::CreateDisplayIdListN(primary_id, 2);

  auto placements = {
      display::DisplayPlacement::BOTTOM,
      display::DisplayPlacement::TOP,
      display::DisplayPlacement::LEFT,
      display::DisplayPlacement::RIGHT,
  };

  gfx::Rect expected_bounds;
  for (auto placement : placements) {
    SCOPED_TRACE(placement);

    display::DisplayLayoutBuilder builder(primary_id);
    builder.AddDisplayPlacement(list[1], primary_id, placement, 0);
    display_manager()->layout_store()->RegisterLayoutForDisplayIdList(
        list, builder.Build());

    // Use two displays.
    UpdateDisplay("500x400,600x500");

    gfx::Rect second_display_bounds =
        display_manager()->GetDisplayAt(1).bounds();
    std::unique_ptr<Window> window0(
        CreateTestWindowInShellWithBounds(second_display_bounds));
    // Activate this window so that the secondary display becomes the one where
    // the Alt+Tab UI is shown.
    wm::ActivateWindow(window0.get());
    std::unique_ptr<Window> window1(
        CreateTestWindowInShellWithBounds(second_display_bounds));

    WindowCycleController* controller = Shell::Get()->window_cycle_controller();
    controller->HandleCycleWindow(
        WindowCycleController::WindowCyclingDirection::kForward);

    const gfx::Rect bounds =
        GetWindowCycleListWidget()->GetWindowBoundsInScreen();
    EXPECT_TRUE(second_display_bounds.Contains(bounds));
    EXPECT_FALSE(
        display_manager()->GetDisplayAt(0).bounds().Intersects(bounds));
    const gfx::Rect display_relative_bounds =
        bounds - second_display_bounds.OffsetFromOrigin();
    // Base case sets the expectation for other cases.
    if (expected_bounds.IsEmpty())
      expected_bounds = display_relative_bounds;
    else
      EXPECT_EQ(expected_bounds, display_relative_bounds);
    CompleteCycling(controller);
  }
}

TEST_F(WindowCycleControllerTest, CycleShowsAllDesksWindows) {
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(3u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  const Desk* desk_3 = desks_controller->GetDeskAtIndex(2);
  ActivateDesk(desk_3);
  EXPECT_EQ(desk_3, desks_controller->active_desk());
  auto win3 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  // All desks' windows are included in the cycle list.
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(4u, cycle_windows.size());
  EXPECT_TRUE(base::Contains(cycle_windows, win0.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win2.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win3.get()));

  // The MRU order is {win3, win2, win1, win0}. We're now at win2. Cycling one
  // more time and completing the cycle, will activate win1 which exists on a
  // desk_1. This should activate desk_1.
  {
    base::HistogramTester histogram_tester;
    cycle_controller->HandleCycleWindow(
        WindowCycleController::WindowCyclingDirection::kForward);
    CompleteCyclingAndDeskSwitching(cycle_controller);
    Desk* desk_1 = desks_controller->GetDeskAtIndex(0);
    EXPECT_EQ(desk_1, desks_controller->active_desk());
    EXPECT_EQ(win1.get(), window_util::GetActiveWindow());
    histogram_tester.ExpectUniqueSample(
        "Ash.WindowCycleController.DesksSwitchDistance",
        /*desk distance of 3 - 1 = */ 2, /*expected_count=*/1);
  }

  // Cycle again and activate win2, which exist on desk_2. Expect that desk to
  // be activated, and a histogram sample of distance of 1 is recorded.
  // MRU is {win1, win3, win2, win0}.
  {
    base::HistogramTester histogram_tester;
    cycle_controller->HandleCycleWindow(
        WindowCycleController::WindowCyclingDirection::kForward);
    cycle_controller->HandleCycleWindow(
        WindowCycleController::WindowCyclingDirection::kForward);
    CompleteCyclingAndDeskSwitching(cycle_controller);
    EXPECT_EQ(desk_2, desks_controller->active_desk());
    EXPECT_EQ(win2.get(), window_util::GetActiveWindow());
    histogram_tester.ExpectUniqueSample(
        "Ash.WindowCycleController.DesksSwitchDistance",
        /*desk distance of 2 - 1 = */ 1, /*expected_count=*/1);
  }
}

// Tests that frame throttling starts and ends accordingly when window cycling
// starts and ends.
TEST_F(WindowCycleControllerTest, FrameThrottling) {
  FrameThrottlingController* frame_throttling_controller =
      Shell::Get()->frame_throttling_controller();
  const int window_count = 5;
  std::vector<viz::FrameSinkId> ids{
      {1u, 1u}, {2u, 2u}, {3u, 3u}, {4u, 4u}, {5u, 5u}};
  std::unique_ptr<aura::Window> windows[window_count];
  for (int i = 0; i < window_count; ++i) {
    windows[i] = CreateAppWindow(gfx::Rect(), chromeos::AppType::BROWSER);
    windows[i]->SetEmbedFrameSinkId(ids[i]);
  }

  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));
  CompleteCycling(controller);
  EXPECT_TRUE(frame_throttling_controller->GetFrameSinkIdsToThrottle().empty());
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(),
              testing::UnorderedElementsAreArray(ids));
  controller->CancelCycling();
  EXPECT_TRUE(frame_throttling_controller->GetFrameSinkIdsToThrottle().empty());
}

// Tests that pressing Alt+Tab while there is an on-going desk animation
// prevents a new window cycle from starting.
TEST_F(WindowCycleControllerTest, DoubleAltTabWithDeskSwitch) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  auto win0 = CreateAppWindow(gfx::Rect(250, 100));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_0 = desks_controller->GetDeskAtIndex(0);
  const Desk* desk_1 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_1);
  EXPECT_EQ(desk_1, desks_controller->active_desk());
  auto win1 = CreateAppWindow(gfx::Rect(300, 200));
  ASSERT_EQ(win1.get(), window_util::GetActiveWindow());
  auto desk_1_windows = desk_1->windows();
  EXPECT_EQ(1u, desk_1_windows.size());
  EXPECT_TRUE(base::Contains(desk_1_windows, win1.get()));

  DeskSwitchAnimationWaiter waiter;
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  cycle_controller->CompleteCycling();
  EXPECT_FALSE(cycle_controller->CanCycle());
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_FALSE(cycle_controller->IsCycling());
  waiter.Wait();
  EXPECT_EQ(desk_0, desks_controller->active_desk());
  EXPECT_EQ(win0.get(), window_util::GetActiveWindow());
}

// A regression test for crbug.com/1160676. Tests that the alt-key release
// to quit alt-tab is acknowledged by the accelerator controller.
TEST_F(WindowCycleControllerTest, AltKeyRelease) {
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
  wm::ActivateWindow(window1.get());

  // Press Alt and start cycling.
  auto* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  auto currently_pressed_keys = Shell::Get()
                                    ->accelerator_controller()
                                    ->GetAcceleratorHistory()
                                    ->currently_pressed_keys();
  // Expect exactly one key pressed, which is Alt.
  EXPECT_EQ(1u, currently_pressed_keys.size());
  EXPECT_TRUE(base::Contains(currently_pressed_keys, ui::VKEY_MENU));

  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  // Release Alt key to end alt-tab cycling and open up window0.
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(WindowState::Get(window0.get())->IsActive());

  // Expect all keys pressed to be released.
  currently_pressed_keys = Shell::Get()
                               ->accelerator_controller()
                               ->GetAcceleratorHistory()
                               ->currently_pressed_keys();
  EXPECT_EQ(0u, currently_pressed_keys.size());
  EXPECT_FALSE(base::Contains(currently_pressed_keys, ui::VKEY_MENU));
}

// Tests if tray bubbles will be closed when alt-tab cycling starts.
TEST_F(WindowCycleControllerTest, AltKeyReleaseOnSystemTrayOpen) {
  std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));

  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  ui::test::EventGenerator* event_generator = GetEventGenerator();

  int count = 0;

  // Open tray bubble if it can open otherwise continue to next tray bubble.
  for (TrayBackgroundView* tray_button :
       GetPrimaryShelf()->GetStatusAreaWidget()->tray_buttons()) {
    LeftClickOn(tray_button);
    if (!tray_button->is_active())
      continue;

    count = count + 1;

    // Start window cycling by press Alt + Tab key.
    WindowState::Get(window0.get())->Activate();
    event_generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
    event_generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
    EXPECT_TRUE(controller->IsCycling());
    // Verify the system tray is closed after the alt-tab cycling starts.
    EXPECT_FALSE(tray_button->is_active());

    // Release Alt key, verify alt-tab cycling is ended.
    event_generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
    EXPECT_FALSE(controller->IsCycling());
  }

  // Make sure Alt + Tab procedure above happened at least once.
  EXPECT_GT(count, 0);
}

// Test alt-tab will be shown on the activated display when there are 2
// displays.
TEST_F(WindowCycleControllerTest, AltTabMultiDisplay) {
  UpdateDisplay("500x400,401+0-800x700");

  std::unique_ptr<Window> w0 = CreateTestWindow(gfx::Rect(200, 200));
  std::unique_ptr<Window> w1 = CreateTestWindow(gfx::Rect(420, 10, 200, 200));
  // |w0| needs to be activated to ensure it is the display for new windows.
  wm::ActivateWindow(w0.get());
  // TODO(crbug.com/40638870): Unit tests should be able to simulate mouse input
  // without having to call |CursorManager::SetDisplay|.
  Shell::Get()->cursor_manager()->SetDisplay(
      display::Screen::GetScreen()->GetDisplayNearestWindow(w1.get()));

  // Test alt-tab activates on first display, the display for new windows, not
  // the second display where the cursor is at.
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  auto preview_items = GetWindowCycleItemViews();
  ASSERT_EQ(2u, preview_items.size());
  // Ensure preview is generated in first display where the activated window
  // is at.
  auto preview_display = display::Screen::GetScreen()->GetDisplayNearestWindow(
      GetWindowCycleListWidget()->GetNativeWindow());
  auto activated_window =
      display::Screen::GetScreen()->GetDisplayNearestWindow(w0.get());
  EXPECT_EQ(activated_window, preview_display);
  CompleteCycling(cycle_controller);
}

// Test that alt-tab handles window destruction properly.
TEST_F(WindowCycleControllerTest, WindowDestruction) {
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();

  // Start cycling and then destroy a window. We should still be cycling and
  // there should now only be two items.
  auto* controller = Shell::Get()->window_cycle_controller();
  controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(controller->IsCycling());
  w1.reset();
  EXPECT_TRUE(controller->IsCycling());
  EXPECT_EQ(2u, GetWindows(controller).size());
}

// Tests that when the cycle view is not open, the event filter does not check
// whether events occur within the cycle view.
TEST_F(WindowCycleControllerTest, MouseEventWhenCycleViewDoesNotExist) {
  aura::test::TestWindowDelegate delegate;
  std::unique_ptr<Window> w0(CreateTestWindowInShellWithDelegate(
      &delegate, 0, gfx::Rect(0, 0, 100, 100)));
  EventCounter event_count;
  w0->AddPreTargetHandler(&event_count);
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Mouse events get through if the cycle view is not open.
  // Cycling with one window open ensures the UI doesn't show but the event
  // filter is.
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->MoveMouseToCenterOf(w0.get());
  generator->ClickLeftButton();
  EXPECT_TRUE(controller->IsCycling());
  EXPECT_FALSE(CycleViewExists());
  EXPECT_LT(0, event_count.GetMouseEventCountAndReset());
  CompleteCycling(controller);
}

// When a user hovers their mouse over an item, it should cycle to it.
// The items in the list should not move, only the focus ring.
// If a user clicks on an item, it should complete cycling and activate
// the hovered item.
TEST_F(WindowCycleControllerTest, MouseHoverAndSelect) {
  UpdateDisplay("1200x800");
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  std::unique_ptr<Window> w3 = CreateTestWindow();
  std::unique_ptr<Window> w4 = CreateTestWindow();
  std::unique_ptr<Window> w5 = CreateTestWindow();
  std::unique_ptr<Window> w6 = CreateTestWindow();
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Cycle to the third item, mouse over second item, and release alt-tab.
  // Starting order of windows in cycle list is [6,5,4,3,2,1,0].
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  gfx::Point target_item_center =
      GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint();
  generator->MoveMouseTo(target_item_center);
  EXPECT_EQ(target_item_center,
            GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint());
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w5.get()));

  // Start cycle, mouse over third item, and release alt-tab.
  // Starting order of windows in cycle list is [5,6,4,3,2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  target_item_center =
      GetWindowCycleItemViews()[2]->GetBoundsInScreen().CenterPoint();
  generator->MoveMouseTo(target_item_center);
  EXPECT_EQ(target_item_center,
            GetWindowCycleItemViews()[2]->GetBoundsInScreen().CenterPoint());
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w4.get()));

  // Start cycle, cycle to the fifth item, mouse over seventh item, and click.
  // Starting order of windows in cycle list is [4,5,6,3,2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  for (int i = 0; i < 5; i++)
    controller->HandleCycleWindow(
        WindowCycleController::WindowCyclingDirection::kForward);
  target_item_center =
      GetWindowCycleItemViews()[6]->GetBoundsInScreen().CenterPoint();
  generator->MoveMouseTo(target_item_center);
  EXPECT_EQ(target_item_center,
            GetWindowCycleItemViews()[6]->GetBoundsInScreen().CenterPoint());
  generator->PressLeftButton();
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));
}

// Tests that the left and right keys cycle after the cycle list has been
// initialized.
TEST_F(WindowCycleControllerTest, LeftRightCycle) {
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Start cycle, simulating alt button being held down. Cycle right to the
  // third item.
  // Starting order of windows in cycle list is [2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));

  // Start cycle. Cycle right once, then left two times.
  // Starting order of windows in cycle list is [0,2,1].
  controller->StartCycling(/*same_app_only=*/false);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // Start cycle. Cycle right once, then left once, then right once.
  // Starting order of windows in cycle list is [0,2,1].
  controller->StartCycling(/*same_app_only=*/false);
  generator->PressKey(ui::VKEY_LEFT, ui::EF_ALT_DOWN);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_ALT_DOWN);
  generator->PressKey(ui::VKEY_LEFT, ui::EF_ALT_DOWN);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
}

// Tests that pressing the space key, pressing the enter key, or releasing the
// alt key during window cycle confirms a selection.
TEST_F(WindowCycleControllerTest, KeysConfirmSelection) {
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Start cycle, simulating alt button being held down. Cycle right once and
  // complete cycle using space.
  // Starting order of windows in cycle list is [2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->PressKey(ui::VKEY_SPACE, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // Start cycle, simulating alt button being held down. Cycle right once and
  // complete cycle using enter.
  // Starting order of windows in cycle list is [1,2,0].
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // Start cycle, simulating alt button being held down. Cycle right once and
  // complete cycle by releasing alt key (Views uses VKEY_MENU for both left and
  // right alt keys).
  // Starting order of windows in cycle list is [2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
}

// Tests that pressing the enter key or space key really quickly doesn't crash.
// See crbug.com/1187242.
TEST_F(WindowCycleControllerTest, RapidConfirmSelection) {
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Start cycling and press space twice. This should not crash.
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->PressKey(ui::VKEY_SPACE, ui::EF_NONE);
  generator->PressKey(ui::VKEY_SPACE, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // Start cycling and press enter twice. This should not crash.
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // Press down alt and tab. Release alt key and press enter. This should not
  // crash.
  generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_TRUE(controller->IsCycling());
  generator->ReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // Start cycling and press enter once and then right key. This should not
  // crash and the right key should not affect the selection.
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->PressKey(ui::VKEY_RETURN, ui::EF_NONE);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
}

// Tests that mouse events are filtered until the mouse is actually used,
// preventing the mouse from unexpectedly triggering events.
// See crbug.com/1143275.
TEST_F(WindowCycleControllerTest, FilterMouseEventsUntilUsed) {
  UpdateDisplay("1200x800");
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  EventCounter event_count;
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Start cycling.
  // Current window order is [2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  auto item_views = GetWindowCycleItemViews();
  item_views[2]->AddPreTargetHandler(&event_count);

  // Move the mouse over to the third item and complete cycling. These mouse
  // events shouldn't be filtered since the user has moved their mouse.
  generator->MoveMouseTo(gfx::Point(0, 0));
  const gfx::Point third_item_center =
      GetWindowCycleItemViews()[2]->GetBoundsInScreen().CenterPoint();
  generator->MoveMouseTo(third_item_center);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));
  EXPECT_LT(0, event_count.GetMouseEventCountAndReset());

  // Start cycling again while the mouse is over where the third item will be
  // when cycling starts.
  // Current window order is [0,2,1].
  controller->StartCycling(/*same_app_only=*/false);
  item_views = GetWindowCycleItemViews();
  item_views[2]->AddPreTargetHandler(&event_count);

  // Generate mouse events at the cursor's initial position. These mouse events
  // should be filtered because the user hasn't moved their mouse yet.
  generator->MoveMouseTo(third_item_center);
  CompleteCycling(controller);
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));
  EXPECT_EQ(0, event_count.GetMouseEventCountAndReset());

  // Start cycling again and click. This should not be filtered out.
  // Current window order is [0,2,1].
  controller->StartCycling(/*same_app_only=*/false);
  generator->PressLeftButton();
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
}

// When a user has the window cycle list open and clicks outside of it, it
// should cancel cycling.
TEST_F(WindowCycleControllerTest, MousePressOutsideOfListCancelsCycling) {
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Cycle to second item, move to above the window cycle list, and click.
  controller->StartCycling(/*same_app_only=*/false);
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  gfx::Point above_window_cycle_list =
      GetWindowCycleListWidget()->GetWindowBoundsInScreen().top_center();
  above_window_cycle_list.Offset(0, 100);
  generator->MoveMouseTo(above_window_cycle_list);
  generator->ClickLeftButton();
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
}

// When the user has one window open, the window cycle view isn't shown. In this
// case we should not eat mouse events.
TEST_F(WindowCycleControllerTest, MouseEventsNotEatenWhenCycleViewNotVisible) {
  std::unique_ptr<Window> w0 = CreateTestWindow();
  EventCounter event_count;
  w0->AddPreTargetHandler(&event_count);
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Start cycling. Since there's only one window the cycle view shouldn't be
  // visible.
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  ASSERT_TRUE(controller->IsCycling());
  ASSERT_FALSE(controller->IsWindowListVisible());

  generator->MoveMouseToCenterOf(w0.get());
  generator->ClickLeftButton();
  EXPECT_LT(0, event_count.GetMouseEventCountAndReset());
}

// Tests three finger horizontal scroll gesture to move selection left or right.
TEST_F(WindowCycleControllerTest,
       ThreeFingerHorizontalScrollInWindowCycleList) {
  const gfx::Rect bounds(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds);
  const float horizontal_scroll =
      WindowCycleEventFilter::kHorizontalThresholdDp;

  auto scroll_until_window_highlighted_and_confirm = [this](float x_offset,
                                                            float y_offset) {
    WindowCycleController* controller = Shell::Get()->window_cycle_controller();
    controller->StartCycling(/*same_app_only=*/false);
    Scroll(x_offset, GetOffsetY(y_offset), kNumFingersForTrackpad);
    CompleteCycling(controller);
  };

  // Start cycle, simulating alt key being held down. Scroll right to fourth
  // item.
  // Current order is [5,4,3,2,1].
  scroll_until_window_highlighted_and_confirm(horizontal_scroll * 3, 0);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // Start cycle. Scroll left to third item.
  // Current order is [2,5,4,3,1].
  scroll_until_window_highlighted_and_confirm(-horizontal_scroll * 3, 0);
  EXPECT_TRUE(wm::IsActiveWindow(window4.get()));

  // Start cycle. Scroll right to second item.
  // Current order is [4,2,5,3,1].
  scroll_until_window_highlighted_and_confirm(horizontal_scroll, 0);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // Open an overview session.
  EnterOverview();
  EXPECT_TRUE(InOverviewSession());

  // Open the window cycle list. Scroll right to second item. Overview mode
  // should be dismissed at this point as they do the same thing by design.
  // Current order is [2,4,5,3,1].
  auto* cycle_controller = Shell::Get()->window_cycle_controller();
  cycle_controller->StartCycling(/*same_app_only=*/false);
  Scroll(horizontal_scroll, 0, kNumFingersForTrackpad);
  EXPECT_FALSE(InOverviewSession());

  CompleteCycling(cycle_controller);
  EXPECT_FALSE(InOverviewSession());
  EXPECT_TRUE(wm::IsActiveWindow(window4.get()));
}

// Tests two finger horizontal scroll gesture to move selection left or right.
TEST_F(WindowCycleControllerTest, TwoFingerHorizontalScrollInWindowCycleList) {
  const gfx::Rect bounds(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds);
  const float horizontal_scroll =
      WindowCycleEventFilter::kHorizontalThresholdDp;

  auto scroll_until_window_highlighted_and_confirm = [this](float x_offset,
                                                            float y_offset) {
    WindowCycleController* controller = Shell::Get()->window_cycle_controller();
    controller->StartCycling(/*same_app_only=*/false);
    // Since two finger swipes are negated, negate in tests to mimic how this
    // actually behaves on devices.
    Scroll(-x_offset, GetOffsetY(y_offset), kNumFingersForMouseWheel);
    CompleteCycling(controller);
  };

  // Start cycle, simulating alt key being held down. Scroll right to fourth
  // item.
  // Current order is [5,4,3,2,1].
  scroll_until_window_highlighted_and_confirm(horizontal_scroll * 3, 0);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // Start cycle. Scroll left to third item.
  // Current order is [2,5,4,3,1].
  scroll_until_window_highlighted_and_confirm(-horizontal_scroll * 3, 0);
  EXPECT_TRUE(wm::IsActiveWindow(window4.get()));

  // Start cycle. Scroll right to second item.
  // Current order is [4,2,5,3,1].
  scroll_until_window_highlighted_and_confirm(horizontal_scroll, 0);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
}

// Tests mouse wheel scroll gesture to move selection left or right.
TEST_F(WindowCycleControllerTest, MouseWheelScrollInWindowCycleList) {
  const gfx::Rect bounds(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds);
  const float horizontal_scroll =
      WindowCycleEventFilter::kHorizontalThresholdDp;

  auto scroll_until_window_highlighted_and_confirm = [this](float x_offset,
                                                            float y_offset,
                                                            int num_of_times) {
    WindowCycleController* controller = Shell::Get()->window_cycle_controller();
    controller->StartCycling(/*same_app_only=*/false);
    MouseWheelScroll(x_offset, y_offset, num_of_times);
    CompleteCycling(controller);
  };

  // Start cycle, simulating alt key being held down. Scroll right to fourth
  // item.
  // Current order is [5,4,3,2,1].
  scroll_until_window_highlighted_and_confirm(0, -horizontal_scroll, 3);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // Start cycle. Scroll left to third item.
  // Current order is [2,5,4,3,1].
  scroll_until_window_highlighted_and_confirm(0, horizontal_scroll, 3);
  EXPECT_TRUE(wm::IsActiveWindow(window4.get()));

  // Start cycle. Scroll right to second item.
  // Current order is [4,2,5,3,1].
  scroll_until_window_highlighted_and_confirm(0, -horizontal_scroll, 1);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
}

// Tests that swiping up closes window cycle if it's open and starts overview
// mode.
TEST_F(WindowCycleControllerTest, VerticalScroll) {
  std::unique_ptr<aura::Window> window1 = CreateTestWindow();
  std::unique_ptr<aura::Window> window2 = CreateTestWindow();
  const float vertical_scroll = 2 * WmGestureHandler::kVerticalThresholdDp;
  const float horizontal_scroll =
      WindowCycleEventFilter::kHorizontalThresholdDp;
  auto* window_cycle_controller = Shell::Get()->window_cycle_controller();

  // Start cycling and then swipe up to open up overview.
  window_cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(window_cycle_controller->IsCycling());
  Scroll(0, vertical_scroll, 3);
  EXPECT_TRUE(InOverviewSession());
  EXPECT_FALSE(window_cycle_controller->IsCycling());

  // Start cycling and then swipe down.
  window_cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(window_cycle_controller->IsCycling());
  Scroll(0, -vertical_scroll, 3);
  EXPECT_TRUE(window_cycle_controller->IsCycling());

  // Swipe diagonally with horizontal bias.
  Scroll(horizontal_scroll * 3, vertical_scroll, 3);
  EXPECT_TRUE(window_cycle_controller->IsCycling());
  EXPECT_FALSE(InOverviewSession());

  // Swipe diagonally with vertical bias.
  Scroll(horizontal_scroll, vertical_scroll, 3);
  EXPECT_FALSE(window_cycle_controller->IsCycling());
  EXPECT_TRUE(InOverviewSession());
}

// Tests that touch continuous scrolls for the window cycle list.
TEST_F(WindowCycleControllerTest, TouchScroll) {
  UpdateDisplay("900x600");
  const gfx::Rect bounds(0, 0, 200, 200);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  auto* cycle_controller = Shell::Get()->window_cycle_controller();
  auto* event_generator = GetEventGenerator();

  // Start cycling.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  ASSERT_TRUE(cycle_controller->IsCycling());
  ASSERT_EQ(window2.get(), GetTargetWindow());

  // There should be five preview items and the first three should be contained
  // by the screen. The fourth should be in the screen, but not contained. The
  // last one should not be in the screen at all.
  auto preview_items = GetWindowCycleItemViews();
  ASSERT_EQ(5u, preview_items.size());
  auto cycle_view_bounds =
      GetWindowCycleListWidget()->GetWindowBoundsInScreen();
  ASSERT_TRUE(cycle_view_bounds.x() <
              preview_items[0]->GetBoundsInScreen().x());
  ASSERT_TRUE(preview_items[2]->GetBoundsInScreen().x() <
              cycle_view_bounds.right());
  ASSERT_TRUE(cycle_view_bounds.right() <
              preview_items[3]->GetBoundsInScreen().right());
  ASSERT_TRUE(preview_items[3]->GetBoundsInScreen().x() <
              cycle_view_bounds.right());
  ASSERT_TRUE(cycle_view_bounds.right() <
              preview_items[4]->GetBoundsInScreen().x());

  // Drag from the middle of the first item to the right. The preview items
  // should not move since we're at the beginning of the cycle list. Also the
  // focus ring should not move.
  auto drag_origin = preview_items[0]->GetBoundsInScreen().CenterPoint();
  auto drag_dest = preview_items[1]->GetBoundsInScreen().CenterPoint();
  event_generator->GestureScrollSequence(drag_origin, drag_dest,
                                         base::Seconds(1), 10);
  EXPECT_EQ(drag_origin, preview_items[0]->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(window2.get(), GetTargetWindow());

  // Drag from the middle of the second item to the left. The item should follow
  // the cursor and the focus ring should not move.
  drag_origin = preview_items[1]->GetBoundsInScreen().CenterPoint();
  drag_dest = preview_items[0]->GetBoundsInScreen().CenterPoint();
  event_generator->GestureScrollSequence(drag_origin, drag_dest,
                                         base::Seconds(1), 10);
  EXPECT_TRUE(base::IsApproximatelyEqual(
      drag_dest.x(), preview_items[1]->GetBoundsInScreen().CenterPoint().x(),
      10));
  EXPECT_TRUE(preview_items[0]->GetBoundsInScreen().CenterPoint().x() <
              cycle_view_bounds.x());
  EXPECT_EQ(window2.get(), GetTargetWindow());

  // The last preview item should now be visible, but it shouldn't be contained.
  EXPECT_TRUE(preview_items[4]->GetBoundsInScreen().x() <
              cycle_view_bounds.right());
  EXPECT_TRUE(cycle_view_bounds.right() <
              preview_items[4]->GetBoundsInScreen().right());

  // Drag from the middle of the fourth item to the left one preview item's
  // width. Since the last item is already visible, the mirror container should
  // not be dragged the full amount and the last item's right edge should be at
  // the end of the cycle view.
  drag_origin = preview_items[3]->GetBoundsInScreen().CenterPoint();
  drag_dest = preview_items[1]->GetBoundsInScreen().CenterPoint();
  event_generator->GestureScrollSequence(drag_origin, drag_dest,
                                         base::Seconds(1), 10);
  EXPECT_EQ(cycle_view_bounds.right(),
            preview_items[4]->GetBoundsInScreen().right() +
                WindowCycleView::kInsideBorderHorizontalPaddingDp);
  EXPECT_EQ(window2.get(), GetTargetWindow());

  // Diagonally drag from the middle of the fourth item to the right, ending up
  // outside of the cycle view. This should still drag the full distance.
  drag_origin = preview_items[3]->GetBoundsInScreen().CenterPoint();
  drag_dest = preview_items[4]->GetBoundsInScreen().CenterPoint();
  drag_dest.set_y(cycle_view_bounds.bottom() + 100);
  event_generator->GestureScrollSequence(drag_origin, drag_dest,
                                         base::Seconds(1), 10);
  EXPECT_TRUE(base::IsApproximatelyEqual(
      drag_dest.x(), preview_items[3]->GetBoundsInScreen().CenterPoint().x(),
      10));
}

// Tests that a vertical touch scroll doesn't crash. See crbug.com/1224969.
TEST_F(WindowCycleControllerTest, VerticalTouchScroll) {
  const gfx::Rect bounds(0, 0, 200, 200);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  auto* cycle_controller = Shell::Get()->window_cycle_controller();
  auto* event_generator = GetEventGenerator();

  // Start cycling.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  ASSERT_TRUE(cycle_controller->IsCycling());
  ASSERT_EQ(window2.get(), GetTargetWindow());

  // Vertical touch scroll from the second item. This will cause a
  // ui::EventType::kScrollFlingStart event to be generated. This should not
  // crash and do nothing to the window cycle list.
  auto preview_items = GetWindowCycleItemViews();
  auto drag_origin = preview_items[0]->GetBoundsInScreen().CenterPoint();
  auto drag_dest = drag_origin + gfx::Vector2d(0, 200);
  event_generator->GestureScrollSequence(drag_origin, drag_dest,
                                         base::Milliseconds(10), 10);
  EXPECT_EQ(drag_origin, preview_items[0]->GetBoundsInScreen().CenterPoint());
  EXPECT_EQ(window2.get(), GetTargetWindow());
}

// When a user taps on an item, it should set the focus ring to that item. After
// they release their finger it should confirm the selection.
TEST_F(WindowCycleControllerTest, TapSelect) {
  std::unique_ptr<aura::Window> w0 = CreateTestWindow();
  std::unique_ptr<aura::Window> w1 = CreateTestWindow();
  std::unique_ptr<aura::Window> w2 = CreateTestWindow();
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  auto generate_gesture_event = [](ui::test::EventGenerator* generator,
                                   const gfx::Point& location,
                                   ui::EventType type) {
    ui::GestureEvent event(location.x(), location.y(),
                           /*flags=*/0, base::TimeTicks::Now(),
                           ui::GestureEventDetails{type});
    generator->Dispatch(&event);
  };

  auto tap_without_release = [generate_gesture_event](
                                 ui::test::EventGenerator* generator,
                                 const gfx::Point& location) {
    // Generates the following events at |location| in the given order:
    // EventType::kGestureBegin, EventType::kGestureTapDown,
    // EventType::kGestureShowPress
    generate_gesture_event(generator, location, ui::EventType::kGestureBegin);
    generate_gesture_event(generator, location, ui::EventType::kGestureTapDown);
    generate_gesture_event(generator, location,
                           ui::EventType::kGestureShowPress);
  };

  // Start cycle and tap third item without releasing finger. On tap down, the
  // focus ring should be set to the third item. Selection should not be
  // confirmed since finger was not released. Starting order of windows in cycle
  // list is [2,1,0].
  controller->StartCycling(/*same_app_only=*/false);
  gfx::Point center_point =
      GetWindowCycleItemViews()[2]->GetBoundsInScreen().CenterPoint();
  tap_without_release(generator, center_point);
  EXPECT_TRUE(controller->IsCycling());
  EXPECT_EQ(GetTargetWindow(), w0.get());

  // Complete cycling and confirm window 0 is active.
  CompleteCycling(controller);
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));

  // Start cycle and tap second item without releasing finger. On tap down, the
  // focus ring should be set to the second item. Selection should not be
  // confirmed since finger was not released. Starting order of windows in cycle
  // list is [0,2,1].
  controller->StartCycling(/*same_app_only=*/false);
  center_point =
      GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint();
  tap_without_release(generator, center_point);
  EXPECT_TRUE(controller->IsCycling());
  EXPECT_EQ(GetTargetWindow(), w2.get());

  // Complete cycling and confirm window 2 is active.
  CompleteCycling(controller);
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // Start cycling again and tap and release.  This should confirm the
  // selection. Starting order of windows in cycle list is [2,0,1].
  controller->StartCycling(/*same_app_only=*/false);
  center_point =
      GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint();
  generator->GestureTapDownAndUp(center_point);
  EXPECT_FALSE(controller->IsCycling());
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));
}

// Tests that releasing alt key while continuing tapping on the window cycle
// list doesn't lead to a crash. Regression test for https://crbug.com/1228381.
TEST_F(WindowCycleControllerTest, AltReleaseWithoutReleasingTap) {
  std::unique_ptr<Window> w0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> w1(CreateTestWindowInShellWithId(1));
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Start window cycling by press Alt + Tab key.
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowState::Get(w0.get())->Activate();
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_TRUE(controller->IsCycling());
  generator->ReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_TRUE(controller->IsCycling());

  // Tapping on the window cycle list without releasing the tap.
  gfx::Point center_point =
      GetWindowCycleItemViews()[1]->GetBoundsInScreen().CenterPoint();
  generator->PressTouch(center_point);

  // Release the alt key. Make sure no crash happens.
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_FALSE(controller->IsCycling());
}

// Tests that pressing arrow key before cycle view UI is ready doesn't lead to a
// crash. Regression test for https://crbug.com/1246251.
TEST_F(WindowCycleControllerTest, ArrowKeyBeforeCycleViewUI) {
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  std::unique_ptr<Window> w0(CreateTestWindowInShellWithId(0));
  std::unique_ptr<Window> w1(CreateTestWindowInShellWithId(1));
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  // Enable initial delay for testing so that once it starts cycling, the cycle
  // view UI will not be shown right away.
  WindowCycleList::SetDisableInitialDelayForTesting(false);
  controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(controller->IsCycling());
  EXPECT_FALSE(CycleViewExists());
  controller->HandleKeyboardNavigation(
      WindowCycleController::KeyboardNavDirection::kUp);
  controller->HandleKeyboardNavigation(
      WindowCycleController::KeyboardNavDirection::kDown);
  controller->HandleKeyboardNavigation(
      WindowCycleController::KeyboardNavDirection::kLeft);
  controller->HandleKeyboardNavigation(
      WindowCycleController::KeyboardNavDirection::kRight);
  CompleteCycling(controller);
}

// Tests that raster scale is not set for alt-tab on visible windows.
TEST_F(WindowCycleControllerTest, RasterScaleNotSetForVisibleWindows) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();

  std::unique_ptr<Window> window0(CreateAppWindow(gfx::Rect(600, 600)));
  std::unique_ptr<Window> window1(CreateAppWindow(gfx::Rect(600, 600)));
  wm::ActivateWindow(window0.get());

  auto tracker0 = RasterScaleChangeTracker(window0.get());
  auto tracker1 = RasterScaleChangeTracker(window1.get());

  // Simulate pressing and releasing Alt-tab.
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
  controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  EXPECT_EQ(std::vector<float>{}, tracker0.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{}, tracker1.TakeRasterScaleChanges());

  CompleteCycling(controller);

  EXPECT_EQ(std::vector<float>{}, tracker0.TakeRasterScaleChanges());
  EXPECT_EQ(std::vector<float>{}, tracker1.TakeRasterScaleChanges());
}

// Tests the UAF issue reported in https://crbug.com/1350558. `OnFlingStep()`
// triggers layout, which may trigger an `OnFlingEnd()` where the
// `WmFlingHandler` is destroyed while still in the middle of its
// `WmFlingHandler::OnAnimationStep()`. This test simulates the use case when we
// initiate an alt + tab session, start a fling, trigger another alt + tab and
// make sure this doesn't trigger a UAF crash in ASAN builds.
TEST_F(WindowCycleControllerTest, SimulateFlingInAltTab) {
  ui::ScopedAnimationDurationScaleMode animation_scale(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  std::unique_ptr<Window> w0 = CreateTestWindow();
  std::unique_ptr<Window> w1 = CreateTestWindow();
  std::unique_ptr<Window> w2 = CreateTestWindow();
  std::unique_ptr<Window> w3 = CreateTestWindow();

  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());

  auto preview_items = GetWindowCycleItemViews();
  EXPECT_EQ(preview_items.size(), 4u);
  const auto cycle_item_view_bounds = preview_items[1]->GetBoundsInScreen();

  const gfx::Point start_point = cycle_item_view_bounds.CenterPoint();
  const gfx::Point target_point = start_point + gfx::Vector2d(50, 0);

  GetEventGenerator()->GestureScrollSequence(start_point, target_point,
                                             base::Milliseconds(10), 2);
  base::RunLoop().RunUntilIdle();
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(cycle_controller->IsCycling());
}

TEST_F(WindowCycleControllerTest, WindowCycleItemViewAccessibleProperties) {
  std::unique_ptr<Window> window = CreateTestWindow();
  std::unique_ptr<WindowCycleItemView> item_view =
      std::make_unique<WindowCycleItemView>(window.get());

  ui::AXNodeData data;
  item_view->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.role, ax::mojom::Role::kWindow);
  // Default title for test window.
  ASSERT_EQ(window->GetTitle(), u"Window -1");
  EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
            u"Window -1");
  EXPECT_FALSE(data.HasState(ax::mojom::State::kIgnored));

  // Test when source window title is empty.
  data = ui::AXNodeData();
  item_view->source_window()->SetTitle(std::u16string());
  item_view->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.GetStringAttribute(ax::mojom::StringAttribute::kName),
            l10n_util::GetStringUTF8(IDS_WM_WINDOW_CYCLER_UNTITLED_WINDOW));

  // Test that accessible name is updated when source window title changes.
  data = ui::AXNodeData();
  item_view->source_window()->SetTitle(u"Some title");
  item_view->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
            u"Some title");

  // Test that view is hidden to a11y when source window is destroyed.
  item_view->OnWindowDestroying(window.get());
  ASSERT_TRUE(item_view);
  data = ui::AXNodeData();
  item_view->GetViewAccessibility().GetAccessibleNodeData(&data);
  EXPECT_TRUE(data.HasState(ax::mojom::State::kIgnored));
}

class ReverseGestureWindowCycleControllerTest
    : public WindowCycleControllerTest {
 public:
  ReverseGestureWindowCycleControllerTest() = default;
  ReverseGestureWindowCycleControllerTest(
      const ReverseGestureWindowCycleControllerTest&) = delete;
  ReverseGestureWindowCycleControllerTest& operator=(
      const ReverseGestureWindowCycleControllerTest&) = delete;
  ~ReverseGestureWindowCycleControllerTest() override = default;

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

    // Set natural scroll on.
    PrefService* pref =
        Shell::Get()->session_controller()->GetActivePrefService();
    pref->SetBoolean(prefs::kTouchpadEnabled, true);
    pref->SetBoolean(prefs::kNaturalScroll, true);
    pref->SetBoolean(prefs::kMouseReverseScroll, true);
  }
};

// Tests mouse wheel scroll gesture to move selection left or right. Mouse
// reverse scroll should reverse its direction.
TEST_F(ReverseGestureWindowCycleControllerTest,
       MouseWheelScrollInWindowCycleList) {
  const gfx::Rect bounds(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds);
  const float horizontal_scroll =
      WindowCycleEventFilter::kHorizontalThresholdDp;

  auto scroll_until_window_highlighted_and_confirm = [this](float x_offset,
                                                            float y_offset,
                                                            int num_of_times) {
    WindowCycleController* controller = Shell::Get()->window_cycle_controller();
    controller->StartCycling(/*same_app_only=*/false);
    MouseWheelScroll(x_offset, y_offset, num_of_times);
    CompleteCycling(controller);
  };

  // Start cycle, simulating alt key being held down. Scroll right to fourth
  // item.
  // Current order is [5,4,3,2,1].
  scroll_until_window_highlighted_and_confirm(0, horizontal_scroll, 3);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // Start cycle. Scroll left to third item.
  // Current order is [2,5,4,3,1].
  scroll_until_window_highlighted_and_confirm(0, -horizontal_scroll, 3);
  EXPECT_TRUE(wm::IsActiveWindow(window4.get()));

  // Start cycle. Scroll right to second item.
  // Current order is [4,2,5,3,1].
  scroll_until_window_highlighted_and_confirm(0, horizontal_scroll, 1);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));

  // Turn mouse reverse scroll off.
  PrefService* pref =
      Shell::Get()->session_controller()->GetActivePrefService();
  pref->SetBoolean(prefs::kMouseReverseScroll, false);

  // Start cycle. Scroll left once.
  // Current order is [2,4,5,3,1].
  scroll_until_window_highlighted_and_confirm(0, horizontal_scroll, 1);
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Start cycle. Scroll right once.
  // Current order is [1,2,4,5,3].
  scroll_until_window_highlighted_and_confirm(0, -horizontal_scroll, 1);
  EXPECT_TRUE(wm::IsActiveWindow(window2.get()));
}

// Tests that natural scroll affects two finger horizontal scrolling for the
// window cycle list, and doesn't affect three finger scrolling.
TEST_F(ReverseGestureWindowCycleControllerTest,
       WindowCycleListTrackpadGestures) {
  const gfx::Rect bounds(0, 0, 400, 400);
  std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds);
  std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds);
  const float horizontal_scroll =
      WindowCycleEventFilter::kHorizontalThresholdDp;

  auto scroll_until_window_highlighted_and_confirm = [this](float x_offset,
                                                            float y_offset,
                                                            int num_fingers) {
    WindowCycleController* controller = Shell::Get()->window_cycle_controller();
    controller->StartCycling(/*same_app_only=*/false);
    Scroll(x_offset, y_offset, num_fingers);
    CompleteCycling(controller);
  };

  // Start cycle, scroll right with two finger gesture. Note: two finger swipes
  // are negated, so negate in tests to mimic how this actually behaves on
  // devices.
  // Current order is [5,4,3,2,1].
  scroll_until_window_highlighted_and_confirm(-horizontal_scroll, 0,
                                              kNumFingersForMouseWheel);
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Start cycle, scroll right with three finger gesture.
  // Current order is [1,5,4,3,2].
  scroll_until_window_highlighted_and_confirm(horizontal_scroll, 0,
                                              kNumFingersForTrackpad);
  EXPECT_TRUE(wm::IsActiveWindow(window5.get()));

  // Turn natural scroll off.
  PrefService* pref =
      Shell::Get()->session_controller()->GetActivePrefService();
  pref->SetBoolean(prefs::kNaturalScroll, false);

  // Start cycle, scroll right with two finger gesture. Note: two finger swipes
  // are negated, so negate in tests to mimic how this actually behaves on
  // devices.
  // Current order is [5,1,4,3,2].
  scroll_until_window_highlighted_and_confirm(-horizontal_scroll, 0,
                                              kNumFingersForMouseWheel);
  EXPECT_TRUE(wm::IsActiveWindow(window1.get()));

  // Start cycle, scroll right with three finger gesture.
  // Current order is [1,5,4,3,2].
  scroll_until_window_highlighted_and_confirm(horizontal_scroll, 0,
                                              kNumFingersForTrackpad);
  EXPECT_TRUE(wm::IsActiveWindow(window5.get()));
}

class ModeSelectionWindowCycleControllerTest
    : public WindowCycleControllerTest {
 public:
  ModeSelectionWindowCycleControllerTest() = default;
  ModeSelectionWindowCycleControllerTest(
      const ModeSelectionWindowCycleControllerTest&) = delete;
  ModeSelectionWindowCycleControllerTest& operator=(
      const ModeSelectionWindowCycleControllerTest&) = delete;
  ~ModeSelectionWindowCycleControllerTest() override = default;

  // WindowCycleControllerTest:
  void SetUp() override {
    WindowCycleControllerTest::SetUp();
    generator_ = GetEventGenerator();
  }

  void SwitchPerDeskAltTabMode(bool per_desk_mode,
                               bool use_slow_duration = false) {
    ui::ScopedAnimationDurationScaleMode animation_scale(
        use_slow_duration
            ? ui::ScopedAnimationDurationScaleMode::SLOW_DURATION
            : ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
    gfx::Point button_center =
        GetWindowCycleTabSliderButtons()[per_desk_mode ? 1 : 0]
            ->GetBoundsInScreen()
            .CenterPoint();
    generator_->MoveMouseTo(button_center);
    generator_->ClickLeftButton();
    EXPECT_EQ(per_desk_mode,
              Shell::Get()->window_cycle_controller()->IsAltTabPerActiveDesk());
  }

  bool IsAnimatingModeSwitch() const {
    return WindowCycleListTestApi(GetCycleList()).IsCycleViewAnimating();
  }

 private:
  raw_ptr<ui::test::EventGenerator, DanglingUntriaged> generator_;
};

// Tests that when user taps tab slider buttons, the active mode should
// correspondingly change.
TEST_F(ModeSelectionWindowCycleControllerTest, ModeChangesOnTap) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Create one window for desk1 and two windows for desk2.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win1 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win2 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  auto generate_gesture_event = [](ui::test::EventGenerator* generator,
                                   const gfx::Point& location,
                                   ui::EventType type) {
    ui::GestureEvent event(location.x(), location.y(),
                           /*flags=*/0, base::TimeTicks::Now(),
                           ui::GestureEventDetails{type});
    generator->Dispatch(&event);
  };

  auto tap = [generate_gesture_event](ui::test::EventGenerator* generator,
                                      const gfx::Point& location) {
    // Generates the following events at |location| in the given order:
    // EventType::kGestureBegin, EventType::kGestureTapDown,
    // ui::EventType::kGestureShowPress, EventType::kGestureEnd
    generate_gesture_event(generator, location, ui::EventType::kGestureBegin);
    generate_gesture_event(generator, location, ui::EventType::kGestureTapDown);
    generate_gesture_event(generator, location,
                           ui::EventType::kGestureShowPress);
    generate_gesture_event(generator, location, ui::EventType::kGestureTap);
    generate_gesture_event(generator, location, ui::EventType::kGestureEnd);
  };

  // Start cycle. Alt-tab should contain windows from all desks with tab slider.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  auto tab_slider_buttons = GetWindowCycleTabSliderButtons();
  EXPECT_EQ(2u, tab_slider_buttons.size());

  // Tap current desk tab slider button. Switch to current desk mode.
  gfx::Point current_desk_button_center_point =
      tab_slider_buttons[1]->GetBoundsInScreen().CenterPoint();
  tap(generator, current_desk_button_center_point);
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);

  // Tap all desks tab slider button. Switch back to all desks mode.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  tab_slider_buttons = GetWindowCycleTabSliderButtons();
  gfx::Point all_desks_button_center_point =
      tab_slider_buttons[0]->GetBoundsInScreen().CenterPoint();
  tap(generator, all_desks_button_center_point);
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);
}

// Tests that when user taps tab slider buttons, but then scrolls and releases
// finger on a window. Mode change should not happen in this use case.
TEST_F(ModeSelectionWindowCycleControllerTest,
       TapTabSliderButtonButReleaseOnWindow) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Create one window for desk1 and two windows for desk2.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win1 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win2 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  auto generate_gesture_event = [](ui::test::EventGenerator* generator,
                                   const gfx::Point& location,
                                   ui::EventType type) {
    ui::GestureEvent event(location.x(), location.y(),
                           /*flags=*/0, base::TimeTicks::Now(),
                           ui::GestureEventDetails{type});
    generator->Dispatch(&event);
  };

  auto tap_then_scroll = [generate_gesture_event](
                             ui::test::EventGenerator* generator,
                             const gfx::Point& location) {
    // Generates the following events at |location| in the given order:
    // EventType::kGestureBegin, EventType::kGestureTapDown,
    // T_GESTURE_SCROLL_BEGIN, ui::EventType::kGestureScrollUpdate
    generate_gesture_event(generator, location, ui::EventType::kGestureBegin);
    generate_gesture_event(generator, location, ui::EventType::kGestureTapDown);
    generate_gesture_event(generator, location,
                           ui::EventType::kGestureScrollBegin);
    generate_gesture_event(generator, location,
                           ui::EventType::kGestureScrollUpdate);
  };

  auto scroll_update = [generate_gesture_event](
                           ui::test::EventGenerator* generator,
                           const gfx::Point& location) {
    // Generates the following events at |location| in the given order:
    // EventType::kGestureScrollUpdate, EventType::kGestureScrollEnd,
    // EventType::kGestureEnd
    generate_gesture_event(generator, location,
                           ui::EventType::kGestureScrollUpdate);
    generate_gesture_event(generator, location,
                           ui::EventType::kGestureScrollEnd);
    generate_gesture_event(generator, location, ui::EventType::kGestureEnd);
  };

  // Start cycle. Alt-tab should contain windows from all desks with tab slider.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  auto tab_slider_buttons = GetWindowCycleTabSliderButtons();
  EXPECT_EQ(2u, tab_slider_buttons.size());

  // Tap current desk tab slider button, scroll from it to one window,
  // mode change should not happen. It's still the all desks mode.
  gfx::Point current_desk_button_center_point =
      tab_slider_buttons[1]->GetBoundsInScreen().CenterPoint();
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  tab_slider_buttons = GetWindowCycleTabSliderButtons();
  cycle_windows = GetWindows(cycle_controller);
  current_desk_button_center_point =
      tab_slider_buttons[1]->GetBoundsInScreen().CenterPoint();
  gfx::Point window_center_point =
      cycle_windows[0]->GetBoundsInScreen().CenterPoint();
  tap_then_scroll(generator, current_desk_button_center_point);
  scroll_update(generator, window_center_point);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);
}

// Tests that if user uses only one desk, the tab slider and no recent items
// are not shown. Moreover, `SetAltTabMode()` should not change the windows
// list.
TEST_F(ModeSelectionWindowCycleControllerTest, SingleDeskHidesInteractiveMode) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two windows in the current desk.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  ASSERT_EQ(1u, desks_controller->desks().size());

  // Alt-tab should contain windows from all desks without any the tab slider
  // and no-recent-items view.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(!GetWindowCycleNoRecentItemsLabel());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(2u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  const gfx::Rect alttab_bounds_without_tab_slider =
      GetWindowCycleListWidget()->GetWindowBoundsInScreen();
  CompleteCycling(cycle_controller);

  // Create an empty desk_2 and start alt-tab to enter the all-desks mode.
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());

  // Expect mode-switching buttons and no-recent-item label to exist.
  EXPECT_FALSE(!GetWindowCycleNoRecentItemsLabel());
  auto tab_slider_buttons = GetWindowCycleTabSliderButtons();
  EXPECT_EQ(2u, tab_slider_buttons.size());
  const gfx::Rect alttab_bounds_with_tab_slider =
      GetWindowCycleListWidget()->GetWindowBoundsInScreen();
  const int window_cycle_list_y =
      GetWindowCycleItemViews()[0]->GetBoundsInScreen().y();
  const gfx::Rect tab_slider_button_bound =
      tab_slider_buttons[0]->GetBoundsInScreen();
  // Expect that alt-tab views height is larger due to the tab slider insertion
  // and expect that window cycle list is placed below the tab slider.
  EXPECT_LT(alttab_bounds_without_tab_slider.height(),
            alttab_bounds_with_tab_slider.height());
  EXPECT_LT(tab_slider_button_bound.y() + tab_slider_button_bound.height(),
            window_cycle_list_y);

  CompleteCyclingAndDeskSwitching(cycle_controller);
}

// Tests that alt-tab shows all windows in an all-desk mode by default and
// shows only windows in the current desk in a current-desk mode. Switching
// between two modes should refresh the window list, while re-entering alt-tab
// should display the most recently selected mode.
TEST_F(ModeSelectionWindowCycleControllerTest, CycleShowsWindowsPerMode) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two windows for desk1 and three windows for desk2.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win3 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  auto win4 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  // By default should contain windows from all desks.
  auto* generator = GetEventGenerator();
  // Press and hold an alt key to test that alt + left clicking a button works.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(5u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, win0.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win2.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win3.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win4.get()));

  // Switching alt-tab to the current-desk mode should show windows in the
  // active desk.
  SwitchPerDeskAltTabMode(true);
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, win2.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win3.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win4.get()));
  CompleteCycling(cycle_controller);

  // Activate desk1 and start alt-tab.
  const Desk* desk_1 = desks_controller->GetDeskAtIndex(0);
  ActivateDesk(desk_1);
  cycle_controller->StartCycling(/*same_app_only=*/false);
  // Should start alt-tab with the current-desk mode and show only two windows
  // from desk1.
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, win0.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));

  // Switch to the all-desks mode, check and stop alt-tab.
  SwitchPerDeskAltTabMode(false);
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(5u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());

  CompleteCyclingAndDeskSwitching(cycle_controller);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
}

// For one window in the current-desk mode, if there are other windows in other
// desk, tests that alt-tab view always shows up, so the user can switch mode
// and select other windows.
TEST_F(ModeSelectionWindowCycleControllerTest, OneWindowInActiveDesk) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two desks with a window each.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win1 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));

  // Starting alt-tab should shows all desks.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());

  // Switching to an active desk mode should shows a single window in desk2.
  SwitchPerDeskAltTabMode(true);
  EXPECT_TRUE(cycle_controller->IsCycling());
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(1u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));
  CompleteCycling(cycle_controller);

  // Re-opening alt-tab again in the current-desk mode should work because the
  // user should be able to switch to the window in other desks.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(cycle_controller->IsCycling());
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(1u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, win1.get()));
  CompleteCycling(cycle_controller);
}

// For one window in the current-desk mode, if there is no other window in
// other desks, tests that alt-tab does not show up, but still activates the
// window properly.
TEST_F(ModeSelectionWindowCycleControllerTest, OneWindowTotalInActiveDesk) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two desk with only one window in the current desk.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));

  // Starting alt-tab should not show the view and only activate the window.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_FALSE(CycleViewExists());
  EXPECT_TRUE(wm::IsActiveWindow(win0.get()));
  EXPECT_TRUE(WindowState::Get(win0.get())->IsActive());
  CompleteCycling(cycle_controller);

  // Tests that the same behavior for an inactive window.
  WindowState::Get(win0.get())->Minimize();
  EXPECT_FALSE(wm::IsActiveWindow(win0.get()));
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_FALSE(CycleViewExists());
  EXPECT_TRUE(wm::IsActiveWindow(win0.get()));
  EXPECT_TRUE(WindowState::Get(win0.get())->IsActive());
  CompleteCycling(cycle_controller);
}

// For no window in the current-desk mode, if there are other windows in other
// desk, tests that alt-tab view always shows up, so the user can switch mode
// and select other windows. Additionally, tests that while the focus is on the
// tab slider button, pressing the Down arrow key does nothing.
TEST_F(ModeSelectionWindowCycleControllerTest, NoWindowInActiveDesk) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Create two desks with a window in desk1.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);

  // Activate desk2.
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());

  // Starting alt-tab should show all windows from all desks.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_FALSE(GetWindowCycleNoRecentItemsLabel()->GetVisible());

  // Switching to an current-desk mode should not show any mirror window
  // and should display "no recent items" label.
  SwitchPerDeskAltTabMode(true);
  EXPECT_TRUE(cycle_controller->IsCycling());
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(0u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(GetWindowCycleNoRecentItemsLabel()->GetVisible());

  // Switching back to an all-desks mode should hide the label.
  SwitchPerDeskAltTabMode(false);
  EXPECT_FALSE(GetWindowCycleNoRecentItemsLabel()->GetVisible());

  // Focus the current-desk button and make sure that pressing Down arrow
  // key does nothing, i.e. the focus remains on the mode button.
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  generator->PressKey(ui::VKEY_DOWN, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());

  CompleteCycling(cycle_controller);

  // Entering alt-tab in the current-desk mode with zero window should work and
  // show no-recent-item label.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(0u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_EQ(nullptr, GetTargetWindow());
  EXPECT_TRUE(GetWindowCycleNoRecentItemsLabel()->GetVisible());
}

// For no window in the current-desk mode, if there is no other window in
// other desks, tests that alt-tab does not show up.
TEST_F(ModeSelectionWindowCycleControllerTest, NoWindowTotalInActiveDesk) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two desks with all two windows in desk1.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);

  // Activate desk2.
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());

  // Starting alt-tab should show all windows from all desks.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_FALSE(CycleViewExists());
  CompleteCycling(cycle_controller);
}

// Tests that switching between modes correctly reset the alt-tab-highlighted
// window to the second most recently used window, i.e. the next window to tab
// into from the currently used window. Since the window cycle list is ordered
// by MRU, such window is therefore the second window in the MRU list.
TEST_F(ModeSelectionWindowCycleControllerTest,
       SwitchingModeUpdatesWindowHighlight) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two windows for desk1 and three windows for desk2 in the reversed
  // order of the most recently active window.
  auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win1 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  auto win0 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  // Enter the all-desk mode by default with the window order [0, 1, 2, 3 ,4].
  cycle_controller->StartCycling(/*same_app_only=*/false);
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);

  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  auto cycle_windows = GetWindows(cycle_controller);
  // The window list is MRU ordered.
  EXPECT_EQ(win0.get(), cycle_windows[0]);
  EXPECT_EQ(win1.get(), cycle_windows[1]);
  EXPECT_EQ(win2.get(), cycle_windows[2]);
  EXPECT_EQ(win3.get(), cycle_windows[3]);
  EXPECT_EQ(win4.get(), cycle_windows[4]);
  // Alt-Tab should highlight the second most recently used window, which is
  // the second window in the MRU list, win1.
  EXPECT_EQ(win1.get(), GetTargetWindow());

  // Step to win2 and win3, so we are now select a window in a non-active desk.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win2.get(), GetTargetWindow());
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win3.get(), GetTargetWindow());

  // Switching from the all-desks mode, which highlights a non-current-desk
  // window to the current-desk mode [0, 1, 2] should resolve the highlight
  // correctly to win1, the second window in the cycle list.
  SwitchPerDeskAltTabMode(true);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  EXPECT_EQ(win1.get(), cycle_windows[1]);
  // Step to win2.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win2.get(), GetTargetWindow());

  // Switching back to the all-desk mode should reset highlight to win1 again.
  SwitchPerDeskAltTabMode(false);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  CompleteCycling(cycle_controller);
}

// Similar to `SwitchingModeUpdatesWindowHighlight`, tests that switching the
// alt-tab mode updates the highlighted window to the first window (most
// recently used) in the special case where all windows are minimized.
// When they are minimized, cycling forward should help unminimize the most
// recently used window rather than trying to open the second most recently
// used window.
TEST_F(ModeSelectionWindowCycleControllerTest,
       SwitchingModeUpdatesMinimizedWindowHighlight) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two windows for desk1 and three windows for desk2 in the reversed
  // order of the most recently active window.
  auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win1 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  auto win0 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  // Minimize all windows to test this special case.
  WindowState::Get(win4.get())->Minimize();
  WindowState::Get(win3.get())->Minimize();
  WindowState::Get(win2.get())->Minimize();
  WindowState::Get(win1.get())->Minimize();
  WindowState::Get(win0.get())->Minimize();
  EXPECT_FALSE(WindowState::Get(win0.get())->IsActive());
  EXPECT_FALSE(WindowState::Get(win1.get())->IsActive());
  EXPECT_FALSE(WindowState::Get(win2.get())->IsActive());
  EXPECT_FALSE(WindowState::Get(win3.get())->IsActive());
  EXPECT_FALSE(WindowState::Get(win4.get())->IsActive());

  // Enter the all-desk mode by default with the window order [0, 1, 2, 3 ,4].
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win0.get(), GetTargetWindow());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(5u, GetWindowCycleItemViews().size());
  // The window list is MRU ordered.
  EXPECT_EQ(win0.get(), cycle_windows[0]);
  EXPECT_EQ(win1.get(), cycle_windows[1]);
  EXPECT_EQ(win2.get(), cycle_windows[2]);
  EXPECT_EQ(win3.get(), cycle_windows[3]);
  EXPECT_EQ(win4.get(), cycle_windows[4]);
  // Step forward a few times and switch to all-desks mode. This should
  // highlight win0, the first window in the current-desk cycle list.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win2.get(), GetTargetWindow());
  SwitchPerDeskAltTabMode(true);
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  EXPECT_EQ(win0.get(), cycle_windows[0]);

  // Stepping to win1 and switching back to the all-desk mode should reset
  // a highlight to win0 again.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  SwitchPerDeskAltTabMode(false);
  EXPECT_EQ(5u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  CompleteCycling(cycle_controller);
}

// Tests that pressing an up arrow focus the active tab slider button.
// While a tab slider button is focus, user can switch to the other button
// via left or right key. Note that if user already selects the left button,
// attempting to go further left would do nothing.
TEST_F(ModeSelectionWindowCycleControllerTest, KeyboardNavigation) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two windows for desk1 and three windows for desk2 in the reversed
  // order of the most recently active window.
  auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win1 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  auto win0 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  // Start alt-tab.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  std::vector<TabSliderButton*> tab_slider_buttons =
      GetWindowCycleTabSliderButtons();
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());

  // Focus tab slider mode: pressing the up arrow key should focus the
  // default all-desks, which is the left button. This should not affect
  // the focus on the window cycle.
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win1.get(), GetTargetWindow());

  // Switching to the right, current-desk button via a right arrow key changes
  // to current-desk mode and does not affect the highlighted window.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win1.get(), GetTargetWindow());
  // Trying to move the focus further right should do nothing since it is
  // already on the right most button.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_FALSE(wm::IsActiveWindow(win1.get()));
  EXPECT_EQ(win1.get(), GetTargetWindow());
  CompleteCycling(cycle_controller);
  // Exit alt-tab while focusing the tab slider and check that the keyboard
  // navigation within the tab slider does not affect the window activation.
  EXPECT_TRUE(wm::IsActiveWindow(win1.get()));

  // Start alt-tab and focus the tab slider. The order of cycle window is now
  // [1, 0, 2, 3, 4].
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  // Switching to the left, all-desks button via a left arrow key changes
  // to current-desk mode and does not affect the highlighted window.
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(5u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  // Trying to move the focus further left should do nothing since it is
  // already on the left most button.
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win0.get(), GetTargetWindow());

  // Stop focusing the tab slider button by pressing a down arrow key to
  // continue navigation in the window cycle list.
  generator->PressKey(ui::VKEY_DOWN, ui::EF_NONE);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(5u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());

  // Now navigating left and right should only affect the highlighted window
  // but not the tab slider buttons.
  // Pressing right twice should move the focus to win3.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win3.get(), GetTargetWindow());
  // Pressing left once should move focus back to win2.
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win2.get(), GetTargetWindow());

  CompleteCycling(cycle_controller);
  EXPECT_TRUE(wm::IsActiveWindow(win2.get()));
}

// Tests that clicking the alt-tab slider button removes the focus from the
// button and resets the focus to the target window in the cycle list. After
// clicking the button, the user then needs to press tge Up arrow key again if
// the user want to switch between the alt-tab modes via keyboard navigation.
TEST_F(ModeSelectionWindowCycleControllerTest, KeyboardNavigationAfterClick) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create two windows for desk1 and three windows for desk2 in the reversed
  // order of the most recently active window.
  auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win1 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  auto win0 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  // Start alt-tab.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  std::vector<TabSliderButton*> tab_slider_buttons =
      GetWindowCycleTabSliderButtons();
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());

  // Focus tab slider mode: pressing the Up arrow key should focus the
  // default all-desks, which is the left button. This should not affect
  // the focus on the window cycle.
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win1.get(), GetTargetWindow());

  // Switching to the right, current-desk button via the Right arrow key changes
  // to the current-desk mode and does not affect the highlighted window.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win1.get(), GetTargetWindow());

  // Clicking the same current-desk button should do nothing.
  SwitchPerDeskAltTabMode(true);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win1.get(), GetTargetWindow());

  // Clicking the all-desk button should remove the focus from the alt-tab
  // slider and pressing the Left or Right arrow change the target cycle window
  // rather than switching the mode.
  SwitchPerDeskAltTabMode(false);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(5u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win1.get(), GetTargetWindow());

  // Pressing the Right arrow key should cycle forward rather than switch to
  // the current-desk mode.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(5u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win2.get(), GetTargetWindow());
  CompleteCycling(cycle_controller);
  // Make sure that cycling navigation after a click resets the focus does
  // not affect the correctness of window activation.
  EXPECT_TRUE(wm::IsActiveWindow(win2.get()));

  // The window order is now [2, 0, 1, 3, 4] in the all-desks mode.
  // Similar to the test above but focus the all-desks button before clicking
  // and make sure that exiting alt-tab after a click resets the focus
  // activates the right window.
  // Start alt-tab and press the Up arrow key to focus the all-desks button.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win0.get(), GetTargetWindow());

  // Click the current-desk button.
  SwitchPerDeskAltTabMode(true);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  CompleteCycling(cycle_controller);

  // Exiting after the click resets the focus should activate the right window.
  EXPECT_TRUE(wm::IsActiveWindow(win0.get()));
}

// Tests that ChromeVox alerts the mode change, new target window and
// Down-arrow directional cue correctly when the user uses keyboard navigation
// and button clicking.
TEST_F(ModeSelectionWindowCycleControllerTest, ChromeVox) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
      true, A11Y_NOTIFICATION_NONE);

  // Create two windows for desk1 and one window for desk2 in the reversed
  // order of the most recently active window.
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win1 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win0 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));

  TestAccessibilityControllerClient client;
  const std::string kAllDesksSelected =
      l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_ALL_DESKS_MODE_SELECTED_TITLE);
  const std::string kCurrentDeskSelected = l10n_util::GetStringUTF8(
      IDS_ASH_ALT_TAB_CURRENT_DESK_MODE_SELECTED_TITLE);
  const std::string kFocusWindowDirectionalCue =
      l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_FOCUS_WINDOW_LIST_TITLE);

  // Start alt-tab in the default all-desks mode.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win1.get(), GetTargetWindow());
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_FOCUS_ALL_DESKS_MODE),
            client.last_alert_message());

  // Pressing the up arrow key should focus and alert all-desks mode.
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win1.get(), GetTargetWindow());
  EXPECT_EQ(kAllDesksSelected, client.last_alert_message());

  // Pressing (->) announces the new mode, the new focused window, and the
  // Down-arrow directional cue.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(1u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  std::string last_alert_message = client.last_alert_message();
  EXPECT_TRUE(base::Contains(last_alert_message, kCurrentDeskSelected));
  EXPECT_TRUE(base::Contains(
      last_alert_message,
      l10n_util::GetStringFUTF8(IDS_ASH_ALT_TAB_WINDOW_SELECTED_TITLE,
                                win0->GetTitle())));
  EXPECT_TRUE(base::Contains(last_alert_message, kFocusWindowDirectionalCue));

  // Pressing (<-) announces the new mode, the new focused window and the
  // Down-arrow directional cue.
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win1.get(), GetTargetWindow());
  last_alert_message = client.last_alert_message();
  EXPECT_TRUE(base::Contains(last_alert_message, kAllDesksSelected));
  EXPECT_TRUE(base::Contains(
      last_alert_message,
      l10n_util::GetStringFUTF8(IDS_ASH_ALT_TAB_WINDOW_SELECTED_TITLE,
                                win1->GetTitle())));
  EXPECT_TRUE(base::Contains(last_alert_message, kFocusWindowDirectionalCue));

  // Clicking the current-desk button notifies the new mode and the new focused
  // window but not the Down-arrow directional cue because the focus is moved
  // to the window, which is the bottom most component.
  SwitchPerDeskAltTabMode(true);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(1u, GetWindowCycleItemViews().size());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  last_alert_message = client.last_alert_message();
  EXPECT_TRUE(base::Contains(last_alert_message, kCurrentDeskSelected));
  EXPECT_TRUE(base::Contains(
      last_alert_message,
      l10n_util::GetStringFUTF8(IDS_ASH_ALT_TAB_WINDOW_SELECTED_TITLE,
                                win0->GetTitle())));
  EXPECT_FALSE(base::Contains(last_alert_message, kFocusWindowDirectionalCue));

  // Pressing the Down arrow key while focusing the tab slider button should
  // alert only the focused window.
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  generator->PressKey(ui::VKEY_DOWN, ui::EF_NONE);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  last_alert_message = client.last_alert_message();
  EXPECT_FALSE(base::Contains(last_alert_message, kCurrentDeskSelected));
  EXPECT_TRUE(base::Contains(
      last_alert_message,
      l10n_util::GetStringFUTF8(IDS_ASH_ALT_TAB_WINDOW_SELECTED_TITLE,
                                win0->GetTitle())));
  EXPECT_FALSE(base::Contains(last_alert_message, kFocusWindowDirectionalCue));

  CompleteCycling(cycle_controller);
  EXPECT_TRUE(wm::IsActiveWindow(win0.get()));

  // Start alt-tab in the current-desk mode.
  // Need to create one more window so we have >1 window to enter alt-tab.
  auto win3 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win0.get(), GetTargetWindow());
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_FOCUS_CURRENT_DESK_MODE),
            client.last_alert_message());
  CompleteCyclingAndDeskSwitching(cycle_controller);
  EXPECT_TRUE(wm::IsActiveWindow(win0.get()));
}

// Regression test to make sure there is no crash if a desk is added while the
// window cycle ui is visible. Regression test for https://crbug.com/1216238.
TEST_F(ModeSelectionWindowCycleControllerTest, NoCrashAfterAddingDesk) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create enough windows so that cycling will result in the WindowCycleView
  // being layouted.
  const size_t num_windows = 10;
  std::vector<std::unique_ptr<aura::Window>> windows(num_windows);
  for (size_t i = 0; i < num_windows; ++i)
    windows[i] = CreateAppWindow(gfx::Rect(200, 200));

  auto* desks_controller = DesksController::Get();
  ASSERT_EQ(1u, desks_controller->desks().size());

  // Start window cycle, the desk mode switcher UI should not be shown.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  ASSERT_TRUE(GetWindowCycleTabSliderButtons().empty());
  ASSERT_FALSE(GetWindowCycleNoRecentItemsLabel());

  // Create a new desk while cycling, which should stop cycling.
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  EXPECT_FALSE(cycle_controller->IsCycling());

  // Cycling forward should trigger a relayout, which should not crash.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_FALSE(GetWindowCycleTabSliderButtons().empty());
  EXPECT_TRUE(GetWindowCycleNoRecentItemsLabel());
}

// Tests that destroying all windows while cycling does not cause a crash.
TEST_F(ModeSelectionWindowCycleControllerTest, WindowDestructionWhileCycling) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  auto win1 = CreateAppWindow(gfx::Rect(200, 200));
  auto win2 = CreateAppWindow(gfx::Rect(200, 200));

  // Start window cycle, the desk mode switcher UI should not be shown.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  ASSERT_TRUE(GetWindowCycleTabSliderButtons().empty());
  ASSERT_FALSE(GetWindowCycleNoRecentItemsLabel());

  win1.reset();
  win2.reset();
}

// Tests that ChromeVox alerts correctly when the current desk has no window
// during alt-tab mode switching via both keyboard navigation and button click.
TEST_F(ModeSelectionWindowCycleControllerTest, ChromeVoxNoWindow) {
  ui::test::EventGenerator* generator = GetEventGenerator();
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
      true, A11Y_NOTIFICATION_NONE);

  // Create two desks with all two windows in the non-active desk.
  auto win1 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  auto win0 = CreateAppWindow(gfx::Rect(10, 30, 400, 200));
  win1->SetTitle(u"win1");
  win0->SetTitle(u"win0");
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());

  TestAccessibilityControllerClient client;
  const std::string kAllDesksSelected =
      l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_ALL_DESKS_MODE_SELECTED_TITLE);
  const std::string kCurrentDeskSelected = l10n_util::GetStringUTF8(
      IDS_ASH_ALT_TAB_CURRENT_DESK_MODE_SELECTED_TITLE);
  const std::string kFocusWindowDirectionalCue =
      l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_FOCUS_WINDOW_LIST_TITLE);
  const std::string kNoRecentItems =
      l10n_util::GetStringUTF8(IDS_ASH_OVERVIEW_NO_RECENT_ITEMS);

  // Start alt-tab.
  cycle_controller->HandleCycleWindow(
      WindowCycleController::WindowCyclingDirection::kForward);
  EXPECT_EQ(win0.get(), GetTargetWindow());
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_NE(kAllDesksSelected, client.last_alert_message());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ASH_ALT_TAB_FOCUS_ALL_DESKS_MODE),
            client.last_alert_message());

  // Pressing the up arrow key should focus and alert all-desks mode.
  generator->PressKey(ui::VKEY_UP, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(win0.get(), GetTargetWindow());
  EXPECT_EQ(kAllDesksSelected, client.last_alert_message());

  // Pressing (->) announces the new mode and the new focused window but not
  // the Down-arrow directional cue, which is a useless move.
  generator->PressKey(ui::VKEY_RIGHT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(0u, GetWindowCycleItemViews().size());
  EXPECT_EQ(nullptr, GetTargetWindow());
  EXPECT_TRUE(GetWindowCycleNoRecentItemsLabel()->GetVisible());
  std::string last_alert_message = client.last_alert_message();
  EXPECT_TRUE(base::Contains(last_alert_message, kCurrentDeskSelected));
  EXPECT_TRUE(base::Contains(last_alert_message, kNoRecentItems));
  EXPECT_FALSE(base::Contains(last_alert_message, kFocusWindowDirectionalCue));

  // Pressing (<-) announces the new mode, the new focused window and the
  // Down-arrow directional cue.
  generator->PressKey(ui::VKEY_LEFT, ui::EF_NONE);
  EXPECT_TRUE(cycle_controller->IsTabSliderFocused());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  // Expect alt-tab to select the first window in the MRU because it is in
  // another desk.
  EXPECT_EQ(win0.get(), GetTargetWindow());
  EXPECT_FALSE(GetWindowCycleNoRecentItemsLabel()->GetVisible());

  // Similar to (->), Clicking the current-desk button notifies the new mode
  // and the new focused window but not the Down-arrow directional cue.
  SwitchPerDeskAltTabMode(true);
  EXPECT_FALSE(cycle_controller->IsTabSliderFocused());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(0u, GetWindowCycleItemViews().size());
  EXPECT_EQ(nullptr, GetTargetWindow());
  EXPECT_TRUE(GetWindowCycleNoRecentItemsLabel()->GetVisible());
  last_alert_message = client.last_alert_message();
  EXPECT_TRUE(base::Contains(last_alert_message, kCurrentDeskSelected));
  EXPECT_TRUE(base::Contains(last_alert_message, kNoRecentItems));
  EXPECT_FALSE(base::Contains(last_alert_message, kFocusWindowDirectionalCue));

  CompleteCycling(cycle_controller);
  EXPECT_FALSE(wm::IsActiveWindow(win0.get()));
  EXPECT_FALSE(wm::IsActiveWindow(win1.get()));
}

// Tests that alt-tab handles window destruction during mode switch.
TEST_F(ModeSelectionWindowCycleControllerTest, WindowDestruction) {
  UpdateDisplay("1200x800");

  // Create four windows on the current desk.
  const gfx::Rect default_rect(0, 0, 100, 200);
  std::unique_ptr<Window> w0 = CreateAppWindow(default_rect);
  std::unique_ptr<Window> w1 = CreateAppWindow(default_rect);
  std::unique_ptr<Window> w2 = CreateAppWindow(default_rect);
  std::unique_ptr<Window> w3 = CreateAppWindow(default_rect);

  // Create a second desk, switch to it and create 2 windows.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  std::unique_ptr<Window> w4 = CreateAppWindow(default_rect);
  std::unique_ptr<Window> w5 = CreateAppWindow(default_rect);

  // Start cycling. The default mode is all desks so there should be 6 windows
  // in the window cycle list currently.
  auto* cycle_controller = Shell::Get()->window_cycle_controller();
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(6u, GetWindows(cycle_controller).size());

  // Switch modes to per-desk alt-tab. During the scaling animation, destroy
  // |w5|. This shouldn't crash, the mode should be switched and we should still
  // be cycling.
  SwitchPerDeskAltTabMode(true, /*use_slow_duration=*/true);
  EXPECT_TRUE(IsAnimatingModeSwitch());
  w5.reset();
  EXPECT_EQ(1u, GetWindows(cycle_controller).size());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_TRUE(cycle_controller->IsCycling());
}

// Runs the accessibility paint checks on the active button selector.
// There should be no DCHECK failures.
TEST_F(ModeSelectionWindowCycleControllerTest,
       AccessibilityPaintChecksOnActiveButtonSelector) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Create a second desk.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());

  // Put one window on each desk.
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  ActivateDesk(desks_controller->GetDeskAtIndex(1));
  auto win1 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));

  // Start cycle. Verify the slider buttons are present.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  auto tab_slider_buttons = GetWindowCycleTabSliderButtons();
  EXPECT_EQ(2u, tab_slider_buttons.size());

  EXPECT_FALSE(cycle_controller->window_cycle_list()
                   ->cycle_view()
                   ->IsTabSliderFocused());

  CompleteCycling(cycle_controller);
}

namespace {

constexpr char kUser1Email[] = "user1@alttab";
constexpr char kUser2Email[] = "user2@alttab";

}  // namespace

class MultiUserWindowCycleControllerTest
    : public NoSessionAshTestBase,
      public MultiUserWindowManagerDelegate {
 public:
  MultiUserWindowCycleControllerTest() = default;
  MultiUserWindowCycleControllerTest(
      const MultiUserWindowCycleControllerTest&) = delete;
  MultiUserWindowCycleControllerTest& operator=(
      const MultiUserWindowCycleControllerTest&) = delete;
  ~MultiUserWindowCycleControllerTest() override = default;

  MultiUserWindowManager* multi_user_window_manager() {
    return multi_user_window_manager_.get();
  }
  TestingPrefServiceSimple* user_1_prefs() { return user_1_prefs_; }
  TestingPrefServiceSimple* user_2_prefs() { return user_2_prefs_; }

  void SetUp() override {
    NoSessionAshTestBase::SetUp();

    WindowCycleList::SetDisableInitialDelayForTesting(true);
    shelf_view_test_ = std::make_unique<ShelfViewTestAPI>(
        GetPrimaryShelf()->GetShelfViewForTesting());
    shelf_view_test_->SetAnimationDuration(base::Milliseconds(1));

    generator_ = GetEventGenerator();

    TestSessionControllerClient* session_controller =
        GetSessionControllerClient();
    session_controller->Reset();

    // Inject our own PrefServices for each user which enables us to setup the
    // desks restore data before the user signs in.
    auto user_1_prefs = std::make_unique<TestingPrefServiceSimple>();
    user_1_prefs_ = user_1_prefs.get();
    RegisterUserProfilePrefs(user_1_prefs_->registry(), /*country=*/"",
                             /*for_test=*/true);
    auto user_2_prefs = std::make_unique<TestingPrefServiceSimple>();
    user_2_prefs_ = user_2_prefs.get();
    RegisterUserProfilePrefs(user_2_prefs_->registry(), /*country=*/"",
                             /*for_test=*/true);
    session_controller->AddUserSession(kUser1Email,
                                       user_manager::UserType::kRegular,
                                       /*provide_pref_service=*/false);
    session_controller->SetUserPrefService(GetUser1AccountId(),
                                           std::move(user_1_prefs));
    session_controller->AddUserSession(kUser2Email,
                                       user_manager::UserType::kRegular,
                                       /*provide_pref_service=*/false);
    session_controller->SetUserPrefService(GetUser2AccountId(),
                                           std::move(user_2_prefs));
  }

  void TearDown() override {
    multi_user_window_manager_.reset();
    NoSessionAshTestBase::TearDown();
  }

  // MultiUserWindowManagerDelegate:
  void OnWindowOwnerEntryChanged(aura::Window* window,
                                 const AccountId& account_id,
                                 bool was_minimized,
                                 bool teleported) override {}
  void OnTransitionUserShelfToNewAccount() override {}

  void SwitchPerDeskAltTabModeFromUIAndCheckPrefs(bool per_desk_mode) {
    auto* cycle_controller = Shell::Get()->window_cycle_controller();
    EXPECT_TRUE(cycle_controller->IsCycling());
    gfx::Point button_center =
        GetWindowCycleTabSliderButtons()[per_desk_mode ? 1 : 0]
            ->GetBoundsInScreen()
            .CenterPoint();
    generator_->MoveMouseTo(button_center);
    generator_->ClickLeftButton();
    // Check that alt-tab mode in UI and user prefs are updated.
    EXPECT_EQ(per_desk_mode, cycle_controller->IsAltTabPerActiveDesk());
    EXPECT_EQ(per_desk_mode, IsActivePrefsPerDeskMode());
    EXPECT_TRUE(cycle_controller->IsCycling());
  }

  AccountId GetUser1AccountId() const {
    return AccountId::FromUserEmail(kUser1Email);
  }

  AccountId GetUser2AccountId() const {
    return AccountId::FromUserEmail(kUser2Email);
  }

  bool IsActivePrefsPerDeskMode() {
    PrefService* active_user_prefs =
        Shell::Get()->session_controller()->GetActivePrefService();
    DCHECK(active_user_prefs);
    return active_user_prefs->GetBoolean(prefs::kAltTabPerDesk);
  }

  void SetActivePrefsPerDeskMode(bool per_desk) {
    PrefService* active_user_prefs =
        Shell::Get()->session_controller()->GetActivePrefService();
    DCHECK(active_user_prefs);
    active_user_prefs->SetBoolean(prefs::kAltTabPerDesk, per_desk);
  }

  PrefService* GetUserPrefsService(bool primary) {
    return Shell::Get()->session_controller()->GetUserPrefServiceForUser(
        primary ? GetUser1AccountId() : GetUser2AccountId());
  }

  void SwitchActiveUser(const AccountId& account_id) {
    GetSessionControllerClient()->SwitchActiveUser(account_id);
  }

  void SimulateUserLogin(const AccountId& account_id) {
    SwitchActiveUser(account_id);
    multi_user_window_manager_ =
        MultiUserWindowManager::Create(this, account_id);
    MultiUserWindowManagerImpl::Get()->SetAnimationSpeedForTest(
        MultiUserWindowManagerImpl::ANIMATION_SPEED_DISABLED);
    GetSessionControllerClient()->SetSessionState(
        session_manager::SessionState::ACTIVE);
  }

  const aura::Window::Windows GetWindows(WindowCycleController* controller) {
    return WindowCycleListTestApi(controller->window_cycle_list()).windows();
  }

  const views::View::Views& GetWindowCycleItemViews() const {
    return WindowCycleListTestApi(GetCycleList()).GetWindowCycleItemViews();
  }

  const std::vector<TabSliderButton*> GetWindowCycleTabSliderButtons() const {
    return WindowCycleListTestApi(GetCycleList()).GetTabSliderButtons();
  }

  void CompleteCycling(WindowCycleController* controller) {
    controller->CompleteCycling();
    base::RunLoop().RunUntilIdle();
  }

  void CompleteCyclingAndDeskSwitching(WindowCycleController* controller) {
    DeskSwitchAnimationWaiter waiter;
    controller->CompleteCycling();
    base::RunLoop().RunUntilIdle();
    auto* desks_controller = Shell::Get()->desks_controller();
    if (desks_controller->AreDesksBeingModified())
      waiter.Wait();
  }

 private:
  raw_ptr<ui::test::EventGenerator, DanglingUntriaged> generator_;

  std::unique_ptr<ShelfViewTestAPI> shelf_view_test_;

  std::unique_ptr<MultiUserWindowManager> multi_user_window_manager_;

  raw_ptr<TestingPrefServiceSimple, DanglingUntriaged> user_1_prefs_ = nullptr;
  raw_ptr<TestingPrefServiceSimple, DanglingUntriaged> user_2_prefs_ = nullptr;
};

// Tests that when the active user prefs' |prefs::kAltTabPerDesk| is updated,
// the tab slider UI and the window cycle list are refreshed.
TEST_F(MultiUserWindowCycleControllerTest, AltTabModePrefsUpdateUI) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  auto* desks_controller = DesksController::Get();
  // Login with user_1 and create two desks and three windows where two windows
  // are in the current desk to avoid failure to enter alt-tab.
  SimulateUserLogin(GetUser1AccountId());
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  multi_user_window_manager()->SetWindowOwner(win0.get(), GetUser1AccountId());
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  // Activate desk2 and create two windows.
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  multi_user_window_manager()->SetWindowOwner(win1.get(), GetUser1AccountId());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  multi_user_window_manager()->SetWindowOwner(win2.get(), GetUser1AccountId());

  // user_1 prefs and alt-tab mode should default to the all-desk mode.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_FALSE(IsActivePrefsPerDeskMode());

  // Setting alt-tab mode prefs to current-desk should update the alt-tab UI to
  // current-desk mode.
  bool per_desk = true;
  SetActivePrefsPerDeskMode(per_desk);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(per_desk, IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());

  // Setting alt-tab mode prefs to all-desks should update the alt-tab UI to
  // all-desks mode.
  per_desk = false;
  SetActivePrefsPerDeskMode(per_desk);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(per_desk, IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);

  // Switch to the secondary user_2 and setup the profile with four windows.
  SwitchActiveUser(GetUser2AccountId());
  const Desk* desk_1 = desks_controller->GetDeskAtIndex(0);
  EXPECT_TRUE(desk_1->is_active());
  auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 200));
  multi_user_window_manager()->SetWindowOwner(win3.get(), GetUser2AccountId());
  auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 200));
  multi_user_window_manager()->SetWindowOwner(win4.get(), GetUser2AccountId());
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win5 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  multi_user_window_manager()->SetWindowOwner(win5.get(), GetUser2AccountId());
  auto win6 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  multi_user_window_manager()->SetWindowOwner(win6.get(), GetUser2AccountId());

  // user_2 prefs and alt-tab mode should default to the all-desk mode.
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
  EXPECT_FALSE(IsActivePrefsPerDeskMode());

  // Setting alt-tab mode prefs to current-desk should update the alt-tab UI to
  // current-desk mode.
  per_desk = true;
  SetActivePrefsPerDeskMode(per_desk);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(per_desk, IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());

  // Setting alt-tab mode prefs to all-desks should update the alt-tab UI to
  // all-desks mode.
  per_desk = false;
  SetActivePrefsPerDeskMode(per_desk);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(per_desk, IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);
}

// Tests that switching between users restores each user's alt-tab mode
// correctly. In addition, pressing a tab slider button to switch the mode,
// `SwitchPerDeskAltTabModeFromUIAndCheckPrefs()` checks that alt-tab
// successfully switches the mode and updates the user prefs.
TEST_F(MultiUserWindowCycleControllerTest,
       AltTabModeUserSwitchAndUIUpdatesPref) {
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();

  // Setup user_1 with two windows out of three in the current desk and
  // set the mode to non-default current-desk for test preparation.
  SimulateUserLogin(GetUser1AccountId());
  auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
  multi_user_window_manager()->SetWindowOwner(win0.get(), GetUser1AccountId());
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200));
  multi_user_window_manager()->SetWindowOwner(win1.get(), GetUser1AccountId());
  auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200));
  multi_user_window_manager()->SetWindowOwner(win2.get(), GetUser1AccountId());

  // In preparation for multi-user alt-tab mode switching, start alt-tab with
  // user_1 prefs set to current-desk mode.
  bool per_desk = true;
  SetActivePrefsPerDeskMode(per_desk);
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(per_desk, IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);

  // Switch to user_2 and open up two windows out of four in the current desk.
  SwitchActiveUser(GetUser2AccountId());
  const Desk* desk_1 = desks_controller->GetDeskAtIndex(0);
  EXPECT_TRUE(desk_1->is_active());
  auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 200));
  multi_user_window_manager()->SetWindowOwner(win3.get(), GetUser2AccountId());
  auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 200));
  multi_user_window_manager()->SetWindowOwner(win4.get(), GetUser2AccountId());
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  auto win5 = CreateAppWindow(gfx::Rect(0, 0, 250, 200));
  multi_user_window_manager()->SetWindowOwner(win5.get(), GetUser2AccountId());
  auto win6 = CreateAppWindow(gfx::Rect(0, 0, 250, 200));
  multi_user_window_manager()->SetWindowOwner(win6.get(), GetUser2AccountId());

  // In preparation for multi-user alt-tab mode switching, start alt-tab with
  // user_2 prefs set to current-desk mode.
  SetActivePrefsPerDeskMode(per_desk);
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_EQ(per_desk, IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());

  // Test that the primary user_1's mode is loaded correctly after switch
  // from secondary user_2, who just changes the mode to the opposite.

  // Currently, both users choose the current-desk mode, so we try change
  // user_2 to all-desks mode from the tab slider UI to see if user_1's mode
  // remains correctly unaffected.
  SwitchPerDeskAltTabModeFromUIAndCheckPrefs(false);
  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);

  // Switch back to user_1. Make sure that user_1 prefs remains unaffected
  // and the alt-tab enter with the correct current-desk mode.
  SwitchActiveUser(GetUser1AccountId());
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_TRUE(IsActivePrefsPerDeskMode());
  EXPECT_EQ(IsActivePrefsPerDeskMode(),
            cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);

  // In preparation for the next test, change user_2 back the current-desk mode
  // to make sure both users start at the same mode selection.
  SwitchActiveUser(GetUser2AccountId());
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  SwitchPerDeskAltTabModeFromUIAndCheckPrefs(true);
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);

  // Test that the secondary user_2's mode is loaded correctly after switching
  // from primary user_1, who just changes the mode to the opposite.

  // Currently, both users choose the current-desk mode, so we try change
  // user_1 to all-desks mode to see if user_2's mode will change.
  SwitchActiveUser(GetUser1AccountId());
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  SwitchPerDeskAltTabModeFromUIAndCheckPrefs(false);
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  CompleteCyclingAndDeskSwitching(cycle_controller);

  // Switch back to user_2 and make sure that the mode is restored
  // to the current-desk mode correctly.
  SwitchActiveUser(GetUser2AccountId());
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  cycle_controller->StartCycling(/*same_app_only=*/false);
  EXPECT_TRUE(cycle_controller->IsCycling());
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  EXPECT_EQ(2u, GetWindowCycleItemViews().size());
  CompleteCycling(cycle_controller);
}

class SameAppWindowCycleControllerTest : public WindowCycleControllerTest {
 public:
  SameAppWindowCycleControllerTest()
      : scoped_feature_list_(features::kSameAppWindowCycle) {}
  SameAppWindowCycleControllerTest(const SameAppWindowCycleControllerTest&) =
      delete;
  SameAppWindowCycleControllerTest& operator=(
      const SameAppWindowCycleControllerTest&) = delete;
  ~SameAppWindowCycleControllerTest() override = default;

  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;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(SameAppWindowCycleControllerTest, NotEnoughWindows) {
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Cycling doesn't crash if there are no windows.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);

  // Cycling with 1 window doesn't change anything.
  std::unique_ptr<Window> window0(CreateTestWindowWithAppID(std::string("0")));
  wm::ActivateWindow(window0.get());
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));

  // Cycling with 1 unfocused window focuses the window.
  Shell::Get()->focus_cycler()->RotateFocus(FocusCycler::FORWARD);
  EXPECT_FALSE(wm::IsActiveWindow(window0.get()));
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
}

// Cycles between windows of the same app, ignoring other windows not of the
// same app.
TEST_F(SameAppWindowCycleControllerTest, CycleSameApps) {
  WindowCycleController* controller = Shell::Get()->window_cycle_controller();
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Create two windows with app id 0 and three windows with app id 1.
  std::unique_ptr<aura::Window> w0(CreateTestWindowWithAppID(std::string("0")));
  std::unique_ptr<aura::Window> w1(CreateTestWindowWithAppID(std::string("0")));
  std::unique_ptr<aura::Window> w2(CreateTestWindowWithAppID(std::string("1")));
  std::unique_ptr<aura::Window> w3(CreateTestWindowWithAppID(std::string("1")));
  std::unique_ptr<aura::Window> w4(CreateTestWindowWithAppID(std::string("1")));
  EXPECT_TRUE(wm::IsActiveWindow(w4.get()));

  // Simulate pressing and releasing Alt-backtick.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);

  // The window cycle controller should only have 3 windows; the windows with
  // app id 1.
  ASSERT_EQ(3u, GetWindows(controller).size());

  // 1 press should cycle from w4 to w3.
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w3.get()));

  // Current MRU order: w3 -> w4 -> w2 -> w1 -> w0. 2 presses of alt backtick
  // should cycle to w2.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  EXPECT_TRUE(controller->IsCycling());
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // Current MRU order: w2 -> w3 -> w4 -> w1 -> w0. Current same app window
  // cycle list order: w2 -> w3 -> w4. 5 presses of alt backtick should cycle to
  // w4.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w4.get()));
}

// Alternates between window cycling and same app window cycling.
TEST_F(SameAppWindowCycleControllerTest, AlternateCyclingTypes) {
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Create one window with app id 1, two windows with app id 2, and three
  // windows with app id 3.
  std::unique_ptr<aura::Window> w0(CreateTestWindowWithAppID(std::string("1")));
  std::unique_ptr<aura::Window> w1(CreateTestWindowWithAppID(std::string("2")));
  std::unique_ptr<aura::Window> w2(CreateTestWindowWithAppID(std::string("2")));
  std::unique_ptr<aura::Window> w3(CreateTestWindowWithAppID(std::string("3")));
  std::unique_ptr<aura::Window> w4(CreateTestWindowWithAppID(std::string("3")));
  std::unique_ptr<aura::Window> w5(CreateTestWindowWithAppID(std::string("3")));
  EXPECT_TRUE(wm::IsActiveWindow(w5.get()));

  // Alt-tab 3 times to cycle to w2.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // MRU order: w2 -> w5 -> w4 -> w3 -> w1 -> w0. Same app order: w2 -> w1.
  // Alt-backtick once to cycle to w1.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // MRU order: w1 -> w2 -> w5 -> w4 -> w3 -> w0. Alt-tab 5 times to get to w0.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));

  // Alt-backtick once to stay on w0.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w0.get()));

  // MRU order: w0 -> w1 -> w2 -> w5 -> w4 -> w3. Shift-alt-tab once to cycle
  // back to w3.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB,
                                ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w3.get()));

  // MRU order: w3 -> w0 -> w1 -> w2 -> w5 -> w4. Same app order: w3 -> w5 ->
  // w4. Shift-alt-backtick twice to get to w5
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3,
                                ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3,
                                ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w5.get()));
}

// Tests that same app window cycling works in all desk mode, current desk mode,
// switching between the two modes, and switching between same app window
// cycling and normal window cycling.
TEST_F(SameAppWindowCycleControllerTest, PerDeskMode) {
  // On desk 1 create 1 window of app A and 3 windows of 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("B")));
  std::unique_ptr<aura::Window> w3(CreateTestWindowWithAppID(std::string("B")));

  // On desk 2 create 2 windows of app A and 4 windows of app B.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
  ASSERT_EQ(2u, desks_controller->desks().size());
  const Desk* desk_2 = desks_controller->GetDeskAtIndex(1);
  ActivateDesk(desk_2);
  EXPECT_EQ(desk_2, desks_controller->active_desk());
  std::unique_ptr<aura::Window> w4(CreateTestWindowWithAppID(std::string("A")));
  std::unique_ptr<aura::Window> w5(CreateTestWindowWithAppID(std::string("A")));
  std::unique_ptr<aura::Window> w6(CreateTestWindowWithAppID(std::string("B")));
  std::unique_ptr<aura::Window> w7(CreateTestWindowWithAppID(std::string("B")));
  std::unique_ptr<aura::Window> w8(CreateTestWindowWithAppID(std::string("B")));
  std::unique_ptr<aura::Window> w9(CreateTestWindowWithAppID(std::string("B")));

  // Start cycling, all desks mode should be default so we should see 7 windows
  // of app B.
  auto* generator = GetEventGenerator();
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  auto cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(7u, cycle_windows.size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, w1.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w2.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w3.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w6.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w7.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w8.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w9.get()));

  // Select current-desk mode. We should see 4 windows of app B.
  generator->MoveMouseTo(
      GetWindowCycleTabSliderButtons()[1]->GetBoundsInScreen().CenterPoint());
  generator->ClickLeftButton();
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, w6.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w7.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w8.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w9.get()));
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);

  // Go to desk 1 and start cycling, we should still be on current-desk mode and
  // see 3 windows of app B.
  ActivateDesk(desks_controller->GetDeskAtIndex(0));
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(3u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, w1.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w2.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w3.get()));
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);

  // Start alt tabbing. The mode selection should be shared between alt tab and
  // alt backtick so we should still be on current-desk mode and see 4 windows.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  cycle_windows = GetWindows(cycle_controller);
  EXPECT_EQ(4u, GetWindowCycleItemViews().size());
  EXPECT_EQ(cycle_windows.size(), GetWindowCycleItemViews().size());
  EXPECT_TRUE(base::Contains(cycle_windows, w0.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w1.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w2.get()));
  EXPECT_TRUE(base::Contains(cycle_windows, w3.get()));
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
}

TEST_F(SameAppWindowCycleControllerTest, RecordingSameAppCycleMetrics) {
  // Create another desk so that desk mode metrics are collected.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);

  // Create 2 windows of app A and 3 windows of app B.
  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("B")));
  std::unique_ptr<aura::Window> w4(CreateTestWindowWithAppID(std::string("B")));
  std::unique_ptr<aura::Window> w5(CreateTestWindowWithAppID(std::string("B")));

  // Alt backtick once. The new MRU order should be w4 - w5 - w3 - w2 - w1.
  base::HistogramTester histogram_tester;
  auto* generator = GetEventGenerator();
  WindowCycleController* cycle_controller =
      Shell::Get()->window_cycle_controller();
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  EXPECT_FALSE(cycle_controller->IsAltTabPerActiveDesk());
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w4.get()));

  // DeskMode should have 1 in kAllDesks since all desks mode is default.
  histogram_tester.ExpectUniqueSample(
      "Ash.WindowCycleController.SameApp.DeskMode", /*sample=*/0,
      /*expected_bucket_count=*/1);

  // IsSameApp should be 1 since we've alt backticked once and haven't yet alt
  // tabbed.
  histogram_tester.ExpectTotalCount(
      "Ash.WindowCycleController.SameApp.IsSameApp", 1);

  // Skipped should have 1 entry in the 0 bucket since there were no windows of
  // app type A between the two windows of app type B in the MRU order.
  histogram_tester.ExpectUniqueSample(
      "Ash.WindowCycleController.SameApp.SkippedWindows", /*sample=*/0,
      /*expected_bucket_count=*/1);

  // Alt tab to w2 and then to w4. This should change none of the alt backtick
  // metrics. The new MRU order should be w4 - w2 - w5 - w3 - w1.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w4.get()));

  // Alt backticking once should get us from w4 to w5, skipping w2. The new MRU
  // order should be w5 - w4 - w2 - w3 - w1.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w5.get()));

  // The kAllDesks bucket in DeskMode should now be 2. We've alt backticked
  // twice so the true bucket for IsSameApp should be 2. Skipped should have a
  // new bucket with a skip distance of 1, and a value of 1 entry in that
  // bucket.
  histogram_tester.ExpectUniqueSample(
      "Ash.WindowCycleController.SameApp.DeskMode", /*sample=*/0,
      /*expected_bucket_count=*/2);
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.IsSameApp", true, 2);
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.SkippedWindows", /*sample=*/1,
      /*expected_count=*/1);

  // Alt tab to w2 and switch the per desk mode to current desk. The new MRU
  // order should be w2 - w5 - w4 - w3 - w1.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->MoveMouseTo(
      GetWindowCycleTabSliderButtons()[1]->GetBoundsInScreen().CenterPoint());
  generator->ClickLeftButton();
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  EXPECT_TRUE(cycle_controller->IsAltTabPerActiveDesk());
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // Alt Backtick once from w2 to w1, skipping w5, w4, and w3. The new MRU order
  // should be w1 - w2 - w5 - w4 - w3.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // DeskMode should have a new bucket for kCurrentDesk with a value of 1,
  // IsSameApp true should be 3, and we should have a new skip distance bucket
  // of 3 with 1 entry.
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.DeskMode", /*sample=*/1,
      /*expected_count=*/1);
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.IsSameApp", true, 3);
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.SkippedWindows", /*sample=*/3,
      /*expected_count=*/1);

  // DeskMode and Skipped should both only have 3 entries between all their
  // buckets: the number of times we alt backticked.
  histogram_tester.ExpectTotalCount(
      "Ash.WindowCycleController.SameApp.DeskMode", 3);
  histogram_tester.ExpectTotalCount(
      "Ash.WindowCycleController.SameApp.SkippedWindows", 3);

  // Alt tab to w5 and then back to w1. The new MRU order should be w1 - w5 - w2
  // - w4 - w3.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w5.get()));
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  // Alt backtick through the whole cycle list and then to w2. The loop
  // shouldn't double count the skipped window. The new MRU order should be w2 -
  // w1 - w5 - w4 - w3.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3, ui::EF_ALT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));

  // Skipping the same window twice should only count as one window skipped, so
  // the bucket for a skip distance of 1 should have one more.
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.SkippedWindows", /*sample=*/1,
      /*expected_count=*/2);

  // We've alt tabbed 5 times, so the false bucket of IsSameApp should be 5.
  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.IsSameApp", false, 5);

  // Shift Alt backtick once to w1. A skip distance of 3 should be counted.
  generator->PressKey(ui::VKEY_MENU, ui::EF_NONE);
  generator->PressAndReleaseKey(ui::VKEY_OEM_3,
                                ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN);
  generator->ReleaseKey(ui::VKEY_MENU, ui::EF_NONE);
  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  histogram_tester.ExpectBucketCount(
      "Ash.WindowCycleController.SameApp.SkippedWindows", /*sample=*/3,
      /*expected_count=*/2);
}

}  // namespace ash