chromium/ash/wm/multi_display/multi_display_metrics_controller_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/wm/multi_display/multi_display_metrics_controller.h"

#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/test/metrics/histogram_tester.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/wm/core/window_util.h"

namespace ash {

class MultiDisplayMetricsControllerTest : public AshTestBase {
 public:
  MultiDisplayMetricsControllerTest() = default;
  MultiDisplayMetricsControllerTest(const MultiDisplayMetricsControllerTest&) =
      delete;
  MultiDisplayMetricsControllerTest& operator=(
      const MultiDisplayMetricsControllerTest&) = delete;
  ~MultiDisplayMetricsControllerTest() override = default;

  void MaybeFireTimerNow() {
    base::OneShotTimer* timer =
        &Shell::Get()->multi_display_metrics_controller()->timer_;
    if (timer->IsRunning()) {
      timer->FireNow();
    }
  }

  // Helpers to move and resize a given window using the event generator.
  // Asserts that the window bounds have changed.
  void MoveWindow(aura::Window* window) {
    wm::ActivateWindow(window);

    const gfx::Rect old_bounds = window->GetBoundsInScreen();
    const gfx::Point point_on_header_in_screen(old_bounds.CenterPoint().x(),
                                               old_bounds.y() + 10);
    GetEventGenerator()->set_current_screen_location(point_on_header_in_screen);
    GetEventGenerator()->DragMouseBy(10, 10);
    const gfx::Rect expected_new_bounds = old_bounds + gfx::Vector2d(10, 10);
    ASSERT_EQ(expected_new_bounds, window->GetBoundsInScreen());
  }

  void ResizeWindow(aura::Window* window) {
    wm::ActivateWindow(window);

    const gfx::Rect old_bounds = window->GetBoundsInScreen();
    GetEventGenerator()->set_current_screen_location(old_bounds.bottom_right());
    GetEventGenerator()->DragMouseBy(5, 5);
    gfx::Rect expected_new_bounds(old_bounds);
    expected_new_bounds.Outset(gfx::Outsets::TLBR(0, 0, 5, 5));
    ASSERT_EQ(expected_new_bounds, window->GetBoundsInScreen());
  }

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
    for (int i = 0; i < 4; ++i) {
      test_windows_.push_back(CreateAppWindow());
    }
  }

  void TearDown() override {
    test_windows_.clear();
    AshTestBase::TearDown();
  }

 protected:
  // Most tests in this suite use multiple windows per test. This vector stores
  // the needed app windows, which are resizable by default and would be in the
  // MRU list.
  std::vector<std::unique_ptr<aura::Window>> test_windows_;

  base::HistogramTester histogram_tester_;
};

TEST_F(MultiDisplayMetricsControllerTest, DisplayRotated) {
  // Rotate the display. Do a double rotation so our move and resize helpers
  // work properly.
  const int64_t display_id =
      display::Screen::GetScreen()->GetPrimaryDisplay().id();
  display::DisplayManager* display_manager = Shell::Get()->display_manager();
  display::test::ScopedSetInternalDisplayId set_internal(display_manager,
                                                         display_id);
  ScreenOrientationControllerTestApi orientation_test_api(
      Shell::Get()->screen_orientation_controller());
  orientation_test_api.SetDisplayRotation(
      display::Display::ROTATE_90, display::Display::RotationSource::ACTIVE);
  orientation_test_api.SetDisplayRotation(
      display::Display::ROTATE_0, display::Display::RotationSource::ACTIVE);

  MoveWindow(test_windows_[0].get());
  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kRotatedHistogram, static_cast<int>(true),
      1);
}

TEST_F(MultiDisplayMetricsControllerTest, DisplaySizeChanged) {
  UpdateDisplay("800x600");

  UpdateDisplay("2000x1000");
  MoveWindow(test_windows_[0].get());
  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(true), 1);
}

TEST_F(MultiDisplayMetricsControllerTest, DisplayWorkAreaChanged) {
  // We will use the docked magnifier to modify the work area in this test.
  DockedMagnifierController* docked_magnifier_controller =
      Shell::Get()->docked_magnifier_controller();
  docked_magnifier_controller->SetEnabled(true);
  ASSERT_GT(docked_magnifier_controller->GetMagnifierHeightForTesting(), 0);

  MoveWindow(test_windows_[0].get());

  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(true), 1);
}

// The following tests use display size change to test several edge cases. The
// logic between all the display changes is the same and display size changed is
// the simplest scenario to test.

// Tests that if no windows are moved or resized after a display change, the
// histogram reflects that.
TEST_F(MultiDisplayMetricsControllerTest, NoWindowsMovedOrResized) {
  UpdateDisplay("800x600");

  UpdateDisplay("2000x1000");
  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(false), 1);
}

TEST_F(MultiDisplayMetricsControllerTest, MultipleWindowsMoved) {
  UpdateDisplay("800x600");

  UpdateDisplay("2000x1000");

  // Move a couple of different windows after resizing a display. Test that we
  // only record it once.
  MoveWindow(test_windows_[0].get());
  MoveWindow(test_windows_[1].get());
  MoveWindow(test_windows_[2].get());
  MoveWindow(test_windows_[3].get());

  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(true), 1);
}

// Tests that resizing window after a display changes records properly.
TEST_F(MultiDisplayMetricsControllerTest, WindowsResized) {
  UpdateDisplay("800x600");

  UpdateDisplay("2000x1000");
  ResizeWindow(test_windows_[0].get());

  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(true), 1);
}

TEST_F(MultiDisplayMetricsControllerTest, MultipleDisplayChanges) {
  UpdateDisplay("800x600");

  UpdateDisplay("1200x800");
  UpdateDisplay("2000x1000");
  UpdateDisplay("3000x2000");

  // Move the window after changing the display size multiple times. Test that
  // we only record it once.
  MoveWindow(test_windows_[0].get());

  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(true), 1);
}

// Tests that windows that are added after a display change and then moved
// and/or resized do not count.
TEST_F(MultiDisplayMetricsControllerTest, WindowAddedAfterDisplayChangeMoved) {
  UpdateDisplay("800x600");

  UpdateDisplay("1200x800");

  // Add a new window after the display has changed and move the window.
  auto new_window = CreateAppWindow();
  MoveWindow(new_window.get());
  ResizeWindow(new_window.get());

  // Verify we don't record the window change, since it was added after the
  // display change.
  MaybeFireTimerNow();
  histogram_tester_.ExpectUniqueSample(
      MultiDisplayMetricsController::kWorkAreaChangedHistogram,
      static_cast<int>(false), 1);
}

}  // namespace ash