chromium/chrome/browser/ash/crosapi/input_methods_ash_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/ash/crosapi/input_methods_ash.h"

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "chromeos/crosapi/mojom/input_methods.mojom-forward.h"
#include "chromeos/crosapi/mojom/input_methods.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/mock_input_method_manager.h"

namespace crosapi {

namespace {

class InputMethodManagerFake
    : public ash::input_method::MockInputMethodManager {
 public:
  InputMethodManagerFake() : state_(base::MakeRefCounted<TestState>()) {}

  InputMethodManagerFake(const InputMethodManagerFake&) = delete;
  InputMethodManagerFake& operator=(const InputMethodManagerFake&) = delete;

  void SetEnabledInputMethods(std::vector<std::string> input_methods) {
    state_->SetEnabledInputMethods(std::move(input_methods));
  }

  // Sets callback that will be used in `GetMigratedInputMethodID`.
  void SetInputMethodIdMigrationMethod(
      base::RepeatingCallback<std::string(const std::string&)>
          migration_method) {
    migration_method_ = migration_method;
  }

  std::optional<std::string> GetLastSetInputMethod() const {
    return state_->GetLastSetInputMethod();
  }

 private:
  class TestState : public State {
   public:
    TestState() = default;

    void SetEnabledInputMethods(std::vector<std::string> input_methods) {
      enabled_input_methods_ = std::move(input_methods);
    }

    std::optional<std::string> GetLastSetInputMethod() const {
      return current_input_method_;
    }

    // `State` implementation:
    void ChangeInputMethod(const std::string& value,
                           bool show_message) override {
      current_input_method_ = value;
    }

    const std::vector<std::string>& GetEnabledInputMethodIds() const override {
      return enabled_input_methods_;
    }

   protected:
    friend base::RefCounted<ash::input_method::InputMethodManager::State>;
    ~TestState() override = default;

   private:
    std::vector<std::string> enabled_input_methods_;
    std::optional<std::string> current_input_method_;
  };

  // `MockInputMethodManager` implementation:
  scoped_refptr<ash::input_method::InputMethodManager::State>
  GetActiveIMEState() override {
    return state_;
  }

  std::string GetMigratedInputMethodID(
      const std::string& input_method_id) override {
    return migration_method_.Run(input_method_id);
  }

 private:
  scoped_refptr<TestState> state_;

  base::RepeatingCallback<std::string(const std::string&)> migration_method_ =
      base::BindRepeating([](const std::string& id) { return id; });
};

}  // namespace

class InputMethodsAshTest : public ::testing::Test {
 public:
  void SetUp() override {
    // Ownership of this pointer is taken by `InputMethodManager::Initialize()`.
    input_method_manager_ = new InputMethodManagerFake;
    ash::input_method::InputMethodManager::Initialize(
        input_method_manager_.get());

    input_methods_ash_.BindReceiver(remote_.BindNewPipeAndPassReceiver());
  }

  void TearDown() override {
    input_method_manager_ = nullptr;
    ash::input_method::InputMethodManager::Shutdown();
  }

  InputMethodManagerFake& input_method_manager() {
    return *input_method_manager_;
  }

  mojo::Remote<mojom::InputMethods>& mojom_remote() { return remote_; }

  bool ChangeInputMethodAndWaitForResponse(
      mojo::Remote<mojom::InputMethods>& remote,
      const std::string& input_method) {
    base::test::TestFuture<bool> future;
    remote->ChangeInputMethod(input_method, future.GetCallback());
    EXPECT_TRUE(future.Wait());
    return future.Get();
  }

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;

  raw_ptr<InputMethodManagerFake> input_method_manager_;
  InputMethodsAsh input_methods_ash_;
  mojo::Remote<mojom::InputMethods> remote_;
};

TEST_F(InputMethodsAshTest, ShouldAcceptEnabledInputMethod) {
  input_method_manager().SetEnabledInputMethods({"en-us", "fr-fr"});

  bool succeeded = ChangeInputMethodAndWaitForResponse(mojom_remote(), "fr-fr");

  EXPECT_TRUE(succeeded);
  EXPECT_EQ(input_method_manager().GetLastSetInputMethod(), "fr-fr");
}

TEST_F(InputMethodsAshTest, ShouldRejectDisableInputMethod) {
  input_method_manager().SetEnabledInputMethods({"en-us", "fr-fr"});

  bool succeeded = ChangeInputMethodAndWaitForResponse(mojom_remote(), "it-it");

  EXPECT_FALSE(succeeded);
  EXPECT_EQ(input_method_manager().GetLastSetInputMethod(), std::nullopt);
}

TEST_F(InputMethodsAshTest, ShouldUseMigratedInputMethodIds) {
  input_method_manager().SetEnabledInputMethods({"migrated-be-nl"});
  input_method_manager().SetInputMethodIdMigrationMethod(base::BindRepeating(
      [](const std::string& id) { return "migrated-" + id; }));

  bool succeeded = ChangeInputMethodAndWaitForResponse(mojom_remote(), "be-nl");

  EXPECT_TRUE(succeeded);
  EXPECT_EQ(input_method_manager().GetLastSetInputMethod(), "migrated-be-nl");
}

}  // namespace crosapi