chromium/ash/system/mahi/mahi_panel_view_pixeltest.cc

// Copyright 2024 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 <string>
#include <vector>

#include "ash/constants/ash_pref_names.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/mahi/mahi_constants.h"
#include "ash/system/mahi/mahi_panel_view.h"
#include "ash/system/mahi/mahi_ui_controller.h"
#include "ash/system/mahi/test/mock_mahi_manager.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "ash/test/ash_test_util.h"
#include "ash/test/pixel/ash_pixel_differ.h"
#include "ash/test/pixel/ash_pixel_test_init_params.h"
#include "base/strings/strcat.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

namespace ash {

// Pixel tests for Chrome OS Status Area. This relates to all tray buttons in
// the bottom right corner.
class MahiPanelViewPixelTest : public AshTestBase {
 public:
  // AshTestBase:
  std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
      const override {
    return pixel_test::InitParams();
  }

  // AshTestBase:
  void SetUp() override {
    scoped_feature_list_.InitWithFeatures(
        {chromeos::features::kMahi, chromeos::features::kFeatureManagementMahi},
        {});
    AshTestBase::SetUp();

    scoped_setter_ = std::make_unique<chromeos::ScopedMahiManagerSetter>(
        &mock_mahi_manager_);

    CreatePanelWidget();
  }

  void TearDown() override {
    panel_view_ = nullptr;
    widget_.reset();
    scoped_setter_.reset();

    AshTestBase::TearDown();
  }

  void CreatePanelWidget() {
    widget_ = CreateFramelessTestWidget();
    widget_->SetBounds(
        gfx::Rect(/*x=*/0, /*y=*/0,
                  /*width=*/mahi_constants::kPanelDefaultWidth,
                  /*height=*/mahi_constants::kPanelDefaultHeight));
    panel_view_ = widget_->SetContentsView(
        std::make_unique<MahiPanelView>(&ui_controller_));
  }

  void RecreatePanelWidget() {
    panel_view_ = nullptr;
    widget_.reset();

    CreatePanelWidget();
  }

  // Scroll the scroll view inside Mahi panel to the bottom.
  void ScrollToBottom() {
    auto* scroll_view = views::AsViewClass<views::ScrollView>(
        panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView));
    ASSERT_TRUE(scroll_view);
    scroll_view->vertical_scroll_bar()->ScrollByAmount(
        views::ScrollBar::ScrollAmount::kEnd);
  }

  MockMahiManager& mock_mahi_manager() { return mock_mahi_manager_; }

  MahiUiController* ui_controller() { return &ui_controller_; }

  MahiPanelView* panel_view() { return panel_view_; }

  views::Widget* widget() { return widget_.get(); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  testing::NiceMock<MockMahiManager> mock_mahi_manager_;
  std::unique_ptr<chromeos::ScopedMahiManagerSetter> scoped_setter_;
  MahiUiController ui_controller_;
  raw_ptr<MahiPanelView> panel_view_ = nullptr;
  std::unique_ptr<views::Widget> widget_;
};

TEST_F(MahiPanelViewPixelTest, MainPanel) {
  ON_CALL(mock_mahi_manager(), GetContentTitle)
      .WillByDefault(testing::Return(u"Test content title"));
  ON_CALL(mock_mahi_manager(), GetContentIcon)
      .WillByDefault(testing::Return(
          gfx::test::CreateImageSkia(/*size=*/128, SK_ColorBLUE)));

  ON_CALL(mock_mahi_manager(), GetSummary)
      .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
        std::move(callback).Run(
            base::StrCat(std::vector<std::u16string>(30, u"Summary text ")),
            chromeos::MahiResponseStatus::kSuccess);
      });

  ui_controller()->RefreshContents();
  views::test::RunScheduledLayout(widget());

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "panel_view", /*revision_number=*/7, panel_view()));
}

TEST_F(MahiPanelViewPixelTest, ContentSourceButton) {
  ON_CALL(mock_mahi_manager(), GetContentTitle)
      .WillByDefault(testing::Return(base::StrCat(
          std::vector<std::u16string>(3, u"Long content title "))));
  ON_CALL(mock_mahi_manager(), GetContentIcon)
      .WillByDefault(testing::Return(
          gfx::test::CreateImageSkia(/*size=*/200, SK_ColorRED)));

  ui_controller()->RefreshContents();
  views::test::RunScheduledLayout(widget());

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "content_source", /*revision_number=*/2,
      panel_view()->GetViewByID(mahi_constants::ViewId::kContentSourceButton)));
}

TEST_F(MahiPanelViewPixelTest, SummaryView) {
  ON_CALL(mock_mahi_manager(), GetSummary)
      .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
        std::move(callback).Run(
            base::StrCat(std::vector<std::u16string>(35, u"Summary text ")),
            chromeos::MahiResponseStatus::kSuccess);
      });

  ui_controller()->RefreshContents();
  views::test::RunScheduledLayout(widget());

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "summary_view", /*revision_number=*/4,
      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView)));
}

TEST_F(MahiPanelViewPixelTest, PanelWithoutFeedbackButtons) {
  Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
      prefs::kHmrFeedbackAllowed, false);
  ON_CALL(mock_mahi_manager(), GetSummary)
      .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
        std::move(callback).Run(
            base::StrCat(std::vector<std::u16string>(35, u"Summary text ")),
            chromeos::MahiResponseStatus::kSuccess);
      });

  RecreatePanelWidget();

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "scroll_view", /*revision_number=*/0,
      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView)));

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "footer", /*revision_number=*/0,
      panel_view()->GetViewByID(mahi_constants::ViewId::kFooterLabel)));
}

TEST_F(MahiPanelViewPixelTest, QuestionAnswerViewBasic) {
  auto* const send_button =
      panel_view()->GetViewByID(mahi_constants::ViewId::kAskQuestionSendButton);
  auto* const question_textfield = views::AsViewClass<views::Textfield>(
      panel_view()->GetViewByID(mahi_constants::ViewId::kQuestionTextfield));

  const std::u16string answer(u"test answer");
  ON_CALL(mock_mahi_manager(), AnswerQuestion)
      .WillByDefault(
          [&answer](
              const std::u16string& question, bool current_panel_content,
              chromeos::MahiManager::MahiAnswerQuestionCallback callback) {
            std::move(callback).Run(answer,
                                    chromeos::MahiResponseStatus::kSuccess);
          });

  // Set a valid text in the question textfield.
  const std::u16string question(u"question");
  question_textfield->SetText(question);

  // Pressing the send button should create a question and answer text bubble.
  LeftClickOn(send_button);

  views::test::RunScheduledLayout(widget());

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "question_answer_view_basic", /*revision_number=*/5,
      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView)));
}

TEST_F(MahiPanelViewPixelTest, QuestionAnswerViewLongText) {
  auto* const send_button =
      panel_view()->GetViewByID(mahi_constants::ViewId::kAskQuestionSendButton);
  auto* const question_textfield = views::AsViewClass<views::Textfield>(
      panel_view()->GetViewByID(mahi_constants::ViewId::kQuestionTextfield));

  const std::u16string answer =
      base::StrCat(std::vector<std::u16string>(25, u"Long Answer "));
  ON_CALL(mock_mahi_manager(), AnswerQuestion)
      .WillByDefault(
          [&answer](
              const std::u16string& question, bool current_panel_content,
              chromeos::MahiManager::MahiAnswerQuestionCallback callback) {
            std::move(callback).Run(answer,
                                    chromeos::MahiResponseStatus::kSuccess);
          });

  // Set a valid text in the question textfield.
  const std::u16string question =
      base::StrCat(std::vector<std::u16string>(25, u"Long Question "));
  question_textfield->SetText(question);

  // Pressing the send button should create a question and answer text bubble.
  LeftClickOn(send_button);

  views::test::RunScheduledLayout(widget());

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "question_answer_view_long_text", /*revision_number=*/7,
      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView)));
}

TEST_F(MahiPanelViewPixelTest, SummaryViewScrollToBottom) {
  ON_CALL(mock_mahi_manager(), GetSummary)
      .WillByDefault([](chromeos::MahiManager::MahiSummaryCallback callback) {
        std::move(callback).Run(
            base::StrCat(std::vector<std::u16string>(60, u"Summary text ")),
            chromeos::MahiResponseStatus::kSuccess);
      });

  ui_controller()->RefreshContents();
  views::test::RunScheduledLayout(widget());

  ScrollToBottom();

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "summary_view_bottom", /*revision_number=*/3,
      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView)));
}

TEST_F(MahiPanelViewPixelTest, QuestionAnswerViewScrollToBottom) {
  const std::u16string answer =
      base::StrCat(std::vector<std::u16string>(35, u"Long Answer "));
  ON_CALL(mock_mahi_manager(), AnswerQuestion)
      .WillByDefault(
          [&answer](
              const std::u16string& question, bool current_panel_content,
              chromeos::MahiManager::MahiAnswerQuestionCallback callback) {
            std::move(callback).Run(answer,
                                    chromeos::MahiResponseStatus::kSuccess);
          });

  // Set a valid text in the question textfield.
  const std::u16string question =
      base::StrCat(std::vector<std::u16string>(25, u"Long Question "));
  views::AsViewClass<views::Textfield>(
      panel_view()->GetViewByID(mahi_constants::ViewId::kQuestionTextfield))
      ->SetText(question);

  // Pressing the send button should create a question and answer text bubble.
  LeftClickOn(panel_view()->GetViewByID(
      mahi_constants::ViewId::kAskQuestionSendButton));

  views::test::RunScheduledLayout(widget());

  ScrollToBottom();

  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
      "question_answer_bottom", /*revision_number=*/4,
      panel_view()->GetViewByID(mahi_constants::ViewId::kScrollView)));
}

}  // namespace ash