chromium/chrome/browser/translate/android/auto_translate_snackbar_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 "chrome/browser/translate/android/auto_translate_snackbar_controller.h"

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/language/core/browser/language_model.h"
#include "components/language/core/browser/language_prefs.h"
#include "components/language/core/browser/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/translate/core/browser/language_state.h"
#include "components/translate/core/browser/mock_translate_client.h"
#include "components/translate/core/browser/mock_translate_driver.h"
#include "components/translate/core/browser/mock_translate_ranker.h"
#include "components/translate/core/browser/translate_client.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/translate/core/browser/translate_manager.h"
#include "components/translate/core/browser/translate_pref_names.h"
#include "components/translate/core/browser/translate_prefs.h"
#include "components/translate/core/common/translate_errors.h"
#include "components/translate/core/common/translate_metrics.h"
#include "components/translate/core/common/translate_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace translate {
namespace {

using ::testing::_;
using ::testing::Return;

class TestBridge : public AutoTranslateSnackbarController::Bridge {
 public:
  ~TestBridge() override;

  base::WeakPtr<TestBridge> GetWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

  std::string GetTargetLanguage() { return target_language_; }

  MOCK_METHOD(bool,
              CreateAutoTranslateSnackbarController,
              (JNIEnv*,
               content::WebContents*,
               AutoTranslateSnackbarController*),
              (override));

  void ShowSnackbar(
      JNIEnv* env,
      base::android::ScopedJavaLocalRef<jstring> target_language) override;
  void WasDismissed() override;
  bool IsSnackbarShowing() override;
  MOCK_METHOD(void, DismissSnackbar, (JNIEnv * env), (override));
  MOCK_METHOD(bool, CanShowSnackbar, (), (override));

 private:
  bool is_showing_;
  std::string target_language_;
  base::WeakPtrFactory<TestBridge> weak_ptr_factory_{this};
};

TestBridge::~TestBridge() = default;

void TestBridge::ShowSnackbar(
    JNIEnv* env,
    base::android::ScopedJavaLocalRef<jstring> target_language) {
  is_showing_ = true;
  target_language_ =
      base::android::ConvertJavaStringToUTF8(env, target_language);
}

void TestBridge::WasDismissed() {
  is_showing_ = false;
}

bool TestBridge::IsSnackbarShowing() {
  return is_showing_;
}

class TestLanguageModel : public language::LanguageModel {
  std::vector<LanguageDetails> GetLanguages() override {
    return {LanguageDetails("en", 1.0)};
  }
};

class TestTranslateDriver : public testing::MockTranslateDriver {
 public:
  ~TestTranslateDriver() override;
  MOCK_METHOD(void, RevertTranslation, (int), (override));
  MOCK_METHOD(bool, IsIncognito, (), (const override));
  MOCK_METHOD(bool, HasCurrentPage, (), (const override));
};

TestTranslateDriver::~TestTranslateDriver() = default;

constexpr const char kInfobarEventHistogram[] =
    "Translate.CompactInfobar.Event";

class AutoTranslateSnackbarControllerTest : public ::testing::Test {
 public:
  AutoTranslateSnackbarControllerTest() {
    // Needed so that driver->ShowTranslteStep will work
    ON_CALL(driver_, IsIncognito()).WillByDefault(Return(false));
    ON_CALL(driver_, HasCurrentPage()).WillByDefault(Return(true));
    driver_.SetLastCommittedURL(GURL("http://www.example.com/"));
  }

 protected:
  void SetUp() override {
    ::testing::Test::SetUp();

    // Setup Mock Translate Manager
    pref_service_ =
        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
    language::LanguagePrefs::RegisterProfilePrefs(pref_service_->registry());
    TranslatePrefs::RegisterProfilePrefs(pref_service_->registry());

    ranker_ = std::make_unique<testing::MockTranslateRanker>();
    client_ = std::make_unique<testing::MockTranslateClient>(
        &driver_, pref_service_.get());
    // Make Translate Manager
    manager_ = std::make_unique<TranslateManager>(client_.get(), ranker_.get(),
                                                  language_model_.get());
    manager_->GetLanguageState()->set_translation_declined(false);
    TranslateDownloadManager::GetInstance()->set_application_locale("en");

    auto bridge = std::make_unique<TestBridge>();
    bridge_ = bridge->GetWeakPtr();

    auto_snackbar_controller_ =
        std::make_unique<AutoTranslateSnackbarController>(
            /* web_contents= */ nullptr, manager_->GetWeakPtr(),
            std::move(bridge));
  }

  TestTranslateDriver driver_;
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> pref_service_;
  std::unique_ptr<testing::MockTranslateClient> client_;
  std::unique_ptr<testing::MockTranslateRanker> ranker_;
  std::unique_ptr<TestLanguageModel> language_model_;
  std::unique_ptr<TranslateManager> manager_;

  base::WeakPtr<TestBridge> bridge_;
  std::unique_ptr<AutoTranslateSnackbarController> auto_snackbar_controller_;
};

TEST_F(AutoTranslateSnackbarControllerTest, CreateAndDismissSnackbarNoAction) {
  JNIEnv* env = base::android::AttachCurrentThread();
  EXPECT_CALL(*bridge_, DismissSnackbar(env));
  EXPECT_CALL(*bridge_, CanShowSnackbar()).WillRepeatedly(Return(true));

  // Show snackbar
  EXPECT_FALSE(bridge_->IsSnackbarShowing());
  auto_snackbar_controller_->ShowSnackbar("tl");
  EXPECT_TRUE(bridge_->IsSnackbarShowing());
  EXPECT_EQ("tl", bridge_->GetTargetLanguage());

  // Dismiss snackbar from Java
  auto_snackbar_controller_->OnDismissNoAction(env);
  EXPECT_FALSE(bridge_->IsSnackbarShowing());
}

TEST_F(AutoTranslateSnackbarControllerTest,
       CreateAndDismissSnackbarWithAction) {
  base::HistogramTester histogram_tester;

  JNIEnv* env = base::android::AttachCurrentThread();
  EXPECT_CALL(*bridge_, DismissSnackbar(env));
  EXPECT_CALL(driver_, RevertTranslation(_));
  EXPECT_CALL(*bridge_, CanShowSnackbar()).WillRepeatedly(Return(true));

  // Show snackbar and fake translation
  EXPECT_FALSE(bridge_->IsSnackbarShowing());
  manager_->PageTranslated("en", "tl", TranslateErrors::NONE);
  auto_snackbar_controller_->ShowSnackbar("tl");
  EXPECT_TRUE(bridge_->IsSnackbarShowing());
  EXPECT_EQ("tl", bridge_->GetTargetLanguage());

  // Click on Undo translation from Java
  auto j_string = base::android::ConvertUTF8ToJavaString(env, "tl");
  auto_snackbar_controller_->OnUndoActionPressed(
      env, base::android::JavaParamRef<jstring>(env, j_string.obj()));
  EXPECT_FALSE(bridge_->IsSnackbarShowing());

  // Check that the INFOBAR_REVERT histogram was recorded
  histogram_tester.ExpectUniqueSample(kInfobarEventHistogram,
                                      InfobarEvent::INFOBAR_REVERT, 1);
}

TEST_F(AutoTranslateSnackbarControllerTest, DismissFromNative) {
  JNIEnv* env = base::android::AttachCurrentThread();
  EXPECT_CALL(*bridge_, DismissSnackbar(env)).Times(2);
  EXPECT_CALL(*bridge_, CanShowSnackbar()).WillRepeatedly(Return(true));

  // Show snackbar
  EXPECT_FALSE(bridge_->IsSnackbarShowing());
  auto_snackbar_controller_->ShowSnackbar("tl");
  EXPECT_TRUE(bridge_->IsSnackbarShowing());
  EXPECT_EQ("tl", bridge_->GetTargetLanguage());

  auto_snackbar_controller_->NativeDismissSnackbar();
}

TEST_F(AutoTranslateSnackbarControllerTest,
       CreateAuotTranslateSnackbarFailsThenSucceeds) {
  JNIEnv* env = base::android::AttachCurrentThread();

  // The first call to CreateAutoTranslateSnakbarController fails.
  EXPECT_CALL(*bridge_, CanShowSnackbar()).WillRepeatedly(Return(false));
  EXPECT_CALL(*bridge_, CreateAutoTranslateSnackbarController(env, _, _))
      .WillOnce(Return(false));
  EXPECT_CALL(*bridge_, DismissSnackbar(env));

  auto_snackbar_controller_->ShowSnackbar("tl");

  // The Snackbar should not be showing if the controller was not created.
  EXPECT_FALSE(auto_snackbar_controller_->IsShowing());

  // The second call to CreateAutoTranslateSnakbarController succeeds.
  EXPECT_CALL(*bridge_, CreateAutoTranslateSnackbarController(env, _, _))
      .WillOnce(Return(true));

  auto_snackbar_controller_->ShowSnackbar("tl");

  // The Snackbar should now be showing since the controller was succefully
  // created.
  EXPECT_TRUE(auto_snackbar_controller_->IsShowing());
}

}  // namespace
}  // namespace translate