chromium/ash/wm/test/wm_pixel_diff_test.cc

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

#include <memory>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_util.h"
#include "ash/test/pixel/ash_pixel_differ.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_grid_test_api.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/window_cycle/window_cycle_controller.h"
#include "ash/wm/window_cycle/window_cycle_list.h"
#include "ash/wm/window_cycle/window_cycle_view.h"
#include "ash/wm/window_restore/informed_restore_contents_data.h"
#include "ash/wm/window_restore/informed_restore_controller.h"
#include "ash/wm/window_state.h"
#include "base/strings/strcat.h"
#include "base/test/scoped_feature_list.h"
#include "components/app_constants/constants.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"

namespace ash {

// TODO(b/261084863): For now, add some basic tests. Further investigation is
// needed to determine the location of the test files, whether the tests should
// cover more user journeys and whether we should parameterize for RTL,
// dark/light mode, tablet mode, etc.
class WmPixelDiffTest : public AshTestBase {
 public:
  WmPixelDiffTest() {
    scoped_features_.InitWithFeatures(
        {features::kForestFeature, features::kSavedDeskUiRevamp,
         features::kDeskBarWindowOcclusionOptimization},
        {});
  }

  // AshTestBase:
  std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
      const override {
    return pixel_test::InitParams();
  }

 private:
  base::test::ScopedFeatureList scoped_features_;
};

// A basic overview pixel test that shows three overview windows and the virtual
// desks bar.
TEST_F(WmPixelDiffTest, OverviewAndDesksBarBasic) {
  UpdateDisplay("1600x1000");

  // Create a second desk so the desks bar view shows up.
  auto* controller = DesksController::Get();
  controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  controller->desks()[0]->SetName(u"Desk1", /*set_by_user=*/true);
  controller->desks()[1]->SetName(u"Desk2", /*set_by_user=*/true);

  // Create windows of different positions and sizes so they aren't all stacked
  // on top of each other in the desk preview view, and that we can pixel test
  // extreme cases in overview.
  auto window1 = CreateAppWindow(gfx::Rect(300, 300));
  auto window2 = CreateAppWindow(gfx::Rect(600, 600, 500, 200));
  auto window3 = CreateAppWindow(gfx::Rect(100, 400, 100, 600));

  DecorateWindow(window1.get(), u"Window1", SK_ColorDKGRAY);
  DecorateWindow(window2.get(), u"Window2", SK_ColorBLUE);
  DecorateWindow(window3.get(), u"Window3", SK_ColorGRAY);

  EnterOverview();

  auto* desk_widget = const_cast<views::Widget*>(
      GetOverviewGridForRoot(Shell::GetPrimaryRootWindow())->desks_widget());
  auto* overview_widget1 =
      GetOverviewItemForWindow(window1.get())->item_widget();
  auto* overview_widget2 =
      GetOverviewItemForWindow(window2.get())->item_widget();
  auto* overview_widget3 =
      GetOverviewItemForWindow(window3.get())->item_widget();

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "overview_and_desks_bar_basic",
      /*revision_number=*/16, desk_widget, overview_widget1, overview_widget2,
      overview_widget3));
}

// TODO(crbug.com/40929874): Test is flaky.
TEST_F(WmPixelDiffTest, DISABLED_OverviewTabletSnap) {
  UpdateDisplay("1600x1000");

  ShellTestApi().SetTabletModeEnabledForTest(true);

  auto window1 = CreateAppWindow(gfx::Rect(300, 300));
  auto window2 = CreateAppWindow(gfx::Rect(300, 300));
  auto window3 = CreateAppWindow(gfx::Rect(300, 300));

  DecorateWindow(window1.get(), u"Window1", SK_ColorDKGRAY);
  DecorateWindow(window2.get(), u"Window2", SK_ColorBLUE);
  DecorateWindow(window3.get(), u"Window3", SK_ColorRED);

  // Minimize `window3` so it tests a different code path than `window2`.
  // Activate and snap `window` and verify we have entered overview with one
  // snapped window.
  WindowState::Get(window3.get())->Minimize();
  WindowSnapWMEvent wm_left_snap_event(WM_EVENT_SNAP_PRIMARY);
  WindowState::Get(window1.get())->Activate();
  WindowState::Get(window1.get())->OnWMEvent(&wm_left_snap_event);
  ASSERT_TRUE(SplitViewController::Get(Shell::GetPrimaryRootWindow())
                  ->InSplitViewMode());
  ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());

  auto* const snapped_window_widget =
      views::Widget::GetWidgetForNativeWindow(window1.get());
  auto* const overview_widget2 =
      GetOverviewItemForWindow(window2.get())->item_widget();
  auto* const overview_widget3 =
      GetOverviewItemForWindow(window3.get())->item_widget();

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "overview_tablet_snap",
      /*revision_number=*/2, snapped_window_widget, overview_widget2,
      overview_widget3));
}

// A basic window cycle pixel test that shows three windows and the window cycle
// tab slider.
TEST_F(WmPixelDiffTest, WindowCycleBasic) {
  UpdateDisplay("1600x1000");

  // Create a second desk so the window cycle tab slider shows up. This slider
  // lets users toggle between seeing windows on the current desk only, or
  // windows on all desks.
  auto* desks_controller = DesksController::Get();
  desks_controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
  desks_controller->desks()[0]->SetName(u"Desk1", /*set_by_user=*/true);
  desks_controller->desks()[1]->SetName(u"Desk2", /*set_by_user=*/true);

  // Create a couple windows of different sizes.
  auto window1 = CreateAppWindow(gfx::Rect(300, 300));
  auto window2 = CreateAppWindow(gfx::Rect(500, 200));
  auto window3 = CreateAppWindow(gfx::Rect(100, 600));
  auto window4 = CreateAppWindow(gfx::Rect(800, 600));

  DecorateWindow(window1.get(), u"Window1", SK_ColorDKGRAY);
  DecorateWindow(window2.get(), u"Window2", SK_ColorBLUE);
  DecorateWindow(window3.get(), u"Window3", SK_ColorGRAY);
  DecorateWindow(window4.get(), u"Window4", SK_ColorGREEN);

  // Press alt+tab to bring up the window cycle UI.
  WindowCycleList::SetDisableInitialDelayForTesting(true);
  GetEventGenerator()->PressKey(ui::VKEY_TAB, ui::EF_ALT_DOWN);

  const WindowCycleView* cycle_view = Shell::Get()
                                          ->window_cycle_controller()
                                          ->window_cycle_list()
                                          ->cycle_view();
  ASSERT_TRUE(cycle_view);
  views::Widget* widget = const_cast<views::Widget*>(cycle_view->GetWidget());

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "window_cycle_basic",
      /*revision_number=*/21, widget));
}

TEST_F(WmPixelDiffTest, InformedRestoreNoScreenshotDialog) {
  UpdateDisplay("1600x1000");

  // Chrome apps are unique as they show tab info additionally. Create one
  // chrome info with some example favicons.
  InformedRestoreContentsData::AppInfo chrome_app_info(
      app_constants::kChromeAppId, "Chrome", /*window_id=*/1);
  chrome_app_info.tab_urls = std::vector<GURL>(5, GURL("http://example.com"));
  chrome_app_info.tab_count = 23;

  auto data = std::make_unique<InformedRestoreContentsData>();
  data->apps_infos.push_back(chrome_app_info);

  // Add a couple more windows with generic names. This will trigger the
  // overflow view.
  for (int i = 2; i < 12; ++i) {
    data->apps_infos.emplace_back(
        "test_id", base::StrCat({"Window ", base::NumberToString(i)}),
        /*window_id=*/i);
  }

  // Enter the informed restore overview session.
  Shell::Get()->informed_restore_controller()->MaybeStartInformedRestoreSession(
      std::move(data));
  WaitForOverviewEntered();

  OverviewGrid* grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow());
  ASSERT_TRUE(grid);
  auto* widget = OverviewGridTestApi(grid).informed_restore_widget();
  ASSERT_TRUE(widget);

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "informed_restore_no_screenshot",
      /*revision_number=*/1, widget));
}

}  // namespace ash