blob: a6a3de7adbea66394d32befbf8758ad7bd6f5bc4 [file] [log] [blame]
// Copyright 2010-2014, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "session/session.h"
#include <string>
#include <vector>
#include "base/logging.h"
#include "base/system_util.h"
#include "base/util.h"
#include "composer/composer.h"
#include "composer/table.h"
#include "config/config.pb.h"
#include "config/config_handler.h"
#include "converter/conversion_request.h"
#include "converter/converter_mock.h"
#include "converter/segments.h"
#include "data_manager/user_pos_manager.h"
#include "engine/engine_interface.h"
#include "engine/mock_converter_engine.h"
#include "engine/mock_data_engine_factory.h"
#include "rewriter/transliteration_rewriter.h"
#include "session/candidates.pb.h"
#include "session/commands.pb.h"
#include "session/internal/ime_context.h"
#include "session/internal/keymap.h"
#include "session/key_parser.h"
#include "session/request_test_util.h"
#include "session/session_converter_interface.h"
#include "testing/base/public/googletest.h"
#include "testing/base/public/gunit.h"
#include "usage_stats/usage_stats.h"
#include "usage_stats/usage_stats_testing_util.h"
using ::mozc::commands::Request;
using ::mozc::usage_stats::UsageStats;
DECLARE_string(test_tmpdir);
namespace mozc {
class ConverterInterface;
class PredictorInterface;
class SuppressionDictionary;
namespace session {
namespace {
// "あいうえお"
const char kAiueo[] =
"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A";
// " "
const char kFullWidthSpace[] = "\xE3\x80\x80";
// "アイウエオ"
const char kKatakanaAiueo[] =
"\xe3\x82\xa2\xe3\x82\xa4\xe3\x82\xa6\xe3\x82\xa8\xe3\x82\xaa";
// "あ"
const char kHiraganaA[] = "\xE3\x81\x82";
// "a"
const char kFullWidthSmallA[] = "\xEF\xBD\x81";
void SetSendKeyCommandWithKeyString(const string &key_string,
commands::Command *command) {
command->Clear();
command->mutable_input()->set_type(commands::Input::SEND_KEY);
commands::KeyEvent *key = command->mutable_input()->mutable_key();
key->set_key_string(key_string);
}
bool SetSendKeyCommand(const string &key, commands::Command *command) {
command->Clear();
command->mutable_input()->set_type(commands::Input::SEND_KEY);
return KeyParser::ParseKey(key, command->mutable_input()->mutable_key());
}
bool SendKey(const string &key,
Session *session,
commands::Command *command) {
if (!SetSendKeyCommand(key, command)) {
return false;
}
return session->SendKey(command);
}
bool SendKeyWithMode(const string &key,
commands::CompositionMode mode,
Session *session,
commands::Command *command) {
if (!SetSendKeyCommand(key, command)) {
return false;
}
command->mutable_input()->mutable_key()->set_mode(mode);
return session->SendKey(command);
}
bool SendKeyWithModeAndActivated(const string &key,
bool activated,
commands::CompositionMode mode,
Session *session,
commands::Command *command) {
if (!SetSendKeyCommand(key, command)) {
return false;
}
command->mutable_input()->mutable_key()->set_activated(activated);
command->mutable_input()->mutable_key()->set_mode(mode);
return session->SendKey(command);
}
bool TestSendKey(const string &key,
Session *session,
commands::Command *command) {
if (!SetSendKeyCommand(key, command)) {
return false;
}
return session->TestSendKey(command);
}
bool TestSendKeyWithMode(const string &key,
commands::CompositionMode mode,
Session *session,
commands::Command *command) {
if (!SetSendKeyCommand(key, command)) {
return false;
}
command->mutable_input()->mutable_key()->set_mode(mode);
return session->TestSendKey(command);
}
bool TestSendKeyWithModeAndActivated(const string &key,
bool activated,
commands::CompositionMode mode,
Session *session,
commands::Command *command) {
if (!SetSendKeyCommand(key, command)) {
return false;
}
command->mutable_input()->mutable_key()->set_activated(activated);
command->mutable_input()->mutable_key()->set_mode(mode);
return session->TestSendKey(command);
}
bool SendSpecialKey(commands::KeyEvent::SpecialKey special_key,
Session* session,
commands::Command* command) {
command->Clear();
command->mutable_input()->set_type(commands::Input::SEND_KEY);
command->mutable_input()->mutable_key()->set_special_key(special_key);
return session->SendKey(command);
}
void SetSendCommandCommand(commands::SessionCommand::CommandType type,
commands::Command *command) {
command->Clear();
command->mutable_input()->set_type(commands::Input::SEND_COMMAND);
command->mutable_input()->mutable_command()->set_type(type);
}
bool SendCommand(commands::SessionCommand::CommandType type,
Session *session,
commands::Command *command) {
SetSendCommandCommand(type, command);
return session->SendCommand(command);
}
bool InsertCharacterCodeAndString(const char key_code,
const string &key_string,
Session *session,
commands::Command *command) {
command->Clear();
commands::KeyEvent *key_event = command->mutable_input()->mutable_key();
key_event->set_key_code(key_code);
key_event->set_key_string(key_string);
return session->InsertCharacter(command);
}
Segment::Candidate *AddCandidate(const string &key, const string &value,
Segment *segment) {
Segment::Candidate *candidate = segment->add_candidate();
candidate->key = key;
candidate->content_key = key;
candidate->value = value;
return candidate;
}
Segment::Candidate *AddMetaCandidate(const string &key, const string &value,
Segment *segment) {
Segment::Candidate *candidate = segment->add_meta_candidate();
candidate->key = key;
candidate->content_key = key;
candidate->value = value;
return candidate;
}
string GetComposition(const commands::Command &command) {
if (!command.output().has_preedit()) {
return "";
}
string preedit;
for (size_t i = 0; i < command.output().preedit().segment_size(); ++i) {
preedit.append(command.output().preedit().segment(i).value());
}
return preedit;
}
::testing::AssertionResult EnsurePreedit(const string &expected,
const commands::Command &command) {
if (!command.output().has_preedit()) {
return ::testing::AssertionFailure() << "No preedit.";
}
string actual;
for (size_t i = 0; i < command.output().preedit().segment_size(); ++i) {
actual.append(command.output().preedit().segment(i).value());
}
if (expected == actual) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure()
<< "expected: " << expected << ", actual: " << actual;
}
::testing::AssertionResult EnsureSingleSegment(
const string &expected, const commands::Command &command) {
if (!command.output().has_preedit()) {
return ::testing::AssertionFailure() << "No preedit.";
}
if (command.output().preedit().segment_size() != 1) {
return ::testing::AssertionFailure()
<< "Not single segment. segment size: "
<< command.output().preedit().segment_size();
}
const commands::Preedit::Segment &segment =
command.output().preedit().segment(0);
if (!segment.has_value()) {
return ::testing::AssertionFailure() << "No segment value.";
}
const string &actual = segment.value();
if (expected == actual) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure()
<< "expected: " << expected << ", actual: " << actual;
}
::testing::AssertionResult EnsureSingleSegmentAndKey(
const string &expected_value,
const string &expected_key,
const commands::Command &command) {
if (!command.output().has_preedit()) {
return ::testing::AssertionFailure() << "No preedit.";
}
if (command.output().preedit().segment_size() != 1) {
return ::testing::AssertionFailure()
<< "Not single segment. segment size: "
<< command.output().preedit().segment_size();
}
const commands::Preedit::Segment &segment =
command.output().preedit().segment(0);
if (!segment.has_value()) {
return ::testing::AssertionFailure() << "No segment value.";
}
if (!segment.has_key()) {
return ::testing::AssertionFailure() << "No segment key.";
}
const string &actual_value = segment.value();
const string &actual_key = segment.key();
if (expected_value == actual_value && expected_key == actual_key) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure()
<< "expected_value: " << expected_value
<< ", actual_value: " << actual_value
<< ", expected_key: " << expected_key
<< ", actual_key: " << actual_key;
}
::testing::AssertionResult EnsureResult(const string &expected,
const commands::Command &command) {
if (!command.output().has_result()) {
return ::testing::AssertionFailure() << "No result.";
}
if (!command.output().result().has_value()) {
return ::testing::AssertionFailure() << "No result value.";
}
const string &actual = command.output().result().value();
if (expected == actual) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure()
<< "expected: " << expected << ", actual: " << actual;
}
::testing::AssertionResult EnsureResultAndKey(
const string &expected_value,
const string &expected_key,
const commands::Command &command) {
if (!command.output().has_result()) {
return ::testing::AssertionFailure() << "No result.";
}
if (!command.output().result().has_value()) {
return ::testing::AssertionFailure() << "No result value.";
}
if (!command.output().result().has_key()) {
return ::testing::AssertionFailure() << "No result value.";
}
const string &actual_value = command.output().result().value();
const string &actual_key = command.output().result().key();
if (expected_value == actual_value && expected_key == actual_key) {
return ::testing::AssertionSuccess();
}
return ::testing::AssertionFailure()
<< "expected_value: " << expected_value
<< ", actual_value: " << actual_value
<< ", expected_key: " << expected_key
<< ", actual_key: " << actual_key;
}
::testing::AssertionResult TryUndoAndAssertSuccess(Session *session) {
commands::Command command;
session->RequestUndo(&command);
if (!command.output().consumed()) {
return ::testing::AssertionFailure() << "Not consumed.";
}
if (!command.output().has_callback()) {
return ::testing::AssertionFailure() << "No callback.";
}
if (command.output().callback().session_command().type() !=
commands::SessionCommand::UNDO) {
return ::testing::AssertionFailure() <<
"Callback type is not Undo. Actual type: " <<
command.output().callback().session_command().type();
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult TryUndoAndAssertDoNothing(Session *session) {
commands::Command command;
session->RequestUndo(&command);
if (command.output().consumed()) {
return ::testing::AssertionFailure()
<< "Key event is consumed against expectation.";
}
return ::testing::AssertionSuccess();
}
#define EXPECT_PREEDIT(expected, command) \
EXPECT_TRUE(EnsurePreedit(expected, command))
#define EXPECT_SINGLE_SEGMENT(expected, command) \
EXPECT_TRUE(EnsureSingleSegment(expected, command))
#define EXPECT_SINGLE_SEGMENT_AND_KEY(expected_value, expected_key, command) \
EXPECT_TRUE(EnsureSingleSegmentAndKey(expected_value, \
expected_key, command))
#define EXPECT_RESULT(expected, command) \
EXPECT_TRUE(EnsureResult(expected, command))
#define EXPECT_RESULT_AND_KEY(expected_value, expected_key, command) \
EXPECT_TRUE(EnsureResultAndKey(expected_value, expected_key, command))
void SetCaretLocation(const commands::Rectangle rectangle, Session *session) {
commands::Command command;
SetSendCommandCommand(commands::SessionCommand::SEND_CARET_LOCATION,
&command);
command.mutable_input()->mutable_command()->mutable_caret_rectangle()->
CopyFrom(rectangle);
EXPECT_TRUE(session->SendCommand(&command));
}
void SwitchInputFieldType(commands::Context::InputFieldType type,
Session *session) {
commands::Command command;
SetSendCommandCommand(commands::SessionCommand::SWITCH_INPUT_FIELD_TYPE,
&command);
command.mutable_input()->mutable_context()->set_input_field_type(type);
EXPECT_TRUE(session->SendCommand(&command));
EXPECT_EQ(type, session->context().composer().GetInputFieldType());
}
void SwitchInputMode(commands::CompositionMode mode, Session *session) {
commands::Command command;
SetSendCommandCommand(commands::SessionCommand::SWITCH_INPUT_MODE, &command);
command.mutable_input()->mutable_command()->set_composition_mode(mode);
EXPECT_TRUE(session->SendCommand(&command));
}
// since History segments are almost hidden from
class ConverterMockForReset : public ConverterMock {
public:
virtual bool ResetConversion(Segments *segments) const {
reset_conversion_called_ = true;
return true;
}
bool reset_conversion_called() const {
return reset_conversion_called_;
}
void Reset() {
reset_conversion_called_ = false;
}
ConverterMockForReset() : reset_conversion_called_(false) {}
private:
mutable bool reset_conversion_called_;
};
class MockConverterEngineForReset : public EngineInterface {
public:
MockConverterEngineForReset() : converter_mock_(new ConverterMockForReset) {}
virtual ~MockConverterEngineForReset() {}
virtual ConverterInterface *GetConverter() const {
return converter_mock_.get();
}
virtual PredictorInterface *GetPredictor() const {
return NULL;
}
virtual SuppressionDictionary *GetSuppressionDictionary() {
return NULL;
}
virtual bool Reload() {
return true;
}
virtual UserDataManagerInterface *GetUserDataManager() {
return NULL;
}
const ConverterMockForReset &converter_mock() const {
return *converter_mock_;
}
ConverterMockForReset *mutable_converter_mock() {
return converter_mock_.get();
}
private:
scoped_ptr<ConverterMockForReset> converter_mock_;
};
class ConverterMockForRevert : public ConverterMock {
public:
virtual bool RevertConversion(Segments *segments) const {
revert_conversion_called_ = true;
return true;
}
bool revert_conversion_called() const {
return revert_conversion_called_;
}
void Reset() {
revert_conversion_called_ = false;
}
ConverterMockForRevert() : revert_conversion_called_(false) {}
private:
mutable bool revert_conversion_called_;
};
class MockConverterEngineForRevert : public EngineInterface {
public:
MockConverterEngineForRevert()
: converter_mock_(new ConverterMockForRevert) {}
virtual ~MockConverterEngineForRevert() {}
virtual ConverterInterface *GetConverter() const {
return converter_mock_.get();
}
virtual PredictorInterface *GetPredictor() const {
return NULL;
}
virtual SuppressionDictionary *GetSuppressionDictionary() {
return NULL;
}
virtual bool Reload() {
return true;
}
virtual UserDataManagerInterface *GetUserDataManager() {
return NULL;
}
const ConverterMockForRevert &converter_mock() const {
return *converter_mock_;
}
ConverterMockForRevert *mutable_converter_mock() {
return converter_mock_.get();
}
private:
scoped_ptr<ConverterMockForRevert> converter_mock_;
};
} // namespace
class SessionTest : public testing::Test {
protected:
virtual void SetUp() {
SystemUtil::SetUserProfileDirectory(FLAGS_test_tmpdir);
config::Config config;
config::ConfigHandler::GetDefaultConfig(&config);
config::ConfigHandler::SetConfig(config);
UsageStats::ClearAllStatsForTest();
mobile_request_.reset(new Request);
commands::RequestForUnitTest::FillMobileRequest(mobile_request_.get());
mock_data_engine_.reset(MockDataEngineFactory::Create());
engine_.reset(new MockConverterEngine);
t13n_rewriter_.reset(
new TransliterationRewriter(
*UserPosManager::GetUserPosManager()->GetPOSMatcher()));
}
virtual void TearDown() {
UsageStats::ClearAllStatsForTest();
// just in case, reset the config in test_tmpdir
config::Config config;
config::ConfigHandler::GetDefaultConfig(&config);
config::ConfigHandler::SetConfig(config);
}
void InsertCharacterChars(const string &chars,
Session *session,
commands::Command *command) const {
const uint32 kNoModifiers = 0;
for (int i = 0; i < chars.size(); ++i) {
command->Clear();
commands::KeyEvent *key_event = command->mutable_input()->mutable_key();
key_event->set_key_code(chars[i]);
key_event->set_modifiers(kNoModifiers);
session->InsertCharacter(command);
}
}
void InsertCharacterCharsWithContext(const string &chars,
const commands::Context &context,
Session *session,
commands::Command *command) const {
const uint32 kNoModifiers = 0;
for (size_t i = 0; i < chars.size(); ++i) {
command->Clear();
command->mutable_input()->mutable_context()->CopyFrom(context);
commands::KeyEvent *key_event = command->mutable_input()->mutable_key();
key_event->set_key_code(chars[i]);
key_event->set_modifiers(kNoModifiers);
session->InsertCharacter(command);
}
}
void InsertCharacterString(const string &key_strings,
const string &chars,
Session *session,
commands::Command *command) const {
const uint32 kNoModifiers = 0;
vector<string> inputs;
const char *begin = key_strings.data();
const char *end = key_strings.data() + key_strings.size();
while (begin < end) {
const size_t mblen = Util::OneCharLen(begin);
inputs.push_back(string(begin, mblen));
begin += mblen;
}
CHECK_EQ(inputs.size(), chars.size());
for (int i = 0; i < chars.size(); ++i) {
command->Clear();
commands::KeyEvent *key_event = command->mutable_input()->mutable_key();
key_event->set_key_code(chars[i]);
key_event->set_modifiers(kNoModifiers);
key_event->set_key_string(inputs[i]);
session->InsertCharacter(command);
}
}
// set result for "あいうえお"
void SetAiueo(Segments *segments) {
segments->Clear();
Segment *segment;
Segment::Candidate *candidate;
segment = segments->add_segment();
// "あいうえお"
segment->set_key(kAiueo);
candidate = segment->add_candidate();
// "あいうえお"
candidate->key = kAiueo;
candidate->content_key = kAiueo;
candidate->value = kAiueo;
candidate = segment->add_candidate();
// "アイウエオ"
candidate->key = kAiueo;
candidate->content_key = kAiueo;
candidate->value = kKatakanaAiueo;
}
void InitSessionToDirect(Session* session) {
InitSessionToPrecomposition(session);
commands::Command command;
session->IMEOff(&command);
}
void InitSessionToConversionWithAiueo(Session *session) {
InitSessionToPrecomposition(session);
commands::Command command;
InsertCharacterChars("aiueo", session, &command);
ConversionRequest request;
Segments segments;
SetComposer(session, &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
EXPECT_TRUE(session->Convert(&command));
EXPECT_EQ(ImeContext::CONVERSION, session->context().state());
}
void InitSessionToPrecomposition(Session* session) {
#ifdef OS_WIN
// Session is created with direct mode on Windows
// Direct status
commands::Command command;
session->IMEOn(&command);
#endif // OS_WIN
InitSessionWithRequest(session, commands::Request::default_instance());
}
void InitSessionToPrecomposition(
Session* session,
const commands::Request &request) {
#ifdef OS_WIN
// Session is created with direct mode on Windows
// Direct status
commands::Command command;
session->IMEOn(&command);
#endif // OS_WIN
InitSessionWithRequest(session, request);
}
void InitSessionWithRequest(
Session* session,
const commands::Request &request) {
session->SetRequest(&request);
table_.reset(new composer::Table());
table_->InitializeWithRequestAndConfig(
request, config::ConfigHandler::GetConfig());
session->SetTable(table_.get());
}
// set result for "like"
void SetLike(Segments *segments) {
Segment *segment;
Segment::Candidate *candidate;
segments->Clear();
segment = segments->add_segment();
// "ぃ"
segment->set_key("\xE3\x81\x83");
candidate = segment->add_candidate();
// "ぃ"
candidate->value = "\xE3\x81\x83";
candidate = segment->add_candidate();
// "ィ"
candidate->value = "\xE3\x82\xA3";
segment = segments->add_segment();
// "け"
segment->set_key("\xE3\x81\x91");
candidate = segment->add_candidate();
// "家"
candidate->value = "\xE5\xAE\xB6";
candidate = segment->add_candidate();
// "け"
candidate->value = "\xE3\x81\x91";
}
void FillT13Ns(const ConversionRequest &request, Segments *segments) {
t13n_rewriter_->Rewrite(request, segments);
}
void SetComposer(Session *session, ConversionRequest *request) {
DCHECK(request);
request->set_composer(session->get_internal_composer_only_for_unittest());
}
void SetupMockForReverseConversion(const string &kanji,
const string &hiragana) {
// Set up Segments for reverse conversion.
Segments reverse_segments;
Segment *segment;
segment = reverse_segments.add_segment();
segment->set_key(kanji);
Segment::Candidate *candidate;
candidate = segment->add_candidate();
// For reverse conversion, key is the original kanji string.
candidate->key = kanji;
candidate->value = hiragana;
GetConverterMock()->SetStartReverseConversion(&reverse_segments, true);
// Set up Segments for forward conversion.
Segments segments;
segment = segments.add_segment();
segment->set_key(hiragana);
candidate = segment->add_candidate();
candidate->key = hiragana;
candidate->value = kanji;
GetConverterMock()->SetStartConversionForRequest(&segments, true);
}
void SetupCommandForReverseConversion(const string &text,
commands::Input *input) {
input->Clear();
input->set_type(commands::Input::SEND_COMMAND);
input->mutable_command()->set_type(
commands::SessionCommand::CONVERT_REVERSE);
input->mutable_command()->set_text(text);
}
void SetupZeroQuerySuggestionReady(bool enable,
Session *session,
commands::Request *request) {
InitSessionToPrecomposition(session);
// Enable zero query suggest.
request->set_zero_query_suggestion(enable);
session->SetRequest(request);
// Type "google".
commands::Command command;
InsertCharacterChars("google", session, &command);
{
// Set up a mock conversion result.
Segments segments;
segments.set_request_type(Segments::CONVERSION);
Segment *segment;
segment = segments.add_segment();
segment->set_key("google");
segment->add_candidate()->value = "GOOGLE";
GetConverterMock()->SetStartConversionForRequest(&segments, true);
}
command.Clear();
session->Convert(&command);
{
// Set up a mock suggestion result.
Segments segments;
segments.set_request_type(Segments::SUGGESTION);
Segment *segment;
segment = segments.add_segment();
segment->set_key("");
AddCandidate("search", "search", segment);
AddCandidate("input", "input", segment);
GetConverterMock()->SetStartSuggestionForRequest(&segments, true);
}
}
void SetupZeroQuerySuggestion(Session *session,
commands::Request *request,
commands::Command *command) {
SetupZeroQuerySuggestionReady(true, session, request);
command->Clear();
session->Commit(command);
}
void SetUndoContext(Session *session) {
commands::Command command;
Segments segments;
{ // Create segments
InsertCharacterChars("aiueo", session, &command);
SetAiueo(&segments);
// Don't use FillT13Ns(). It makes platform dependent segments.
// TODO(hsumita): Makes FillT13Ns() independent from platforms.
Segment::Candidate *candidate;
candidate = segments.mutable_segment(0)->add_candidate();
candidate->value = "aiueo";
candidate = segments.mutable_segment(0)->add_candidate();
candidate->value = "AIUEO";
}
{ // Commit the composition to make an undo context.
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
EXPECT_FALSE(command.output().has_result());
// "あいうえお"
EXPECT_PREEDIT(kAiueo, command);
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->Commit(&command);
EXPECT_FALSE(command.output().has_preedit());
// "あいうえお"
EXPECT_RESULT(kAiueo, command);
}
}
ConverterMock *GetConverterMock() {
return engine_->mutable_converter_mock();
}
// IMPORTANT: Use scoped_ptr and instanciate an object in SetUp() method
// if the target object should be initialized *AFTER* global settings
// such as user profile dir or global config are set up for unit test.
// If you directly define a variable here without scoped_ptr, its
// constructor will be called *BEFORE* SetUp() is called.
scoped_ptr<MockConverterEngine> engine_;
scoped_ptr<EngineInterface> mock_data_engine_;
scoped_ptr<TransliterationRewriter> t13n_rewriter_;
scoped_ptr<composer::Table> table_;
scoped_ptr<Request> mobile_request_;
mozc::usage_stats::scoped_usage_stats_enabler usage_stats_enabler_;
};
// This test is intentionally defined at this location so that this
// test can ensure that the first SetUp() initialized global
// config, and table object to the default state.
// Please do not define another test before this.
// FYI, each TEST_F will be eventually expanded into a global variable
// and global variables in a single translation unit (source file) are
// always initialized in the order in which they are defined.
TEST_F(SessionTest, TestOfTestForSetup) {
config::Config config;
config::ConfigHandler::GetConfig(&config);
EXPECT_FALSE(config.has_use_auto_conversion())
<< "Global config should be initialized for each text fixture.";
// Make sure that the default roman table is initialized.
{
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
SendKey("a", session.get(), &command);
// "あ"
EXPECT_SINGLE_SEGMENT(kHiraganaA, command)
<< "Global Romaji table should be initialized for each text fixture.";
}
// intentionally leave non-default value so that |TestOfTestForTearDown|
// can test it later.
config.set_use_auto_conversion(true);
config::ConfigHandler::SetConfig(config);
}
// This test ensures that the TearDown() against |TestOfTestForSetup|
// restored global config, and table object to the default state
// Please do not define another test between |TestOfTestForSetup| and
// this test.
// FYI, each TEST_F will be eventually expanded into a global variable
// and global variables in a single translation unit (source file) are
// always initialized in the order in which they are defined.
TEST_F(SessionTest, TestOfTestForTearDown) {
// Make sure that the initial global config has default value.
config::Config config;
config::ConfigHandler::GetConfig(&config);
EXPECT_FALSE(config.has_use_auto_conversion())
<< "Global config should be initialized for each text fixture.";
// Make sure that the initial roman table has default value.
{
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
SendKey("a", session.get(), &command);
// "あ"
EXPECT_SINGLE_SEGMENT(kHiraganaA, command)
<< "Global Romaji table should be initialized for each text fixture.";
}
}
TEST_F(SessionTest, TestSendKey) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
// Precomposition status
TestSendKey("Up", session.get(), &command);
EXPECT_FALSE(command.output().consumed());
SendKey("Up", session.get(), &command);
EXPECT_FALSE(command.output().consumed());
// InsertSpace on Precomposition status
// TODO(komatsu): Test both cases of GET_CONFIG(ascii_character_form) is
// FULL_WIDTH and HALF_WIDTH after dependency injection of GET_CONFIG.
TestSendKey("Space", session.get(), &command);
const bool consumed_on_testsendkey = command.output().consumed();
SendKey("Space", session.get(), &command);
const bool consumed_on_sendkey = command.output().consumed();
EXPECT_EQ(consumed_on_sendkey, consumed_on_testsendkey);
// Precomposition status
TestSendKey("G", session.get(), &command);
EXPECT_TRUE(command.output().consumed());
SendKey("G", session.get(), &command);
EXPECT_TRUE(command.output().consumed());
// Composition status
TestSendKey("Up", session.get(), &command);
EXPECT_TRUE(command.output().consumed());
SendKey("Up", session.get(), &command);
EXPECT_TRUE(command.output().consumed());
}
TEST_F(SessionTest, SendCommand) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
command.mutable_input()->set_type(commands::Input::SEND_COMMAND);
InsertCharacterChars("kanji", session.get(), &command);
// REVERT
SendCommand(commands::SessionCommand::REVERT, session.get(), &command);
EXPECT_TRUE(command.output().consumed());
EXPECT_FALSE(command.output().has_result());
EXPECT_FALSE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_candidates());
// SUBMIT
InsertCharacterChars("k", session.get(), &command);
SendCommand(commands::SessionCommand::SUBMIT, session.get(), &command);
EXPECT_TRUE(command.output().consumed());
// "k"
EXPECT_RESULT("\xef\xbd\x8b", command);
EXPECT_FALSE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_candidates());
// SWITCH_INPUT_MODE
SendKey("a", session.get(), &command);
EXPECT_SINGLE_SEGMENT(kHiraganaA, command);
SwitchInputMode(commands::FULL_ASCII, session.get());
SendKey("a", session.get(), &command);
// "あa"
EXPECT_SINGLE_SEGMENT("\xE3\x81\x82\xEF\xBD\x81", command);
// GET_STATUS
SendCommand(commands::SessionCommand::GET_STATUS, session.get(), &command);
// FULL_ASCII was set at the SWITCH_INPUT_MODE testcase.
SwitchInputMode(commands::FULL_ASCII, session.get());
// RESET_CONTEXT
// test of reverting composition
InsertCharacterChars("kanji", session.get(), &command);
SendCommand(commands::SessionCommand::RESET_CONTEXT, session.get(), &command);
EXPECT_TRUE(command.output().consumed());
EXPECT_FALSE(command.output().has_result());
EXPECT_FALSE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_candidates());
// test of reseting the history segements
scoped_ptr<MockConverterEngineForReset> engine(
new MockConverterEngineForReset);
session.reset(new Session(engine.get()));
InitSessionToPrecomposition(session.get());
SendCommand(commands::SessionCommand::RESET_CONTEXT, session.get(), &command);
EXPECT_FALSE(command.output().consumed());
EXPECT_TRUE(engine->converter_mock().reset_conversion_called());
// USAGE_STATS_EVENT
SendCommand(commands::SessionCommand::USAGE_STATS_EVENT, session.get(),
&command);
EXPECT_TRUE(command.output().has_consumed());
EXPECT_FALSE(command.output().consumed());
}
TEST_F(SessionTest, SwitchInputMode) {
{
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
// SWITCH_INPUT_MODE
SendKey("a", session.get(), &command);
EXPECT_SINGLE_SEGMENT(kHiraganaA, command);
SwitchInputMode(commands::FULL_ASCII, session.get());
SendKey("a", session.get(), &command);
// "あa"
EXPECT_SINGLE_SEGMENT("\xE3\x81\x82\xEF\xBD\x81", command);
// GET_STATUS
SendCommand(commands::SessionCommand::GET_STATUS, session.get(), &command);
// FULL_ASCII was set at the SWITCH_INPUT_MODE testcase.
EXPECT_EQ(commands::FULL_ASCII, command.output().mode());
}
{
// Confirm that we can change the mode from DIRECT
// to other modes directly (without IMEOn command).
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToDirect(session.get());
commands::Command command;
// GET_STATUS
SendCommand(commands::SessionCommand::GET_STATUS, session.get(), &command);
// FULL_ASCII was set at the SWITCH_INPUT_MODE testcase.
EXPECT_EQ(commands::DIRECT, command.output().mode());
// SWITCH_INPUT_MODE
SwitchInputMode(commands::HIRAGANA, session.get());
// GET_STATUS
SendCommand(commands::SessionCommand::GET_STATUS, session.get(), &command);
// FULL_ASCII was set at the SWITCH_INPUT_MODE testcase.
EXPECT_EQ(commands::HIRAGANA, command.output().mode());
SendKey("a", session.get(), &command);
// "あ"
EXPECT_SINGLE_SEGMENT(kHiraganaA, command);
// GET_STATUS
SendCommand(commands::SessionCommand::GET_STATUS, session.get(), &command);
// FULL_ASCII was set at the SWITCH_INPUT_MODE testcase.
EXPECT_EQ(commands::HIRAGANA, command.output().mode());
}
}
TEST_F(SessionTest, RevertComposition) {
// Issue#2237323
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
Segments segments;
SetComposer(session.get(), &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
// REVERT
SendCommand(commands::SessionCommand::REVERT, session.get(), &command);
EXPECT_TRUE(command.output().consumed());
EXPECT_FALSE(command.output().has_result());
EXPECT_FALSE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_candidates());
SendKey("a", session.get(), &command);
EXPECT_SINGLE_SEGMENT(kHiraganaA, command);
}
TEST_F(SessionTest, InputMode) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
EXPECT_TRUE(session->InputModeHalfASCII(&command));
EXPECT_TRUE(command.output().consumed());
EXPECT_EQ(mozc::commands::HALF_ASCII, command.output().mode());
SendKey("a", session.get(), &command);
EXPECT_EQ("a", command.output().preedit().segment(0).key());
command.Clear();
session->Commit(&command);
// Input mode remains even after submission.
command.Clear();
session->GetStatus(&command);
EXPECT_EQ(mozc::commands::HALF_ASCII, command.output().mode());
}
TEST_F(SessionTest, SelectCandidate) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
Segments segments;
SetComposer(session.get(), &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
command.Clear();
session->ConvertNext(&command);
SetSendCommandCommand(commands::SessionCommand::SELECT_CANDIDATE, &command);
command.mutable_input()->mutable_command()->set_id(
-(transliteration::HALF_KATAKANA + 1));
session->SendCommand(&command);
EXPECT_TRUE(command.output().consumed());
EXPECT_FALSE(command.output().has_result());
// "アイウエオ"
EXPECT_PREEDIT(
"\xEF\xBD\xB1\xEF\xBD\xB2\xEF\xBD\xB3\xEF\xBD\xB4\xEF\xBD\xB5", command);
EXPECT_FALSE(command.output().has_candidates());
}
TEST_F(SessionTest, HighlightCandidate) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
Segments segments;
SetComposer(session.get(), &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
command.Clear();
session->ConvertNext(&command);
// "アイウエオ"
EXPECT_SINGLE_SEGMENT(
"\xE3\x82\xA2\xE3\x82\xA4\xE3\x82\xA6\xE3\x82\xA8\xE3\x82\xAA", command);
SetSendCommandCommand(commands::SessionCommand::HIGHLIGHT_CANDIDATE,
&command);
command.mutable_input()->mutable_command()->set_id(
-(transliteration::HALF_KATAKANA + 1));
session->SendCommand(&command);
EXPECT_TRUE(command.output().consumed());
EXPECT_FALSE(command.output().has_result());
// "アイウエオ"
EXPECT_SINGLE_SEGMENT(
"\xEF\xBD\xB1\xEF\xBD\xB2\xEF\xBD\xB3\xEF\xBD\xB4\xEF\xBD\xB5", command);
EXPECT_TRUE(command.output().has_candidates());
}
TEST_F(SessionTest, Conversion) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
Segments segments;
SetComposer(session.get(), &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
// "あいうえお"
EXPECT_SINGLE_SEGMENT_AND_KEY(kAiueo, kAiueo, command);
command.Clear();
session->Convert(&command);
command.Clear();
session->ConvertNext(&command);
string key;
for (int i = 0; i < command.output().preedit().segment_size(); ++i) {
EXPECT_TRUE(command.output().preedit().segment(i).has_value());
EXPECT_TRUE(command.output().preedit().segment(i).has_key());
key += command.output().preedit().segment(i).key();
}
// "あいうえお"
EXPECT_EQ(kAiueo, key);
}
TEST_F(SessionTest, SegmentWidthShrink) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
Segments segments;
SetComposer(session.get(), &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
command.Clear();
session->SegmentWidthShrink(&command);
command.Clear();
session->SegmentWidthShrink(&command);
}
TEST_F(SessionTest, ConvertPrev) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
Segments segments;
SetComposer(session.get(), &request);
SetAiueo(&segments);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
command.Clear();
session->ConvertNext(&command);
command.Clear();
session->ConvertPrev(&command);
command.Clear();
session->ConvertPrev(&command);
}
TEST_F(SessionTest, ResetFocusedSegmentAfterCommit) {
ConversionRequest request;
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("watasinonamaehanakanodesu", session.get(), &command);
// "わたしのなまえはなかのです[]"
segment = segments.add_segment();
// "わたしの"
segment->set_key("\xe3\x82\x8f\xe3\x81\x9f\xe3\x81\x97\xe3\x81\xae");
candidate = segment->add_candidate();
// "私の"
candidate->value = "\xe7\xa7\x81\xe3\x81\xae";
candidate = segment->add_candidate();
// "わたしの"
candidate->value = "\xe3\x82\x8f\xe3\x81\x9f\xe3\x81\x97\xe3\x81\xae";
candidate = segment->add_candidate();
// "渡しの"
candidate->value = "\xe6\xb8\xa1\xe3\x81\x97\xe3\x81\xae";
segment = segments.add_segment();
// "なまえは"
segment->set_key("\xe3\x81\xaa\xe3\x81\xbe\xe3\x81\x88\xe3\x81\xaf");
candidate = segment->add_candidate();
// "名前は"
candidate->value = "\xe5\x90\x8d\xe5\x89\x8d\xe3\x81\xaf";
candidate = segment->add_candidate();
// "ナマエは"
candidate->value = "\xe3\x83\x8a\xe3\x83\x9e\xe3\x82\xa8\xe3\x81\xaf";
segment = segments.add_segment();
// "なかのです"
segment->set_key(
"\xe3\x81\xaa\xe3\x81\x8b\xe3\x81\xae\xe3\x81\xa7\xe3\x81\x99");
candidate = segment->add_candidate();
// "中野です"
candidate->value = "\xe4\xb8\xad\xe9\x87\x8e\xe3\x81\xa7\xe3\x81\x99";
candidate = segment->add_candidate();
// "なかのです"
candidate->value
= "\xe3\x81\xaa\xe3\x81\x8b\xe3\x81\xae\xe3\x81\xa7\xe3\x81\x99";
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
EXPECT_TRUE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_result());
// "[私の]名前は中野です"
command.Clear();
session->SegmentFocusRight(&command);
EXPECT_TRUE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_result());
// "私の[名前は]中野です"
command.Clear();
session->SegmentFocusRight(&command);
EXPECT_TRUE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_result());
// "私の名前は[中野です]"
command.Clear();
session->ConvertNext(&command);
EXPECT_EQ(1, command.output().candidates().focused_index());
EXPECT_TRUE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_result());
// "私の名前は[中のです]"
command.Clear();
session->ConvertNext(&command);
EXPECT_EQ(2, command.output().candidates().focused_index());
EXPECT_TRUE(command.output().has_preedit());
EXPECT_FALSE(command.output().has_result());
// "私の名前は[なかのです]"
command.Clear();
session->Commit(&command);
EXPECT_FALSE(command.output().has_preedit());
EXPECT_TRUE(command.output().has_result());
// "私の名前はなかのです[]"
InsertCharacterChars("a", session.get(), &command);
segments.Clear();
segment = segments.add_segment();
// "あ"
segment->set_key(kHiraganaA);
candidate = segment->add_candidate();
// "阿"
candidate->value = "\xe9\x98\xbf";
candidate = segment->add_candidate();
// "亜"
candidate->value = "\xe4\xba\x9c";
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
// "あ[]"
command.Clear();
session->Convert(&command);
// "[阿]"
command.Clear();
// If the forcused_segment_ was not reset, this raises segmentation fault.
session->ConvertNext(&command);
// "[亜]"
}
TEST_F(SessionTest, ResetFocusedSegmentAfterCancel) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("ai", session.get(), &command);
segment = segments.add_segment();
// "あい"
segment->set_key("\xe3\x81\x82\xe3\x81\x84");
candidate = segment->add_candidate();
// "愛"
candidate->value = "\xe6\x84\x9b";
candidate = segment->add_candidate();
// "相"
candidate->value = "\xe7\x9b\xb8";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
// "あい[]"
command.Clear();
session->Convert(&command);
// "[愛]"
segments.Clear();
segment = segments.add_segment();
// "あ"
segment->set_key(kHiraganaA);
candidate = segment->add_candidate();
// "あ"
candidate->value = kHiraganaA;
segment = segments.add_segment();
// "い"
segment->set_key("\xe3\x81\x84");
candidate = segment->add_candidate();
// "い"
candidate->value = "\xe3\x81\x84";
candidate = segment->add_candidate();
// "位"
candidate->value = "\xe4\xbd\x8d";
GetConverterMock()->SetResizeSegment1(&segments, true);
command.Clear();
session->SegmentWidthShrink(&command);
// "[あ]い"
segment = segments.mutable_segment(0);
segment->set_segment_type(Segment::FIXED_VALUE);
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->SegmentFocusRight(&command);
// "あ[い]"
command.Clear();
session->ConvertNext(&command);
// "あ[位]"
command.Clear();
session->ConvertCancel(&command);
// "あい[]"
segments.Clear();
segment = segments.add_segment();
// "あい"
segment->set_key("\xe3\x81\x82\xe3\x81\x84");
candidate = segment->add_candidate();
// "愛"
candidate->value = "\xe6\x84\x9b";
candidate = segment->add_candidate();
// "相"
candidate->value = "\xe7\x9b\xb8";
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
// "[愛]"
command.Clear();
// If the forcused_segment_ was not reset, this raises segmentation fault.
session->Convert(&command);
// "[相]"
}
TEST_F(SessionTest, KeepFixedCandidateAfterSegmentWidthExpand) {
// Issue#1271099
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("bariniryokouniitta", session.get(), &command);
// "ばりにりょこうにいった[]"
segment = segments.add_segment();
// "ばりに"
segment->set_key("\xe3\x81\xb0\xe3\x82\x8a\xe3\x81\xab");
candidate = segment->add_candidate();
// "バリに"
candidate->value = "\xe3\x83\x90\xe3\x83\xaa\xe3\x81\xab";
candidate = segment->add_candidate();
// "針に"
candidate->value = "\xe9\x87\x9d\xe3\x81\xab";
segment = segments.add_segment();
// "りょこうに"
segment->set_key(
"\xe3\x82\x8a\xe3\x82\x87\xe3\x81\x93\xe3\x81\x86\xe3\x81\xab");
candidate = segment->add_candidate();
// "旅行に"
candidate->value = "\xe6\x97\x85\xe8\xa1\x8c\xe3\x81\xab";
segment = segments.add_segment();
// "いった"
segment->set_key("\xe3\x81\x84\xe3\x81\xa3\xe3\x81\x9f");
candidate = segment->add_candidate();
// "行った"
candidate->value = "\xe8\xa1\x8c\xe3\x81\xa3\xe3\x81\x9f";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
// ex. "[バリに]旅行に行った"
EXPECT_EQ("\xE3\x83\x90\xE3\x83\xAA\xE3\x81\xAB\xE6\x97\x85\xE8\xA1\x8C\xE3"
"\x81\xAB\xE8\xA1\x8C\xE3\x81\xA3\xE3\x81\x9F", GetComposition(command));
command.Clear();
session->ConvertNext(&command);
// ex. "[針に]旅行に行った"
const string first_segment = command.output().preedit().segment(0).value();
segment = segments.mutable_segment(0);
segment->set_segment_type(Segment::FIXED_VALUE);
segment->move_candidate(1, 0);
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->SegmentFocusRight(&command);
// ex. "針に[旅行に]行った"
// Make sure the first segment (i.e. "針に" in the above case) remains
// after moving the focused segment right.
EXPECT_EQ(first_segment, command.output().preedit().segment(0).value());
segment = segments.mutable_segment(1);
// "りょこうにい"
segment->set_key("\xe3\x82\x8a\xe3\x82\x87\xe3\x81\x93"
"\xe3\x81\x86\xe3\x81\xab\xe3\x81\x84");
candidate = segment->mutable_candidate(0);
// "旅行に行"
candidate->value = "\xe6\x97\x85\xe8\xa1\x8c\xe3\x81\xab\xe8\xa1\x8c";
segment = segments.mutable_segment(2);
// "った"
segment->set_key("\xe3\x81\xa3\xe3\x81\x9f");
candidate = segment->mutable_candidate(0);
// "った"
candidate->value = "\xe3\x81\xa3\xe3\x81\x9f";
GetConverterMock()->SetResizeSegment1(&segments, true);
command.Clear();
session->SegmentWidthExpand(&command);
// ex. "針に[旅行に行]った"
// Make sure the first segment (i.e. "針に" in the above case) remains
// after expanding the focused segment.
EXPECT_EQ(first_segment, command.output().preedit().segment(0).value());
}
TEST_F(SessionTest, CommitSegment) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
// Issue#1560608
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("watasinonamae", session.get(), &command);
// "わたしのなまえ[]"
segment = segments.add_segment();
// "わたしの"
segment->set_key("\xe3\x82\x8f\xe3\x81\x9f\xe3\x81\x97\xe3\x81\xae");
candidate = segment->add_candidate();
// "私の"
candidate->value = "\xe7\xa7\x81\xe3\x81\xae";
candidate = segment->add_candidate();
// "わたしの"
candidate->value = "\xe3\x82\x8f\xe3\x81\x9f\xe3\x81\x97\xe3\x81\xae";
candidate = segment->add_candidate();
// "渡しの"
candidate->value = "\xe6\xb8\xa1\xe3\x81\x97\xe3\x81\xae";
segment = segments.add_segment();
// "なまえ"
segment->set_key("\xe3\x81\xaa\xe3\x81\xbe\xe3\x81\x88");
candidate = segment->add_candidate();
// "名前"
candidate->value = "\xe5\x90\x8d\xe5\x89\x8d";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
// "[私の]名前"
EXPECT_EQ(0, command.output().candidates().focused_index());
command.Clear();
session->ConvertNext(&command);
// "[わたしの]名前"
EXPECT_EQ(1, command.output().candidates().focused_index());
command.Clear();
session->ConvertNext(&command);
// "[渡しの]名前" showing a candidate window
EXPECT_EQ(2, command.output().candidates().focused_index());
segment = segments.mutable_segment(0);
segment->set_segment_type(Segment::FIXED_VALUE);
segment->move_candidate(2, 0);
GetConverterMock()->SetCommitSegments(&segments, true);
command.Clear();
session->CommitSegment(&command);
// "渡しの" + "[名前]"
EXPECT_EQ(0, command.output().candidates().focused_index());
}
TEST_F(SessionTest, CommitSegmentAt2ndSegment) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("watasinohaha", session.get(), &command);
// "わたしのはは[]"
segment = segments.add_segment();
// "わたしの"
segment->set_key("\xe3\x82\x8f\xe3\x81\x9f\xe3\x81\x97\xe3\x81\xae");
candidate = segment->add_candidate();
// "私の"
candidate->value = "\xe7\xa7\x81\xe3\x81\xae";
segment = segments.add_segment();
// "はは"
segment->set_key("\xe3\x81\xaf\xe3\x81\xaf");
candidate = segment->add_candidate();
// "母"
candidate->value = "\xe6\xaf\x8d";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
// "[私の]母"
command.Clear();
session->SegmentFocusRight(&command);
// "私の[母]"
segment->set_segment_type(Segment::FIXED_VALUE);
segment->move_candidate(1, 0);
GetConverterMock()->SetCommitSegments(&segments, true);
command.Clear();
session->CommitSegment(&command);
// "私の" + "[母]"
// "は"
segment->set_key("\xe3\x81\xaf");
// "葉"
candidate->value = "\xe8\x91\x89";
segment = segments.add_segment();
// "は"
segment->set_key("\xe3\x81\xaf");
candidate = segment->add_candidate();
// "は"
candidate->value = "\xe3\x81\xaf";
segments.pop_front_segment();
GetConverterMock()->SetResizeSegment1(&segments, true);
command.Clear();
session->SegmentWidthShrink(&command);
// "私の" + "[葉]は"
EXPECT_EQ(2, command.output().preedit().segment_size());
}
TEST_F(SessionTest, Transliterations) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("jishin", session.get(), &command);
segment = segments.add_segment();
// "じしん"
segment->set_key("\xe3\x81\x98\xe3\x81\x97\xe3\x82\x93");
candidate = segment->add_candidate();
// "自信"
candidate->value = "\xe8\x87\xaa\xe4\xbf\xa1";
candidate = segment->add_candidate();
// "自身"
candidate->value = "\xe8\x87\xaa\xe8\xba\xab";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
command.Clear();
session->ConvertNext(&command);
command.Clear();
session->TranslateHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("jishin", command);
command.Clear();
session->TranslateHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("JISHIN", command);
command.Clear();
session->TranslateHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("Jishin", command);
command.Clear();
session->TranslateHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("jishin", command);
}
TEST_F(SessionTest, ConvertToTransliteration) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("jishin", session.get(), &command);
segment = segments.add_segment();
// "じしん"
segment->set_key("\xe3\x81\x98\xe3\x81\x97\xe3\x82\x93");
candidate = segment->add_candidate();
// "自信"
candidate->value = "\xe8\x87\xaa\xe4\xbf\xa1";
candidate = segment->add_candidate();
// "自身"
candidate->value = "\xe8\x87\xaa\xe8\xba\xab";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->ConvertToHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("jishin", command);
command.Clear();
session->ConvertToHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("JISHIN", command);
command.Clear();
session->ConvertToHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("Jishin", command);
command.Clear();
session->ConvertToHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("jishin", command);
}
TEST_F(SessionTest, ConvertToTransliterationWithMultipleSegments) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("like", session.get(), &command);
Segments segments;
SetLike(&segments);
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
// Convert
command.Clear();
session->Convert(&command);
{ // Check the conversion #1
const commands::Output &output = command.output();
EXPECT_FALSE(output.has_result());
EXPECT_TRUE(output.has_preedit());
EXPECT_FALSE(output.has_candidates());
const commands::Preedit &conversion = output.preedit();
EXPECT_EQ(2, conversion.segment_size());
// "ぃ"
EXPECT_EQ("\xE3\x81\x83", conversion.segment(0).value());
// "家"
EXPECT_EQ("\xE5\xAE\xB6", conversion.segment(1).value());
}
// TranslateHalfASCII
command.Clear();
session->TranslateHalfASCII(&command);
{ // Check the conversion #2
const commands::Output &output = command.output();
EXPECT_FALSE(output.has_result());
EXPECT_TRUE(output.has_preedit());
EXPECT_FALSE(output.has_candidates());
const commands::Preedit &conversion = output.preedit();
EXPECT_EQ(2, conversion.segment_size());
EXPECT_EQ("li", conversion.segment(0).value());
}
}
TEST_F(SessionTest, ConvertToHalfWidth) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("abc", session.get(), &command);
Segments segments;
{ // Initialize segments.
Segment *segment = segments.add_segment();
// "あbc"
segment->set_key("\xE3\x81\x82\xEF\xBD\x82\xEF\xBD\x83");
// "あべし"
segment->add_candidate()->value = "\xE3\x81\x82\xE3\x81\xB9\xE3\x81\x97";
}
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->ConvertToHalfWidth(&command);
// "アbc"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\xB1\x62\x63", command);
command.Clear();
session->ConvertToFullASCII(&command);
// The output is "abc".
command.Clear();
session->ConvertToHalfWidth(&command);
EXPECT_SINGLE_SEGMENT("abc", command);
}
TEST_F(SessionTest, ConvertConsonantsToFullAlphanumeric) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("dvd", session.get(), &command);
segment = segments.add_segment();
// "dvd"
segment->set_key("\xEF\xBD\x84\xEF\xBD\x96\xEF\xBD\x84");
candidate = segment->add_candidate();
candidate->value = "DVD";
candidate = segment->add_candidate();
candidate->value = "dvd";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->ConvertToFullASCII(&command);
// "dvd"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\x84\xEF\xBD\x96\xEF\xBD\x84", command);
command.Clear();
session->ConvertToFullASCII(&command);
// "DVD"
EXPECT_SINGLE_SEGMENT("\xEF\xBC\xA4\xEF\xBC\xB6\xEF\xBC\xA4", command);
command.Clear();
session->ConvertToFullASCII(&command);
// "Dvd"
EXPECT_SINGLE_SEGMENT("\xEF\xBC\xA4\xEF\xBD\x96\xEF\xBD\x84", command);
command.Clear();
session->ConvertToFullASCII(&command);
// "dvd"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\x84\xEF\xBD\x96\xEF\xBD\x84", command);
}
TEST_F(SessionTest, ConvertConsonantsToFullAlphanumericWithoutCascadingWindow) {
config::Config config;
config.set_use_cascading_window(false);
config::ConfigHandler::SetConfig(config);
commands::Command command;
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
InsertCharacterChars("dvd", session.get(), &command);
segment = segments.add_segment();
// "dvd"
segment->set_key("\xEF\xBD\x84\xEF\xBD\x96\xEF\xBD\x84");
candidate = segment->add_candidate();
candidate->value = "DVD";
candidate = segment->add_candidate();
candidate->value = "dvd";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->ConvertToFullASCII(&command);
// "dvd"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\x84\xEF\xBD\x96\xEF\xBD\x84", command);
command.Clear();
session->ConvertToFullASCII(&command);
// "DVD"
EXPECT_SINGLE_SEGMENT("\xEF\xBC\xA4\xEF\xBC\xB6\xEF\xBC\xA4", command);
command.Clear();
session->ConvertToFullASCII(&command);
// "Dvd"
EXPECT_SINGLE_SEGMENT("\xEF\xBC\xA4\xEF\xBD\x96\xEF\xBD\x84", command);
command.Clear();
session->ConvertToFullASCII(&command);
// "dvd"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\x84\xEF\xBD\x96\xEF\xBD\x84", command);
}
// Convert input string to Hiragana, Katakana, and Half Katakana
TEST_F(SessionTest, SwitchKanaType) {
{ // From composition mode.
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("abc", session.get(), &command);
Segments segments;
{ // Initialize segments.
Segment *segment = segments.add_segment();
// "あbc"
segment->set_key("\xE3\x81\x82\xEF\xBD\x82\xEF\xBD\x83");
// "あべし"
segment->add_candidate()->value = "\xE3\x81\x82\xE3\x81\xB9\xE3\x81\x97";
}
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->SwitchKanaType(&command);
// "アbc"
EXPECT_SINGLE_SEGMENT("\xE3\x82\xA2\xEF\xBD\x82\xEF\xBD\x83", command);
command.Clear();
session->SwitchKanaType(&command);
// "アbc"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\xB1\x62\x63", command);
command.Clear();
session->SwitchKanaType(&command);
// "あbc"
EXPECT_SINGLE_SEGMENT("\xE3\x81\x82\xEF\xBD\x82\xEF\xBD\x83", command);
command.Clear();
session->SwitchKanaType(&command);
// "アbc"
EXPECT_SINGLE_SEGMENT("\xE3\x82\xA2\xEF\xBD\x82\xEF\xBD\x83", command);
}
{ // From conversion mode.
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("kanji", session.get(), &command);
Segments segments;
{ // Initialize segments.
Segment *segment = segments.add_segment();
// "かんじ"
segment->set_key("\xE3\x81\x8B\xE3\x82\x93\xE3\x81\x98");
// "漢字"
segment->add_candidate()->value = "\xE6\xBC\xA2\xE5\xAD\x97";
}
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
// "漢字"
EXPECT_SINGLE_SEGMENT("\xE6\xBC\xA2\xE5\xAD\x97", command);
command.Clear();
session->SwitchKanaType(&command);
// "かんじ"
EXPECT_SINGLE_SEGMENT("\xE3\x81\x8B\xE3\x82\x93\xE3\x81\x98", command);
command.Clear();
session->SwitchKanaType(&command);
// "カンジ"
EXPECT_SINGLE_SEGMENT("\xE3\x82\xAB\xE3\x83\xB3\xE3\x82\xB8", command);
command.Clear();
session->SwitchKanaType(&command);
// "カンジ"
EXPECT_SINGLE_SEGMENT(
"\xEF\xBD\xB6\xEF\xBE\x9D\xEF\xBD\xBC\xEF\xBE\x9E", command);
command.Clear();
session->SwitchKanaType(&command);
// "かんじ"
EXPECT_SINGLE_SEGMENT("\xE3\x81\x8B\xE3\x82\x93\xE3\x81\x98", command);
}
}
// Rotate input mode among Hiragana, Katakana, and Half Katakana
TEST_F(SessionTest, InputModeSwitchKanaType) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
// HIRAGANA
InsertCharacterChars("a", session.get(), &command);
// "あ"
EXPECT_EQ(kHiraganaA, GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::HIRAGANA, command.output().mode());
// HIRAGANA to FULL_KATAKANA
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeSwitchKanaType(&command);
InsertCharacterChars("a", session.get(), &command);
// "ア"
EXPECT_EQ("\xE3\x82\xA2", GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::FULL_KATAKANA, command.output().mode());
// FULL_KATRAKANA to HALF_KATAKANA
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeSwitchKanaType(&command);
InsertCharacterChars("a", session.get(), &command);
// "ア"
EXPECT_EQ("\xEF\xBD\xB1",
GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::HALF_KATAKANA, command.output().mode());
// HALF_KATAKANA to HIRAGANA
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeSwitchKanaType(&command);
InsertCharacterChars("a", session.get(), &command);
// "あ"
EXPECT_EQ(kHiraganaA, GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::HIRAGANA, command.output().mode());
// To Half ASCII mode.
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeHalfASCII(&command);
InsertCharacterChars("a", session.get(), &command);
// "a"
EXPECT_EQ("a", GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::HALF_ASCII, command.output().mode());
// HALF_ASCII to HALF_ASCII
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeSwitchKanaType(&command);
InsertCharacterChars("a", session.get(), &command);
// "a"
EXPECT_EQ("a", GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::HALF_ASCII, command.output().mode());
// To Full ASCII mode.
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeFullASCII(&command);
InsertCharacterChars("a", session.get(), &command);
// "a"
EXPECT_EQ(kFullWidthSmallA, GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::FULL_ASCII, command.output().mode());
// FULL_ASCII to FULL_ASCII
command.Clear();
session->Commit(&command);
command.Clear();
session->InputModeSwitchKanaType(&command);
InsertCharacterChars("a", session.get(), &command);
// "a"
EXPECT_EQ(kFullWidthSmallA, GetComposition(command));
EXPECT_TRUE(command.output().has_mode());
EXPECT_EQ(commands::FULL_ASCII, command.output().mode());
}
TEST_F(SessionTest, TranslateHalfWidth) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("abc", session.get(), &command);
command.Clear();
session->TranslateHalfWidth(&command);
// "アbc"
EXPECT_SINGLE_SEGMENT("\xEF\xBD\xB1\x62\x63", command);
command.Clear();
session->TranslateFullASCII(&command);
// "abc".
EXPECT_SINGLE_SEGMENT("\xEF\xBD\x81\xEF\xBD\x82\xEF\xBD\x83", command);
command.Clear();
session->TranslateHalfWidth(&command);
EXPECT_SINGLE_SEGMENT("abc", command);
}
TEST_F(SessionTest, UpdatePreferences) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("aiueo", session.get(), &command);
Segments segments;
SetAiueo(&segments);
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
SetSendKeyCommand("SPACE", &command);
command.mutable_input()->mutable_config()->set_use_cascading_window(false);
session->SendKey(&command);
const size_t no_cascading_cand_size =
command.output().candidates().candidate_size();
command.Clear();
session->ConvertCancel(&command);
SetSendKeyCommand("SPACE", &command);
command.mutable_input()->mutable_config()->set_use_cascading_window(true);
session->SendKey(&command);
const size_t cascading_cand_size =
command.output().candidates().candidate_size();
EXPECT_GT(no_cascading_cand_size, cascading_cand_size);
command.Clear();
session->ConvertCancel(&command);
// On MS-IME keymap, EISU key does nothing.
SetSendKeyCommand("EISU", &command);
command.mutable_input()->mutable_config()->set_session_keymap(
config::Config::MSIME);
session->SendKey(&command);
EXPECT_EQ(commands::HALF_ASCII, command.output().status().mode());
EXPECT_EQ(commands::HALF_ASCII, command.output().status().comeback_mode());
// On KOTOERI keymap, EISU key does "ToggleAlphanumericMode".
SetSendKeyCommand("EISU", &command);
command.mutable_input()->mutable_config()->set_session_keymap(
config::Config::KOTOERI);
session->SendKey(&command);
EXPECT_EQ(commands::HIRAGANA, command.output().status().mode());
EXPECT_EQ(commands::HIRAGANA, command.output().status().comeback_mode());
}
TEST_F(SessionTest, RomajiInput) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
composer::Table table;
// "ぱ"
table.AddRule("pa", "\xe3\x81\xb1", "");
// "ん"
table.AddRule("n", "\xe3\x82\x93", "");
// "な"
table.AddRule("na", "\xe3\x81\xaa", "");
// This rule makes the "n" rule ambiguous.
scoped_ptr<Session> session(new Session(engine_.get()));
session->get_internal_composer_only_for_unittest()->SetTable(&table);
InitSessionToPrecomposition(session.get());
commands::Command command;
InsertCharacterChars("pan", session.get(), &command);
// "ぱn"
EXPECT_EQ("\xe3\x81\xb1\xef\xbd\x8e",
command.output().preedit().segment(0).value());
command.Clear();
segment = segments.add_segment();
// "ぱん"
segment->set_key("\xe3\x81\xb1\xe3\x82\x93");
candidate = segment->add_candidate();
// "パン"
candidate->value = "\xe3\x83\x91\xe3\x83\xb3";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
session->ConvertToHiragana(&command);
// "ぱん"
EXPECT_SINGLE_SEGMENT("\xe3\x81\xb1\xe3\x82\x93", command);
command.Clear();
session->ConvertToHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("pan", command);
}
TEST_F(SessionTest, KanaInput) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
composer::Table table;
// "す゛", "ず"
table.AddRule("\xe3\x81\x99\xe3\x82\x9b", "\xe3\x81\x9a", "");
scoped_ptr<Session> session(new Session(engine_.get()));
session->get_internal_composer_only_for_unittest()->SetTable(&table);
InitSessionToPrecomposition(session.get());
commands::Command command;
SetSendKeyCommand("m", &command);
// "も"
command.mutable_input()->mutable_key()->set_key_string("\xe3\x82\x82");
session->SendKey(&command);
SetSendKeyCommand("r", &command);
// "す"
command.mutable_input()->mutable_key()->set_key_string("\xe3\x81\x99");
session->SendKey(&command);
SetSendKeyCommand("@", &command);
// "゛"
command.mutable_input()->mutable_key()->set_key_string("\xe3\x82\x9b");
session->SendKey(&command);
SetSendKeyCommand("h", &command);
// "く"
command.mutable_input()->mutable_key()->set_key_string("\xe3\x81\x8f");
session->SendKey(&command);
SetSendKeyCommand("!", &command);
command.mutable_input()->mutable_key()->set_key_string("!");
session->SendKey(&command);
// "もずく!"
EXPECT_EQ("\xe3\x82\x82\xe3\x81\x9a\xe3\x81\x8f\xef\xbc\x81",
command.output().preedit().segment(0).value());
segment = segments.add_segment();
// "もずく!"
segment->set_key("\xe3\x82\x82\xe3\x81\x9a\xe3\x81\x8f!");
candidate = segment->add_candidate();
// "もずく!"
candidate->value = "\xe3\x82\x82\xe3\x81\x9a\xe3\x81\x8f\xef\xbc\x81";
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->ConvertToHalfASCII(&command);
EXPECT_SINGLE_SEGMENT("mr@h!", command);
}
TEST_F(SessionTest, ExceededComposition) {
Segments segments;
Segment *segment;
Segment::Candidate *candidate;
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
const string exceeded_preedit(500, 'a');
ASSERT_EQ(500, exceeded_preedit.size());
InsertCharacterChars(exceeded_preedit, session.get(), &command);
string long_a;
for (int i = 0; i < 500; ++i) {
// "あ"
long_a += kHiraganaA;
}
segment = segments.add_segment();
segment->set_key(long_a);
candidate = segment->add_candidate();
candidate->value = long_a;
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
EXPECT_FALSE(command.output().has_candidates());
// The status should remain the preedit status, although the
// previous command was convert. The next command makes sure that
// the preedit will disappear by canceling the preedit status.
command.Clear();
command.mutable_input()->mutable_key()->set_special_key(
commands::KeyEvent::ESCAPE);
EXPECT_FALSE(command.output().has_preedit());
}
TEST_F(SessionTest, OutputAllCandidateWords) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
commands::Command command;
Segments segments;
SetAiueo(&segments);
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
SetComposer(session.get(), &request);
FillT13Ns(request, &segments);
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
{
const commands::Output &output = command.output();
EXPECT_TRUE(output.has_all_candidate_words());
EXPECT_EQ(0, output.all_candidate_words().focused_index());
EXPECT_EQ(commands::CONVERSION, output.all_candidate_words().category());
#ifdef OS_LINUX
// Cascading window is not supported on Linux, so the size of
// candidate words is different from other platform.
// TODO(komatsu): Modify the client for Linux to explicitly change
// the preference rather than relying on the exceptional default value.
// [ "あいうえお", "アイウエオ",
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "アイウエオ" (t13n) ]
EXPECT_EQ(9, output.all_candidate_words().candidates_size());
#else
// [ "あいうえお", "アイウエオ", "アイウエオ" (t13n), "あいうえお" (t13n),
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "アイウエオ" (t13n) ]
EXPECT_EQ(11, output.all_candidate_words().candidates_size());
#endif // OS_LINUX
}
command.Clear();
session->ConvertNext(&command);
{
const commands::Output &output = command.output();
EXPECT_TRUE(output.has_all_candidate_words());
EXPECT_EQ(1, output.all_candidate_words().focused_index());
EXPECT_EQ(commands::CONVERSION, output.all_candidate_words().category());
#ifdef OS_LINUX
// Cascading window is not supported on Linux, so the size of
// candidate words is different from other platform.
// TODO(komatsu): Modify the client for Linux to explicitly change
// the preference rather than relying on the exceptional default value.
// [ "あいうえお", "アイウエオ", "アイウエオ" (t13n), "あいうえお" (t13n),
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "アイウエオ" (t13n) ]
EXPECT_EQ(9, output.all_candidate_words().candidates_size());
#else
// [ "あいうえお", "アイウエオ",
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "aiueo" (t13n), "AIUEO" (t13n), "Aieuo" (t13n),
// "アイウエオ" (t13n) ]
EXPECT_EQ(11, output.all_candidate_words().candidates_size());
#endif // OS_LINUX
}
}
TEST_F(SessionTest, UndoForComposition) {
Session session(engine_.get());
InitSessionToPrecomposition(&session);
// Enable zero query suggest.
commands::Request request;
SetupZeroQuerySuggestionReady(true, &session, &request);
// Undo requires capability DELETE_PRECEDING_TEXT.
commands::Capability capability;
capability.set_text_deletion(commands::Capability::DELETE_PRECEDING_TEXT);
session.set_client_capability(capability);
commands::Command command;
Segments segments;
Segments empty_segments;
{ // Undo for CommitFirstSuggestion
SetAiueo(&segments);
GetConverterMock()->SetStartSuggestionForRequest(&segments, true);
InsertCharacterChars("ai", &session, &command);
ConversionRequest request;
SetComposer(&session, &request);
// "あい"
EXPECT_EQ("\xE3\x81\x82\xE3\x81\x84", GetComposition(command));
command.Clear();
GetConverterMock()->SetFinishConversion(&empty_segments, true);
session.CommitFirstSuggestion(&command);
EXPECT_FALSE(command.output().has_preedit());
// "あいうえお"
EXPECT_RESULT(kAiueo, command);
EXPECT_EQ(ImeContext::PRECOMPOSITION, session.context().state());
command.Clear();
session.Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_TRUE(command.output().has_deletion_range());
EXPECT_EQ(-5, command.output().deletion_range().offset());
EXPECT_EQ(5, command.output().deletion_range().length());
// "あい"
EXPECT_SINGLE_SEGMENT("\xE3\x81\x82\xE3\x81\x84", command);
EXPECT_EQ(2, command.output().candidates().size());
EXPECT_EQ(ImeContext::COMPOSITION, session.context().state());
}
}
TEST_F(SessionTest, RequestUndo) {
scoped_ptr<Session> session(new Session(engine_.get()));
// It is OK not to check ImeContext::DIRECT because you cannot
// assign any key event to Undo command in DIRECT mode.
// See "session/internal/keymap_interface.h".
InitSessionToPrecomposition(session.get());
EXPECT_TRUE(TryUndoAndAssertDoNothing(session.get()))
<< "When the UNDO context is empty and the context state is "
"ImeContext::PRECOMPOSITION, UNDO command should be "
"ignored. See b/5553298.";
InitSessionToPrecomposition(session.get());
SetUndoContext(session.get());
EXPECT_TRUE(TryUndoAndAssertSuccess(session.get()));
InitSessionToPrecomposition(session.get());
SetUndoContext(session.get());
session->context_->set_state(ImeContext::COMPOSITION);
EXPECT_TRUE(TryUndoAndAssertSuccess(session.get()));
InitSessionToPrecomposition(session.get());
SetUndoContext(session.get());
session->context_->set_state(ImeContext::CONVERSION);
EXPECT_TRUE(TryUndoAndAssertSuccess(session.get()));
}
TEST_F(SessionTest, UndoForSingleSegment) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
// Undo requires capability DELETE_PRECEDING_TEXT.
commands::Capability capability;
capability.set_text_deletion(commands::Capability::DELETE_PRECEDING_TEXT);
session->set_client_capability(capability);
commands::Command command;
Segments segments;
{ // Create segments
InsertCharacterChars("aiueo", session.get(), &command);
ConversionRequest request;
SetComposer(session.get(), &request);
SetAiueo(&segments);
// Don't use FillT13Ns(). It makes platform dependent segments.
// TODO(hsumita): Makes FillT13Ns() independent from platforms.
Segment::Candidate *candidate;
candidate = segments.mutable_segment(0)->add_candidate();
candidate->value = "aiueo";
candidate = segments.mutable_segment(0)->add_candidate();
candidate->value = "AIUEO";
}
{ // Undo after commitment of composition
GetConverterMock()->SetStartConversionForRequest(&segments, true);
command.Clear();
session->Convert(&command);
EXPECT_FALSE(command.output().has_result());
// "あいうえお"
EXPECT_PREEDIT(kAiueo, command);
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->Commit(&command);
EXPECT_FALSE(command.output().has_preedit());
// "あいうえお"
EXPECT_RESULT(kAiueo, command);
command.Clear();
session->Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_TRUE(command.output().has_deletion_range());
EXPECT_EQ(-5, command.output().deletion_range().offset());
EXPECT_EQ(5, command.output().deletion_range().length());
// "あいうえお"
EXPECT_PREEDIT(kAiueo, command);
// Undo twice - do nothing and keep the previous status.
command.Clear();
session->Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_FALSE(command.output().has_deletion_range());
// "あいうえお"
EXPECT_PREEDIT(kAiueo, command);
}
{ // Undo after commitment of conversion
command.Clear();
session->ConvertNext(&command);
EXPECT_FALSE(command.output().has_result());
// "アイウエオ"
EXPECT_PREEDIT(kKatakanaAiueo, command);
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->Commit(&command);
EXPECT_FALSE(command.output().has_preedit());
// "アイウエオ"
EXPECT_RESULT(kKatakanaAiueo, command);
command.Clear();
session->Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_TRUE(command.output().has_deletion_range());
EXPECT_EQ(-5, command.output().deletion_range().offset());
EXPECT_EQ(5, command.output().deletion_range().length());
// "アイウエオ"
EXPECT_PREEDIT(kKatakanaAiueo, command);
// Undo twice - do nothing and keep the previous status.
command.Clear();
session->Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_FALSE(command.output().has_deletion_range());
// "アイウエオ"
EXPECT_PREEDIT(kKatakanaAiueo, command);
}
{ // Undo after commitment of conversion with Ctrl-Backspace.
command.Clear();
session->ConvertNext(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_PREEDIT("aiueo", command);
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->Commit(&command);
EXPECT_FALSE(command.output().has_preedit());
EXPECT_RESULT("aiueo", command);
config::Config config;
config.set_session_keymap(config::Config::MSIME);
config::ConfigHandler::SetConfig(config);
command.Clear();
session->Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_TRUE(command.output().has_deletion_range());
EXPECT_EQ(-5, command.output().deletion_range().offset());
EXPECT_EQ(5, command.output().deletion_range().length());
EXPECT_PREEDIT("aiueo", command);
}
{
// If capability does not support DELETE_PRECEDIGN_TEXT, Undo is not
// performed.
GetConverterMock()->SetCommitSegmentValue(&segments, true);
command.Clear();
session->Commit(&command);
EXPECT_FALSE(command.output().has_preedit());
EXPECT_RESULT("aiueo", command);
// Reset capability
capability.Clear();
session->set_client_capability(capability);
command.Clear();
session->Undo(&command);
EXPECT_FALSE(command.output().has_result());
EXPECT_FALSE(command.output().has_deletion_range());
EXPECT_FALSE(command.output().has_preedit());
}
}
TEST_F(SessionTest, ClearUndoContextByKeyEvent_Issue5529702) {
scoped_ptr<Session> session(new Session(engine_.get()));
InitSessionToPrecomposition(session.get());
// Undo requires capability DELETE_PRECEDING_TEXT.
commands::Capability capability;
capability.set_text_deletion(commands::Capability::DELETE_PRECEDING_TEXT);
session->set_client_capability(capability);
SetUndoContext(session.get());
commands::Command command;