blob: 3f47e2d20c6cd03c79eb6ca1eae19246a97177ba [file]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/services/ime/ime_service.h"
#include "chromeos/services/ime/public/mojom/constants.mojom.h"
#include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "services/service_manager/public/cpp/service_test.h"
#include "services/service_manager/public/mojom/service_factory.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
using testing::_;
namespace chromeos {
namespace ime {
namespace {
const char kTestServiceName[] = "ime_unittests";
const char kInvalidImeSpec[] = "ime_spec_never_support";
const std::vector<uint8_t> extra{0x66, 0x77, 0x88};
void ConnectCallback(bool* success, bool result) {
*success = result;
}
void TestProcessTextCallback(std::string* res_out,
const std::string& response) {
*res_out = response;
}
class TestClientChannel : mojom::InputChannel {
public:
TestClientChannel() : binding_(this) {}
mojom::InputChannelPtr CreateInterfacePtrAndBind() {
mojom::InputChannelPtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
return ptr;
}
// mojom::InputChannel implementation.
MOCK_METHOD2(ProcessText, void(const std::string&, ProcessTextCallback));
MOCK_METHOD2(ProcessMessage,
void(const std::vector<uint8_t>& message,
ProcessMessageCallback));
private:
mojo::Binding<mojom::InputChannel> binding_;
DISALLOW_COPY_AND_ASSIGN(TestClientChannel);
};
class ImeServiceTestClient : public service_manager::test::ServiceTestClient,
public service_manager::mojom::ServiceFactory {
public:
ImeServiceTestClient(service_manager::test::ServiceTest* test)
: service_manager::test::ServiceTestClient(test) {
registry_.AddInterface<service_manager::mojom::ServiceFactory>(
base::BindRepeating(&ImeServiceTestClient::Create,
base::Unretained(this)));
}
protected:
void OnBindInterface(const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override {
registry_.BindInterface(interface_name, std::move(interface_pipe));
}
// service_manager::mojom::ServiceFactory
void CreateService(
service_manager::mojom::ServiceRequest request,
const std::string& name,
service_manager::mojom::PIDReceiverPtr pid_receiver) override {
if (name == mojom::kServiceName) {
service_context_.reset(new service_manager::ServiceContext(
CreateImeService(), std::move(request)));
}
}
void Create(service_manager::mojom::ServiceFactoryRequest request) {
service_factory_bindings_.AddBinding(this, std::move(request));
}
private:
service_manager::BinderRegistry registry_;
mojo::BindingSet<service_manager::mojom::ServiceFactory>
service_factory_bindings_;
std::unique_ptr<service_manager::ServiceContext> service_context_;
DISALLOW_COPY_AND_ASSIGN(ImeServiceTestClient);
};
class ImeServiceTest : public service_manager::test::ServiceTest {
public:
ImeServiceTest() : service_manager::test::ServiceTest(kTestServiceName) {}
~ImeServiceTest() override {}
MOCK_METHOD1(SentTextCallback, void(const std::string&));
MOCK_METHOD1(SentMessageCallback, void(const std::vector<uint8_t>&));
protected:
void SetUp() override {
ServiceTest::SetUp();
connector()->BindInterface(mojom::kServiceName,
mojo::MakeRequest(&ime_manager_));
// TODO(https://crbug.com/837156): Start or bind other services used.
// Eg. connector()->StartService(mojom::kSomeServiceName);
}
// service_manager::test::ServiceTest
std::unique_ptr<service_manager::Service> CreateService() override {
return std::make_unique<ImeServiceTestClient>(this);
}
void TearDown() override {
ime_manager_.reset();
ServiceTest::TearDown();
}
mojom::InputEngineManagerPtr ime_manager_;
private:
DISALLOW_COPY_AND_ASSIGN(ImeServiceTest);
};
} // namespace
// Tests that the service is instantiated and it will return false when
// activating an IME engine with an invalid IME spec.
TEST_F(ImeServiceTest, ConnectInvalidImeEngine) {
bool success = true;
TestClientChannel test_channel;
mojom::InputChannelPtr to_engine_ptr;
ime_manager_->ConnectToImeEngine(
kInvalidImeSpec, mojo::MakeRequest(&to_engine_ptr),
test_channel.CreateInterfacePtrAndBind(), extra,
base::BindOnce(&ConnectCallback, &success));
ime_manager_.FlushForTesting();
EXPECT_FALSE(success);
}
TEST_F(ImeServiceTest, MultipleClients) {
bool success = false;
TestClientChannel test_channel1;
TestClientChannel test_channel2;
mojom::InputChannelPtr to_engine_ptr1;
mojom::InputChannelPtr to_engine_ptr2;
ime_manager_->ConnectToImeEngine(
"m17n:ar", mojo::MakeRequest(&to_engine_ptr1),
test_channel1.CreateInterfacePtrAndBind(), extra,
base::BindOnce(&ConnectCallback, &success));
ime_manager_.FlushForTesting();
ime_manager_->ConnectToImeEngine(
"m17n:ar", mojo::MakeRequest(&to_engine_ptr2),
test_channel2.CreateInterfacePtrAndBind(), extra,
base::BindOnce(&ConnectCallback, &success));
ime_manager_.FlushForTesting();
std::string response;
std::string process_text_key =
"{\"method\":\"keyEvent\",\"type\":\"keydown\""
",\"code\":\"KeyA\",\"shift\":true,\"altgr\":false,\"caps\":false}";
to_engine_ptr1->ProcessText(
process_text_key, base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr1.FlushForTesting();
to_engine_ptr2->ProcessText(
process_text_key, base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr2.FlushForTesting();
std::string process_text_key_count = "{\"method\":\"countKey\"}";
to_engine_ptr1->ProcessText(
process_text_key_count,
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr1.FlushForTesting();
EXPECT_EQ("1", response);
to_engine_ptr2->ProcessText(
process_text_key_count,
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr2.FlushForTesting();
EXPECT_EQ("1", response);
}
// Tests that the rule-based Arabic keyboard can work correctly.
TEST_F(ImeServiceTest, RuleBasedArabic) {
bool success = false;
TestClientChannel test_channel;
mojom::InputChannelPtr to_engine_ptr;
ime_manager_->ConnectToImeEngine("m17n:ar", mojo::MakeRequest(&to_engine_ptr),
test_channel.CreateInterfacePtrAndBind(),
extra,
base::BindOnce(&ConnectCallback, &success));
ime_manager_.FlushForTesting();
EXPECT_TRUE(success);
// Test Shift+KeyA.
std::string response;
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyA\","
"\"shift\":true,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
const wchar_t* expected_response =
L"{\"result\":true,\"operations\":[{\"method\":\"commitText\","
L"\"arguments\":[\"\u0650\"]}]}";
EXPECT_EQ(base::WideToUTF8(expected_response), response);
// Test KeyB.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyB\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
L"{\"result\":true,\"operations\":[{\"method\":\"commitText\","
L"\"arguments\":[\"\u0644\u0627\"]}]}";
EXPECT_EQ(base::WideToUTF8(expected_response), response);
// Test unhandled key.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"Enter\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
EXPECT_EQ("{\"result\":false}", response);
// Test keyup.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keyup\",\"code\":\"Enter\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
EXPECT_EQ("{\"result\":false}", response);
// Test reset.
to_engine_ptr->ProcessText(
"{\"method\":\"reset\"}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
EXPECT_EQ("{\"result\":true}", response);
// Test invalid request.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\"}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
EXPECT_EQ("{\"result\":false}", response);
}
// Tests that the rule-based DevaPhone keyboard can work correctly.
TEST_F(ImeServiceTest, RuleBasedDevaPhone) {
bool success = false;
TestClientChannel test_channel;
mojom::InputChannelPtr to_engine_ptr;
ime_manager_->ConnectToImeEngine(
"m17n:deva_phone", mojo::MakeRequest(&to_engine_ptr),
test_channel.CreateInterfacePtrAndBind(), extra,
base::BindOnce(&ConnectCallback, &success));
ime_manager_.FlushForTesting();
EXPECT_TRUE(success);
std::string response;
// KeyN.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyN\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
const char* expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"setComposition\","
u8"\"arguments\":[\"\u0928\"]}]}";
EXPECT_EQ(expected_response, response);
// Backspace.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"Backspace\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"setComposition\","
u8"\"arguments\":[\"\"]}]}";
EXPECT_EQ(expected_response, response);
// KeyN + KeyC.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyN\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"KeyC\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"setComposition\","
u8"\"arguments\":[\"\u091e\u094d\u091a\"]}]}";
EXPECT_EQ(expected_response, response);
// Space.
to_engine_ptr->ProcessText(
"{\"method\":\"keyEvent\",\"type\":\"keydown\",\"code\":\"Space\","
"\"shift\":false,\"altgr\":false,\"caps\":false}",
base::BindOnce(&TestProcessTextCallback, &response));
to_engine_ptr.FlushForTesting();
expected_response =
u8"{\"result\":true,\"operations\":[{\"method\":\"commitText\","
u8"\"arguments\":[\"\u091e\u094d\u091a \"]}]}";
EXPECT_EQ(expected_response, response);
}
} // namespace ime
} // namespace chromeos