chromium/ash/game_dashboard/game_dashboard_context_unittest.cc

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

#include "ash/game_dashboard/game_dashboard_context.h"

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "ash/accelerators/accelerator_commands.h"
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/capture_mode/capture_mode_controller.h"
#include "ash/capture_mode/capture_mode_test_util.h"
#include "ash/capture_mode/capture_mode_types.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/game_dashboard/game_dashboard_button.h"
#include "ash/game_dashboard/game_dashboard_constants.h"
#include "ash/game_dashboard/game_dashboard_context_test_api.h"
#include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/game_dashboard/game_dashboard_main_menu_view.h"
#include "ash/game_dashboard/game_dashboard_metrics.h"
#include "ash/game_dashboard/game_dashboard_test_base.h"
#include "ash/game_dashboard/game_dashboard_toolbar_view.h"
#include "ash/game_dashboard/game_dashboard_utils.h"
#include "ash/game_dashboard/test_game_dashboard_delegate.h"
#include "ash/public/cpp/arc_game_controls_flag.h"
#include "ash/public/cpp/capture_mode/capture_mode_test_api.h"
#include "ash/public/cpp/style/dark_light_mode_controller.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/color_palette_controller.h"
#include "ash/style/icon_button.h"
#include "ash/style/pill_button.h"
#include "ash/style/switch.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/time/time_view.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/system/toast/toast_manager_impl.h"
#include "ash/system/unified/feature_tile.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_observer.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_state_util.h"
#include "base/check.h"
#include "base/i18n/time_formatting.h"
#include "base/notreached.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/frame_header.h"
#include "chromeos/ui/wm/window_util.h"
#include "components/ukm/test_ukm_recorder.h"
#include "extensions/common/constants.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"

namespace ash {

namespace {

// Sub-label strings.
const std::u16string& hidden_label = u"Hidden";
const std::u16string& visible_label = u"Visible";

// Touch drag constants.
constexpr base::TimeDelta kTouchDragDuration = base::Milliseconds(200);
const int kTouchDragSteps = 5;

enum class Movement { kTouch, kMouse };

template <typename T>
void VerifyHistogramValues(const base::HistogramTester& histograms,
                           const std::string& histogram_name,
                           const std::map<T, int>& histogram_values) {
  for (const auto& entry : histogram_values) {
    histograms.ExpectBucketCount(histogram_name, entry.first, entry.second);
  }
}

// Verifies UKM event entry size of ToggleMainMenu is `expect_entry_size` and
// the last event entry metric values match `expect_event_values`.
void VerifyToggleMainMenuLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    const std::vector<int64_t>& expect_event_values) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries = ukm_recorder.GetEntriesByName(
      BuildGameDashboardUkmEventName(kGameDashboardToggleMainMenuHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  EXPECT_EQ(2u, expect_event_values.size());
  const size_t last_index = expect_entry_size - 1;
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[last_index],
      ukm::builders::GameDashboard_ToggleMainMenu::kToggleOnName,
      expect_event_values[0]);
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[last_index],
      ukm::builders::GameDashboard_ToggleMainMenu::kToggleMethodName,
      expect_event_values[1]);
}

// Verifies UKM event entry size of ToolbarToggleState is `expect_entry_size`
// and the last event entry metric value matches `expect_event_value`.
void VerifyToolbarToggleStateLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardToolbarToggleStateHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_ToolbarToggleState::kToggleOnName,
      expect_event_value);
}

// Verifies UKM event entry size of RecordingStartSource is `expect_entry_size`
// and the last event entry metric value matches `expect_event_value`.
void VerifyRecordingStartSourceLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardRecordingStartSourceHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_RecordingStartSource::kSourceName,
      expect_event_value);
}

// Verifies UKM event entry size of ScreenshotTakeSource is `expect_entry_size`
// and the last event entry metric value matches `expect_event_value`.
void VerifyScreenshotTakeSourceLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardScreenshotTakeSourceHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_ScreenshotTakeSource::kSourceName,
      expect_event_value);
}

// Verifies UKM event entry size of ControlsEditControlsWithEmptyState is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_value`.
void VerifyGameControlsEditControlsWithEmptyStateLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardEditControlsWithEmptyStateHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_EditControlsWithEmptyState::kEmptyName,
      expect_event_value);
}

// Verifies UKM event entry size of ToolbarClickToExpandState is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_value`.
void VerifyToolbarClickToExpandStateLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardToolbarClickToExpandStateHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_ToolbarClickToExpandState::kExpandedName,
      expect_event_value);
}

// Verifies UKM event entry size of ToolbarNewLocation is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_value`.
void VerifyToolbarNewLocationLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardToolbarNewLocationHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_ToolbarNewLocation::kLocationName,
      expect_event_value);
}

// Verifies UKM event entry size of FunctionTriggered is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_value`.
void VerifyFunctionTriggeredLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries = ukm_recorder.GetEntriesByName(
      BuildGameDashboardUkmEventName(kGameDashboardFunctionTriggeredHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_FunctionTriggered::kFunctionName,
      expect_event_value);
}

// Verifies UKM event entry size of WelcomeDialogNotificationToggleState is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_value`.
void VerifyWelcomeDialogNotificationToggleStateLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardWelcomeDialogNotificationToggleStateHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_WelcomeDialogNotificationToggleState::
          kToggleOnName,
      expect_event_value);
}

// Verifies UKM event entry size of GameControlsHintToggleSource is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_values`.
void VerifyGameControlsHintToggleSourceLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    std::map<std::string, int64_t> expect_event_values) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardControlsHintToggleSourceHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  for (const auto& value_entry : expect_event_values) {
    ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
        ukm_entries[expect_entry_size - 1], value_entry.first,
        value_entry.second);
  }
}

// Verifies UKM event entry size of GameControlsFeatureToggleState is
// `expect_entry_size` and the last event entry metric value matches
// `expect_event_value`.
void VerifyGameControlsFeatureToggleStateLastUkmEvent(
    const ukm::TestAutoSetUkmRecorder& ukm_recorder,
    size_t expect_entry_size,
    int64_t expect_event_value) {
  EXPECT_GE(expect_entry_size, 1u);
  const auto ukm_entries =
      ukm_recorder.GetEntriesByName(BuildGameDashboardUkmEventName(
          kGameDashboardControlsFeatureToggleStateHistogram));
  EXPECT_EQ(expect_entry_size, ukm_entries.size());
  ukm::TestAutoSetUkmRecorder::ExpectEntryMetric(
      ukm_entries[expect_entry_size - 1],
      ukm::builders::GameDashboard_ControlsFeatureToggleState::kToggleOnName,
      expect_event_value);
}

// Records the last mouse event for testing.
class EventCapturer : public ui::EventHandler {
 public:
  EventCapturer() = default;
  EventCapturer(const EventCapturer&) = delete;
  EventCapturer& operator=(const EventCapturer&) = delete;
  ~EventCapturer() override {}

  void Reset() { last_mouse_event_.reset(); }

  ui::MouseEvent* last_mouse_event() { return last_mouse_event_.get(); }

 private:
  void OnMouseEvent(ui::MouseEvent* event) override {
    last_mouse_event_ = std::make_unique<ui::MouseEvent>(*event);
  }

  std::unique_ptr<ui::MouseEvent> last_mouse_event_;
};

// Test model mimicking a default `chromeos::CaptionButtonModel` that ensures
// there's no maximize button within the frame header buttons.
class NonResizableButtonModel : public chromeos::CaptionButtonModel {
 public:
  NonResizableButtonModel() = default;
  NonResizableButtonModel(const NonResizableButtonModel&) = delete;
  NonResizableButtonModel& operator=(const NonResizableButtonModel&) = delete;
  ~NonResizableButtonModel() override = default;

  // chromeos::CaptionButtonModel:
  bool IsVisible(views::CaptionButtonIcon type) const override {
    if (type == views::CAPTION_BUTTON_ICON_MINIMIZE) {
      return true;
    }
    return false;
  }
  bool IsEnabled(views::CaptionButtonIcon type) const override { return true; }
  bool InZoomMode() const override { return false; }
};

}  // namespace

class GameDashboardContextTest : public GameDashboardTestBase {
 public:
  GameDashboardContextTest() = default;
  GameDashboardContextTest(const GameDashboardContextTest&) = delete;
  GameDashboardContextTest& operator=(const GameDashboardContextTest&) = delete;
  ~GameDashboardContextTest() override = default;

  void SetUp() override {
    GameDashboardTestBase::SetUp();
    // Disable the welcome dialog by default.
    game_dashboard_utils::SetShowWelcomeDialog(false);
    game_dashboard_utils::SetShowToolbar(false);
    GetContext()->AddPostTargetHandler(&post_target_event_capturer_);
  }

  void TearDown() override {
    GetContext()->RemovePostTargetHandler(&post_target_event_capturer_);
    CloseGameWindow();
    GameDashboardTestBase::TearDown();
  }

  void CloseGameWindow() {
    game_window_.reset();
    test_api_.reset();
    frame_header_height_ = 0;
  }

  const gfx::Rect app_bounds() const { return app_bounds_; }

  void SetAppBounds(gfx::Rect app_bounds) {
    CHECK(!game_window_)
        << "App bounds cannot be changed after creating window. To set the app "
           "bounds, call CloseWindow() and re-call this function.";
    app_bounds_ = app_bounds;
  }

  int GetToolbarHeight() {
    auto* widget = test_api_->GetToolbarWidget();
    CHECK(widget) << "The toolbar must be opened first before trying to "
                     "retrieve its height.";
    return widget->GetNativeWindow()->GetBoundsInScreen().height();
  }

  // Starts the video recording from `CaptureModeBarView`.
  void ClickOnStartRecordingButtonInCaptureModeBarView() {
    PillButton* start_recording_button = GetStartRecordingButton();
    ASSERT_TRUE(start_recording_button);
    LeftClickOn(start_recording_button);
    WaitForRecordingToStart();
    EXPECT_TRUE(CaptureModeController::Get()->is_recording_in_progress());
  }

  // If `is_arc_window` is true, this function creates the window as an ARC
  // game window. Otherwise, it creates the window as a GeForceNow window.
  // For ARC game windows, if `set_arc_game_controls_flags_prop` is true, then
  // the `kArcGameControlsFlagsKey` window property will be set to
  // `ArcGameControlsFlag::kKnown`, otherwise the property will not be set.
  void CreateGameWindow(bool is_arc_window,
                        bool set_arc_game_controls_flags_prop = true) {
    ASSERT_FALSE(game_window_);
    ASSERT_FALSE(test_api_);
    game_window_ =
        CreateAppWindow((is_arc_window ? TestGameDashboardDelegate::kGameAppId
                                       : extension_misc::kGeForceNowAppId),
                        (is_arc_window ? chromeos::AppType::ARC_APP
                                       : chromeos::AppType::NON_APP),
                        app_bounds());
    auto* context = GameDashboardController::Get()->GetGameDashboardContext(
        game_window_.get());
    ASSERT_TRUE(context);
    test_api_ = std::make_unique<GameDashboardContextTestApi>(
        context, GetEventGenerator());
    ASSERT_TRUE(test_api_);
    frame_header_height_ =
        game_dashboard_utils::GetFrameHeaderHeight(game_window_.get());
    DCHECK_GT(frame_header_height_, 0);

    if (is_arc_window && set_arc_game_controls_flags_prop) {
      // Initially, Game Controls is not available.
      game_window_->SetProperty(kArcGameControlsFlagsKey,
                                ArcGameControlsFlag::kKnown);
    }

    auto* game_dashboard_button_widget =
        test_api_->GetGameDashboardButton()->GetWidget();
    CHECK(game_dashboard_button_widget);
    ASSERT_TRUE(game_dashboard_button_widget->CanActivate());

    // Verify whether the welcome dialog should be shown.
    if (game_dashboard_utils::ShouldShowWelcomeDialog() &&
        game_dashboard_utils::ShouldEnableFeatures()) {
      ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
    } else {
      ASSERT_FALSE(test_api_->GetWelcomeDialogWidget());
    }
  }

  // Opens the main menu and toolbar, and checks Game Controls UI states. At the
  // end of the test, closes the main menu and toolbar.
  // `hint_tile_states` is about feature tile states, {expect_exists,
  // expect_enabled, expect_on}.
  // `details_row_states` is about the Game Controls details row states,
  // {expect_exists, expect_enabled}. `feature_switch_states` is about feature
  // switch button states, {expect_exists, expect_toggled}. `setup_exists` shows
  // if setup button exists.
  void OpenMenuCheckGameControlsUIState(
      std::array<bool, 3> hint_tile_states,
      std::array<bool, 2> details_row_states,
      std::array<bool, 2> feature_switch_states,
      bool setup_exists) {
    test_api_->OpenTheMainMenu();

    if (const auto* tile = test_api_->GetMainMenuGameControlsTile();
        hint_tile_states[0]) {
      ASSERT_TRUE(tile);
      EXPECT_EQ(hint_tile_states[1], tile->GetEnabled());
      EXPECT_EQ(hint_tile_states[2], tile->IsToggled());
    } else {
      EXPECT_FALSE(tile);
    }

    auto* details_row = test_api_->GetMainMenuGameControlsDetailsButton();
    EXPECT_EQ(details_row_states[0], !!details_row);
    if (details_row) {
      EXPECT_EQ(details_row_states[1], details_row->GetEnabled());
    }

    if (const auto* switch_button =
            test_api_->GetMainMenuGameControlsFeatureSwitch();
        feature_switch_states[0]) {
      ASSERT_TRUE(switch_button);
      EXPECT_EQ(feature_switch_states[1], switch_button->GetIsOn());
    } else {
      EXPECT_FALSE(switch_button);
    }

    auto* setup_button = test_api_->GetMainMenuGameControlsSetupButton();
    ASSERT_EQ(!!setup_button, setup_exists);
    if (setup_button) {
      EXPECT_EQ(details_row_states[1], setup_button->GetEnabled());
    }

    // Open toolbar and check the toolbar's Game Controls button state.
    test_api_->OpenTheToolbar();
    // The button state has the same state as the hint tile on the main menu.
    if (const auto* game_controls_button =
            test_api_->GetToolbarGameControlsButton();
        hint_tile_states[0]) {
      ASSERT_TRUE(game_controls_button);
      EXPECT_EQ(hint_tile_states[1], game_controls_button->GetEnabled());
      EXPECT_EQ(hint_tile_states[2], game_controls_button->toggled());
    } else {
      EXPECT_FALSE(game_controls_button);
    }

    test_api_->CloseTheToolbar();
    test_api_->CloseTheMainMenu();
  }

  // The toolbar drag point for `expected_location`.
  gfx::Point DragToolbarPointForPosition(
      GameDashboardToolbarSnapLocation expected_location) {
    const auto window_bounds = game_window_->GetBoundsInScreen();
    const auto window_center_point = window_bounds.CenterPoint();
    const int x_offset = window_bounds.width() / 4;
    const int y_offset = window_bounds.height() / 4;

    switch (expected_location) {
      case GameDashboardToolbarSnapLocation::kTopLeft:
        return gfx::Point(window_center_point.x() - x_offset,
                          window_center_point.y() - y_offset);
      case GameDashboardToolbarSnapLocation::kTopRight:
        return gfx::Point(window_center_point.x() + x_offset,
                          window_center_point.y() - y_offset);
      case GameDashboardToolbarSnapLocation::kBottomRight:
        return gfx::Point(window_center_point.x() + x_offset,
                          window_center_point.y() + y_offset);
      case ash::GameDashboardToolbarSnapLocation::kBottomLeft:
        return gfx::Point(window_center_point.x() - x_offset,
                          window_center_point.y() + y_offset);
      default:
        NOTREACHED();
    }
  }

  void VerifyToolbarDrag(Movement move_type) {
    test_api_->OpenTheMainMenu();
    test_api_->OpenTheToolbar();

    // Verify that be default the snap position should be `kTopRight` and
    // toolbar is placed in the top right quadrant.
    EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
              GameDashboardToolbarSnapLocation::kTopRight);

    // Move toolbar but not outside of the top right quadrant. Tests that even
    // though the snap position does not change, the toolbar is snapped back to
    // its previous position.
    DragToolbarToPoint(move_type,
                       DragToolbarPointForPosition(
                           GameDashboardToolbarSnapLocation::kTopRight));
    EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
              GameDashboardToolbarSnapLocation::kTopRight);

    // Move toolbar to bottom right quadrant and verify snap location is
    // updated.
    DragToolbarToPoint(move_type,
                       DragToolbarPointForPosition(
                           GameDashboardToolbarSnapLocation::kBottomRight));
    EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
              GameDashboardToolbarSnapLocation::kBottomRight);

    // Move toolbar to bottom left quadrant and verify snap location is updated.
    DragToolbarToPoint(move_type,
                       DragToolbarPointForPosition(
                           GameDashboardToolbarSnapLocation::kBottomLeft));
    EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
              GameDashboardToolbarSnapLocation::kBottomLeft);

    // Move toolbar to top left quadrant and verify snap location is updated.
    DragToolbarToPoint(move_type,
                       DragToolbarPointForPosition(
                           GameDashboardToolbarSnapLocation::kTopLeft));
    EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
              GameDashboardToolbarSnapLocation::kTopLeft);
  }

  // Verifies the Game Dashboard button is in the respective state for the given
  // `test_api`. If `is_recording` is true, then the Game Dashboard button must
  // be in the recording state, and the recording timer is running. Otherwise,
  // it should be in the default state and the timer should not be running.
  void VerifyGameDashboardButtonState(GameDashboardContextTestApi* test_api,
                                      bool is_recording) {
    EXPECT_EQ(is_recording, test_api->GetGameDashboardButton()->is_recording());

    std::u16string expected_title;
    if (is_recording) {
      expected_title = l10n_util::GetStringFUTF16(
          IDS_ASH_GAME_DASHBOARD_GAME_DASHBOARD_BUTTON_RECORDING,
          test_api->GetRecordingDuration());
    } else {
      expected_title = l10n_util::GetStringUTF16(
          IDS_ASH_GAME_DASHBOARD_GAME_DASHBOARD_BUTTON_TITLE);
    }
    EXPECT_EQ(expected_title,
              test_api->GetGameDashboardButtonTitle()->GetText());
  }

  void VerifyGameDashboardButtonState(bool is_recording) {
    VerifyGameDashboardButtonState(test_api_.get(), is_recording);
  }

  // Moves the cursor inside the window frame header, half way between the left
  // edge of the window and `GameDashboardMainMenuButton`. Returns the new mouse
  // location.
  gfx::Point MoveCursorToEmptySpaceInFrameHeader(
      GameDashboardContextTestApi* test_api) {
    const auto window_bounds =
        test_api->context()->game_window()->GetBoundsInScreen();
    const auto gd_button_bounds_x =
        test_api->GetGameDashboardButton()->GetBoundsInScreen().x();
    gfx::Point new_mouse_location =
        gfx::Point((window_bounds.x() + gd_button_bounds_x) / 2,
                   window_bounds.y() + frame_header_height_ / 2);
    GetEventGenerator()->MoveMouseTo(new_mouse_location);
    return new_mouse_location;
  }

  // Starts recording `recording_window_test_api`'s window, and verifies its
  // record game buttons are enabled and toggled on, while the record game
  // buttons in `other_window_test_api` are disabled and toggled off.
  void RecordGameAndVerifyButtons(
      GameDashboardContextTestApi* recording_window_test_api,
      GameDashboardContextTestApi* other_window_test_api) {
    auto* event_generator = GetEventGenerator();

    // Verify the initial state of the record buttons.
    for (auto* test_api : {recording_window_test_api, other_window_test_api}) {
      MoveCursorToEmptySpaceInFrameHeader(test_api);
      event_generator->ClickLeftButton();

      test_api->OpenTheMainMenu();
      const auto* record_game_tile = test_api->GetMainMenuRecordGameTile();
      ASSERT_TRUE(record_game_tile);
      EXPECT_TRUE(record_game_tile->GetEnabled());
      EXPECT_FALSE(record_game_tile->IsToggled());

      test_api->OpenTheToolbar();
      const auto* record_game_button = test_api->GetToolbarRecordGameButton();
      ASSERT_TRUE(record_game_button);
      EXPECT_TRUE(record_game_button->GetEnabled());
      EXPECT_FALSE(record_game_button->toggled());
    }
    const auto& recording_window_timer =
        recording_window_test_api->GetRecordingTimer();
    const auto& other_window_timer = other_window_test_api->GetRecordingTimer();

    // Verify the recording timer is not running in both windows.
    EXPECT_FALSE(recording_window_timer.IsRunning());
    EXPECT_FALSE(other_window_timer.IsRunning());

    // Verify the game dashboard buttons are not in the recording state.
    VerifyGameDashboardButtonState(recording_window_test_api,
                                   /*is_recording=*/false);
    VerifyGameDashboardButtonState(other_window_test_api,
                                   /*is_recording=*/false);

    // Activate the recording_window.
    auto* recording_window =
        recording_window_test_api->context()->game_window();
    ASSERT_TRUE(recording_window);
    MoveCursorToEmptySpaceInFrameHeader(recording_window_test_api);
    event_generator->ClickLeftButton();

    // Start recording recording_window.
    recording_window_test_api->OpenTheMainMenu();
    LeftClickOn(recording_window_test_api->GetMainMenuRecordGameTile());
    // Clicking on the record game tile closes the main menu, and asynchronously
    // starts the capture session. Run until idle to ensure that the posted task
    // runs synchronously and completes before proceeding.
    base::RunLoop().RunUntilIdle();
    ClickOnStartRecordingButtonInCaptureModeBarView();

    // Reopen the recording window's main menu, because clicking on the button
    // closed it.
    recording_window_test_api->OpenTheMainMenu();

    // Verify the recording timer is only running in `recording_window`.
    EXPECT_TRUE(recording_window_timer.IsRunning());
    EXPECT_FALSE(other_window_timer.IsRunning());

    // Verify the game dashboard button state.
    VerifyGameDashboardButtonState(recording_window_test_api,
                                   /*is_recording=*/true);
    VerifyGameDashboardButtonState(other_window_test_api,
                                   /*is_recording=*/false);

    // Retrieve the record game buttons for the `recording_window` and verify
    // they're enabled and toggled on.
    VerifyRecordGameStatus(
        recording_window_test_api->GetMainMenuRecordGameTile(),
        recording_window_test_api->GetToolbarRecordGameButton(),
        /*enabled=*/true, /*toggled=*/true);

    // Retrieve the record game buttons for the `other_window`.
    MoveCursorToEmptySpaceInFrameHeader(other_window_test_api);
    event_generator->ClickLeftButton();
    other_window_test_api->OpenTheMainMenu();

    // Retrieve the record game buttons for the `other_window` and verify
    // they're disabled and toggled off.
    VerifyRecordGameStatus(other_window_test_api->GetMainMenuRecordGameTile(),
                           other_window_test_api->GetToolbarRecordGameButton(),
                           /*enabled=*/false, /*toggled=*/false);

    // Stop the video recording session.
    MoveCursorToEmptySpaceInFrameHeader(recording_window_test_api);
    event_generator->ClickLeftButton();
    recording_window_test_api->OpenTheMainMenu();
    LeftClickOn(recording_window_test_api->GetMainMenuRecordGameTile());
    EXPECT_FALSE(CaptureModeController::Get()->is_recording_in_progress());
    WaitForCaptureFileToBeSaved();

    // Verify all the record game buttons for the `recording_window` are enabled
    // and toggled off.
    VerifyRecordGameStatus(
        recording_window_test_api->GetMainMenuRecordGameTile(),
        recording_window_test_api->GetToolbarRecordGameButton(),
        /*enabled=*/true, /*toggled=*/false);

    // Verify all the `other_window` buttons are enabled and toggled off.
    MoveCursorToEmptySpaceInFrameHeader(other_window_test_api);
    event_generator->ClickLeftButton();
    other_window_test_api->OpenTheMainMenu();
    VerifyRecordGameStatus(other_window_test_api->GetMainMenuRecordGameTile(),
                           other_window_test_api->GetToolbarRecordGameButton(),
                           /*enabled=*/true, /*toggled=*/false);

    // Verify the recording timer is not running in both windows.
    EXPECT_FALSE(recording_window_timer.IsRunning());
    EXPECT_FALSE(other_window_timer.IsRunning());

    // Verify the game dashboard buttons are no longer in the recording state.
    VerifyGameDashboardButtonState(recording_window_test_api,
                                   /*is_recording=*/false);
    VerifyGameDashboardButtonState(other_window_test_api,
                                   /*is_recording=*/false);

    // Close the toolbar and main menu in the `other_window`, which is currently
    // open.
    other_window_test_api->CloseTheToolbar();
    other_window_test_api->CloseTheMainMenu();

    // Open the main menu of the recording window to close the toolbar and then
    // the main menu.
    MoveCursorToEmptySpaceInFrameHeader(recording_window_test_api);
    event_generator->ClickLeftButton();
    recording_window_test_api->OpenTheMainMenu();
    recording_window_test_api->CloseTheToolbar();
    recording_window_test_api->CloseTheMainMenu();
  }

  void VerifyRecordGameStatus(FeatureTile* game_tile,
                              IconButton* game_button,
                              bool enabled,
                              bool toggled) {
    ASSERT_TRUE(game_tile);
    ASSERT_TRUE(game_button);
    EXPECT_EQ(enabled, game_tile->GetEnabled());
    EXPECT_EQ(enabled, game_button->GetEnabled());
    EXPECT_EQ(toggled, game_tile->IsToggled());
    EXPECT_EQ(toggled, game_button->toggled());
  }

  void TabNavigateForward() {
    GetEventGenerator()->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
  }

  void TabNavigateBackward() {
    GetEventGenerator()->PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
  }

  void PressKeyAndVerify(ui::KeyboardCode key,
                         GameDashboardToolbarSnapLocation desired_location) {
    GetEventGenerator()->PressAndReleaseKey(key);
    EXPECT_EQ(test_api_->GetToolbarSnapLocation(), desired_location);
  }

  void CreateAnArcAppInFullscreen(std::unique_ptr<chromeos::CaptionButtonModel>
                                      caption_button_model = nullptr) {
    // Create an ARC game window.
    SetAppBounds(gfx::Rect(50, 50, 800, 700));
    CreateGameWindow(/*is_arc_window=*/true,
                     /*set_arc_game_controls_flags_prop=*/true);

    auto* window_state = WindowState::Get(game_window_.get());
    ASSERT_TRUE(window_state->IsNormalStateType());
    views::Widget* button_widget = test_api_->GetGameDashboardButtonWidget();
    CHECK(button_widget);

    if (caption_button_model) {
      // Override the caption button model and ensure the values referencing the
      // model are updated.
      auto* frame_view = NonClientFrameViewAsh::Get(game_window_.get());
      ASSERT_TRUE(frame_view);
      frame_view->SetCaptionButtonModel(std::move(caption_button_model));
    }

    // Set initial state to fullscreen, ensure the animations are complete after
    // toggling the fullscreen state, and verify Game Dashboard button widget is
    // not visible.
    ASSERT_FALSE(test_api_->GetGameDashboardButtonRevealController());
    ToggleFullScreen(window_state, /*delegate=*/nullptr);
    auto* frame_view = NonClientFrameViewAsh::Get(game_window_.get());
    chromeos::FrameCaptionButtonContainerView::TestApi test_api(
        frame_view->GetHeaderView()->caption_button_container());
    test_api.EndAnimations();
    ASSERT_TRUE(window_state->IsFullscreen());
    ASSERT_FALSE(button_widget->IsVisible());
    ASSERT_TRUE(test_api_->GetGameDashboardButtonRevealController());
  }

 protected:
  void DragToolbarToPoint(Movement move_type,
                          const gfx::Point& new_location,
                          bool drop = true) {
    const auto* widget = test_api_->GetToolbarWidget();
    DCHECK(widget) << "Cannot drag toolbar because it's unavailable on screen.";
    const auto toolbar_bounds = widget->GetNativeWindow()->GetBoundsInScreen();
    auto* event_generator = GetEventGenerator();
    event_generator->set_current_screen_location(toolbar_bounds.CenterPoint());

    switch (move_type) {
      case Movement::kMouse:
        event_generator->PressLeftButton();
        event_generator->MoveMouseTo(new_location);
        if (drop) {
          event_generator->ReleaseLeftButton();
        }
        break;
      case Movement::kTouch:
        event_generator->PressTouch();
        // Move the touch by an enough amount in X to make sure it generates a
        // series of gesture scroll events instead of a fling event.
        event_generator->MoveTouchBy(50, 0);
        event_generator->MoveTouch(new_location);
        if (drop) {
          event_generator->ReleaseTouch();
        }
        break;
    }

    // Dragging the toolbar causes the main menu to close asynchronously. Run
    // until idle to ensure that this posted task runs synchronously and
    // completes before proceeding.
    base::RunLoop().RunUntilIdle();
  }

  std::unique_ptr<aura::Window> game_window_;
  std::unique_ptr<GameDashboardContextTestApi> test_api_;
  int frame_header_height_ = 0;
  // Post-target handler that captures the last mouse event.
  EventCapturer post_target_event_capturer_;

 private:
  gfx::Rect app_bounds_ = gfx::Rect(50, 50, 800, 400);
};

// Verifies Game Controls tile state.
// - The tile exists when Game Controls is available.
// - The tile is disabled if Game Controls has empty actions.
// - The tile can only be toggled when Game Controls has at least one action and
//   Game Controls feature is enabled.
TEST_F(GameDashboardContextTest, GameControlsMenuState) {
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  // Game Controls is not available (GC is optout).
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/false, /*expect_on=*/false},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/false},
      /*feature_switch_states=*/
      {/*expect_exists=*/false, /*expect_toggled=*/false},
      /*setup_exists=*/true);

  // Game Controls is available, not empty, but not enabled.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(ArcGameControlsFlag::kKnown |
                                       ArcGameControlsFlag::kAvailable));
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/false, /*expect_on=*/false},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/true},
      /*feature_switch_states=*/
      {/*expect_exists=*/true, /*expect_toggled=*/false},
      /*setup_exists=*/false);

  // Game Controls is available, but empty. Even Game Controls is set enabled,
  // the tile is disabled and can't be toggled.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEmpty | ArcGameControlsFlag::kEnabled));
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/false, /*expect_on=*/false},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/true},
      /*feature_switch_states=*/
      {/*expect_exists=*/false, /*expect_toggled=*/false},
      /*setup_exists=*/true);

  // Game controls is available, not empty, enabled and no mapping hint.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(ArcGameControlsFlag::kKnown |
                                       ArcGameControlsFlag::kAvailable |
                                       ArcGameControlsFlag::kEnabled));
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/true, /*expect_on=*/false},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/true},
      /*feature_switch_states=*/
      {/*expect_exists=*/true, /*expect_toggled=*/true},
      /*setup_exists=*/false);

  // Game controls is available, not empty, enabled and has mapping hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/true, /*expect_on=*/true},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/true},
      /*feature_switch_states=*/
      {/*expect_exists=*/true, /*expect_toggled=*/true},
      /*setup_exists=*/false);
}

TEST_F(GameDashboardContextTest, GameControlsSetupNudge) {
  CreateGameWindow(/*is_arc_window=*/true);

  // Test setup nudge for non-O4C games.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEmpty | ArcGameControlsFlag::kEnabled));

  test_api_->OpenTheMainMenu();
  EXPECT_TRUE(test_api_->GetGameControlsSetupNudge());
  task_environment()->FastForwardBy(
      AnchoredNudgeManagerImpl::kNudgeMediumDuration);
  EXPECT_FALSE(test_api_->GetGameControlsSetupNudge());
  test_api_->CloseTheMainMenu();

  // Enter the setting page immediately, the nudge should disappear.
  test_api_->OpenTheMainMenu();
  EXPECT_TRUE(test_api_->GetGameControlsSetupNudge());
  LeftClickOn(test_api_->GetMainMenuSettingsButton());
  EXPECT_FALSE(test_api_->GetGameControlsSetupNudge());
  LeftClickOn(test_api_->GetSettingsViewBackButton());
  EXPECT_TRUE(test_api_->GetGameControlsSetupNudge());
  test_api_->CloseTheMainMenu();

  // Test setup nudge for O4C games.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEmpty | ArcGameControlsFlag::kEnabled |
          ArcGameControlsFlag::kO4C));
  test_api_->OpenTheMainMenu();
  EXPECT_FALSE(test_api_->GetGameControlsSetupNudge());
}

// Verifies Game Controls button logics.
TEST_F(GameDashboardContextTest, GameControlsMenuFunctions) {
  CreateGameWindow(/*is_arc_window=*/true);

  // Game Controls is available, not empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
  EXPECT_FALSE(game_dashboard_utils::IsFlagSet(
      game_window_->GetProperty(kArcGameControlsFlagsKey),
      ArcGameControlsFlag::kMenu));

  test_api_->OpenTheMainMenu();
  // Disable Game Controls.
  EXPECT_TRUE(game_dashboard_utils::IsFlagSet(
      game_window_->GetProperty(kArcGameControlsFlagsKey),
      ArcGameControlsFlag::kMenu));
  test_api_->OpenTheToolbar();

  auto* detail_row = test_api_->GetMainMenuGameControlsDetailsButton();
  auto* switch_button = test_api_->GetMainMenuGameControlsFeatureSwitch();
  auto* game_controls_button = test_api_->GetToolbarGameControlsButton();
  EXPECT_TRUE(detail_row->GetEnabled());
  EXPECT_TRUE(switch_button->GetEnabled());
  EXPECT_TRUE(switch_button->GetIsOn());
  EXPECT_TRUE(game_controls_button->GetEnabled());
  EXPECT_TRUE(game_controls_button->toggled());
  // Disable Game Controls.
  LeftClickOn(switch_button);
  EXPECT_TRUE(detail_row->GetEnabled());
  EXPECT_TRUE(switch_button->GetEnabled());
  EXPECT_FALSE(switch_button->GetIsOn());
  // Toolbar button should also get updated.
  EXPECT_FALSE(game_controls_button->GetEnabled());

  EXPECT_FALSE(game_dashboard_utils::IsFlagSet(
      game_window_->GetProperty(kArcGameControlsFlagsKey),
      ArcGameControlsFlag::kHint));

  // Since Game Controls is disabled, press on `detail_row` should not turn on
  // `kEdit` flag.
  LeftClickOn(detail_row);
  EXPECT_FALSE(game_dashboard_utils::IsFlagSet(
      game_window_->GetProperty(kArcGameControlsFlagsKey),
      ArcGameControlsFlag::kEdit));

  test_api_->CloseTheToolbar();
  test_api_->CloseTheMainMenu();
  EXPECT_FALSE(game_dashboard_utils::IsFlagSet(
      game_window_->GetProperty(kArcGameControlsFlagsKey),
      ArcGameControlsFlag::kMenu));

  // Open the main menu again to check if the states are preserved and close it.
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/false, /*expect_on=*/false},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/true},
      /*feature_switch_states=*/
      {/*expect_exists=*/true, /*expect_toggled=*/false},
      /*setup_exists=*/false);

  // Open the main menu and toolbar. Enable Game Controls and switch hint button
  // off.
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  detail_row = test_api_->GetMainMenuGameControlsDetailsButton();
  switch_button = test_api_->GetMainMenuGameControlsFeatureSwitch();
  game_controls_button = test_api_->GetToolbarGameControlsButton();
  const auto* game_controls_tile = test_api_->GetMainMenuGameControlsTile();
  // Enable Game Controls.
  LeftClickOn(switch_button);
  EXPECT_TRUE(detail_row->GetEnabled());
  EXPECT_TRUE(switch_button->GetEnabled());
  EXPECT_TRUE(switch_button->GetIsOn());
  EXPECT_TRUE(game_controls_button->GetEnabled());
  EXPECT_TRUE(game_controls_button->toggled());
  EXPECT_TRUE(game_controls_tile->IsToggled());
  // Switch hint off.
  LeftClickOn(game_controls_tile);
  test_api_->CloseTheToolbar();
  test_api_->CloseTheMainMenu();

  // Open the main menu again to check if the states are preserved and close it.
  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/true, /*expect_enabled=*/true, /*expect_on=*/false},
      /*details_row_exists=*/{/*expect_exists=*/true, /*expect_enabled=*/true},
      /*feature_switch_states=*/
      {/*expect_exists=*/true, /*expect_toggled=*/true},
      /*setup_exists=*/false);
}

// Verify Game Dashboard button is disabled and toolbar hides in the edit mode.
TEST_F(GameDashboardContextTest, GameControlsEditMode) {
  CreateGameWindow(/*is_arc_window=*/true);
  // Game Controls is available, not empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
  auto* game_dashboard_button = test_api_->GetGameDashboardButton();
  EXPECT_TRUE(game_dashboard_button->GetEnabled());
  LeftClickOn(game_dashboard_button);
  EXPECT_TRUE(test_api_->GetMainMenuWidget());
  // Show the toolbar.
  test_api_->OpenTheToolbar();
  auto* tool_bar_widget = test_api_->GetToolbarWidget();
  EXPECT_TRUE(tool_bar_widget);
  EXPECT_TRUE(tool_bar_widget->IsVisible());

  // Enter Game Controls edit mode.
  LeftClickOn(test_api_->GetMainMenuGameControlsDetailsButton());
  EXPECT_TRUE(game_dashboard_utils::IsFlagSet(
      game_window_->GetProperty(kArcGameControlsFlagsKey),
      ArcGameControlsFlag::kEdit));
  EXPECT_FALSE(test_api_->GetMainMenuWidget());
  EXPECT_FALSE(tool_bar_widget->IsVisible());
  // In the edit mode, Game Dashboard button is disabled and it doesn't show
  // menu after clicked. The toolbar is also hidden if it shows up.
  EXPECT_FALSE(game_dashboard_button->GetEnabled());
  LeftClickOn(game_dashboard_button);
  EXPECT_FALSE(test_api_->GetMainMenuWidget());
  // Exit edit mode and verify Game Dashboard button and toolbar are resumed.
  ArcGameControlsFlag flags =
      game_window_->GetProperty(kArcGameControlsFlagsKey);
  flags = game_dashboard_utils::UpdateFlag(flags, ArcGameControlsFlag::kEdit,
                                           /*enable_flag=*/false);
  game_window_->SetProperty(kArcGameControlsFlagsKey, flags);
  EXPECT_TRUE(game_dashboard_button->GetEnabled());
  LeftClickOn(game_dashboard_button);
  EXPECT_TRUE(test_api_->GetMainMenuWidget());
  EXPECT_TRUE(tool_bar_widget->IsVisible());
}

// Verify that main menu always stacks above the toolbar.
TEST_F(GameDashboardContextTest, ZorderWithGameControls) {
  CreateGameWindow(/*is_arc_window=*/true);
  // Game Controls is available, not empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));

  // Hide Game Controls mapping hint.
  LeftClickOn(test_api_->GetMainMenuGameControlsTile());
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));
  // Show Game Controls mapping hint.
  LeftClickOn(test_api_->GetMainMenuGameControlsTile());
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));

  // Disable Game Controls feature.
  LeftClickOn(test_api_->GetMainMenuGameControlsFeatureSwitch());
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));
  // Enable Game Controls feature.
  LeftClickOn(test_api_->GetMainMenuGameControlsFeatureSwitch());
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));
}

TEST_F(GameDashboardContextTest,
       RecordEditControlsWithEmptyStateHistogramTest) {
  CreateGameWindow(/*is_arc_window=*/true);
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Game Controls is available, not empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuGameControlsDetailsButton());

  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardEditControlsWithEmptyStateHistogram);
  std::map<bool, int> expected_histogram_values;
  expected_histogram_values[false]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyGameControlsEditControlsWithEmptyStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u, /*expect_event_value=*/0);

  // Game Controls is available, empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kEmpty));
  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuGameControlsDetailsButton());
  expected_histogram_values[true]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyGameControlsEditControlsWithEmptyStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u, /*expect_event_value=*/1);
}

TEST_F(GameDashboardContextTest, RecordControlsHintToggleSourceHistogramTest) {
  CreateGameWindow(/*is_arc_window=*/true);
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  const std::string histogram_name_on =
      BuildGameDashboardHistogramName(
          kGameDashboardControlsHintToggleSourceHistogram)
          .append(kGameDashboardHistogramSeparator)
          .append(kGameDashboardHistogramOn);
  const std::string histogram_name_off =
      BuildGameDashboardHistogramName(
          kGameDashboardControlsHintToggleSourceHistogram)
          .append(kGameDashboardHistogramSeparator)
          .append(kGameDashboardHistogramOff);
  // Game Controls is available, not empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  LeftClickOn(test_api_->GetMainMenuGameControlsTile());
  std::map<GameDashboardMenu, int> expected_off_histogram_values;
  expected_off_histogram_values[GameDashboardMenu::kMainMenu]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyGameControlsHintToggleSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u,
      {{ukm::builders::GameDashboard_ControlsHintToggleSource::kToggleOnName,
        static_cast<int64_t>(false)},
       {ukm::builders::GameDashboard_ControlsHintToggleSource::kSourceName,
        static_cast<int64_t>(GameDashboardMenu::kMainMenu)}});

  LeftClickOn(test_api_->GetToolbarGameControlsButton());
  std::map<GameDashboardMenu, int> expected_on_histogram_values;
  expected_on_histogram_values[GameDashboardMenu::kToolbar]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyGameControlsHintToggleSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u,
      {{ukm::builders::GameDashboard_ControlsHintToggleSource::kToggleOnName,
        static_cast<int64_t>(true)},
       {ukm::builders::GameDashboard_ControlsHintToggleSource::kSourceName,
        static_cast<int64_t>(GameDashboardMenu::kToolbar)}});

  LeftClickOn(test_api_->GetToolbarGameControlsButton());
  expected_off_histogram_values[GameDashboardMenu::kToolbar]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyGameControlsHintToggleSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/3u,
      {{ukm::builders::GameDashboard_ControlsHintToggleSource::kToggleOnName,
        static_cast<int64_t>(false)},
       {ukm::builders::GameDashboard_ControlsHintToggleSource::kSourceName,
        static_cast<int64_t>(GameDashboardMenu::kToolbar)}});
  base::RunLoop().RunUntilIdle();

  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuGameControlsTile());
  expected_on_histogram_values[GameDashboardMenu::kMainMenu]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyGameControlsHintToggleSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/4u,
      {{ukm::builders::GameDashboard_ControlsHintToggleSource::kToggleOnName,
        static_cast<int64_t>(true)},
       {ukm::builders::GameDashboard_ControlsHintToggleSource::kSourceName,
        static_cast<int64_t>(GameDashboardMenu::kMainMenu)}});
}

TEST_F(GameDashboardContextTest,
       RecordControlsFeatureToggleStateHistogramTest) {
  CreateGameWindow(/*is_arc_window=*/true);
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Game Controls is available, not empty, enabled and hint on.
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(
          ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
          ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuGameControlsFeatureSwitch());

  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardControlsFeatureToggleStateHistogram);
  std::map<bool, int> expected_histogram_values;
  expected_histogram_values[false]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyGameControlsFeatureToggleStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u, static_cast<int64_t>(false));

  LeftClickOn(test_api_->GetMainMenuGameControlsFeatureSwitch());
  expected_histogram_values[true]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyGameControlsFeatureToggleStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u, static_cast<int64_t>(true));
}

TEST_F(GameDashboardContextTest, CompatModeArcGame) {
  // Create an ARC game window that supports Compat Mode.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE);

  test_api_->OpenTheMainMenu();

  const auto* screen_size_button =
      test_api_->GetMainMenuScreenSizeSettingsButton();
  ASSERT_TRUE(screen_size_button);
  EXPECT_TRUE(screen_size_button->GetEnabled());
}

// Verifies the screen size row isn't shown on O4C ARC game windows.
TEST_F(GameDashboardContextTest, ScreenSizeRowAvailability) {
  // Create an ARC game window that is O4C.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey, ArcResizeLockType::NONE);
  game_window_->SetProperty(
      kArcGameControlsFlagsKey,
      static_cast<ArcGameControlsFlag>(ArcGameControlsFlag::kKnown |
                                       ArcGameControlsFlag::kO4C));

  test_api_->OpenTheMainMenu();

  EXPECT_FALSE(test_api_->GetMainMenuScreenSizeSettingsButton());
}

// Verifies a not O4C resizable app in portrait mode displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_PortraitResizable) {
  // Create an ARC game window in portrait mode that is resizable.
  SetAppBounds(gfx::Rect(50, 50, 400, 700));
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE);

  test_api_->OpenTheMainMenu();

  EXPECT_EQ(u"Portrait", test_api_->GetMainMenuScreenSizeSubtitle());
}

// Verifies a not O4C resizable app in landscape mode displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_LandscapeResizable) {
  // Create an ARC game window in landscape mode that is resizable.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE);

  test_api_->OpenTheMainMenu();

  EXPECT_EQ(u"Landscape", test_api_->GetMainMenuScreenSizeSubtitle());
}

// Verifies a not O4C resizable app in resizable mode displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_FreeformResizable) {
  // Create an ARC game window in free resizing mode.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE);

  test_api_->OpenTheMainMenu();

  EXPECT_EQ(u"Resizable", test_api_->GetMainMenuScreenSizeSubtitle());
}

// Verifies a not O4C non-resizable app in portrait mode displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_PortraitNonTogglable) {
  // Create an ARC game window that only supports portrait mode.
  SetAppBounds(gfx::Rect(50, 50, 400, 700));
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE);

  test_api_->OpenTheMainMenu();

  EXPECT_EQ(u"Only portrait available",
            test_api_->GetMainMenuScreenSizeSubtitle());
}

// Verifies a not O4C non-resizable app in landscape mode displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_LandscapeNonTogglable) {
  // Create an ARC game window that only supports landscape mode.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE);

  test_api_->OpenTheMainMenu();

  EXPECT_EQ(u"Only landscape available",
            test_api_->GetMainMenuScreenSizeSubtitle());
}

// Verifies a not O4C resizable app in fullscreen displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_FullscreenTogglable) {
  // Create an ARC game window in fullscreen that can be resized via the size
  // button in the frame header.
  CreateAnArcAppInFullscreen();

  // Open the Game Dashboard menu with the accelerator.
  AcceleratorControllerImpl* controller =
      Shell::Get()->accelerator_controller();
  const ui::Accelerator gd_accelerator(ui::VKEY_G, ui::EF_COMMAND_DOWN);
  auto* button_widget = test_api_->GetGameDashboardButtonWidget();
  CHECK(button_widget);
  ASSERT_TRUE(controller->Process(gd_accelerator));
  ASSERT_TRUE(button_widget->IsVisible());

  EXPECT_EQ(u"Exit fullscreen to resize",
            test_api_->GetMainMenuScreenSizeSubtitle());
}

// Verifies a not O4C non-resizable app in fullscreen displays the expected
// description within the screen size row.
TEST_F(GameDashboardContextTest, ScreenSizeRowSubtitle_FullscreenNonTogglable) {
  // Create an ARC game window in fullscreen that can't be resized.
  CreateAnArcAppInFullscreen(
      /*caption_button_model=*/std::make_unique<NonResizableButtonModel>());

  // Open the Game Dashboard menu with the accelerator.
  AcceleratorControllerImpl* controller =
      Shell::Get()->accelerator_controller();
  const ui::Accelerator gd_accelerator(ui::VKEY_G, ui::EF_COMMAND_DOWN);
  auto* button_widget = test_api_->GetGameDashboardButtonWidget();
  CHECK(button_widget);
  ASSERT_TRUE(controller->Process(gd_accelerator));
  ASSERT_TRUE(button_widget->IsVisible());

  EXPECT_EQ(u"Only fullscreen available",
            test_api_->GetMainMenuScreenSizeSubtitle());
}

TEST_F(GameDashboardContextTest, NonCompatModeArcGame) {
  // Create an ARC game window that doesn't support Compat Mode.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE);

  test_api_->OpenTheMainMenu();

  const auto* screen_size_button =
      test_api_->GetMainMenuScreenSizeSettingsButton();
  ASSERT_TRUE(screen_size_button);
  EXPECT_FALSE(screen_size_button->GetEnabled());
  EXPECT_EQ(u"This app supports only this size.",
            screen_size_button->GetTooltipText());
}

// Verifies the Main Menu View closes when the Screen Size row is selected.
TEST_F(GameDashboardContextTest, SelectScreenSizeButton) {
  // Create an ARC game window.
  CreateGameWindow(/*is_arc_window=*/true);
  game_window_->SetProperty(kArcResizeLockTypeKey,
                            ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE);

  test_api_->OpenTheMainMenu();

  const auto* screen_size_button =
      test_api_->GetMainMenuScreenSizeSettingsButton();
  ASSERT_TRUE(screen_size_button);
  ASSERT_TRUE(screen_size_button->GetEnabled());

  LeftClickOn(screen_size_button);

  EXPECT_FALSE(test_api_->GetMainMenuWidget());
}

// Verifies that when one game window starts a recording session, it's
// record game buttons are enabled and the other game's record game buttons
// are disabled.
TEST_F(GameDashboardContextTest, TwoGameWindowsRecordingState) {
  // Create an ARC game window.
  CreateGameWindow(/*is_arc_window=*/true);

  // Create a GFN game window that doesn't overlap with the ARC game window.
  // This allows the test to interact with both windows without having to
  // artificially activate it.
  auto gfn_game_window = CreateAppWindow(extension_misc::kGeForceNowAppId,
                                         chromeos::AppType::NON_APP,
                                         gfx::Rect(950, 550, 400, 200));
  auto* gfn_game_context =
      GameDashboardController::Get()->GetGameDashboardContext(
          gfn_game_window.get());
  ASSERT_TRUE(gfn_game_context);
  auto gfn_window_test_api =
      GameDashboardContextTestApi(gfn_game_context, GetEventGenerator());

  // Start recording the ARC game window, and verify both windows' record game
  // button states.
  RecordGameAndVerifyButtons(
      /*recording_window_test_api=*/test_api_.get(),
      /*other_window_test_api=*/&gfn_window_test_api);

  // Start recording the GFN game window, and verify both windows' "record
  // game" button states.
  RecordGameAndVerifyButtons(
      /*recording_window_test_api=*/&gfn_window_test_api,
      /*other_window_test_api=*/test_api_.get());
}

TEST_F(GameDashboardContextTest, MainMenuClockView) {
  // Enable Game Dashboard utilities flag.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature({features::kGameDashboardUtilities});
  // Create an ARC game window.
  CreateGameWindow(/*is_arc_window=*/true);

  // Set current time to 08:00.
  task_environment()->AdvanceClock(base::Time::Now().LocalMidnight() +
                                   base::Hours(32) - base::Time::Now());

  test_api_->OpenTheMainMenu();
  auto* clock_view = test_api_->GetMainMenuClockView();
  // Ensure that the time and "AM/PM" text is visible and that a 12 hour clock
  // is used by default.
  ASSERT_TRUE(clock_view);
  ASSERT_TRUE(clock_view->GetVisible());
  ASSERT_EQ(clock_view->GetAmPmClockTypeForTesting(),
            base::AmPmClockType::kKeepAmPm);
  ASSERT_EQ(clock_view->GetHourTypeForTesting(),
            base::HourClockType::k12HourClock);

  const auto* horizontal_time_label =
      clock_view->GetHorizontalTimeLabelForTesting();
  const auto current_time = horizontal_time_label->GetText();
  // Verify that the "AM/PM" text is visible.
  ASSERT_NE(current_time.ends_with(u"AM"), current_time.ends_with(u"PM"));
  // Ensure that the clock increments as the time changes.
  AdvanceClock(base::Hours(12));
  const auto next_time = horizontal_time_label->GetText();
  const std::u16string next_am_pm =
      current_time.ends_with(u"AM") ? u"PM" : u"AM";
  ASSERT_NE(current_time, next_time);
  // Verify that the "AM/PM" text changes after advancing 12 hours.
  ASSERT_TRUE(next_time.ends_with(next_am_pm));

  // Change the clock to a 24 hour view.
  Shell::Get()->system_tray_model()->SetUse24HourClock(true);
  // Verify that the clock is still visible.
  ASSERT_TRUE(clock_view->GetVisible());
  // Verify that the clock view is 24 hours.
  ASSERT_EQ(clock_view->GetHourTypeForTesting(),
            base::HourClockType::k24HourClock);
  const auto hour_24_current_time = horizontal_time_label->GetText();
  // Verify that the "AM/PM" text is not visible.
  ASSERT_FALSE(hour_24_current_time.ends_with(u"AM") ||
               hour_24_current_time.ends_with(u"PM"));

  // Revert to the default 12 hour view.
  Shell::Get()->system_tray_model()->SetUse24HourClock(false);
  // Verify that the clock is still visible.
  ASSERT_TRUE(clock_view->GetVisible());
  // Verify that the clock view is 12 hours.
  ASSERT_EQ(clock_view->GetHourTypeForTesting(),
            base::HourClockType::k12HourClock);
  const auto reverted_current_time = horizontal_time_label->GetText();
  // Verify that the "AM/PM" text is visible.
  ASSERT_NE(reverted_current_time.ends_with(u"AM"),
            reverted_current_time.ends_with(u"PM"));
}

TEST_F(GameDashboardContextTest, RecordingTimerStringFormat) {
  // Create an ARC game window.
  CreateGameWindow(/*is_arc_window=*/true);

  // Verify recording duration is 0, by default.
  EXPECT_EQ(u"00:00", test_api_->GetRecordingDuration());

  // Start recording the game window.
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  const auto* record_game_button = test_api_->GetToolbarRecordGameButton();
  ASSERT_TRUE(record_game_button);
  LeftClickOn(record_game_button);
  ClickOnStartRecordingButtonInCaptureModeBarView();

  // Get timer and verify it's running.
  const auto& timer = test_api_->GetRecordingTimer();
  EXPECT_TRUE(timer.IsRunning());

  // Verify initial time of 0 seconds.
  EXPECT_EQ(u"00:00", test_api_->GetRecordingDuration());

  // Advance clock by 1 minute, and verify overflow from seconds to minutes.
  AdvanceClock(base::Minutes(1));
  EXPECT_EQ(u"01:00", test_api_->GetRecordingDuration());

  // Advance clock by 30 seconds.
  AdvanceClock(base::Seconds(30));
  EXPECT_EQ(u"01:30", test_api_->GetRecordingDuration());

  // Advance clock by 50 minutes.
  AdvanceClock(base::Minutes(50));
  EXPECT_EQ(u"51:30", test_api_->GetRecordingDuration());

  // Advance clock by 9 minutes, and verify overflow from minutes to hours.
  AdvanceClock(base::Minutes(9));
  EXPECT_EQ(u"1:00:30", test_api_->GetRecordingDuration());

  // Advance clock by 23 hours, and verify hours doesn't overflow to days.
  AdvanceClock(base::Hours(23));
  EXPECT_EQ(u"24:00:30", test_api_->GetRecordingDuration());

  // Stop the recording.
  LeftClickOn(record_game_button);

  // Verify recording duration is reset to 0.
  EXPECT_EQ(u"00:00", test_api_->GetRecordingDuration());
}

// Verifies the welcome dialog displays when the game window first opens and
// disappears after 4 seconds.
TEST_F(GameDashboardContextTest, WelcomeDialogAutoDismisses) {
  // Open the game window with the welcome dialog enabled.
  game_dashboard_utils::SetShowWelcomeDialog(true);
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  // Verify the welcome dialog is initially shown and is right aligned in the
  // app window.
  gfx::Rect welcome_dialog_bounds =
      test_api_->GetWelcomeDialogWidget()->GetWindowBoundsInScreen();
  EXPECT_EQ(welcome_dialog_bounds.x(),
            (game_window_->GetBoundsInScreen().right() -
             game_dashboard::kWelcomeDialogEdgePadding -
             game_dashboard::kWelcomeDialogFixedWidth -
             game_dashboard::kWelcomeDialogBorderThickness * 2));
  // Border thickness variable is 2x to account for its addition on both sides
  // of the welcome dialog shifting the bounds

  // Dismiss welcome dialog after 4 seconds and verify the dialog is no longer
  // visible.
  task_environment()->FastForwardBy(base::Seconds(4));
  EXPECT_FALSE(test_api_->GetWelcomeDialogWidget());
}

// Verifies the welcome dialog disappears when the main menu view is opened.
TEST_F(GameDashboardContextTest, WelcomeDialogDismissOnMainMenuOpening) {
  // Open the game window with the welcome dialog enabled.
  game_dashboard_utils::SetShowWelcomeDialog(true);
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  // Open the main menu and verify the welcome dialog dismisses.
  test_api_->OpenTheMainMenu();
  EXPECT_FALSE(test_api_->GetWelcomeDialogWidget());
}

// Verifies the welcome dialog is centered when the app window width is small
// enough.
TEST_F(GameDashboardContextTest, WelcomeDialogWithSmallWindow) {
  // Open a new game window with a width of 450.
  game_dashboard_utils::SetShowWelcomeDialog(true);
  SetAppBounds(gfx::Rect(50, 50, 450, 400));
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  // Verify the welcome dialog is centered.
  gfx::Rect welcome_dialog_bounds =
      test_api_->GetWelcomeDialogWidget()->GetWindowBoundsInScreen();
  EXPECT_EQ(welcome_dialog_bounds.x(),
            (game_window_->GetBoundsInScreen().x() +
             (game_window_->GetBoundsInScreen().width() -
              game_dashboard::kWelcomeDialogFixedWidth -
              game_dashboard::kWelcomeDialogBorderThickness * 2) /
                 2));
  // Border thickness variable is 2x to account for its addition on both sides
  // of the welcome dialog shifting the bounds
}

TEST_F(GameDashboardContextTest, MainMenuCursorHandlerEventLocation) {
  // Create an ARC game window.
  SetAppBounds(gfx::Rect(50, 50, 800, 700));
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  auto* event_generator = GetEventGenerator();
  auto* cursor_manager = Shell::Get()->cursor_manager();

  // Move the mouse to the center of the window and verify the cursor is
  // visible.
  event_generator->MoveMouseToCenterOf(game_window_.get());
  ASSERT_TRUE(cursor_manager->IsCursorVisible());

  // Hide the cursor and verify it's hidden.
  cursor_manager->HideCursor();
  ASSERT_FALSE(cursor_manager->IsCursorVisible());

  // Open the main menu and verify `GameDashboardMainMenuCursorHandler` exists
  // and the cursor is visible.
  ASSERT_FALSE(test_api_->GetMainMenuCursorHandler());
  test_api_->OpenTheMainMenu();
  ASSERT_TRUE(test_api_->GetMainMenuCursorHandler());
  ASSERT_TRUE(cursor_manager->IsCursorVisible());

  gfx::Point new_mouse_location =
      MoveCursorToEmptySpaceInFrameHeader(test_api_.get());

  // Verify the mouse event was not consumed by
  // `GameDashboardMainMenuCursorHandler`.
  auto* last_mouse_event = post_target_event_capturer_.last_mouse_event();
  ASSERT_TRUE(last_mouse_event);
  ASSERT_FALSE(last_mouse_event->handled());
  ASSERT_FALSE(last_mouse_event->stopped_propagation());

  // Move the mouse to the center of the window, and below the main menu.
  new_mouse_location.set_x(game_window_->GetBoundsInScreen().CenterPoint().x());
  const auto main_menu_bounds =
      test_api_->GetMainMenuView()->GetBoundsInScreen();
  new_mouse_location.set_y(main_menu_bounds.y() + main_menu_bounds.height() +
                           50);

  // Verify the mouse event was consumed by
  // `GameDashboardMainMenuCursorHandler`.
  post_target_event_capturer_.Reset();
  event_generator->MoveMouseTo(new_mouse_location);
  ASSERT_FALSE(post_target_event_capturer_.last_mouse_event());
}

TEST_F(GameDashboardContextTest, RecordingUpdatesInkDropColor) {
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  auto* game_dashboard_button = test_api_->GetGameDashboardButton();
  ASSERT_TRUE(game_dashboard_button);
  auto* color_provider = game_dashboard_button->GetColorProvider();
  ASSERT_TRUE(color_provider);
  const auto* ink_drop = views::InkDrop::Get(game_dashboard_button);
  ASSERT_TRUE(ink_drop);

  // Verify the InkDrop's base color in the normal state.
  EXPECT_EQ(ink_drop->GetBaseColor(),
            color_provider->GetColor(cros_tokens::kCrosSysRipplePrimary));

  // Start recording the game window.
  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuRecordGameTile());
  base::RunLoop().RunUntilIdle();
  ClickOnStartRecordingButtonInCaptureModeBarView();

  // Verify the InkDrop's base color when recording the game window.
  EXPECT_EQ(
      ink_drop->GetBaseColor(),
      color_provider->GetColor(cros_tokens::kCrosSysRippleNeutralOnProminent));
}

TEST_F(GameDashboardContextTest, GameDashboardButtonFullscreen) {
  // Create an ARC game window.
  SetAppBounds(gfx::Rect(50, 50, 800, 700));
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  AcceleratorControllerImpl* controller =
      Shell::Get()->accelerator_controller();
  const ui::Accelerator gd_accelerator(ui::VKEY_G, ui::EF_COMMAND_DOWN);
  auto* window_state = WindowState::Get(game_window_.get());
  auto* button_widget = test_api_->GetGameDashboardButtonWidget();
  CHECK(button_widget);

  // Initial state.
  ASSERT_FALSE(window_state->IsFullscreen());
  ASSERT_TRUE(button_widget->IsVisible());

  // Switch to fullscreen and verify Game Dashboard button widget is not
  // visible.
  ToggleFullScreen(window_state, /*delegate=*/nullptr);
  ASSERT_TRUE(window_state->IsFullscreen());
  ASSERT_FALSE(button_widget->IsVisible());

  // Open the Game Dashboard menu with the accelerator and verify the game
  // dashboard button widget is visible.
  ASSERT_TRUE(controller->Process(gd_accelerator));
  ASSERT_TRUE(button_widget->IsVisible());

  // Close the Game Dashboard menu with the accelerator and verify the game
  // dashboard button widget is still visible.
  ASSERT_TRUE(controller->Process(gd_accelerator));
  ASSERT_TRUE(button_widget->IsVisible());

  // Move the mouse to the center of the game window and verify the game
  // dashboard button widget is not visible.
  GetEventGenerator()->MoveMouseTo(
      game_window_->GetBoundsInScreen().CenterPoint());
  ASSERT_FALSE(button_widget->IsVisible());

  // Exit fullscreen and verify Game Dashboard button widget is visible.
  ToggleFullScreen(window_state, /*delegate=*/nullptr);
  ASSERT_FALSE(window_state->IsFullscreen());
  ASSERT_TRUE(button_widget->IsVisible());
}

TEST_F(GameDashboardContextTest, GameDashboardButtonFullscreenWithMainMenu) {
  // Create an ARC game window.
  SetAppBounds(gfx::Rect(50, 50, 800, 700));
  CreateGameWindow(/*is_arc_window=*/true,
                   /*set_arc_game_controls_flags_prop=*/true);

  AcceleratorControllerImpl* controller =
      Shell::Get()->accelerator_controller();
  const ui::Accelerator gd_accelerator(ui::VKEY_G, ui::EF_COMMAND_DOWN);
  auto* window_state = WindowState::Get(game_window_.get());
  auto* button_widget = test_api_->GetGameDashboardButtonWidget();
  CHECK(button_widget);

  // Initial state.
  ASSERT_FALSE(window_state->IsFullscreen());
  ASSERT_TRUE(button_widget->IsVisible());
  GetEventGenerator()->MoveMouseTo(
      game_window_->GetBoundsInScreen().CenterPoint());

  // Open the main menu using the accelerator
  ASSERT_TRUE(controller->Process(gd_accelerator));

  // Switch to fullscreen and verify Game Dashboard button widget is visible.
  ToggleFullScreen(window_state, /*delegate=*/nullptr);
  ASSERT_TRUE(window_state->IsFullscreen());
  ASSERT_TRUE(button_widget->IsVisible());

  // Close the main menu using the accelerator and verify the Game Dashboard
  // button widget is visible.
  ASSERT_TRUE(controller->Process(gd_accelerator));
  ASSERT_TRUE(button_widget->IsVisible());

  // Move the mouse slightly and verify the Game Dashboard button widget is not
  // visible.
  GetEventGenerator()->MoveMouseBy(/*x=*/1, /*y=*/1);
  ASSERT_FALSE(button_widget->IsVisible());
}

TEST_F(GameDashboardContextTest,
       GameDashboardButtonFullscreen_MouseOverAndTouchGesture) {
  CreateAnArcAppInFullscreen();
  views::Widget* button_widget = test_api_->GetGameDashboardButtonWidget();
  CHECK(button_widget);

  // Move mouse to top edge of window.
  const auto app_bounds = game_window_->GetBoundsInScreen();
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(app_bounds.top_center());
  base::OneShotTimer& top_edge_hover_timer =
      test_api_->GetRevealControllerTopEdgeHoverTimer();
  ASSERT_TRUE(top_edge_hover_timer.IsRunning());
  top_edge_hover_timer.FireNow();
  ASSERT_TRUE(button_widget->IsVisible());

  // Move mouse to the center of the app, and verify Game Dashboard button
  // widget is not visible.
  event_generator->MoveMouseTo(app_bounds.CenterPoint());
  ASSERT_FALSE(button_widget->IsVisible());

  // Touch drag from top edge of window.
  event_generator->GestureScrollSequence(app_bounds.top_center(),
                                         app_bounds.CenterPoint(),
                                         kTouchDragDuration, kTouchDragSteps);
  ASSERT_TRUE(button_widget->IsVisible());

  // Touch drag to top edge of window.
  event_generator->GestureScrollSequence(app_bounds.CenterPoint(),
                                         app_bounds.top_center(),
                                         kTouchDragDuration, kTouchDragSteps);
  ASSERT_FALSE(button_widget->IsVisible());

  // Re-open the game dashboard button and touch drag to bottom edge of window.
  event_generator->GestureScrollSequence(app_bounds.top_center(),
                                         app_bounds.CenterPoint(),
                                         kTouchDragDuration, kTouchDragSteps);
  ASSERT_TRUE(button_widget->IsVisible());

  event_generator->GestureScrollSequence(app_bounds.CenterPoint(),
                                         app_bounds.bottom_center(),
                                         kTouchDragDuration, kTouchDragSteps);
  ASSERT_FALSE(button_widget->IsVisible());

  // Touch drag to bottom edge of window while the game dashboard button is
  // hidden.
  event_generator->GestureScrollSequence(app_bounds.CenterPoint(),
                                         app_bounds.bottom_center(),
                                         kTouchDragDuration, kTouchDragSteps);
  ASSERT_FALSE(button_widget->IsVisible());
}

TEST_F(GameDashboardContextTest, GameDashboardButtonFullscreen_TouchEvent) {
  CreateAnArcAppInFullscreen();
  views::Widget* button_widget = test_api_->GetGameDashboardButtonWidget();
  CHECK(button_widget);

  // Move mouse to top edge of window, and verify Game Dashboard button
  // widget is visible.
  const auto app_bounds = game_window_->GetBoundsInScreen();
  auto* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(app_bounds.top_center());
  base::OneShotTimer& top_edge_hover_timer =
      test_api_->GetRevealControllerTopEdgeHoverTimer();
  ASSERT_TRUE(top_edge_hover_timer.IsRunning());
  top_edge_hover_timer.FireNow();
  ASSERT_TRUE(button_widget->IsVisible());

  // Touch outside the Game Dashboard button widget's bounds and verify the
  // widget is hidden.
  event_generator->PressTouch(app_bounds.right_center());
  event_generator->ReleaseTouch();
  ASSERT_FALSE(button_widget->IsVisible());
}

TEST_F(GameDashboardContextTest,
       GameDashboardOverviewModeStaticWidgetPosition_ZoomEvent) {
  CreateGameWindow(/*is_arc_window=*/true);
  // Open the Game Dashboard Main Menu and Toolbar widgets, and then enter
  // overview mode.
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  EnterOverview();

  // Slightly zoom in.
  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
      AcceleratorAction::kScaleUiDown, {});

  ExitOverview();

  // Verify that the default snap position is `kTopRight` and toolbar is placed
  // in the top right quadrant.
  EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kTopRight);

  // Verify that the position of the Game Dashboard Main Menu widget is still
  // centered at the top of the game window.
  const gfx::Point expected_button_center_point(
      game_window_->GetBoundsInScreen().top_center().x(),
      app_bounds().y() + frame_header_height_ / 2);
  EXPECT_EQ(expected_button_center_point,
            test_api_->GetGameDashboardButtonWidget()
                ->GetNativeWindow()
                ->GetBoundsInScreen()
                .CenterPoint());
}

// Verifies that during a snap animation, the Game Dashboard and toolbar widgets
// are not visible.
TEST_F(GameDashboardContextTest, UIVisibilityWithWindowSnapAnimation) {
  // Prevent short-circuit animations in this test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);

  // Create an ARC game window.
  CreateGameWindow(/*is_arc_window=*/true);

  // Extract animation layer.
  ui::LayerAnimator* animator = game_window_->layer()->GetAnimator();
  // Prevent animation from automatically running.
  animator->set_disable_timer_for_test(true);

  // Ensure that widgets exist and are visible before animation occurs.
  const auto* game_dashboard_button_widget =
      test_api_->GetGameDashboardButtonWidget();
  ASSERT_TRUE(game_dashboard_button_widget);
  ASSERT_TRUE(game_dashboard_button_widget->IsVisible());
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // Snap Left.
  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
      AcceleratorAction::kWindowCycleSnapLeft, {});
  // Ensure that the animation is occurring.
  ASSERT_TRUE(animator->is_animating());
  // Manually take a step through the animation while it is running. The
  // animation is still incomplete 10 milliseconds in.
  animator->Step(animator->last_step_time() + base::Milliseconds(10));

  // Verify that the widgets are not visible during the animation.
  ASSERT_FALSE(game_dashboard_button_widget->IsVisible());
  const auto* toolbar_widget = test_api_->GetToolbarWidget();
  ASSERT_FALSE(toolbar_widget->IsVisible());

  // Run the animation to completion.
  animator->StopAnimating();

  // Verify that the animation is complete.
  ASSERT_FALSE(animator->is_animating());
  // Ensure that the widgets are visible at the conclusion of the animation.
  ASSERT_TRUE(game_dashboard_button_widget->IsVisible());
  ASSERT_TRUE(toolbar_widget->IsVisible());
  // Ensure that the window is snapped.
  ASSERT_TRUE(WindowState::Get(game_window_.get())->IsSnapped());
}

// Verifies that during a float animation, the Game Dashboard and toolbar
// widgets are not visible.
TEST_F(GameDashboardContextTest, UIVisibilityWithWindowFloatAnimation) {
  // Do not short-circuit animations in this test.
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);

  // Create an ARC game window.
  CreateGameWindow(/*is_arc_window=*/true);

  // Extract animation layer.
  ui::LayerAnimator* animator = game_window_->layer()->GetAnimator();

  // Prevent animation from automatically running.
  animator->set_disable_timer_for_test(true);

  const auto* game_dashboard_button_widget =
      test_api_->GetGameDashboardButtonWidget();
  ASSERT_TRUE(game_dashboard_button_widget);
  ASSERT_TRUE(game_dashboard_button_widget->IsVisible());

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // Trigger the float animation.
  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);

  // Ensure that the animation is occurring.
  ASSERT_TRUE(animator->is_animating());

  // Manually take a step through the animation.
  animator->Step(animator->last_step_time() + base::Milliseconds(10));

  // Verify that the widgets are not visible.
  ASSERT_FALSE(game_dashboard_button_widget->IsVisible());
  ASSERT_FALSE(test_api_->GetToolbarWidget()->IsVisible());

  // Run the animation to completion.
  ShellTestApi().WaitForWindowFinishAnimating(game_window_.get());

  // Ensure that the widgets are visible at the conclusion of the animation.
  EXPECT_TRUE(game_dashboard_button_widget->IsVisible());
  ASSERT_TRUE(test_api_->GetToolbarWidget()->IsVisible());

  ASSERT_TRUE(WindowState::Get(game_window_.get())->IsFloated());
}

// Verifies that at startup and with the welcome dialog is visible, opening
// the main menu dismisses the welcome dialog and shows the toolbar.
TEST_F(GameDashboardContextTest, MainMenuAndToolbarAndWelcomeDialogStartup) {
  game_dashboard_utils::SetShowWelcomeDialog(true);
  game_dashboard_utils::SetShowToolbar(true);
  CreateGameWindow(/*is_arc_window=*/true);

  // Verify the welcome dialog is visible and the toolbar is not visible.
  ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
  ASSERT_FALSE(test_api_->GetToolbarWidget());

  // Advance by 1 second and verify the widgets visibility has not changed.
  task_environment()->FastForwardBy(base::Seconds(1));
  ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
  ASSERT_FALSE(test_api_->GetToolbarWidget());

  // Click on the Game Dashboard button to open the main menu.
  test_api_->OpenTheMainMenu();

  // Verify the welcome dialog is no longer visible, and the toolbar is now
  // visible.
  ASSERT_FALSE(test_api_->GetWelcomeDialogWidget());
  ASSERT_TRUE(test_api_->GetToolbarWidget());
}

// Opens both a GFN and ARC game window with the toolbar visible and verifies
// focus is respected after overview mode exits.
TEST_F(GameDashboardContextTest, OverviewModeWithTwoWindows) {
  // Create a GFN game window with the toolbar displayed.
  game_dashboard_utils::SetShowToolbar(true);
  std::unique_ptr<aura::Window> gfn_game_window =
      CreateAppWindow(extension_misc::kGeForceNowAppId,
                      chromeos::AppType::NON_APP, gfx::Rect(50, 50, 400, 200));
  ASSERT_TRUE(gfn_game_window->HasFocus());
  auto gfn_window_test_api = GameDashboardContextTestApi(
      GameDashboardController::Get()->GetGameDashboardContext(
          gfn_game_window.get()),
      GetEventGenerator());
  ASSERT_TRUE(gfn_window_test_api.GetToolbarWidget());

  // Create an ARC game window with the toolbar displayed.
  CreateGameWindow(/*is_arc_window=*/true);
  auto* arc_game_window = game_window_.get();
  auto arc_window_test_api = GameDashboardContextTestApi(
      GameDashboardController::Get()->GetGameDashboardContext(arc_game_window),
      GetEventGenerator());
  ASSERT_TRUE(arc_window_test_api.GetToolbarWidget());
  ASSERT_FALSE(gfn_game_window->HasFocus());
  ASSERT_TRUE(arc_game_window->HasFocus());
  // In the toolbar, click the gamepad button and press tab so the gamepad
  // button gains focus.
  const auto* arc_gamepad_button =
      arc_window_test_api.GetToolbarGamepadButton();
  ASSERT_TRUE(arc_gamepad_button);
  LeftClickOn(arc_gamepad_button);
  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_TAB);
  ASSERT_FALSE(arc_game_window->HasFocus());
  ASSERT_TRUE(arc_gamepad_button->HasFocus());

  // Enter and exit overview mode and verify the ARC game window's toolbar
  // maintains focus.
  EnterOverview();
  ExitOverview();
  ASSERT_FALSE(gfn_game_window->HasFocus());
  ASSERT_FALSE(arc_game_window->HasFocus());
  ASSERT_TRUE(arc_gamepad_button->HasFocus());
}

TEST_F(GameDashboardContextTest, TabNavigationMainMenu) {
  // Open the main menu and begin tab navigation.
  CreateGameWindow(/*is_arc_window=*/false);
  test_api_->OpenTheMainMenu();
  TabNavigateForward();

  // Verify focus is placed on the main menu's first element then move focus to
  // the last element in the main menu.
  views::Widget* main_menu_widget = test_api_->GetMainMenuWidget();
  EXPECT_TRUE(main_menu_widget->IsActive());
  EXPECT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus());
  main_menu_widget->GetFocusManager()->SetFocusedView(
      test_api_->GetMainMenuSettingsButton());
  EXPECT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus());

  // Tab navigate forward and verify focus is placed on the Game Dashboard
  // Button.
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Tab navigate forward and verify focus is placed back on the main menu's
  // first element.
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus());

  // Tab navigate backwards and verify focus is placed back on the Game
  // Dashboard button.
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Tab navigate backwards and verify focus is placed on the last element in
  // the main menu.
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus());
}

TEST_F(GameDashboardContextTest, TabNavigationMainMenuAndToolbar) {
  // Open the main menu and toolbar, then tab navigate to the last element in
  // the main menu.
  CreateGameWindow(/*is_arc_window=*/false);
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  TabNavigateForward();
  views::Widget* main_menu_widget = test_api_->GetMainMenuWidget();
  ASSERT_TRUE(main_menu_widget->IsActive());
  ASSERT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus());
  main_menu_widget->GetFocusManager()->SetFocusedView(
      test_api_->GetMainMenuSettingsButton());
  ASSERT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus());

  // Tab navigate forward and verify focus is placed on the first element in the
  // toolbar.
  TabNavigateForward();
  views::Widget* toolbar_widget = test_api_->GetToolbarWidget();
  EXPECT_TRUE(toolbar_widget->IsActive());
  EXPECT_TRUE(test_api_->GetToolbarGamepadButton()->HasFocus());

  // Move focus to the last element in the toolbar, tab navigate forward, and
  // verify focus is placed on the Game Dashboard button.
  toolbar_widget->GetFocusManager()->SetFocusedView(
      test_api_->GetToolbarScreenshotButton());
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Tab navigate forward and verify focus is placed back on the main menu's
  // first element.
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetMainMenuToolbarTile()->HasFocus());

  // Tab navigate backwards and verify focus is placed back on the Game
  // Dashboard button.
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Tab navigate backwards and verify focus is placed back on the last element
  // in the toolbar.
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetToolbarScreenshotButton()->HasFocus());

  // Move focus to the first element in the toolbar, tab navigate backwards, and
  // verify focus is placed on the last element in the main menu.
  toolbar_widget->GetFocusManager()->SetFocusedView(
      test_api_->GetToolbarGamepadButton());
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetMainMenuSettingsButton()->HasFocus());

  // Close the toolbar, tab navigate forward, and verify focus is placed on the
  // Game Dashboard Button.
  test_api_->CloseTheToolbar();
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Close the main menu and verify the accessibility tree is updated.
  test_api_->CloseTheMainMenu();
}

TEST_F(GameDashboardContextTest, TabNavigationToolbar) {
  // Open the main menu and toolbar, close the main menu, then begin tab
  // navigation.
  CreateGameWindow(/*is_arc_window=*/false);
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  test_api_->CloseTheMainMenu();
  test_api_->SetFocusOnToolbar();
  ASSERT_TRUE(test_api_->IsToolbarExpanded());
  TabNavigateForward();

  // Verify the toolbar is active and has focus.
  views::Widget* toolbar_widget = test_api_->GetToolbarWidget();
  EXPECT_TRUE(toolbar_widget->IsActive());
  EXPECT_TRUE(test_api_->GetToolbarGamepadButton()->HasFocus());

  // Move focus to the last element in the toolbar, tab navigate forward, and
  // verify focus is placed on the Game Dashboard button.
  toolbar_widget->GetFocusManager()->SetFocusedView(
      test_api_->GetToolbarScreenshotButton());
  ASSERT_TRUE(test_api_->GetToolbarScreenshotButton()->HasFocus());
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Tab navigate forward and verify focus is placed back on the toolbar's
  // first element.
  TabNavigateForward();
  EXPECT_TRUE(test_api_->GetToolbarGamepadButton()->HasFocus());

  // Tab navigate backwards and verify focus is placed back on the Game
  // Dashboard button.
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetGameDashboardButton()->HasFocus());

  // Tab navigate backwards and verify focus is placed back on the last element
  // in the toolbar.
  TabNavigateBackward();
  EXPECT_TRUE(test_api_->GetToolbarScreenshotButton()->HasFocus());
}

class SnapGroupGameDashboardContextTest : public GameDashboardContextTest {
 public:
  SnapGroupGameDashboardContextTest()
      : scoped_feature_list_(features::kSnapGroup) {}

  SnapGroupGameDashboardContextTest(const SnapGroupGameDashboardContextTest&) =
      delete;
  SnapGroupGameDashboardContextTest& operator=(
      const SnapGroupGameDashboardContextTest&) = delete;
  ~SnapGroupGameDashboardContextTest() override = default;

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

// Tests no crash when the game window in a snap group is fullscreen'ed then
// forces a work area change. Regression test for http://b/348668590.
TEST_F(SnapGroupGameDashboardContextTest, NoCrashOnSnapGroupWorkAreaChange) {
  // Create a snap group with the game window.
  CreateGameWindow(/*is_arc_window=*/false);
  std::unique_ptr<aura::Window> w2(AshTestBase::CreateAppWindow());

  WindowState* window_state2 = WindowState::Get(w2.get());
  const WindowSnapWMEvent secondary_snap_event(
      WM_EVENT_SNAP_SECONDARY, chromeos::kDefaultSnapRatio,
      WindowSnapActionSource::kSnapByWindowLayoutMenu);
  window_state2->OnWMEvent(&secondary_snap_event);
  EXPECT_TRUE(window_state2->IsSnapped());

  WindowState* window_state1 = WindowState::Get(game_window_.get());
  const WindowSnapWMEvent primary_snap_event(
      WM_EVENT_SNAP_PRIMARY, chromeos::kDefaultSnapRatio,
      WindowSnapActionSource::kSnapByWindowLayoutMenu);
  window_state1->OnWMEvent(&primary_snap_event);
  EXPECT_TRUE(window_state1->IsSnapped());

  ASSERT_TRUE(SnapGroupController::Get()->AreWindowsInSnapGroup(
      game_window_.get(), w2.get()));
  ASSERT_TRUE(test_api_->GetGameDashboardButtonWidget()->IsVisible());

  // Fullscreen the game window. Test no crash.
  wm::ActivateWindow(game_window_.get());
  ToggleFullScreen(window_state1, /*delegate=*/nullptr);
}

// -----------------------------------------------------------------------------
// GameTypeGameDashboardContextTest:
// Test fixture to test both ARC and GeForceNow game window depending on the
// test param (true for ARC game window, false for GeForceNow window).
class GameTypeGameDashboardContextTest
    : public GameDashboardContextTest,
      public testing::WithParamInterface<bool> {
 public:
  GameTypeGameDashboardContextTest() = default;
  ~GameTypeGameDashboardContextTest() override = default;

  // GameDashboardContextTest:
  void SetUp() override {
    GameDashboardContextTest::SetUp();
    CreateGameWindow(IsArcGame());
  }

 protected:
  bool IsArcGame() const { return GetParam(); }

  void VerifyFeaturesEnabled(bool expect_enabled,
                             bool toolbar_visible = false) {
    auto* event_generator = GetEventGenerator();
    auto* gd_button_widget = test_api_->GetGameDashboardButtonWidget();
    EXPECT_TRUE(gd_button_widget);

    if (expect_enabled) {
      EXPECT_TRUE(gd_button_widget->IsVisible());
      event_generator->PressAndReleaseKey(ui::VKEY_G, ui::EF_COMMAND_DOWN);
      EXPECT_TRUE(test_api_->GetMainMenuWidget());
      test_api_->CloseTheMainMenu();
    } else {
      EXPECT_FALSE(gd_button_widget->IsVisible());
      event_generator->PressAndReleaseKey(ui::VKEY_G, ui::EF_COMMAND_DOWN);
      EXPECT_FALSE(test_api_->GetMainMenuWidget());
    }
    auto* toolbar_widget = test_api_->GetToolbarWidget();
    if (toolbar_visible) {
      EXPECT_TRUE(toolbar_widget);
      EXPECT_TRUE(toolbar_widget->IsVisible());
    } else {
      EXPECT_TRUE(!toolbar_widget || !toolbar_widget->IsVisible());
    }
  }
};

// GameTypeGameDashboardContextTest Tests
// -----------------------------------------------------------------------
// Verifies the default startup sequence of the toolbar and welcome dialog
// widgets.
TEST_P(GameTypeGameDashboardContextTest, DefaultWidgetStartupSequence) {
  // Close the existing window, and clear the overridden Game Dashboard prefs.
  CloseGameWindow();
  auto* active_user_prefs_ =
      Shell::Get()->session_controller()->GetActivePrefService();
  ASSERT_TRUE(active_user_prefs_);
  active_user_prefs_->ClearPref(prefs::kGameDashboardShowToolbar);
  active_user_prefs_->ClearPref(prefs::kGameDashboardShowWelcomeDialog);

  // Verify the preferences have their default value.
  ASSERT_FALSE(game_dashboard_utils::ShouldShowToolbar());
  ASSERT_TRUE(game_dashboard_utils::ShouldShowWelcomeDialog());

  CreateGameWindow(IsArcGame());

  // Verify the welcome dialog is created and visible, and not the toolbar.
  ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
  ASSERT_FALSE(test_api_->GetToolbarWidget());

  // Advance by 4 seconds to dismiss the welcome dialog.
  task_environment()->FastForwardBy(base::Seconds(4));

  // Verify the welcome dialog is closed, and the toolbar is not shown.
  ASSERT_FALSE(test_api_->GetWelcomeDialogWidget());
  ASSERT_FALSE(test_api_->GetToolbarWidget());
}

// Verifies the initial location of the Game Dashboard button widget relative to
// the game window.
TEST_P(GameTypeGameDashboardContextTest,
       GameDashboardButtonWidget_InitialLocation) {
  const gfx::Point expected_button_center_point(
      game_window_->GetBoundsInScreen().top_center().x(),
      app_bounds().y() + frame_header_height_ / 2);
  EXPECT_EQ(expected_button_center_point,
            test_api_->GetGameDashboardButtonWidget()
                ->GetNativeWindow()
                ->GetBoundsInScreen()
                .CenterPoint());
}

// Verifies the Game Dashboard button widget bounds are updated, relative to the
// game window.
TEST_P(GameTypeGameDashboardContextTest,
       GameDashboardButtonWidget_MoveWindowAndVerifyLocation) {
  const auto move_vector = gfx::Vector2d(100, 200);
  const auto* native_window =
      test_api_->GetGameDashboardButtonWidget()->GetNativeWindow();
  const auto expected_widget_location =
      native_window->GetBoundsInScreen() + move_vector;

  game_window_->SetBoundsInScreen(
      game_window_->GetBoundsInScreen() + move_vector, GetPrimaryDisplay());

  EXPECT_EQ(expected_widget_location, native_window->GetBoundsInScreen());
}

// Verifies clicking the Game Dashboard button will open the main menu widget.
TEST_P(GameTypeGameDashboardContextTest, OpenGameDashboardButtonWidget) {
  // Close the window and create a new game window without setting the
  // `kArcGameControlsFlagsKey` property.
  CloseGameWindow();
  CreateGameWindow(IsArcGame(), /*set_arc_game_controls_flags_prop=*/false);

  // Verifies the main menu is closed.
  EXPECT_FALSE(test_api_->GetMainMenuWidget());

  if (IsArcGame()) {
    // Game Dashboard button is not enabled util the Game Controls state is
    // known.
    EXPECT_FALSE(test_api_->GetGameDashboardButton()->GetEnabled());
    LeftClickOn(test_api_->GetGameDashboardButton());
    EXPECT_FALSE(test_api_->GetMainMenuWidget());
    game_window_->SetProperty(kArcGameControlsFlagsKey,
                              ArcGameControlsFlag::kKnown);
  }

  // Open the main menu dialog and verify the main menu is open.
  test_api_->OpenTheMainMenu();
}

// Verifies Game Controls UIs only show up on the ARC games.
TEST_P(GameTypeGameDashboardContextTest, GameControlsUiExistence) {
  const bool is_arc_game = IsArcGame();
  if (is_arc_game) {
    // The ARC game has Game Controls optout in this test.
    game_window_->SetProperty(kArcGameControlsFlagsKey,
                              ArcGameControlsFlag::kKnown);
  }

  OpenMenuCheckGameControlsUIState(
      /*hint_tile_states=*/
      {/*expect_exists=*/is_arc_game, /*expect_enabled=*/false,
       /*expect_on=*/false},
      /*details_row_exists=*/
      {/*expect_exists=*/is_arc_game, /*expect_enabled=*/false},
      /*feature_switch_states=*/
      {/*expect_exists=*/false, /*expect_toggled=*/false},
      /*setup_exists=*/is_arc_game);
}

// Verifies clicking the Game Dashboard button will close the main menu widget
// if it's already open.
TEST_P(GameTypeGameDashboardContextTest, CloseGameDashboardButtonWidget) {
  // Open the main menu widget and verify the main menu open.
  test_api_->OpenTheMainMenu();

  // Close the main menu dialog and verify the main menu is closed.
  test_api_->CloseTheMainMenu();
}

// Verifies hitting the escape key will close the main menu widget. Then,
// clicking on the main menu button will still toggle the main menu widget
// visibility.
TEST_P(GameTypeGameDashboardContextTest, CloseMainMenuViaEscapeButton) {
  // Open the main menu widget and verify the main menu open.
  test_api_->OpenTheMainMenu();

  // Close the main menu dialog but hitting the escape key.
  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_ESCAPE);

  // Hitting the escape key causes the main menu to close asynchronously. Run
  // until idle to ensure that this posted task runs synchronously and completes
  // before proceeding.
  base::RunLoop().RunUntilIdle();
  test_api_->VerifyAccessibilityTree();

  // Open the main menu widget via the main menu button.
  test_api_->OpenTheMainMenu();

  // Close the main menu widget via the main menu button.
  test_api_->CloseTheMainMenu();
}

// Verifies clicking outside the main menu view will close the main menu
// widget. Then, clicking on the main menu button will still toggle the main
// menu widget visibility.
TEST_P(GameTypeGameDashboardContextTest, CloseMainMenuOutsideButtonWidget) {
  // Open the main menu widget and verify the main menu open.
  test_api_->OpenTheMainMenu();

  // Close the main menu dialog by clicking outside the main menu view bounds.
  auto* event_generator = GetEventGenerator();
  gfx::Rect game_bounds = app_bounds();
  const gfx::Point& new_location = {game_bounds.x() + game_bounds.width(),
                                    game_bounds.y() + game_bounds.height()};
  event_generator->set_current_screen_location(new_location);
  event_generator->ClickLeftButton();

  // Clicking outside the main menu causes the main menu to close
  // asynchronously. Run until idle to ensure that this posted task runs
  // synchronously and completes before proceeding.
  base::RunLoop().RunUntilIdle();
  test_api_->VerifyAccessibilityTree();

  // Open the main menu widget via the main menu button.
  test_api_->OpenTheMainMenu();

  // Close the main menu widget via the main menu button.
  test_api_->CloseTheMainMenu();
}

// Verifies the main menu shows all items allowed.
TEST_P(GameTypeGameDashboardContextTest,
       MainMenuDialogWidget_AvailableFeatures) {
  if (IsArcGame()) {
    game_window_->SetProperty(
        kArcGameControlsFlagsKey,
        static_cast<ArcGameControlsFlag>(ArcGameControlsFlag::kKnown |
                                         ArcGameControlsFlag::kAvailable));
  }

  test_api_->OpenTheMainMenu();

  // Verify whether each element available in the main menu is available as
  // expected.
  EXPECT_TRUE(test_api_->GetMainMenuToolbarTile());
  EXPECT_TRUE(test_api_->GetMainMenuRecordGameTile());
  EXPECT_TRUE(test_api_->GetMainMenuScreenshotTile());
  EXPECT_TRUE(test_api_->GetMainMenuFeedbackButton());
  EXPECT_TRUE(test_api_->GetMainMenuHelpButton());
  EXPECT_TRUE(test_api_->GetMainMenuSettingsButton());
  if (IsArcGame()) {
    EXPECT_TRUE(test_api_->GetMainMenuGameControlsTile());
    EXPECT_TRUE(test_api_->GetMainMenuScreenSizeSettingsButton());
  } else {
    EXPECT_FALSE(test_api_->GetMainMenuGameControlsTile());
    EXPECT_FALSE(test_api_->GetMainMenuScreenSizeSettingsButton());
  }
}

// Verifies the main menu doesn't show the record game tile, when the feature is
// disabled.
TEST_P(GameTypeGameDashboardContextTest,
       MainMenuDialogWidget_RecordGameDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      {features::kFeatureManagementGameDashboardRecordGame});

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // Verify that the record game tile is unavailable in the main menu.
  EXPECT_FALSE(test_api_->GetMainMenuRecordGameTile());
  // Verify that the record game button is unavailable in the toolbar.
  EXPECT_FALSE(test_api_->GetToolbarRecordGameButton());
}

// Verifies the main menu screenshot tile will take a screenshot of the game
// window.
TEST_P(GameTypeGameDashboardContextTest, TakeScreenshotFromMainMenu) {
  test_api_->OpenTheMainMenu();

  // Retrieve the screenshot button and verify the initial state.
  const auto* screenshot_tile = test_api_->GetMainMenuScreenshotTile();
  ASSERT_TRUE(screenshot_tile);

  LeftClickOn(screenshot_tile);

  // Verify that a screenshot is taken of the game window.
  const auto file_path = WaitForCaptureFileToBeSaved();
  const auto image = ReadAndDecodeImageFile(file_path);
  EXPECT_EQ(image.Size(), game_window_->bounds().size());
}

// Verifies the record game buttons in the main menu and toolbar are disabled,
// if a recording session was started outside of the Game Dashboard.
TEST_P(GameTypeGameDashboardContextTest,
       CaptureSessionStartedOutsideOfTheGameDashboard) {
  test_api_->OpenTheMainMenu();

  // Verify the game dashboard button is initially not in the recording state.
  VerifyGameDashboardButtonState(/*is_recording=*/false);

  // Retrieve the record game tile from the main menu, and verify it's
  // enabled and toggled off.
  const auto* main_menu_record_game_button =
      test_api_->GetMainMenuRecordGameTile();
  EXPECT_TRUE(main_menu_record_game_button);
  EXPECT_TRUE(main_menu_record_game_button->GetEnabled());
  EXPECT_FALSE(main_menu_record_game_button->IsToggled());

  test_api_->OpenTheToolbar();
  // Retrieve the record game button from the toolbar, and verify it's
  // enabled and toggled off.
  const auto* toolbar_record_game_button =
      test_api_->GetToolbarRecordGameButton();
  EXPECT_TRUE(toolbar_record_game_button);
  EXPECT_TRUE(toolbar_record_game_button->GetEnabled());
  EXPECT_FALSE(toolbar_record_game_button->toggled());

  const auto* capture_mode_controller = CaptureModeController::Get();
  // Start video recording from `CaptureModeController`.
  EXPECT_FALSE(capture_mode_controller->is_recording_in_progress());
  StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
  StartVideoRecordingImmediately();
  EXPECT_TRUE(capture_mode_controller->is_recording_in_progress());

  // Verify the record game buttons are disabled and toggled off.
  EXPECT_FALSE(main_menu_record_game_button->GetEnabled());
  EXPECT_FALSE(main_menu_record_game_button->IsToggled());
  EXPECT_FALSE(toolbar_record_game_button->GetEnabled());
  EXPECT_FALSE(toolbar_record_game_button->toggled());

  // Verify the game dashboard button is not in the recording state.
  VerifyGameDashboardButtonState(/*is_recording=*/false);

  // Stop video recording.
  CaptureModeTestApi().StopVideoRecording();
  EXPECT_FALSE(capture_mode_controller->is_recording_in_progress());

  // Verify the record game buttons are not enabled until the video file is
  // finalized.
  EXPECT_FALSE(capture_mode_controller->can_start_new_recording());
  EXPECT_FALSE(main_menu_record_game_button->GetEnabled());
  EXPECT_FALSE(main_menu_record_game_button->IsToggled());
  EXPECT_FALSE(toolbar_record_game_button->GetEnabled());
  EXPECT_FALSE(toolbar_record_game_button->toggled());
  WaitForCaptureFileToBeSaved();
  EXPECT_TRUE(capture_mode_controller->can_start_new_recording());
  EXPECT_TRUE(main_menu_record_game_button->GetEnabled());
  EXPECT_FALSE(main_menu_record_game_button->IsToggled());
  EXPECT_TRUE(toolbar_record_game_button->GetEnabled());
  EXPECT_FALSE(toolbar_record_game_button->toggled());

  // Verify the game dashboard button is still in not in the recording state.
  VerifyGameDashboardButtonState(/*is_recording=*/false);
}

// Verifies the toolbar opens and closes when the toolbar button in the main
// menu is clicked.
TEST_P(GameTypeGameDashboardContextTest, OpenAndCloseToolbarWidget) {
  if (IsArcGame()) {
    game_window_->SetProperty(
        kArcGameControlsFlagsKey,
        static_cast<ArcGameControlsFlag>(ArcGameControlsFlag::kKnown |
                                         ArcGameControlsFlag::kAvailable));
  }

  test_api_->OpenTheMainMenu();

  // Retrieve the toolbar button and verify the toolbar widget is not enabled.
  auto* toolbar_tile = test_api_->GetMainMenuToolbarTile();
  ASSERT_TRUE(toolbar_tile);
  EXPECT_FALSE(toolbar_tile->IsToggled());
  EXPECT_EQ(toolbar_tile->sub_label()->GetText(), hidden_label);

  // Open the toolbar, verify the main menu toolbar tile's sub-label is updated,
  // and verify available feature buttons.
  test_api_->OpenTheToolbar();
  EXPECT_EQ(toolbar_tile->sub_label()->GetText(), visible_label);
  EXPECT_TRUE(test_api_->GetToolbarGamepadButton());
  EXPECT_TRUE(test_api_->GetToolbarRecordGameButton());
  EXPECT_TRUE(test_api_->GetToolbarScreenshotButton());
  // Verify that main menu always stacks above the toolbar.
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));
  if (IsArcGame()) {
    EXPECT_TRUE(test_api_->GetToolbarGameControlsButton());
  } else {
    EXPECT_FALSE(test_api_->GetToolbarGameControlsButton());
  }

  // Verify toggling the main menu visibility doesn't affect the toolbar.
  test_api_->CloseTheMainMenu();
  EXPECT_TRUE(test_api_->GetToolbarWidget());
  test_api_->OpenTheMainMenu();
  toolbar_tile = test_api_->GetMainMenuToolbarTile();
  EXPECT_EQ(toolbar_tile->sub_label()->GetText(), visible_label);
  EXPECT_TRUE(test_api_->GetToolbarWidget());
  // Verify that main menu always stacks above the toolbar.
  EXPECT_TRUE(test_api_->GetMainMenuWidget()->IsStackedAbove(
      test_api_->GetToolbarWidget()->GetNativeView()));
  test_api_->CloseTheToolbar();

  // Verify that the toolbar widget is no longer available and is toggled off.
  EXPECT_FALSE(test_api_->GetToolbarWidget());
  EXPECT_FALSE(toolbar_tile->IsToggled());
  EXPECT_EQ(toolbar_tile->sub_label()->GetText(), hidden_label);
}

// Verifies the toolbar screenshot button will take a screenshot of the game
// window.
TEST_P(GameTypeGameDashboardContextTest, TakeScreenshotFromToolbar) {
  // Open the toolbar via the main menu.
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // Click on the screenshot button within the toolbar.
  const auto* screenshot_button = test_api_->GetToolbarScreenshotButton();
  ASSERT_TRUE(screenshot_button);
  LeftClickOn(screenshot_button);

  // Verify that a screenshot is taken of the game window.
  const auto file_path = WaitForCaptureFileToBeSaved();
  const auto image = ReadAndDecodeImageFile(file_path);
  EXPECT_EQ(image.Size(), game_window_->GetBoundsInScreen().size());
}

// Verifies clicking the toolbar's gamepad button will expand and collapse the
// toolbar.
TEST_P(GameTypeGameDashboardContextTest, CollapseAndExpandToolbarWidget) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  const int initial_height = GetToolbarHeight();
  EXPECT_NE(initial_height, 0);

  // Click on the gamepad button within the toolbar.
  auto* gamepad_button = test_api_->GetToolbarGamepadButton();
  ASSERT_TRUE(gamepad_button);
  LeftClickOn(gamepad_button);
  int updated_height = GetToolbarHeight();

  // Verify that the initial y coordinate of the toolbar was larger than the
  // updated y value.
  EXPECT_GT(initial_height, updated_height);

  // Click on the gamepad button within the toolbar again.
  LeftClickOn(gamepad_button);
  updated_height = GetToolbarHeight();

  // Verify that the toolbar is back to its initially expanded height.
  EXPECT_EQ(initial_height, updated_height);
}

// Verifies the toolbar won't follow the mouse cursor outside of the game window
// bounds.
TEST_P(GameTypeGameDashboardContextTest, MoveToolbarOutOfBounds) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  ASSERT_TRUE(test_api_->GetToolbarWidget());
  ASSERT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kTopRight);

  auto window_bounds = game_window_->GetBoundsInScreen();
  const int screen_point_x = kScreenBounds.x();
  const int screen_point_right = screen_point_x + kScreenBounds.width();
  const int screen_point_y = kScreenBounds.y();
  const int screen_point_bottom = screen_point_y + kScreenBounds.height();

  // Verify the screen bounds are larger than the game bounds.
  auto game_bounds = app_bounds();
  ASSERT_LT(screen_point_x, game_bounds.x());
  ASSERT_LT(screen_point_y, game_bounds.y());
  ASSERT_GT(screen_point_right, game_bounds.x() + game_bounds.width());
  ASSERT_GT(screen_point_bottom, game_bounds.y() + game_bounds.height());

  // Drag toolbar, moving the mouse past the game window to the top right corner
  // of the screen bounds, and verify the toolbar doesn't go past the game
  // window.
  DragToolbarToPoint(Movement::kMouse, {screen_point_right, screen_point_y},
                     false);
  const auto* native_window = test_api_->GetToolbarWidget()->GetNativeWindow();
  auto toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.right(), window_bounds.right());
  EXPECT_EQ(toolbar_bounds.y(), window_bounds.y());

  // Drag toolbar, moving the mouse past the game window to the top left corner
  // of the screen bounds.
  DragToolbarToPoint(Movement::kMouse, {screen_point_x, screen_point_y}, false);
  toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.x(), window_bounds.x());
  EXPECT_EQ(toolbar_bounds.y(), window_bounds.y());

  // Drag toolbar, moving the mouse past the game window to the bottom left
  // corner of the screen bounds.
  DragToolbarToPoint(Movement::kMouse, {screen_point_x, screen_point_bottom},
                     false);
  toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.x(), window_bounds.x());
  EXPECT_EQ(toolbar_bounds.bottom(), window_bounds.bottom());

  // Drag toolbar, moving the mouse past the game window to the bottom right
  // corner of the screen bounds.
  DragToolbarToPoint(Movement::kMouse,
                     {screen_point_right, screen_point_bottom}, false);
  toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.right(), window_bounds.right());
  EXPECT_EQ(toolbar_bounds.bottom(), window_bounds.bottom());

  GetEventGenerator()->ReleaseLeftButton();
}

// Verifies the toolbar can be moved around via the mouse.
TEST_P(GameTypeGameDashboardContextTest, MoveToolbarWidgetViaMouse) {
  VerifyToolbarDrag(Movement::kMouse);
}

// Verifies the toolbar can be moved around via touch.
TEST_P(GameTypeGameDashboardContextTest, MoveToolbarWidgetViaTouch) {
  VerifyToolbarDrag(Movement::kTouch);
}

// Verifies the toolbar can be moved around via keyboard arrows.
TEST_P(GameTypeGameDashboardContextTest, MoveToolbarWidgetViaArrowKeys) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  test_api_->SetFocusOnToolbar();

  // Verify that be default the snap position should be `kTopRight` and
  // toolbar is placed in the top right quadrant.
  EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kTopRight);

  // Press tab so the toolbar gains focus
  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_TAB);

  // Press right arrow key and verify toolbar does not leave top right quadrant.
  PressKeyAndVerify(ui::VKEY_RIGHT,
                    GameDashboardToolbarSnapLocation::kTopRight);

  // Press left arrow key and verify toolbar moved to top left quadrant.
  PressKeyAndVerify(ui::VKEY_LEFT, GameDashboardToolbarSnapLocation::kTopLeft);

  // Press down arrow key and verify toolbar moved to bottom left quadrant.
  PressKeyAndVerify(ui::VKEY_DOWN,
                    GameDashboardToolbarSnapLocation::kBottomLeft);

  // Press right arrow key and verify toolbar moved to bottom right quadrant.
  PressKeyAndVerify(ui::VKEY_RIGHT,
                    GameDashboardToolbarSnapLocation::kBottomRight);

  // Press up arrow key and verify toolbar moved to top right quadrant.
  PressKeyAndVerify(ui::VKEY_UP, GameDashboardToolbarSnapLocation::kTopRight);

  // Press up arrow key again and verify toolbar does not leave top right
  // quadrant.
  PressKeyAndVerify(ui::VKEY_UP, GameDashboardToolbarSnapLocation::kTopRight);

  // Press down arrow key and verify toolbar moved to bottom right quadrant.
  PressKeyAndVerify(ui::VKEY_DOWN,
                    GameDashboardToolbarSnapLocation::kBottomRight);

  // Press down arrow key again and verify toolbar does not leave bottom right
  // quadrant.
  PressKeyAndVerify(ui::VKEY_DOWN,
                    GameDashboardToolbarSnapLocation::kBottomRight);

  // Press left arrow key and verify toolbar moved to bottom left quadrant.
  PressKeyAndVerify(ui::VKEY_LEFT,
                    GameDashboardToolbarSnapLocation::kBottomLeft);

  // Press up arrow key and verify toolbar moved to top left quadrant.
  PressKeyAndVerify(ui::VKEY_UP, GameDashboardToolbarSnapLocation::kTopLeft);

  // Press right arrow key and verify toolbar moved to top right quadrant.
  PressKeyAndVerify(ui::VKEY_RIGHT,
                    GameDashboardToolbarSnapLocation::kTopRight);
}

// Verifies the toolbar's physical placement on screen in each quadrant.
TEST_P(GameTypeGameDashboardContextTest, VerifyToolbarPlacementInQuadrants) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  const auto window_bounds = game_window_->GetBoundsInScreen();
  const auto window_center_point = window_bounds.CenterPoint();
  const int x_offset = window_bounds.width() / 4;
  const int y_offset = window_bounds.height() / 4;

  // Verify initial placement in top right quadrant.
  auto game_bounds = app_bounds();
  const auto* native_window = test_api_->GetToolbarWidget()->GetNativeWindow();
  auto toolbar_bounds = native_window->GetBoundsInScreen();
  const auto toolbar_size =
      test_api_->GetToolbarWidget()->GetContentsView()->GetPreferredSize();
  EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kTopRight);
  EXPECT_EQ(toolbar_bounds.x(), game_bounds.right() -
                                    game_dashboard::kToolbarEdgePadding -
                                    toolbar_size.width());
  EXPECT_EQ(toolbar_bounds.y(), game_bounds.y() +
                                    game_dashboard::kToolbarEdgePadding +
                                    frame_header_height_);

  // Move toolbar to top left quadrant and verify toolbar placement.
  DragToolbarToPoint(Movement::kMouse, {window_center_point.x() - x_offset,
                                        window_center_point.y() - y_offset});
  EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kTopLeft);
  toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.x(),
            game_bounds.x() + game_dashboard::kToolbarEdgePadding);
  EXPECT_EQ(toolbar_bounds.y(), game_bounds.y() +
                                    game_dashboard::kToolbarEdgePadding +
                                    frame_header_height_);

  // Move toolbar to bottom right quadrant and verify toolbar placement.
  DragToolbarToPoint(Movement::kMouse, {window_center_point.x() + x_offset,
                                        window_center_point.y() + y_offset});
  toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.x(), game_bounds.right() -
                                    game_dashboard::kToolbarEdgePadding -
                                    toolbar_size.width());
  EXPECT_EQ(toolbar_bounds.y(), game_bounds.bottom() -
                                    game_dashboard::kToolbarEdgePadding -
                                    toolbar_size.height());

  // Move toolbar to bottom left quadrant and verify toolbar placement.
  DragToolbarToPoint(Movement::kMouse, {window_center_point.x() - x_offset,
                                        window_center_point.y() + y_offset});
  toolbar_bounds = native_window->GetBoundsInScreen();
  EXPECT_EQ(toolbar_bounds.x(),
            game_bounds.x() + game_dashboard::kToolbarEdgePadding);
  EXPECT_EQ(toolbar_bounds.y(), game_bounds.bottom() -
                                    game_dashboard::kToolbarEdgePadding -
                                    toolbar_size.height());
}

// Verifies the toolbar's snap location is preserved even after the visibility
// is hidden via the main menu view.
TEST_P(GameTypeGameDashboardContextTest, MoveAndHideToolbarWidget) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // Move toolbar to bottom left quadrant and verify snap location is updated.
  const auto window_bounds = game_window_->GetBoundsInScreen();
  const auto window_center_point = window_bounds.CenterPoint();
  DragToolbarToPoint(Movement::kMouse,
                     {window_center_point.x() - (window_bounds.width() / 4),
                      window_center_point.y() + (window_bounds.height() / 4)});
  EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kBottomLeft);

  // Hide then show the toolbar and verify the toolbar was placed back into the
  // bottom left quadrant.
  test_api_->OpenTheMainMenu();
  test_api_->CloseTheToolbar();
  test_api_->OpenTheToolbar();
  EXPECT_EQ(test_api_->GetToolbarSnapLocation(),
            GameDashboardToolbarSnapLocation::kBottomLeft);
}

// Verifies the settings view can be closed via the back arrow and the Game
// Dashboard button.
TEST_P(GameTypeGameDashboardContextTest, OpenAndCloseSettingsView) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenMainMenuSettings();

  // Close the settings page via the back button and verify the main menu is now
  // displayed.
  test_api_->CloseTheSettings();
  auto* main_menu_container = test_api_->GetMainMenuContainer();
  EXPECT_TRUE(test_api_->GetMainMenuView());
  EXPECT_TRUE(main_menu_container && main_menu_container->GetVisible());

  // Re-open the settings view and close it via the Game Dashboard button.
  test_api_->OpenMainMenuSettings();
  test_api_->CloseTheMainMenu();
}

// Verifies the Welcome Dialog switch can be toggled off in the settings and its
// state preserved.
TEST_P(GameTypeGameDashboardContextTest, ToggleWelcomeDialogSettings) {
  // Open the settings with the welcome dialog flag disabled.
  test_api_->OpenTheMainMenu();
  test_api_->OpenMainMenuSettings();

  // Verify the initial welcome dialog switch state is disabled.
  EXPECT_FALSE(test_api_->GetSettingsViewWelcomeDialogSwitch()->GetIsOn());

  // Toggle the switch on, close the main menu, then reopen settings and verify
  // the switch is still on.
  test_api_->ToggleWelcomeDialogSettingsSwitch();
  EXPECT_TRUE(test_api_->GetSettingsViewWelcomeDialogSwitch()->GetIsOn());
  test_api_->CloseTheMainMenu();
  test_api_->OpenTheMainMenu();
  test_api_->OpenMainMenuSettings();
  EXPECT_TRUE(test_api_->GetSettingsViewWelcomeDialogSwitch()->GetIsOn());
}

TEST_P(GameTypeGameDashboardContextTest, TabletMode) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // App is launched in desktop mode in Setup and switch to the tablet mode.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  VerifyFeaturesEnabled(/*expect_enabled=*/false);
  EXPECT_TRUE(
      ToastManager::Get()->IsToastShown(game_dashboard::kTabletToastId));
  // Switch back to the desktop mode and this feature is resumed.
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  ASSERT_FALSE(display::Screen::GetScreen()->InTabletMode());
  VerifyFeaturesEnabled(/*expect_enabled=*/true, /*toolbar_visible=*/true);
  EXPECT_FALSE(
      ToastManager::Get()->IsToastShown(game_dashboard::kTabletToastId));
  CloseGameWindow();

  // No toast shown when there is no game window.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EXPECT_FALSE(
      ToastManager::Get()->IsToastShown(game_dashboard::kTabletToastId));

  // Launch app in the tablet mode and switch to the desktop mode.
  CreateGameWindow(IsArcGame());
  VerifyFeaturesEnabled(/*expect_enabled=*/false);
  EXPECT_FALSE(
      ToastManager::Get()->IsToastShown(game_dashboard::kTabletToastId));
  // Switch back to the desktop mode and this feature is resumed.
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  ASSERT_FALSE(display::Screen::GetScreen()->InTabletMode());
  VerifyFeaturesEnabled(/*expect_enabled=*/true);
  EXPECT_FALSE(
      ToastManager::Get()->IsToastShown(game_dashboard::kTabletToastId));

  // Start recording in the desktop mode and switch to the tablet mode.
  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuRecordGameTile());
  // Clicking on the record game tile closes the main menu, and asynchronously
  // starts the capture session. Run until idle to ensure that the posted task
  // runs synchronously and completes before proceeding.
  base::RunLoop().RunUntilIdle();
  ClickOnStartRecordingButtonInCaptureModeBarView();
  EXPECT_TRUE(CaptureModeController::Get()->is_recording_in_progress());
  ash::TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_FALSE(CaptureModeController::Get()->is_recording_in_progress());
  EXPECT_TRUE(
      ToastManager::Get()->IsToastShown(game_dashboard::kTabletToastId));
}

// Test tab navigation when the game window is focused.
TEST_P(GameTypeGameDashboardContextTest, TabNavigationGameWindow) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  test_api_->CloseTheMainMenu();

  aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow())
      ->FocusWindow(game_window_.get());
  TabNavigateForward();

  // Once the focus is on the game window, it's hard to know if it reaches to
  // the last focusable view inside. Keep the focus inside of the game window.
  EXPECT_FALSE(test_api_->GetGameDashboardButton()->HasFocus());
  EXPECT_FALSE(test_api_->GetToolbarGamepadButton()->HasFocus());
}

// -----------------------------------------------------------------------------
// OnOverviewModeEndedWaiter:
class OnOverviewModeEndedWaiter : public OverviewObserver {
 public:
  OnOverviewModeEndedWaiter()
      : overview_controller_(OverviewController::Get()) {
    CHECK(overview_controller_);
    overview_controller_->AddObserver(this);
  }
  OnOverviewModeEndedWaiter(const OnOverviewModeEndedWaiter&) = delete;
  OnOverviewModeEndedWaiter& operator=(const OnOverviewModeEndedWaiter&) =
      delete;
  ~OnOverviewModeEndedWaiter() override {
    overview_controller_->RemoveObserver(this);
  }

  void Wait() { run_loop_.Run(); }

  // OverviewObserver:
  void OnOverviewModeEnded() override { run_loop_.Quit(); }

 private:
  base::RunLoop run_loop_;
  // Owned by Shell.
  const raw_ptr<OverviewController> overview_controller_;
};

// Verifies that in overview mode, the Game Dashboard button is not visible, the
// main menu is closed, and the toolbar visibility is unchanged.
TEST_P(GameTypeGameDashboardContextTest, OverviewMode) {
  auto* game_dashboard_button_widget =
      test_api_->GetGameDashboardButtonWidget();
  ASSERT_TRUE(game_dashboard_button_widget);

  // Open the main menu view and toolbar.
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  // Verify the initial state.
  // Game Dashboard button is visible.
  EXPECT_TRUE(game_dashboard_button_widget->IsVisible());
  // Toolbar is visible.
  const auto* toolbar_widget = test_api_->GetToolbarWidget();
  ASSERT_TRUE(toolbar_widget);
  EXPECT_TRUE(toolbar_widget->IsVisible());
  // Main menu is visible.
  const auto* main_menu_widget = test_api_->GetMainMenuWidget();
  ASSERT_TRUE(main_menu_widget);
  EXPECT_TRUE(main_menu_widget->IsVisible());

  EnterOverview();
  const auto* overview_controller = OverviewController::Get();
  ASSERT_TRUE(overview_controller->InOverviewSession());

  // Verify states in overview mode.
  EXPECT_FALSE(game_dashboard_button_widget->IsVisible());
  ASSERT_EQ(toolbar_widget, test_api_->GetToolbarWidget());
  EXPECT_TRUE(toolbar_widget->IsVisible());
  EXPECT_FALSE(test_api_->GetMainMenuWidget());

  OnOverviewModeEndedWaiter waiter;
  ExitOverview();
  waiter.Wait();
  ASSERT_FALSE(overview_controller->InOverviewSession());

  // Verify states after exiting overview mode.
  EXPECT_TRUE(game_dashboard_button_widget->IsVisible());
  ASSERT_EQ(toolbar_widget, test_api_->GetToolbarWidget());
  EXPECT_TRUE(toolbar_widget->IsVisible());
  EXPECT_FALSE(test_api_->GetMainMenuWidget());
}

TEST_P(GameTypeGameDashboardContextTest, OverviewModeWithTabletMode) {
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  const auto* overview_controller = OverviewController::Get();

  // 1. Clamshell -> overview -> tablet-> exit overview.
  ASSERT_FALSE(display::Screen::GetScreen()->InTabletMode());
  EnterOverview();
  ASSERT_TRUE(overview_controller->InOverviewSession());
  VerifyFeaturesEnabled(/*expect_enabled=*/false, /*toolbar_visible=*/true);
  ash::TabletModeControllerTestApi().EnterTabletMode();
  VerifyFeaturesEnabled(/*expect_enabled=*/false);
  ExitOverview();
  ASSERT_FALSE(overview_controller->InOverviewSession());
  VerifyFeaturesEnabled(/*expect_enabled=*/false);

  // 2. Tablet -> overview -> exit overview -> clamshell.
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EnterOverview();
  ASSERT_TRUE(overview_controller->InOverviewSession());
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  VerifyFeaturesEnabled(/*expect_enabled=*/false);
  ExitOverview();
  ASSERT_FALSE(overview_controller->InOverviewSession());
  VerifyFeaturesEnabled(/*expect_enabled=*/false);
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  ASSERT_FALSE(display::Screen::GetScreen()->InTabletMode());
  VerifyFeaturesEnabled(/*expect_enabled=*/true, /*toolbar_visible=*/true);

  // 3. Tablet -> overview -> clamshell -> exit overview.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  ASSERT_TRUE(display::Screen::GetScreen()->InTabletMode());
  EnterOverview();
  ASSERT_TRUE(overview_controller->InOverviewSession());
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  ASSERT_FALSE(display::Screen::GetScreen()->InTabletMode());
  ASSERT_TRUE(overview_controller->InOverviewSession());
  VerifyFeaturesEnabled(/*expect_enabled=*/false, /*toolbar_visible=*/true);
  ExitOverview();
  ASSERT_FALSE(overview_controller->InOverviewSession());
  VerifyFeaturesEnabled(/*expect_enabled=*/true, /*toolbar_visible=*/true);
}

TEST_P(GameTypeGameDashboardContextTest, RecordToggleMainMenuHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  const std::string histogram_name_on =
      BuildGameDashboardHistogramName(kGameDashboardToggleMainMenuHistogram)
          .append(kGameDashboardHistogramSeparator)
          .append(kGameDashboardHistogramOn);
  const std::string histogram_name_off =
      BuildGameDashboardHistogramName(kGameDashboardToggleMainMenuHistogram)
          .append(kGameDashboardHistogramSeparator)
          .append(kGameDashboardHistogramOff);

  // Toggle on/off main menu by pressing Game Dashboard button.
  test_api_->OpenTheMainMenu();
  std::map<GameDashboardMainMenuToggleMethod, int> expected_on_histogram_values;
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);

  const int64_t gd_button_toggle_method = static_cast<int64_t>(
      GameDashboardMainMenuToggleMethod::kGameDashboardButton);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});

  test_api_->CloseTheMainMenu();
  std::map<GameDashboardMainMenuToggleMethod, int>
      expected_off_histogram_values;
  expected_off_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u,
      std::vector<int64_t>{/*toggle_on=*/0,
                           /*toggle_method=*/gd_button_toggle_method});

  // Toggle on/off main menu by Search+G.
  auto* event_generator = GetEventGenerator();
  event_generator->PressAndReleaseKey(ui::VKEY_G, ui::EF_COMMAND_DOWN);
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kSearchPlusG]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/3u,
      std::vector<int64_t>{
          /*toggle_on=*/1,
          /*toggle_method=*/static_cast<int64_t>(
              GameDashboardMainMenuToggleMethod::kSearchPlusG)});

  event_generator->PressAndReleaseKey(ui::VKEY_G, ui::EF_COMMAND_DOWN);
  expected_off_histogram_values
      [GameDashboardMainMenuToggleMethod::kSearchPlusG]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/4u,
      std::vector<int64_t>{
          /*toggle_on=*/0,
          /*toggle_method=*/static_cast<int64_t>(
              GameDashboardMainMenuToggleMethod::kSearchPlusG)});

  // Toggle off main menu by key Esc.
  test_api_->OpenTheMainMenu();
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/5u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});
  event_generator->PressAndReleaseKey(ui::VKEY_ESCAPE);
  // Main menu is closed asynchronously. Run until idle to ensure that this
  // posted task runs synchronously and completes before proceeding.
  base::RunLoop().RunUntilIdle();
  expected_off_histogram_values[GameDashboardMainMenuToggleMethod::kEsc]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/6u,
      std::vector<int64_t>{/*toggle_on=*/0,
                           /*toggle_method=*/static_cast<int64_t>(
                               GameDashboardMainMenuToggleMethod::kEsc)});

  // Toggle off main menu by activating a new feature.
  test_api_->OpenTheMainMenu();
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/7u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});
  LeftClickOn(test_api_->GetMainMenuScreenshotTile());
  expected_off_histogram_values
      [GameDashboardMainMenuToggleMethod::kActivateNewFeature]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/8u,
      std::vector<int64_t>{
          /*toggle_on=*/0,
          /*toggle_method=*/static_cast<int64_t>(
              GameDashboardMainMenuToggleMethod::kActivateNewFeature)});

  // Toggle off main menu by entering overview mode.
  test_api_->OpenTheMainMenu();
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/9u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});
  EnterOverview();
  expected_off_histogram_values[GameDashboardMainMenuToggleMethod::kOverview]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/10u,
      std::vector<int64_t>{/*toggle_on=*/0,
                           /*toggle_method=*/static_cast<int64_t>(
                               GameDashboardMainMenuToggleMethod::kOverview)});
  OnOverviewModeEndedWaiter waiter;
  ExitOverview();
  waiter.Wait();

  // Toggle off main menu by entering the tablet mode.
  test_api_->OpenTheMainMenu();
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/11u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});
  ash::TabletModeControllerTestApi().EnterTabletMode();
  expected_off_histogram_values
      [GameDashboardMainMenuToggleMethod::kTabletMode]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/12u,
      std::vector<int64_t>{
          /*toggle_on=*/0,
          /*toggle_method=*/static_cast<int64_t>(
              GameDashboardMainMenuToggleMethod::kTabletMode)});

  // Toggle off main menu by clicking outside of the main menu.
  test_api_->OpenTheMainMenu();
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/13u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});
  const gfx::Point bottom_center =
      test_api_->GetMainMenuView()->GetBoundsInScreen().bottom_center();
  event_generator->MoveMouseTo(
      gfx::Point(bottom_center.x(), bottom_center.y() + 10));
  event_generator->ClickLeftButton();
  // Main menu is closed asynchronously. Run until idle to ensure that this
  // posted task runs synchronously and completes before proceeding.
  base::RunLoop().RunUntilIdle();
  expected_off_histogram_values[GameDashboardMainMenuToggleMethod::kOthers]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/14u,
      std::vector<int64_t>{/*toggle_on=*/0,
                           /*toggle_method=*/static_cast<int64_t>(
                               GameDashboardMainMenuToggleMethod::kOthers)});

  test_api_->OpenTheMainMenu();
  expected_on_histogram_values
      [GameDashboardMainMenuToggleMethod::kGameDashboardButton]++;
  VerifyHistogramValues(histograms, histogram_name_on,
                        expected_on_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/15u,
      std::vector<int64_t>{/*toggle_on=*/1,
                           /*toggle_method=*/gd_button_toggle_method});
  CloseGameWindow();
  // Main menu is closed asynchronously. Run until idle to ensure that this
  // posted task runs synchronously and completes before proceeding.
  base::RunLoop().RunUntilIdle();
  expected_off_histogram_values[GameDashboardMainMenuToggleMethod::kOthers]++;
  VerifyHistogramValues(histograms, histogram_name_off,
                        expected_off_histogram_values);
  VerifyToggleMainMenuLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/16u,
      std::vector<int64_t>{/*toggle_on=*/0,
                           /*toggle_method=*/static_cast<int64_t>(
                               GameDashboardMainMenuToggleMethod::kOthers)});
}

TEST_P(GameTypeGameDashboardContextTest,
       RecordToolbarToggleStateHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardToolbarToggleStateHistogram);
  std::map<bool, int> expected_histogram_values;
  expected_histogram_values[true]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarToggleStateLastUkmEvent(ukm_recorder, /*expect_entry_size=*/1u,
                                       /*expect_event_value=*/1);

  test_api_->CloseTheToolbar();
  expected_histogram_values[false]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarToggleStateLastUkmEvent(ukm_recorder, /*expect_entry_size=*/2u,
                                       /*expect_event_value=*/0);
}

TEST_P(GameTypeGameDashboardContextTest,
       RecordToolbarClickToExpandStateHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  LeftClickOn(test_api_->GetToolbarGamepadButton());
  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardToolbarClickToExpandStateHistogram);
  std::map<bool, int> expected_histogram_values;
  expected_histogram_values[false]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarClickToExpandStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u, static_cast<int64_t>(false));

  LeftClickOn(test_api_->GetToolbarGamepadButton());
  expected_histogram_values[true]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarClickToExpandStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u, static_cast<int64_t>(true));
}

TEST_P(GameTypeGameDashboardContextTest,
       RecordToolbarNewLocationHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();

  DragToolbarToPoint(
      Movement::kMouse,
      DragToolbarPointForPosition(GameDashboardToolbarSnapLocation::kTopRight));
  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardToolbarNewLocationHistogram);
  std::map<GameDashboardToolbarSnapLocation, int> expected_histogram_values;
  expected_histogram_values[GameDashboardToolbarSnapLocation::kTopRight]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarNewLocationLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u,
      static_cast<int64_t>(GameDashboardToolbarSnapLocation::kTopRight));

  DragToolbarToPoint(Movement::kMouse,
                     DragToolbarPointForPosition(
                         GameDashboardToolbarSnapLocation::kBottomLeft));
  expected_histogram_values[GameDashboardToolbarSnapLocation::kBottomLeft]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarNewLocationLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u,
      static_cast<int64_t>(GameDashboardToolbarSnapLocation::kBottomLeft));

  DragToolbarToPoint(Movement::kTouch,
                     DragToolbarPointForPosition(
                         GameDashboardToolbarSnapLocation::kBottomRight));
  expected_histogram_values[GameDashboardToolbarSnapLocation::kBottomRight]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarNewLocationLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/3u,
      static_cast<int64_t>(GameDashboardToolbarSnapLocation::kBottomRight));

  DragToolbarToPoint(
      Movement::kTouch,
      DragToolbarPointForPosition(GameDashboardToolbarSnapLocation::kTopLeft));
  expected_histogram_values[GameDashboardToolbarSnapLocation::kTopLeft]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyToolbarNewLocationLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/4u,
      static_cast<int64_t>(GameDashboardToolbarSnapLocation::kTopLeft));
}

TEST_P(GameTypeGameDashboardContextTest,
       RecordRecordingStartSourceHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Start recording from the main menu.
  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  LeftClickOn(test_api_->GetMainMenuRecordGameTile());
  // Clicking on the record game tile closes the main menu, and asynchronously
  // starts the capture session. Run until idle to ensure that the posted task
  // runs synchronously and completes before proceeding.
  base::RunLoop().RunUntilIdle();
  ClickOnStartRecordingButtonInCaptureModeBarView();
  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardRecordingStartSourceHistogram);
  std::map<GameDashboardMenu, int> expected_histogram_values;
  expected_histogram_values[GameDashboardMenu::kMainMenu]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyRecordingStartSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u, /*expect_event_value=*/
      static_cast<int64_t>(GameDashboardMenu::kMainMenu));

  // Stop recording.
  LeftClickOn(test_api_->GetToolbarRecordGameButton());
  WaitForCaptureFileToBeSaved();

  // Start recording from the toolbar.
  LeftClickOn(test_api_->GetToolbarRecordGameButton());
  ClickOnStartRecordingButtonInCaptureModeBarView();
  expected_histogram_values[GameDashboardMenu::kToolbar]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyRecordingStartSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u, /*expect_event_value=*/
      static_cast<int64_t>(GameDashboardMenu::kToolbar));
}

TEST_P(GameTypeGameDashboardContextTest,
       RecordScreenshotTakeSourceHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuScreenshotTile());
  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardScreenshotTakeSourceHistogram);
  std::map<GameDashboardMenu, int> expected_histogram_values;
  expected_histogram_values[GameDashboardMenu::kMainMenu]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyScreenshotTakeSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u, /*expect_event_value=*/
      static_cast<int64_t>(GameDashboardMenu::kMainMenu));

  test_api_->OpenTheMainMenu();
  test_api_->OpenTheToolbar();
  LeftClickOn(test_api_->GetToolbarScreenshotButton());
  expected_histogram_values[GameDashboardMenu::kToolbar]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyScreenshotTakeSourceLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u, /*expect_event_value=*/
      static_cast<int64_t>(GameDashboardMenu::kToolbar));
}

TEST_P(GameTypeGameDashboardContextTest,
       RecordGameDashboardFunctionTriggeredHistogramTest) {
  if (IsArcGame()) {
    game_window_->SetProperty(
        kArcGameControlsFlagsKey,
        static_cast<ArcGameControlsFlag>(
            ArcGameControlsFlag::kKnown | ArcGameControlsFlag::kAvailable |
            ArcGameControlsFlag::kEnabled | ArcGameControlsFlag::kHint));
    game_window_->SetProperty(kArcResizeLockTypeKey,
                              ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE);
  }

  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  test_api_->OpenTheMainMenu();
  LeftClickOn(test_api_->GetMainMenuFeedbackButton());
  const std::string histogram_name =
      BuildGameDashboardHistogramName(kGameDashboardFunctionTriggeredHistogram);
  std::map<GameDashboardFunction, int> expected_histogram_values;
  expected_histogram_values[GameDashboardFunction::kFeedback]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyFunctionTriggeredLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u,
      static_cast<int64_t>(GameDashboardFunction::kFeedback));
  task_environment()->RunUntilIdle();

  LeftClickOn(test_api_->GetMainMenuHelpButton());
  expected_histogram_values[GameDashboardFunction::kHelp]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyFunctionTriggeredLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u,
      static_cast<int64_t>(GameDashboardFunction::kHelp));

  LeftClickOn(test_api_->GetMainMenuSettingsButton());
  expected_histogram_values[GameDashboardFunction::kSetting]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyFunctionTriggeredLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/3u,
      static_cast<int64_t>(GameDashboardFunction::kSetting));

  LeftClickOn(test_api_->GetSettingsViewBackButton());
  expected_histogram_values[GameDashboardFunction::kSettingBack]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyFunctionTriggeredLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/4u,
      static_cast<int64_t>(GameDashboardFunction::kSettingBack));

  if (IsArcGame()) {
    LeftClickOn(test_api_->GetMainMenuScreenSizeSettingsButton());
    base::RunLoop().RunUntilIdle();
    expected_histogram_values[GameDashboardFunction::kScreenSize]++;
    VerifyHistogramValues(histograms, histogram_name,
                          expected_histogram_values);
    VerifyFunctionTriggeredLastUkmEvent(
        ukm_recorder, /*expect_entry_size=*/5u,
        static_cast<int64_t>(GameDashboardFunction::kScreenSize));

    test_api_->OpenTheMainMenu();
    LeftClickOn(test_api_->GetMainMenuGameControlsDetailsButton());
    expected_histogram_values
        [GameDashboardFunction::kGameControlsSetupOrEdit]++;
    VerifyHistogramValues(histograms, histogram_name,
                          expected_histogram_values);
    VerifyFunctionTriggeredLastUkmEvent(
        ukm_recorder, /*expect_entry_size=*/6u,
        static_cast<int64_t>(GameDashboardFunction::kGameControlsSetupOrEdit));
  }
}

TEST_P(GameTypeGameDashboardContextTest,
       WelcomeDialogNotificationToggleStateHistogramTest) {
  base::HistogramTester histograms;
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  test_api_->OpenTheMainMenu();
  test_api_->OpenMainMenuSettings();

  test_api_->ToggleWelcomeDialogSettingsSwitch();
  const std::string histogram_name = BuildGameDashboardHistogramName(
      kGameDashboardWelcomeDialogNotificationToggleStateHistogram);
  std::map<bool, int> expected_histogram_values;
  expected_histogram_values[true]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyWelcomeDialogNotificationToggleStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/1u, static_cast<int64_t>(true));

  test_api_->ToggleWelcomeDialogSettingsSwitch();
  expected_histogram_values[false]++;
  VerifyHistogramValues(histograms, histogram_name, expected_histogram_values);
  VerifyWelcomeDialogNotificationToggleStateLastUkmEvent(
      ukm_recorder, /*expect_entry_size=*/2u, static_cast<int64_t>(false));
}

INSTANTIATE_TEST_SUITE_P(All,
                         GameTypeGameDashboardContextTest,
                         testing::Bool());

// -----------------------------------------------------------------------------
// GameDashboardEnabledFeatureTileVerificationTest:
// Test fixture to test available Feature Tiles and their types depending on the
// test params (param to create an ARC Game window, param to enable game
// recording).
class GameDashboardEnabledFeatureTileVerificationTest
    : public GameDashboardContextTest,
      public testing::WithParamInterface<
          std::tuple</*is_arc_game=*/bool,
                     /*enable_recording_feature=*/bool>> {
 public:
  GameDashboardEnabledFeatureTileVerificationTest()
      : is_arc_game_(std::get<0>(GetParam())),
        enable_recording_feature_(std::get<1>(GetParam())) {}
  ~GameDashboardEnabledFeatureTileVerificationTest() override = default;

  void SetUp() override {
    GameDashboardContextTest::SetUp();
    scoped_feature_list_.InitWithFeatureState(
        features::kFeatureManagementGameDashboardRecordGame,
        enable_recording_feature_);
    CreateGameWindow(is_arc_game_);
  }

 protected:
  const bool is_arc_game_;
  const bool enable_recording_feature_;

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

// GameDashboardEnabledFeatureTileVerificationTest Test
// -----------------------------------------------------------------------
// Verifies both the existence of Game Dashboard Main Menu Feature Tiles as well
// as any given tile's type for every possible allowed combination of Feature
// Tiles in the Main Menu.
TEST_P(GameDashboardEnabledFeatureTileVerificationTest, MainMenuShortcutTiles) {
  test_api_->OpenTheMainMenu();

  auto* toolbar_tile = test_api_->GetMainMenuToolbarTile();
  auto* screenshot_tile = test_api_->GetMainMenuScreenshotTile();
  ASSERT_TRUE(toolbar_tile);
  ASSERT_TRUE(screenshot_tile);

  auto* game_controls_tile = test_api_->GetMainMenuGameControlsTile();
  if (is_arc_game_) {
    ASSERT_TRUE(game_controls_tile);
    ASSERT_EQ(game_controls_tile->tile_type(), FeatureTile::TileType::kCompact);
  } else {
    ASSERT_FALSE(game_controls_tile);
  }

  auto* record_game_tile = test_api_->GetMainMenuRecordGameTile();
  if (enable_recording_feature_) {
    ASSERT_TRUE(record_game_tile);
    ASSERT_EQ(record_game_tile->tile_type(), FeatureTile::TileType::kCompact);
  } else {
    ASSERT_FALSE(test_api_->GetMainMenuRecordGameTile());
  }

  FeatureTile::TileType expected_tile_type = FeatureTile::TileType::kCompact;
  if (!is_arc_game_ && !enable_recording_feature_) {
    expected_tile_type = FeatureTile::TileType::kPrimary;
  }

  ASSERT_EQ(toolbar_tile->tile_type(), expected_tile_type);
  ASSERT_EQ(screenshot_tile->tile_type(), expected_tile_type);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    GameDashboardEnabledFeatureTileVerificationTest,
    testing::Combine(/*is_arc_game=*/testing::Bool(),
                     /*enable_recording_feature=*/testing::Bool()));

// -----------------------------------------------------------------------------
// GameDashboardStartAndStopCaptureSessionTest:
// Test fixture to verify the game window can be started and stopped from the
// main menu and toolbar, for both ARC and GeForceNow game windows.
class GameDashboardStartAndStopCaptureSessionTest
    : public GameDashboardContextTest,
      public testing::WithParamInterface<
          std::tuple</*is_arc_game_=*/bool,
                     /*should_start_from_main_menu_=*/bool,
                     /*should_stop_from_main_menu_=*/bool>> {
 public:
  GameDashboardStartAndStopCaptureSessionTest()
      : is_arc_game_(std::get<0>(GetParam())),
        should_start_from_main_menu_(std::get<1>(GetParam())),
        should_stop_from_main_menu_(std::get<2>(GetParam())) {}
  ~GameDashboardStartAndStopCaptureSessionTest() override = default;

  void SetUp() override {
    GameDashboardContextTest::SetUp();
    CreateGameWindow(is_arc_game_);
  }

 protected:
  const bool is_arc_game_;
  const bool should_start_from_main_menu_;
  const bool should_stop_from_main_menu_;
};

// GameDashboardStartAndStopCaptureSessionTest Tests
// -----------------------------------------------------------------------
// Verifies the game window recording starts and stops for the given set of test
// parameters.
TEST_P(GameDashboardStartAndStopCaptureSessionTest, RecordGameFromMainMenu) {
  const auto* capture_mode_controller = CaptureModeController::Get();
  const auto& timer = test_api_->GetRecordingTimer();

  test_api_->OpenTheMainMenu();
  EXPECT_FALSE(capture_mode_controller->is_recording_in_progress());
  EXPECT_FALSE(timer.IsRunning());
  VerifyGameDashboardButtonState(/*is_recording=*/false);

  if (should_start_from_main_menu_) {
    // Retrieve the record game tile from the main menu.
    const auto* record_game_tile = test_api_->GetMainMenuRecordGameTile();
    ASSERT_TRUE(record_game_tile);

    // Start the video recording from the main menu.
    LeftClickOn(record_game_tile);
    // Clicking on the record game tile closes the main menu, and asynchronously
    // starts the capture session. Run until idle to ensure that the posted task
    // runs synchronously and completes before proceeding.
    base::RunLoop().RunUntilIdle();
  } else {
    // Retrieve the record game button from the toolbar.
    CHECK(!test_api_->GetToolbarView());
    test_api_->OpenTheToolbar();
    test_api_->CloseTheMainMenu();
    const auto* record_game_button = test_api_->GetToolbarRecordGameButton();
    ASSERT_TRUE(record_game_button);

    // Start the video recording from the toolbar.
    LeftClickOn(record_game_button);
  }
  ClickOnStartRecordingButtonInCaptureModeBarView();

  EXPECT_TRUE(capture_mode_controller->is_recording_in_progress());
  EXPECT_TRUE(timer.IsRunning());
  VerifyGameDashboardButtonState(/*is_recording=*/true);

  if (should_stop_from_main_menu_) {
    // Stop the video recording from the main menu.
    test_api_->OpenTheMainMenu();
    LeftClickOn(test_api_->GetMainMenuRecordGameTile());
  } else {
    // Open the toolbar, if the video recording started from the main menu.
    if (should_start_from_main_menu_) {
      test_api_->OpenTheMainMenu();
      test_api_->OpenTheToolbar();
      test_api_->CloseTheMainMenu();
    }
    // Verify the toolbar is open.
    CHECK(test_api_->GetToolbarView());
    // Stop the video recording from the toolbar.
    LeftClickOn(test_api_->GetToolbarRecordGameButton());
  }
  EXPECT_FALSE(capture_mode_controller->is_recording_in_progress());
  EXPECT_FALSE(timer.IsRunning());
  VerifyGameDashboardButtonState(/*is_recording=*/false);
  WaitForCaptureFileToBeSaved();
}

INSTANTIATE_TEST_SUITE_P(
    All,
    GameDashboardStartAndStopCaptureSessionTest,
    testing::Combine(/*is_arc_game_=*/testing::Bool(),
                     /*should_start_from_main_menu_=*/testing::Bool(),
                     /*should_stop_from_main_menu_=*/testing::Bool()));

// -----------------------------------------------------------------------------
// GameDashboardUIStartupSequenceTest:
// Test fixture to verify the toolbar and welcome dialog startup sequence when
// opening a game window. This fixture runs through all combinations of whether
// the toolbar and welcome dialog should be shown or not.
class GameDashboardUIStartupSequenceTest
    : public GameDashboardContextTest,
      public testing::WithParamInterface<
          std::tuple</*show_toolbar=*/bool,
                     /*show_welcome_dialog=*/bool>> {
 public:
  GameDashboardUIStartupSequenceTest()
      : should_show_toolbar_(std::get<0>(GetParam())),
        should_show_welcome_dialog_(std::get<1>(GetParam())) {}
  ~GameDashboardUIStartupSequenceTest() override = default;

  void SetUp() override {
    GameDashboardContextTest::SetUp();
    game_dashboard_utils::SetShowWelcomeDialog(should_show_welcome_dialog_);
    game_dashboard_utils::SetShowToolbar(should_show_toolbar_);
    CreateGameWindow(/*is_arc_window=*/true,
                     /*set_arc_game_controls_flags_prop=*/true);
  }

  void VerifyToolbarVisibility(bool visible) {
    if (visible) {
      ASSERT_TRUE(test_api_->GetToolbarWidget());
    } else {
      ASSERT_FALSE(test_api_->GetToolbarWidget());
    }
  }

  void VerifyWelcomeDialogVisibility(bool visible) {
    if (visible) {
      ASSERT_TRUE(test_api_->GetWelcomeDialogWidget());
    } else {
      ASSERT_FALSE(test_api_->GetWelcomeDialogWidget());
    }
  }

 protected:
  const bool should_show_toolbar_;
  const bool should_show_welcome_dialog_;
};

// GameDashboardUIStartupSequenceTest Tests
// -----------------------------------------------------------------------
// Verifies the toolbar is visible after the welcome dialog is dismissed.
TEST_P(GameDashboardUIStartupSequenceTest, ToolbarAndShowWelcomeDialogStartup) {
  if (should_show_welcome_dialog_) {
    // Verify the welcome dialog is visible and the toolbar is not visible.
    VerifyWelcomeDialogVisibility(/*visible=*/true);
    VerifyToolbarVisibility(/*visible=*/false);

    // Advance by 4 seconds to dismiss the welcome dialog.
    task_environment()->FastForwardBy(base::Seconds(4));
  }

  VerifyWelcomeDialogVisibility(/*visible=*/false);
  VerifyToolbarVisibility(/*visible=*/should_show_toolbar_);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    GameDashboardUIStartupSequenceTest,
    testing::Combine(/*should_show_toolbar_=*/testing::Bool(),
                     /*should_show_welcome_dialog_=*/testing::Bool()));

}  // namespace ash