chromium/ash/glanceables/tasks/glanceables_task_view_pixeltest.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 <memory>
#include <optional>
#include <string>
#include <tuple>

#include "ash/api/tasks/tasks_types.h"
#include "ash/constants/ash_features.h"
#include "ash/glanceables/common/glanceables_util.h"
#include "ash/glanceables/common/glanceables_view_id.h"
#include "ash/glanceables/tasks/glanceables_task_view.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/pixel/ash_pixel_differ.h"
#include "ash/test/pixel/ash_pixel_test_init_params.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/test/gtest_tags.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/types/cxx23_to_underlying.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"

namespace ash {

// Pixel tests for `GlanceablesTaskView` that covers all possible permutations
// of UI states.
class GlanceablesTaskViewPixelTest
    : public AshTestBase,
      public testing::WithParamInterface<
          std::tuple</*use_rtl=*/bool,
                     /*has_due_date=*/bool,
                     /*has_subtasks=*/bool,
                     /*has_notes=*/bool,
                     /*completed=*/bool,
                     /*is_in_edit_state=*/bool>> {
 public:
  void SetUp() override {
    AshTestBase::SetUp();

    glanceables_util::SetIsNetworkConnectedForTest(true);

    base::Time due_date;
    ASSERT_TRUE(base::Time::FromString("2022-12-21T00:00:00.000Z", &due_date));

    task_ = std::make_unique<api::Task>(
        "task-id", "Task title",
        has_due_date() ? std::make_optional(due_date) : std::nullopt,
        /*completed=*/false, has_subtasks(),
        /*has_email_link=*/false,
        /*has_notes=*/has_notes(), /*updated=*/base::Time(),
        /*web_view_link=*/GURL(), api::Task::OriginSurfaceType::kRegular);

    widget_ = CreateFramelessTestWidget();
    widget_->SetFullscreen(true);

    auto* const container =
        widget_->SetContentsView(std::make_unique<views::BoxLayoutView>());
    container->SetCrossAxisAlignment(
        views::BoxLayout::CrossAxisAlignment::kStart);
    view_ = container->AddChildView(std::make_unique<GlanceablesTaskView>(
        task_.get(), /*mark_as_completed_callback=*/base::DoNothing(),
        /*save_callback=*/base::DoNothing(),
        /*edit_in_browser_callback=*/base::DoNothing(),
        /*show_error_message_callback=*/base::DoNothing()));

    widget_->LayoutRootViewIfNecessary();
  }

  std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
      const override {
    pixel_test::InitParams init_params;
    init_params.under_rtl = use_rtl();
    return init_params;
  }

  std::string GenerateScreenshotName() const {
    std::vector<std::string> parameters = {
        use_rtl() ? "rtl" : "ltr",
        has_due_date() ? "has_due_date=true" : "has_due_date=false",
        has_subtasks() ? "has_subtasks=true" : "has_subtasks=false",
        has_notes() ? "has_notes=true" : "has_notes=false",
        completed() ? "completed=true" : "completed=false",
        is_in_edit_state() ? "is_in_edit_state=true"
                           : "is_in_edit_state=false"};

    std::string stringified_params = base::JoinString(parameters, "|");
    return base::JoinString({"glanceables_task_view", stringified_params}, ".");
  }

  views::Widget* widget() const { return widget_.get(); }
  GlanceablesTaskView* view() const { return view_; }
  bool use_rtl() const { return std::get<0>(GetParam()); }
  bool has_due_date() const { return std::get<1>(GetParam()); }
  bool has_subtasks() const { return std::get<2>(GetParam()); }
  bool has_notes() const { return std::get<3>(GetParam()); }
  bool completed() const { return std::get<4>(GetParam()); }
  bool is_in_edit_state() const { return std::get<5>(GetParam()); }

 private:
  base::test::ScopedFeatureList feature_list_{
      features::kGlanceablesTimeManagementTasksView};
  std::unique_ptr<api::Task> task_;
  std::unique_ptr<views::Widget> widget_;
  raw_ptr<GlanceablesTaskView> view_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         GlanceablesTaskViewPixelTest,
                         testing::Combine(
                             /*use_rtl=*/testing::Bool(),
                             /*has_due_date=*/testing::Bool(),
                             /*has_subtasks=*/testing::Bool(),
                             /*has_notes=*/testing::Bool(),
                             /*completed=*/testing::Bool(),
                             /*is_in_edit_state=*/testing::Bool()));

TEST_P(GlanceablesTaskViewPixelTest, GlanceablesTaskView) {
  base::AddFeatureIdTagToTestResult(
      "screenplay-c5b9a047-dd51-4ccd-8f47-50c0fdfae7d1");  // due date +
                                                           // description
  base::AddFeatureIdTagToTestResult(
      "screenplay-c67b5acd-d0e8-41e8-a921-8060756cd807");  // subtasks

  ASSERT_TRUE(widget());

  ASSERT_FALSE(view()->GetCompletedForTest());
  if (completed()) {
    const auto* const checkbox = view()->GetCheckButtonForTest();
    ASSERT_TRUE(checkbox);
    LeftClickOn(checkbox);
    ASSERT_TRUE(view()->GetCompletedForTest());
  }

  if (is_in_edit_state()) {
    const auto* const title_label =
        views::AsViewClass<views::Label>(view()->GetViewByID(
            base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel)));
    ASSERT_TRUE(title_label);
    LeftClickOn(title_label);

    auto* const title_text_field =
        views::AsViewClass<views::Textfield>(view()->GetViewByID(
            base::to_underlying(GlanceablesViewId::kTaskItemTitleTextField)));
    ASSERT_TRUE(title_text_field);
    views::TextfieldTestApi(title_text_field).SetCursorLayerOpacity(0.f);
  }

  widget()->LayoutRootViewIfNecessary();

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      GenerateScreenshotName(), /*revision_number=*/0, widget()));
}

}  // namespace ash