chromium/chrome/browser/ui/chromeos/magic_boost/magic_boost_card_controller_unittest.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 "chrome/browser/ui/chromeos/magic_boost/magic_boost_card_controller.h"

#include <memory>

#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ash/magic_boost/magic_boost_state_ash.h"
#include "chrome/browser/ash/magic_boost/mock_magic_boost_state.h"
#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_metrics.h"
#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_opt_in_card.h"
#include "chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller_crosapi.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "chromeos/components/magic_boost/public/cpp/magic_boost_state.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/lottie/resource.h"
#include "ui/views/view_utils.h"

namespace chromeos {

class MagicBoostCardControllerTest : public ChromeViewsTestBase {
 public:
  MagicBoostCardControllerTest() {
// Sets the default functions for the test to create image with the lottie
// resource id. Otherwise there's no `g_parse_lottie_as_still_image_` set in the
// `ResourceBundle`.
#if BUILDFLAG(IS_CHROMEOS_ASH)
    ui::ResourceBundle::SetLottieParsingFunctions(
        &lottie::ParseLottieAsStillImage,
        &lottie::ParseLottieAsThemedStillImage);
#endif
  }

  // ChromeViewsTestBase:
  void SetUp() override {
    ChromeViewsTestBase::SetUp();

    // Replace the production `MagicBoostController` with a mock for testing
#if BUILDFLAG(IS_CHROMEOS_LACROS)
    card_controller_.BindMagicBoostControllerCrosapiForTesting(
        receiver_.BindNewPipeAndPassRemote());
#else   // BUILDFLAG(IS_CHROMEOS_ASH)
    card_controller_.SetMagicBoostControllerCrosapiForTesting(
        &crosapi_controller_);
#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
    card_controller_.SetOptInFeature(OptInFeatures::kHmrOnly);

    magic_boost_state_ = std::make_unique<ash::MockMagicBoostState>();
  }

  void TearDown() override {
    magic_boost_state_.reset();
    ChromeViewsTestBase::TearDown();
  }

 protected:
  std::unique_ptr<ash::MockMagicBoostState> magic_boost_state_;
  MagicBoostCardController card_controller_;
  testing::NiceMock<MockMagicBoostControllerCrosapi> crosapi_controller_;
  mojo::Receiver<crosapi::mojom::MagicBoostController> receiver_{
      &crosapi_controller_};
};

// Tests the behavior of the controller when `OnTextAvailable()` and
// `OnDismiss()` are triggered.
TEST_F(MagicBoostCardControllerTest, OnTextAvailableAndDismiss) {
  // Initially the opt-in widget is not visible.
  EXPECT_FALSE(card_controller_.opt_in_widget_for_test());

  // Show the opt-in widget and test that the proper views are set.
  EXPECT_CALL(crosapi_controller_, CloseDisclaimerUi);
  card_controller_.OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
                                   /*selected_text=*/"",
                                   /*surrounding_text=*/"");
  auto* opt_in_widget = card_controller_.opt_in_widget_for_test();
  ASSERT_TRUE(opt_in_widget);
  EXPECT_TRUE(opt_in_widget->IsVisible());
  EXPECT_TRUE(views::IsViewClass<MagicBoostOptInCard>(
      opt_in_widget->GetContentsView()));

  // Test that the opt-in widget is closed on `CloseOptInUI`.
  card_controller_.OnDismiss(/*is_other_command_executed=*/false);
  EXPECT_FALSE(card_controller_.opt_in_widget_for_test());
}

// Tests the behavior of the controller when `OnAnchorBoundsChanged()` is
// triggered.
TEST_F(MagicBoostCardControllerTest, BoundsChanged) {
  EXPECT_FALSE(card_controller_.opt_in_widget_for_test());

  EXPECT_CALL(crosapi_controller_, CloseDisclaimerUi);
  gfx::Rect anchor_bounds = gfx::Rect(50, 50, 25, 100);
  card_controller_.OnTextAvailable(anchor_bounds,
                                   /*selected_text=*/"",
                                   /*surrounding_text=*/"");
  auto* widget = card_controller_.opt_in_widget_for_test();
  EXPECT_TRUE(widget);

  // Correct bounds should be set.
  EXPECT_EQ(editor_menu::GetEditorMenuBounds(anchor_bounds,
                                             widget->GetContentsView()),
            widget->GetRestoredBounds());

  anchor_bounds = gfx::Rect(0, 50, 55, 80);

  // Widget should change bounds accordingly.
  card_controller_.OnAnchorBoundsChanged(anchor_bounds);
  EXPECT_EQ(editor_menu::GetEditorMenuBounds(anchor_bounds,
                                             widget->GetContentsView()),
            widget->GetRestoredBounds());
}

TEST_F(MagicBoostCardControllerTest, DisclaimerUi) {
  int expected_display_id = 2;
  auto expected_action =
      crosapi::mojom::MagicBoostController::TransitionAction::kShowEditorPanel;
  auto expected_features =
      crosapi::mojom::MagicBoostController::OptInFeatures::kOrcaAndHmr;

  card_controller_.set_transition_action(expected_action);
  card_controller_.SetOptInFeature(expected_features);

  EXPECT_CALL(crosapi_controller_, ShowDisclaimerUi)
      .WillOnce(
          [expected_display_id, expected_action, expected_features](
              int display_id,
              crosapi::mojom::MagicBoostController::TransitionAction action,
              /*opt_in_features=*/
              crosapi::mojom::MagicBoostController::OptInFeatures features) {
            EXPECT_EQ(expected_display_id, display_id);
            EXPECT_EQ(expected_action, action);
            EXPECT_EQ(expected_features, features);
          });

  card_controller_.ShowDisclaimerUi(expected_display_id);
}

TEST_F(MagicBoostCardControllerTest, ShowOptInCardAgain) {
  // Shows the disclaimer view.
  EXPECT_CALL(crosapi_controller_, ShowDisclaimerUi);
  card_controller_.ShowDisclaimerUi(
      /*display_id=*/1);
  EXPECT_FALSE(card_controller_.opt_in_widget_for_test());

  // Shows the opt-in widget. It should close the discalimer view.
  EXPECT_CALL(crosapi_controller_, CloseDisclaimerUi);
  card_controller_.OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
                                   /*selected_text=*/"",
                                   /*surrounding_text=*/"");
  ASSERT_TRUE(card_controller_.opt_in_widget_for_test());
}

TEST_F(MagicBoostCardControllerTest, ShowOptInCardMetrics) {
  auto histogram_tester = std::make_unique<base::HistogramTester>();
  std::string histogram_name = magic_boost::kMagicBoostOptInCardHistogram;
  histogram_tester->ExpectTotalCount(histogram_name + "Total", 0);
  histogram_tester->ExpectTotalCount(histogram_name + "HmrOnly", 0);
  histogram_tester->ExpectTotalCount(histogram_name + "OrcaAndHmr", 0);

  // Shows the opt-in widget from hmr feature.
  card_controller_.SetOptInFeature(OptInFeatures::kHmrOnly);
  card_controller_.ShowOptInUi(/*anchor_bounds=*/gfx::Rect());
  histogram_tester->ExpectTotalCount(histogram_name + "Total", 1);
  histogram_tester->ExpectTotalCount(histogram_name + "HmrOnly", 1);
  histogram_tester->ExpectBucketCount(
      histogram_name + "HmrOnly", magic_boost::OptInCardAction::kShowCard, 1);
  card_controller_.CloseOptInUi();

  // Shows the opt-in widget from both hmr and orca feature.
  card_controller_.SetOptInFeature(OptInFeatures::kOrcaAndHmr);
  card_controller_.ShowOptInUi(/*anchor_bounds=*/gfx::Rect());
  histogram_tester->ExpectTotalCount(histogram_name + "Total", 2);
  histogram_tester->ExpectTotalCount(histogram_name + "OrcaAndHmr", 1);
  card_controller_.CloseOptInUi();
}

}  // namespace chromeos