1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 // Tests for session converter.
31 //
32 // Note that we have a lot of tests which assume that the converter fills
33 // T13Ns. If you want to add test case related to T13Ns, please make sure
34 // you set T13Ns to the result for a mock converter.
35
36 #include "session/session_converter.h"
37
38 #include <memory>
39 #include <set>
40 #include <string>
41 #include <vector>
42
43 #include "base/logging.h"
44 #include "base/system_util.h"
45 #include "base/util.h"
46 #include "composer/composer.h"
47 #include "composer/table.h"
48 #include "config/config_handler.h"
49 #include "converter/converter_mock.h"
50 #include "converter/segments.h"
51 #include "data_manager/testing/mock_data_manager.h"
52 #include "protocol/candidates.pb.h"
53 #include "protocol/commands.pb.h"
54 #include "protocol/config.pb.h"
55 #include "session/internal/candidate_list.h"
56 #include "session/internal/keymap.h"
57 #include "session/request_test_util.h"
58 #include "testing/base/public/googletest.h"
59 #include "testing/base/public/gunit.h"
60 #include "testing/base/public/testing_util.h"
61 #include "transliteration/transliteration.h"
62 #include "usage_stats/usage_stats.h"
63 #include "usage_stats/usage_stats_testing_util.h"
64
65 namespace mozc {
66 namespace session {
67
68 using ::mozc::commands::Context;
69 using ::mozc::commands::Request;
70 using ::mozc::commands::RequestForUnitTest;
71 using ::mozc::config::Config;
72
73 static const char kChars_Aiueo[] = "あいうえお";
74 static const char kChars_Mo[] = "も";
75 static const char kChars_Mozuku[] = "もずく";
76 static const char kChars_Mozukusu[] = "もずくす";
77 static const char kChars_Momonga[] = "ももんが";
78
79 class SessionConverterTest : public ::testing::Test {
80 protected:
81 // Workaround for C2512 error (no default appropriate constructor) on MSVS.
SessionConverterTest()82 SessionConverterTest() {}
~SessionConverterTest()83 ~SessionConverterTest() override {}
84
SetUp()85 void SetUp() override {
86 convertermock_.reset(new ConverterMock());
87 SystemUtil::SetUserProfileDirectory(FLAGS_test_tmpdir);
88 mozc::usage_stats::UsageStats::ClearAllStatsForTest();
89
90 config_.reset(new Config);
91 config_->set_use_cascading_window(true);
92 request_.reset(new Request);
93
94 table_.reset(new composer::Table);
95 table_->InitializeWithRequestAndConfig(*request_, *config_,
96 mock_data_manager_);
97 composer_.reset(
98 new composer::Composer(table_.get(), request_.get(), config_.get()));
99 }
100
TearDown()101 void TearDown() override {
102 table_.reset();
103 composer_.reset();
104
105 mozc::usage_stats::UsageStats::ClearAllStatsForTest();
106 }
107
GetSegments(const SessionConverter & converter,Segments * dest)108 static void GetSegments(const SessionConverter &converter, Segments *dest) {
109 CHECK(dest);
110 dest->CopyFrom(*converter.segments_.get());
111 }
112
SetSegments(const Segments & src,SessionConverter * converter)113 static void SetSegments(const Segments &src, SessionConverter *converter) {
114 CHECK(converter);
115 converter->segments_->CopyFrom(src);
116 }
117
GetResult(const SessionConverter & converter)118 static const commands::Result &GetResult(const SessionConverter &converter) {
119 return *converter.result_;
120 }
121
GetCandidateList(const SessionConverter & converter)122 static const CandidateList &GetCandidateList(
123 const SessionConverter &converter) {
124 return *converter.candidate_list_;
125 }
126
GetState(const SessionConverter & converter)127 static SessionConverterInterface::State GetState(
128 const SessionConverter &converter) {
129 return converter.state_;
130 }
131
SetState(SessionConverterInterface::State state,SessionConverter * converter)132 static void SetState(SessionConverterInterface::State state,
133 SessionConverter *converter) {
134 converter->state_ = state;
135 }
136
GetSegmentIndex(const SessionConverter & converter)137 static size_t GetSegmentIndex(const SessionConverter &converter) {
138 return converter.segment_index_;
139 }
140
IsCandidateListVisible(const SessionConverter & converter)141 static bool IsCandidateListVisible(const SessionConverter &converter) {
142 return converter.candidate_list_visible_;
143 }
144
GetRequest(const SessionConverter & converter)145 static const commands::Request &GetRequest(
146 const SessionConverter &converter) {
147 return *converter.request_;
148 }
149
GetPreedit(const SessionConverter & converter,size_t index,size_t size,string * conversion)150 static void GetPreedit(const SessionConverter &converter, size_t index,
151 size_t size, string *conversion) {
152 converter.GetPreedit(index, size, conversion);
153 }
154
GetConversion(const SessionConverter & converter,size_t index,size_t size,string * conversion)155 static void GetConversion(const SessionConverter &converter, size_t index,
156 size_t size, string *conversion) {
157 converter.GetConversion(index, size, conversion);
158 }
159
AppendCandidateList(SessionConverter * converter)160 static void AppendCandidateList(SessionConverter *converter) {
161 converter->AppendCandidateList();
162 }
163
164 // set result for "あいうえお"
SetAiueo(Segments * segments)165 static void SetAiueo(Segments *segments) {
166 segments->Clear();
167 Segment *segment;
168 Segment::Candidate *candidate;
169
170 segment = segments->add_segment();
171 segment->set_key("あいうえお");
172 candidate = segment->add_candidate();
173 candidate->key = "あいうえお";
174 candidate->value = candidate->key;
175 candidate = segment->add_candidate();
176 candidate->key = "あいうえお";
177 candidate->value = "アイウエオ";
178 }
179
180 // set result for "かまぼこのいんぼう"
SetKamaboko(Segments * segments)181 static void SetKamaboko(Segments *segments) {
182 Segment *segment;
183 Segment::Candidate *candidate;
184
185 segments->Clear();
186 segment = segments->add_segment();
187
188 segment->set_key("かまぼこの");
189 candidate = segment->add_candidate();
190 candidate->value = "かまぼこの";
191 candidate = segment->add_candidate();
192 candidate->value = "カマボコの";
193 segment = segments->add_segment();
194 segment->set_key("いんぼう");
195 candidate = segment->add_candidate();
196 candidate->value = "陰謀";
197 candidate = segment->add_candidate();
198 candidate->value = "印房";
199
200 // Set dummy T13Ns
201 std::vector<Segment::Candidate> *meta_candidates =
202 segment->mutable_meta_candidates();
203 meta_candidates->resize(transliteration::NUM_T13N_TYPES);
204 for (size_t i = 0; i < transliteration::NUM_T13N_TYPES; ++i) {
205 meta_candidates->at(i).Init();
206 meta_candidates->at(i).value = segment->key();
207 meta_candidates->at(i).content_value = segment->key();
208 meta_candidates->at(i).content_key = segment->key();
209 }
210 }
211
212 // set T13N candidates to segments using composer
FillT13Ns(Segments * segments,const composer::Composer * composer)213 static void FillT13Ns(Segments *segments,
214 const composer::Composer *composer) {
215 size_t composition_pos = 0;
216 for (size_t i = 0; i < segments->conversion_segments_size(); ++i) {
217 Segment *segment = segments->mutable_conversion_segment(i);
218 CHECK(segment);
219 const size_t composition_len = Util::CharsLen(segment->key());
220 std::vector<string> t13ns;
221 composer->GetSubTransliterations(
222 composition_pos, composition_len, &t13ns);
223 std::vector<Segment::Candidate> *meta_candidates =
224 segment->mutable_meta_candidates();
225 meta_candidates->resize(transliteration::NUM_T13N_TYPES);
226 for (size_t j = 0; j < transliteration::NUM_T13N_TYPES; ++j) {
227 meta_candidates->at(j).Init();
228 meta_candidates->at(j).value = t13ns[j];
229 meta_candidates->at(j).content_value = t13ns[j];
230 meta_candidates->at(j).content_key = segment->key();
231 }
232 composition_pos += composition_len;
233 }
234 }
235
236 // set result for "like"
InitConverterWithLike(Segments * segments)237 void InitConverterWithLike(Segments *segments) {
238 composer_->InsertCharacterKeyAndPreedit("li", "ぃ");
239 composer_->InsertCharacterKeyAndPreedit("ke", "け");
240
241 Segment *segment;
242 Segment::Candidate *candidate;
243
244 segments->Clear();
245 segment = segments->add_segment();
246
247 segment->set_key("ぃ");
248 candidate = segment->add_candidate();
249 candidate->value = "ぃ";
250
251 candidate = segment->add_candidate();
252 candidate->value = "ィ";
253
254 segment = segments->add_segment();
255 segment->set_key("け");
256 candidate = segment->add_candidate();
257 candidate->value = "家";
258 candidate = segment->add_candidate();
259 candidate->value = "け";
260
261 FillT13Ns(segments, composer_.get());
262 convertermock_->SetStartConversionForRequest(segments, true);
263 }
264
InsertASCIISequence(const string text,composer::Composer * composer)265 static void InsertASCIISequence(const string text,
266 composer::Composer *composer) {
267 for (size_t i = 0; i < text.size(); ++i) {
268 commands::KeyEvent key;
269 key.set_key_code(text[i]);
270 composer->InsertCharacterKeyEvent(key);
271 }
272 }
273
ExpectSameSessionConverter(const SessionConverter & lhs,const SessionConverter & rhs)274 static void ExpectSameSessionConverter(const SessionConverter &lhs,
275 const SessionConverter &rhs) {
276 EXPECT_EQ(lhs.IsActive(), rhs.IsActive());
277 EXPECT_EQ(IsCandidateListVisible(lhs), IsCandidateListVisible(rhs));
278 EXPECT_EQ(GetSegmentIndex(lhs), GetSegmentIndex(rhs));
279
280 EXPECT_EQ(lhs.conversion_preferences().use_history,
281 rhs.conversion_preferences().use_history);
282 EXPECT_EQ(lhs.conversion_preferences().max_history_size,
283 rhs.conversion_preferences().max_history_size);
284 EXPECT_EQ(IsCandidateListVisible(lhs),
285 IsCandidateListVisible(rhs));
286
287 Segments segments_lhs, segments_rhs;
288 GetSegments(lhs, &segments_lhs);
289 GetSegments(rhs, &segments_rhs);
290 EXPECT_EQ(segments_lhs.segments_size(),
291 segments_rhs.segments_size());
292 for (size_t i = 0; i < segments_lhs.segments_size(); ++i) {
293 Segment segment_lhs, segment_rhs;
294 segment_lhs.CopyFrom(segments_lhs.segment(i));
295 segment_rhs.CopyFrom(segments_rhs.segment(i));
296 EXPECT_EQ(segment_lhs.key(), segment_rhs.key()) << " i=" << i;
297 EXPECT_EQ(segment_lhs.segment_type(),
298 segment_rhs.segment_type()) << " i=" << i;
299 EXPECT_EQ(segment_lhs.candidates_size(), segment_rhs.candidates_size());
300 }
301
302 const CandidateList &candidate_list_lhs = GetCandidateList(lhs);
303 const CandidateList &candidate_list_rhs = GetCandidateList(rhs);
304 EXPECT_EQ(candidate_list_lhs.name(), candidate_list_rhs.name());
305 EXPECT_EQ(candidate_list_lhs.page_size(), candidate_list_rhs.page_size());
306 EXPECT_EQ(candidate_list_lhs.size(), candidate_list_rhs.size());
307 EXPECT_EQ(candidate_list_lhs.last_index(), candidate_list_rhs.last_index());
308 EXPECT_EQ(candidate_list_lhs.focused_id(), candidate_list_rhs.focused_id());
309 EXPECT_EQ(candidate_list_lhs.focused_index(),
310 candidate_list_rhs.focused_index());
311 EXPECT_EQ(candidate_list_lhs.focused(), candidate_list_rhs.focused());
312
313 for (int i = 0; i < candidate_list_lhs.size(); ++i) {
314 const Candidate &candidate_lhs = candidate_list_lhs.candidate(i);
315 const Candidate &candidate_rhs = candidate_list_rhs.candidate(i);
316 EXPECT_EQ(candidate_lhs.id(), candidate_rhs.id());
317 EXPECT_EQ(candidate_lhs.attributes(), candidate_rhs.attributes());
318 EXPECT_EQ(candidate_lhs.IsSubcandidateList(),
319 candidate_rhs.IsSubcandidateList());
320 if (candidate_lhs.IsSubcandidateList()) {
321 EXPECT_EQ(candidate_lhs.subcandidate_list().size(),
322 candidate_rhs.subcandidate_list().size());
323 }
324 }
325
326 EXPECT_PROTO_EQ(GetResult(lhs), GetResult(rhs));
327 EXPECT_PROTO_EQ(GetRequest(lhs), GetRequest(rhs));
328 }
329
ExpectSelectedCandidateIndices(const char *,const char *,const SessionConverter & converter,const std::vector<int> & expected)330 static ::testing::AssertionResult ExpectSelectedCandidateIndices(
331 const char *, const char *,
332 const SessionConverter &converter, const std::vector<int> &expected) {
333 const std::vector<int> &actual = converter.selected_candidate_indices_;
334
335 if (expected.size() != actual.size()) {
336 return ::testing::AssertionFailure()
337 << "Indices size mismatch.\n"
338 << "Expected: " << expected.size() << "\n"
339 << "Actual: " << actual.size();
340 }
341
342 for (size_t i = 0; i < expected.size(); ++i) {
343 if (expected[i] != actual[i]) {
344 return ::testing::AssertionFailure()
345 << "Index mismatch.\n"
346 << "Expected: " << expected[i] << "\n"
347 << "Actual: " << actual[i];
348 }
349 }
350
351 return ::testing::AssertionSuccess();
352 }
353
SetCommandCandidate(Segments * segments,int segment_index,int canidate_index,Segment::Candidate::Command command)354 static void SetCommandCandidate(
355 Segments *segments, int segment_index, int canidate_index,
356 Segment::Candidate::Command command) {
357 segments->mutable_conversion_segment(segment_index)
358 ->mutable_candidate(canidate_index)->attributes
359 |= Segment::Candidate::COMMAND_CANDIDATE;
360 segments->mutable_conversion_segment(segment_index)
361 ->mutable_candidate(canidate_index)->command = command;
362 }
363
364 std::unique_ptr<ConverterMock> convertermock_;
365
366 std::unique_ptr<composer::Composer> composer_;
367 std::unique_ptr<composer::Table> table_;
368 std::unique_ptr<Request> request_;
369 std::unique_ptr<Config> config_;
370 mozc::usage_stats::scoped_usage_stats_enabler usage_stats_enabler_;
371
372 private:
373 const testing::MockDataManager mock_data_manager_;
374 };
375
376 #define EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, indices) \
377 EXPECT_PRED_FORMAT2(ExpectSelectedCandidateIndices, converter, indices);
378
TEST_F(SessionConverterTest,Convert)379 TEST_F(SessionConverterTest, Convert) {
380 SessionConverter converter(
381 convertermock_.get(), request_.get(), config_.get());
382 Segments segments;
383 SetAiueo(&segments);
384 FillT13Ns(&segments, composer_.get());
385 convertermock_->SetStartConversionForRequest(&segments, true);
386 std::vector<int> expected_indices;
387 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
388
389 composer_->InsertCharacterPreedit(kChars_Aiueo);
390 EXPECT_TRUE(converter.Convert(*composer_));
391 ASSERT_TRUE(converter.IsActive());
392 expected_indices.push_back(0);
393 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
394
395 commands::Output output;
396 converter.FillOutput(*composer_, &output);
397 EXPECT_FALSE(output.has_result());
398 EXPECT_TRUE(output.has_preedit());
399 EXPECT_FALSE(output.has_candidates());
400
401 const commands::Preedit &conversion = output.preedit();
402 EXPECT_EQ(1, conversion.segment_size());
403 EXPECT_EQ(commands::Preedit::Segment::HIGHLIGHT,
404 conversion.segment(0).annotation());
405 EXPECT_EQ(kChars_Aiueo, conversion.segment(0).value());
406 EXPECT_EQ(kChars_Aiueo, conversion.segment(0).key());
407
408 // Converter should be active before submittion
409 EXPECT_TRUE(converter.IsActive());
410 EXPECT_FALSE(IsCandidateListVisible(converter));
411
412 converter.Commit(*composer_, Context::default_instance());
413 composer_->Reset();
414 output.Clear();
415 converter.FillOutput(*composer_, &output);
416 EXPECT_TRUE(output.has_result());
417 EXPECT_FALSE(output.has_preedit());
418 EXPECT_FALSE(output.has_candidates());
419 expected_indices.clear();
420 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
421
422 const commands::Result &result = output.result();
423 EXPECT_EQ(kChars_Aiueo, result.value());
424 EXPECT_EQ(kChars_Aiueo, result.key());
425
426 // Converter should be inactive after submittion
427 EXPECT_FALSE(converter.IsActive());
428 EXPECT_FALSE(IsCandidateListVisible(converter));
429
430 EXPECT_COUNT_STATS("Commit", 1);
431 EXPECT_COUNT_STATS("CommitFromConversion", 1);
432 EXPECT_COUNT_STATS("ConversionCandidates0", 1);
433 }
434
TEST_F(SessionConverterTest,ConvertWithSpellingCorrection)435 TEST_F(SessionConverterTest, ConvertWithSpellingCorrection) {
436 SessionConverter converter(
437 convertermock_.get(), request_.get(), config_.get());
438 Segments segments;
439 SetAiueo(&segments);
440 FillT13Ns(&segments, composer_.get());
441 segments.mutable_conversion_segment(0)->mutable_candidate(0)->attributes |=
442 Segment::Candidate::SPELLING_CORRECTION;
443 convertermock_->SetStartConversionForRequest(&segments, true);
444
445 composer_->InsertCharacterPreedit(kChars_Aiueo);
446 EXPECT_TRUE(converter.Convert(*composer_));
447 ASSERT_TRUE(converter.IsActive());
448 EXPECT_TRUE(IsCandidateListVisible(converter));
449 }
450
TEST_F(SessionConverterTest,ConvertToTransliteration)451 TEST_F(SessionConverterTest, ConvertToTransliteration) {
452 SessionConverter converter(
453 convertermock_.get(), request_.get(), config_.get());
454 Segments segments;
455 SetAiueo(&segments);
456
457 composer_->InsertCharacterKeyAndPreedit("aiueo", kChars_Aiueo);
458 FillT13Ns(&segments, composer_.get());
459 convertermock_->SetStartConversionForRequest(&segments, true);
460
461 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
462 transliteration::HALF_ASCII));
463 std::vector<int> expected_indices;
464 expected_indices.push_back(0);
465 { // Check the conversion #1
466 commands::Output output;
467 converter.FillOutput(*composer_, &output);
468 EXPECT_FALSE(output.has_result());
469 EXPECT_TRUE(output.has_preedit());
470 EXPECT_FALSE(output.has_candidates());
471
472 const commands::Preedit &conversion = output.preedit();
473 EXPECT_EQ(1, conversion.segment_size());
474 EXPECT_EQ("aiueo", conversion.segment(0).value());
475 EXPECT_FALSE(IsCandidateListVisible(converter));
476 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
477 }
478
479 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
480 transliteration::HALF_ASCII));
481 { // Check the conversion #2
482 commands::Output output;
483 converter.FillOutput(*composer_, &output);
484 EXPECT_FALSE(output.has_result());
485 EXPECT_TRUE(output.has_preedit());
486 EXPECT_FALSE(output.has_candidates());
487
488 const commands::Preedit &conversion = output.preedit();
489 EXPECT_EQ(1, conversion.segment_size());
490 EXPECT_EQ("AIUEO", conversion.segment(0).value());
491 EXPECT_FALSE(IsCandidateListVisible(converter));
492 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
493 }
494
495 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
496 transliteration::FULL_ASCII));
497 { // Check the conversion #3
498 commands::Output output;
499 converter.FillOutput(*composer_, &output);
500 EXPECT_FALSE(output.has_result());
501 EXPECT_TRUE(output.has_preedit());
502 EXPECT_FALSE(output.has_candidates());
503
504 const commands::Preedit &conversion = output.preedit();
505 EXPECT_EQ(1, conversion.segment_size());
506 EXPECT_EQ("AIUEO", conversion.segment(0).value());
507 EXPECT_FALSE(IsCandidateListVisible(converter));
508 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
509 }
510
511 converter.Commit(*composer_, Context::default_instance());
512
513 EXPECT_COUNT_STATS("Commit", 1);
514 EXPECT_COUNT_STATS("CommitFromConversion", 1);
515 EXPECT_COUNT_STATS("ConversionCandidates0", 1);
516 }
517
TEST_F(SessionConverterTest,ConvertToTransliterationWithMultipleSegments)518 TEST_F(SessionConverterTest, ConvertToTransliterationWithMultipleSegments) {
519 Segments segments;
520 InitConverterWithLike(&segments);
521 SessionConverter converter(
522 convertermock_.get(), request_.get(), config_.get());
523
524 // Convert
525 EXPECT_TRUE(converter.Convert(*composer_));
526 std::vector<int> expected_indices;
527 expected_indices.push_back(0);
528 expected_indices.push_back(0);
529 { // Check the conversion #1
530 commands::Output output;
531 converter.FillOutput(*composer_, &output);
532 EXPECT_FALSE(output.has_result());
533 EXPECT_TRUE(output.has_preedit());
534 EXPECT_FALSE(output.has_candidates());
535
536 const commands::Preedit &conversion = output.preedit();
537 EXPECT_EQ(2, conversion.segment_size());
538 EXPECT_EQ("ぃ", conversion.segment(0).value());
539 EXPECT_EQ("家", conversion.segment(1).value());
540 EXPECT_FALSE(IsCandidateListVisible(converter));
541 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
542 }
543
544 // Convert to half-width alphanumeric.
545 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
546 transliteration::HALF_ASCII));
547 { // Check the conversion #2
548 commands::Output output;
549 converter.FillOutput(*composer_, &output);
550 EXPECT_FALSE(output.has_result());
551 EXPECT_TRUE(output.has_preedit());
552 EXPECT_FALSE(output.has_candidates());
553
554 const commands::Preedit &conversion = output.preedit();
555 EXPECT_EQ(2, conversion.segment_size());
556 EXPECT_EQ("li", conversion.segment(0).value());
557 EXPECT_FALSE(IsCandidateListVisible(converter));
558 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
559 }
560 }
561
TEST_F(SessionConverterTest,ConvertToTransliterationWithoutCascadigWindow)562 TEST_F(SessionConverterTest, ConvertToTransliterationWithoutCascadigWindow) {
563 SessionConverter converter(
564 convertermock_.get(), request_.get(), config_.get());
565 Segments segments;
566 {
567 Segment *segment;
568 Segment::Candidate *candidate;
569 segment = segments.add_segment();
570 segment->set_key("dvd");
571 candidate = segment->add_candidate();
572 candidate->value = "dvd";
573 candidate = segment->add_candidate();
574 candidate->value = "DVD";
575 }
576 { // Set OperationPreferences
577 converter.set_use_cascading_window(false);
578 converter.set_selection_shortcut(config::Config::NO_SHORTCUT);
579 }
580
581 composer_->InsertCharacterKeyAndPreedit("dvd", "dvd");
582 FillT13Ns(&segments, composer_.get());
583 convertermock_->SetStartConversionForRequest(&segments, true);
584 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
585 transliteration::FULL_ASCII));
586 std::vector<int> expected_indices;
587 expected_indices.push_back(0);
588 { // Check the conversion #1
589 commands::Output output;
590 converter.FillOutput(*composer_, &output);
591 EXPECT_FALSE(output.has_result());
592 EXPECT_TRUE(output.has_preedit());
593 EXPECT_FALSE(output.has_candidates());
594
595 const commands::Preedit &conversion = output.preedit();
596 EXPECT_EQ(1, conversion.segment_size());
597 EXPECT_EQ("dvd", conversion.segment(0).value());
598 EXPECT_FALSE(IsCandidateListVisible(converter));
599 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
600 }
601
602 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
603 transliteration::FULL_ASCII));
604 { // Check the conversion #2
605 commands::Output output;
606 converter.FillOutput(*composer_, &output);
607 EXPECT_FALSE(output.has_result());
608 EXPECT_TRUE(output.has_preedit());
609 EXPECT_FALSE(output.has_candidates());
610
611 const commands::Preedit &conversion = output.preedit();
612 EXPECT_EQ(1, conversion.segment_size());
613 EXPECT_EQ("DVD", conversion.segment(0).value());
614 EXPECT_FALSE(IsCandidateListVisible(converter));
615 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
616 }
617
618 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
619 transliteration::FULL_ASCII));
620 { // Check the conversion #3
621 commands::Output output;
622 converter.FillOutput(*composer_, &output);
623 EXPECT_FALSE(output.has_result());
624 EXPECT_TRUE(output.has_preedit());
625 EXPECT_FALSE(output.has_candidates());
626
627 const commands::Preedit &conversion = output.preedit();
628 EXPECT_EQ(1, conversion.segment_size());
629 EXPECT_EQ("Dvd", conversion.segment(0).value());
630 EXPECT_FALSE(IsCandidateListVisible(converter));
631 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
632 }
633 }
634
TEST_F(SessionConverterTest,MultiSegmentsConversion)635 TEST_F(SessionConverterTest, MultiSegmentsConversion) {
636 SessionConverter converter(
637 convertermock_.get(), request_.get(), config_.get());
638 Segments segments;
639 SetKamaboko(&segments);
640 const string kKamabokono = "かまぼこの";
641 const string kInbou = "いんぼう";
642
643 // Test for conversion
644 composer_->InsertCharacterPreedit(kKamabokono + kInbou);
645 FillT13Ns(&segments, composer_.get());
646 convertermock_->SetStartConversionForRequest(&segments, true);
647 EXPECT_TRUE(converter.Convert(*composer_));
648 std::vector<int> expected_indices;
649 expected_indices.push_back(0);
650 expected_indices.push_back(0);
651 {
652 EXPECT_EQ(0, GetSegmentIndex(converter));
653 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
654
655 commands::Output output;
656 converter.FillOutput(*composer_, &output);
657 EXPECT_FALSE(output.has_result());
658 EXPECT_TRUE(output.has_preedit());
659 EXPECT_FALSE(output.has_candidates());
660
661 const commands::Preedit &conversion = output.preedit();
662 EXPECT_EQ(2, conversion.segment_size());
663 EXPECT_EQ(commands::Preedit::Segment::HIGHLIGHT,
664 conversion.segment(0).annotation());
665 EXPECT_EQ(kKamabokono, conversion.segment(0).key());
666 EXPECT_EQ(kKamabokono, conversion.segment(0).value());
667
668 EXPECT_EQ(commands::Preedit::Segment::UNDERLINE,
669 conversion.segment(1).annotation());
670 EXPECT_EQ(kInbou, conversion.segment(1).key());
671 EXPECT_EQ("陰謀", conversion.segment(1).value());
672 }
673
674 // Test for candidates [CandidateNext]
675 EXPECT_FALSE(IsCandidateListVisible(converter));
676 converter.CandidateNext(*composer_);
677 expected_indices[0] += 1;
678 {
679 EXPECT_TRUE(IsCandidateListVisible(converter));
680 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
681 }
682
683 // Test for candidates [CandidatePrev]
684 converter.CandidatePrev();
685 expected_indices[0] -= 1;
686 {
687 EXPECT_TRUE(IsCandidateListVisible(converter));
688 EXPECT_EQ(0, GetSegmentIndex(converter));
689 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
690 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
691
692 commands::Output output;
693 converter.FillOutput(*composer_, &output);
694 EXPECT_FALSE(output.has_result());
695 EXPECT_TRUE(output.has_preedit());
696 EXPECT_TRUE(output.has_candidates());
697
698 const commands::Candidates &candidates = output.candidates();
699 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
700 EXPECT_EQ(0, candidates.position());
701 EXPECT_EQ(kKamabokono, candidates.candidate(0).value());
702 EXPECT_EQ("カマボコの", candidates.candidate(1).value());
703 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
704 }
705
706 // Test for segment motion. [SegmentFocusRight]
707 converter.SegmentFocusRight();
708 {
709 EXPECT_EQ(1, GetSegmentIndex(converter));
710 EXPECT_FALSE(IsCandidateListVisible(converter));
711 converter.SetCandidateListVisible(true);
712
713 commands::Output output;
714 converter.FillOutput(*composer_, &output);
715 EXPECT_FALSE(output.has_result());
716 EXPECT_TRUE(output.has_preedit());
717 EXPECT_TRUE(output.has_candidates());
718 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
719
720 const commands::Candidates &candidates = output.candidates();
721 EXPECT_EQ(0, candidates.focused_index());
722 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
723 EXPECT_EQ(5, candidates.position());
724 EXPECT_EQ("陰謀", candidates.candidate(0).value());
725 EXPECT_EQ("印房", candidates.candidate(1).value());
726 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
727 }
728
729 // Test for segment motion. [SegmentFocusLeft]
730 converter.SegmentFocusLeft();
731 {
732 EXPECT_EQ(0, GetSegmentIndex(converter));
733 EXPECT_FALSE(IsCandidateListVisible(converter));
734 converter.SetCandidateListVisible(true);
735 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
736
737 commands::Output output;
738 converter.FillOutput(*composer_, &output);
739 EXPECT_FALSE(output.has_result());
740 EXPECT_TRUE(output.has_preedit());
741 EXPECT_TRUE(output.has_candidates());
742
743 const commands::Candidates &candidates = output.candidates();
744 EXPECT_EQ(0, candidates.focused_index());
745 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
746 EXPECT_EQ(0, candidates.position());
747 EXPECT_EQ(kKamabokono, candidates.candidate(0).value());
748 EXPECT_EQ("カマボコの", candidates.candidate(1).value());
749 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
750 }
751
752 // Test for segment motion. [SegmentFocusLeft] at the head of segments.
753 // http://b/2990134
754 // Focus changing at the tail of segments to right,
755 // and at the head of segments to left, should work.
756 converter.SegmentFocusLeft();
757 {
758 EXPECT_EQ(1, GetSegmentIndex(converter));
759 EXPECT_FALSE(IsCandidateListVisible(converter));
760 converter.SetCandidateListVisible(true);
761 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
762
763 commands::Output output;
764 converter.FillOutput(*composer_, &output);
765 EXPECT_FALSE(output.has_result());
766 EXPECT_TRUE(output.has_preedit());
767 EXPECT_TRUE(output.has_candidates());
768
769 const commands::Candidates &candidates = output.candidates();
770 EXPECT_EQ(0, candidates.focused_index());
771 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
772 EXPECT_EQ(5, candidates.position());
773 EXPECT_EQ("陰謀", candidates.candidate(0).value());
774 EXPECT_EQ("印房", candidates.candidate(1).value());
775 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
776 }
777
778 // Test for segment motion. [SegmentFocusRight] at the tail of segments.
779 // http://b/2990134
780 // Focus changing at the tail of segments to right,
781 // and at the head of segments to left, should work.
782 converter.SegmentFocusRight();
783 {
784 EXPECT_FALSE(IsCandidateListVisible(converter));
785 converter.SetCandidateListVisible(true);
786 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
787
788 commands::Output output;
789 EXPECT_EQ(0, GetSegmentIndex(converter));
790 converter.FillOutput(*composer_, &output);
791 EXPECT_FALSE(output.has_result());
792 EXPECT_TRUE(output.has_preedit());
793 EXPECT_TRUE(output.has_candidates());
794
795 const commands::Candidates &candidates = output.candidates();
796 EXPECT_EQ(0, candidates.focused_index());
797 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
798 EXPECT_EQ(0, candidates.position());
799 EXPECT_EQ(kKamabokono, candidates.candidate(0).value());
800 EXPECT_EQ("カマボコの", candidates.candidate(1).value());
801 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
802 }
803
804 // Test for candidate motion. [CandidateNext]
805 converter.SegmentFocusRight(); // Focus to the last segment.
806 EXPECT_EQ(1, GetSegmentIndex(converter));
807 converter.CandidateNext(*composer_);
808 expected_indices[1] += 1;
809 {
810 EXPECT_TRUE(IsCandidateListVisible(converter));
811 commands::Output output;
812 converter.FillOutput(*composer_, &output);
813 EXPECT_FALSE(output.has_result());
814 EXPECT_TRUE(output.has_preedit());
815 EXPECT_TRUE(output.has_candidates());
816 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
817
818 const commands::Candidates &candidates = output.candidates();
819 EXPECT_EQ(1, candidates.focused_index());
820 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
821 EXPECT_EQ(5, candidates.position());
822 EXPECT_EQ("陰謀", candidates.candidate(0).value());
823 EXPECT_EQ("印房", candidates.candidate(1).value());
824 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
825
826 const commands::Preedit &conversion = output.preedit();
827 EXPECT_EQ(kKamabokono, conversion.segment(0).value());
828 EXPECT_EQ("印房", conversion.segment(1).value());
829 }
830
831 // Test for segment motion again [SegmentFocusLeftEdge] [SegmentFocusLast]
832 // The positions of "陰謀" and "印房" should be swapped.
833 {
834 Segments fixed_segments;
835 SetKamaboko(&fixed_segments);
836 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
837
838 ASSERT_EQ("陰謀",
839 fixed_segments.segment(1).candidate(0).value);
840 ASSERT_EQ("印房",
841 fixed_segments.segment(1).candidate(1).value);
842 // swap the values.
843 fixed_segments.mutable_segment(1)->mutable_candidate(0)->value.swap(
844 fixed_segments.mutable_segment(1)->mutable_candidate(1)->value);
845 ASSERT_EQ("印房",
846 fixed_segments.segment(1).candidate(0).value);
847 ASSERT_EQ("陰謀",
848 fixed_segments.segment(1).candidate(1).value);
849 convertermock_->SetCommitSegmentValue(&fixed_segments, true);
850 }
851 converter.SegmentFocusLeftEdge();
852 {
853 EXPECT_EQ(0, GetSegmentIndex(converter));
854 EXPECT_FALSE(IsCandidateListVisible(converter));
855 converter.SegmentFocusLast();
856 EXPECT_EQ(1, GetSegmentIndex(converter));
857 EXPECT_FALSE(IsCandidateListVisible(converter));
858 converter.SetCandidateListVisible(true);
859
860 commands::Output output;
861 converter.FillOutput(*composer_, &output);
862 EXPECT_FALSE(output.has_result());
863 EXPECT_TRUE(output.has_preedit());
864 EXPECT_TRUE(output.has_candidates());
865
866 const commands::Candidates &candidates = output.candidates();
867 EXPECT_EQ(0, candidates.focused_index());
868 EXPECT_EQ(3, candidates.size()); // two candidates + one t13n sub list.
869 EXPECT_EQ(5, candidates.position());
870 EXPECT_EQ("印房", candidates.candidate(0).value());
871
872 EXPECT_EQ("陰謀", candidates.candidate(1).value());
873
874 EXPECT_EQ("そのほかの文字種", candidates.candidate(2).value());
875
876 const commands::Preedit &conversion = output.preedit();
877 EXPECT_EQ(kKamabokono, conversion.segment(0).value());
878 EXPECT_EQ("印房", conversion.segment(1).value());
879 }
880
881 converter.Commit(*composer_, Context::default_instance());
882 expected_indices.clear();
883 {
884 composer_->Reset();
885 EXPECT_FALSE(IsCandidateListVisible(converter));
886 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
887
888 commands::Output output;
889 converter.FillOutput(*composer_, &output);
890 EXPECT_TRUE(output.has_result());
891 EXPECT_FALSE(output.has_preedit());
892 EXPECT_FALSE(output.has_candidates());
893
894 const commands::Result &result = output.result();
895 EXPECT_EQ("かまぼこの印房", result.value());
896 EXPECT_EQ("かまぼこのいんぼう", result.key());
897 EXPECT_FALSE(converter.IsActive());
898 }
899 }
900
TEST_F(SessionConverterTest,Transliterations)901 TEST_F(SessionConverterTest, Transliterations) {
902 SessionConverter converter(
903 convertermock_.get(), request_.get(), config_.get());
904 composer_->InsertCharacterKeyAndPreedit("h", "く");
905 composer_->InsertCharacterKeyAndPreedit("J", "ま");
906
907 Segments segments;
908 { // Initialize segments.
909 Segment *segment = segments.add_segment();
910 segment->set_key("くま");
911 segment->add_candidate()->value = "クマー";
912 }
913 FillT13Ns(&segments, composer_.get());
914 convertermock_->SetStartConversionForRequest(&segments, true);
915 EXPECT_TRUE(converter.Convert(*composer_));
916 std::vector<int> expected_indices;
917 expected_indices.push_back(0);
918 EXPECT_FALSE(IsCandidateListVisible(converter));
919 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
920
921 // Move to the t13n list.
922 converter.CandidateNext(*composer_);
923 expected_indices[0] = -1;
924 EXPECT_TRUE(IsCandidateListVisible(converter));
925 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
926
927 commands::Output output;
928 converter.FillOutput(*composer_, &output);
929 EXPECT_FALSE(output.has_result());
930 EXPECT_TRUE(output.has_preedit());
931 EXPECT_TRUE(output.has_candidates());
932
933 const commands::Candidates &candidates = output.candidates();
934 EXPECT_EQ(2, candidates.size()); // one candidate + one t13n sub list.
935 EXPECT_EQ(1, candidates.focused_index());
936 EXPECT_EQ("そのほかの文字種", candidates.candidate(1).value());
937
938 std::vector<string> t13ns;
939 composer_->GetTransliterations(&t13ns);
940
941 EXPECT_TRUE(candidates.has_subcandidates());
942 EXPECT_EQ(t13ns.size(), candidates.subcandidates().size());
943 EXPECT_EQ(9, candidates.subcandidates().candidate_size());
944
945 for (size_t i = 0; i < candidates.subcandidates().candidate_size(); ++i) {
946 EXPECT_EQ(t13ns[i], candidates.subcandidates().candidate(i).value());
947 }
948 }
949
TEST_F(SessionConverterTest,T13NWithResegmentation)950 TEST_F(SessionConverterTest, T13NWithResegmentation) {
951 SessionConverter converter(
952 convertermock_.get(), request_.get(), config_.get());
953 {
954 Segments segments;
955 Segment *segment = segments.add_segment();
956 Segment::Candidate *candidate;
957 CHECK(segment);
958 segment->set_key("かまぼこの");
959 candidate = segment->add_candidate();
960 CHECK(candidate);
961 candidate->value = "かまぼこの";
962
963 segment = segments.add_segment();
964 CHECK(segment);
965 segment->set_key("いんぼう");
966 candidate = segment->add_candidate();
967 CHECK(candidate);
968 candidate->value = "いんぼう";
969
970 InsertASCIISequence("kamabokonoinbou", composer_.get());
971 FillT13Ns(&segments, composer_.get());
972 convertermock_->SetStartConversionForRequest(&segments, true);
973 }
974 EXPECT_TRUE(converter.Convert(*composer_));
975 std::vector<int> expected_indices;
976 expected_indices.push_back(0);
977 expected_indices.push_back(0);
978 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
979
980 // Test for segment motion. [SegmentFocusRight]
981 converter.SegmentFocusRight();
982 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
983 // Shrink segment
984 {
985 Segments segments;
986 Segment *segment;
987 Segment::Candidate *candidate;
988
989 segments.Clear();
990 segment = segments.add_segment();
991
992 segment->set_key("かまぼこの");
993 candidate = segment->add_candidate();
994 candidate->value = "かまぼこの";
995 candidate = segment->add_candidate();
996 candidate->value = "カマボコの";
997
998 segment = segments.add_segment();
999 segment->set_key("いんぼ");
1000 candidate = segment->add_candidate();
1001 candidate->value = "インボ";
1002
1003 segment = segments.add_segment();
1004 segment->set_key("う");
1005 candidate = segment->add_candidate();
1006 candidate->value = "ウ";
1007
1008 FillT13Ns(&segments, composer_.get());
1009 convertermock_->SetResizeSegment1(&segments, true);
1010 }
1011 converter.SegmentWidthShrink(*composer_);
1012 expected_indices.push_back(0);
1013 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1014
1015 // Convert to half katakana. Expected index should be 0.
1016 converter.ConvertToTransliteration(*composer_,
1017 transliteration::HALF_KATAKANA);
1018 expected_indices[0] = 0;
1019 {
1020 commands::Output output;
1021 converter.FillOutput(*composer_, &output);
1022 EXPECT_FALSE(output.has_result());
1023 EXPECT_TRUE(output.has_preedit());
1024 const commands::Preedit &preedit = output.preedit();
1025 EXPECT_EQ(3, preedit.segment_size());
1026 EXPECT_EQ("インボ",
1027 preedit.segment(1).value());
1028 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1029 }
1030 }
1031
TEST_F(SessionConverterTest,ConvertToHalfWidth)1032 TEST_F(SessionConverterTest, ConvertToHalfWidth) {
1033 SessionConverter converter(
1034 convertermock_.get(), request_.get(), config_.get());
1035 std::vector<int> expected_indices;
1036 composer_->InsertCharacterKeyAndPreedit("a", "あ");
1037 composer_->InsertCharacterKeyAndPreedit("b", "b");
1038 composer_->InsertCharacterKeyAndPreedit("c", "c");
1039
1040 Segments segments;
1041 { // Initialize segments.
1042 Segment *segment = segments.add_segment();
1043 segment->set_key("あbc");
1044 segment->add_candidate()->value = "あべし";
1045 }
1046 FillT13Ns(&segments, composer_.get());
1047 convertermock_->SetStartConversionForRequest(&segments, true);
1048 EXPECT_TRUE(converter.ConvertToHalfWidth(*composer_));
1049 expected_indices.push_back(0);
1050 EXPECT_FALSE(IsCandidateListVisible(converter));
1051 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1052
1053 { // Make sure the output
1054 commands::Output output;
1055 converter.FillOutput(*composer_, &output);
1056 EXPECT_FALSE(output.has_result());
1057 EXPECT_TRUE(output.has_preedit());
1058 EXPECT_FALSE(output.has_candidates());
1059
1060 const commands::Preedit &conversion = output.preedit();
1061 EXPECT_EQ(1, conversion.segment_size());
1062 EXPECT_EQ("アbc", conversion.segment(0).value());
1063 }
1064
1065 // Composition will be transliterated to "abc".
1066 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
1067 transliteration::FULL_ASCII));
1068 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1069 { // Make sure the output
1070 commands::Output output;
1071 converter.FillOutput(*composer_, &output);
1072 EXPECT_FALSE(output.has_result());
1073 EXPECT_TRUE(output.has_preedit());
1074 EXPECT_FALSE(output.has_candidates());
1075
1076 const commands::Preedit &conversion = output.preedit();
1077 EXPECT_EQ(1, conversion.segment_size());
1078 EXPECT_EQ("abc",
1079 conversion.segment(0).value());
1080 }
1081
1082 EXPECT_TRUE(converter.ConvertToHalfWidth(*composer_));
1083 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1084 EXPECT_FALSE(IsCandidateListVisible(converter));
1085 { // Make sure the output
1086 commands::Output output;
1087 converter.FillOutput(*composer_, &output);
1088 EXPECT_FALSE(output.has_result());
1089 EXPECT_TRUE(output.has_preedit());
1090 EXPECT_FALSE(output.has_candidates());
1091
1092 const commands::Preedit &conversion = output.preedit();
1093 EXPECT_EQ(1, conversion.segment_size());
1094 EXPECT_EQ("abc", conversion.segment(0).value());
1095 }
1096
1097 EXPECT_TRUE(converter.ConvertToHalfWidth(*composer_));
1098 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1099 EXPECT_FALSE(IsCandidateListVisible(converter));
1100 { // Make sure the output
1101 commands::Output output;
1102 converter.FillOutput(*composer_, &output);
1103 EXPECT_FALSE(output.has_result());
1104 EXPECT_TRUE(output.has_preedit());
1105 EXPECT_FALSE(output.has_candidates());
1106
1107 const commands::Preedit &conversion = output.preedit();
1108 EXPECT_EQ(1, conversion.segment_size());
1109 EXPECT_EQ("ABC", conversion.segment(0).value());
1110 }
1111 }
1112
TEST_F(SessionConverterTest,ConvertToHalfWidth_2)1113 TEST_F(SessionConverterTest, ConvertToHalfWidth_2) {
1114 // http://b/2517514
1115 // ConvertToHalfWidth converts punctuations differently w/ or w/o kana.
1116 SessionConverter converter(
1117 convertermock_.get(), request_.get(), config_.get());
1118 composer_->InsertCharacterKeyAndPreedit("q", "q");
1119 composer_->InsertCharacterKeyAndPreedit(",", "、");
1120 composer_->InsertCharacterKeyAndPreedit(".", "。");
1121
1122 Segments segments;
1123 { // Initialize segments.
1124 Segment *segment = segments.add_segment();
1125 segment->set_key("q、。");
1126 segment->add_candidate()->value = "q,.";
1127 segment->add_candidate()->value = "q、。";
1128 }
1129 FillT13Ns(&segments, composer_.get());
1130 convertermock_->SetStartConversionForRequest(&segments, true);
1131 EXPECT_TRUE(converter.ConvertToHalfWidth(*composer_));
1132 std::vector<int> expected_indices;
1133 expected_indices.push_back(0);
1134 EXPECT_FALSE(IsCandidateListVisible(converter));
1135 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1136
1137 { // Make sure the output
1138 commands::Output output;
1139 converter.FillOutput(*composer_, &output);
1140 EXPECT_FALSE(output.has_result());
1141 EXPECT_TRUE(output.has_preedit());
1142 EXPECT_FALSE(output.has_candidates());
1143
1144 const commands::Preedit &conversion = output.preedit();
1145 EXPECT_EQ(1, conversion.segment_size());
1146 EXPECT_EQ("q、。", conversion.segment(0).value());
1147 }
1148 }
1149
TEST_F(SessionConverterTest,SwitchKanaType)1150 TEST_F(SessionConverterTest, SwitchKanaType) {
1151 { // From composition mode.
1152 SessionConverter converter(
1153 convertermock_.get(), request_.get(), config_.get());
1154 composer_->InsertCharacterKeyAndPreedit("a", "あ");
1155 composer_->InsertCharacterKeyAndPreedit("b", "b");
1156 composer_->InsertCharacterKeyAndPreedit("c", "c");
1157
1158 Segments segments;
1159 { // Initialize segments.
1160 Segment *segment = segments.add_segment();
1161 segment->set_key("あbc");
1162 segment->add_candidate()->value = "あべし";
1163 }
1164 FillT13Ns(&segments, composer_.get());
1165 convertermock_->SetStartConversionForRequest(&segments, true);
1166 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1167 std::vector<int> expected_indices;
1168 expected_indices.push_back(0);
1169 EXPECT_FALSE(IsCandidateListVisible(converter));
1170 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1171
1172 { // Make sure the output
1173 commands::Output output;
1174 converter.FillOutput(*composer_, &output);
1175 EXPECT_FALSE(output.has_result());
1176 EXPECT_TRUE(output.has_preedit());
1177 EXPECT_FALSE(output.has_candidates());
1178
1179 const commands::Preedit &conversion = output.preedit();
1180 EXPECT_EQ(1, conversion.segment_size());
1181 EXPECT_EQ("アbc",
1182 conversion.segment(0).value());
1183 }
1184
1185 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1186 EXPECT_FALSE(IsCandidateListVisible(converter));
1187 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1188 {
1189 commands::Output output;
1190 converter.FillOutput(*composer_, &output);
1191 EXPECT_FALSE(output.has_result());
1192 EXPECT_TRUE(output.has_preedit());
1193 EXPECT_FALSE(output.has_candidates());
1194
1195 const commands::Preedit &conversion = output.preedit();
1196 EXPECT_EQ(1, conversion.segment_size());
1197 EXPECT_EQ("アbc", conversion.segment(0).value());
1198 }
1199
1200 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1201 EXPECT_FALSE(IsCandidateListVisible(converter));
1202 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1203 {
1204 commands::Output output;
1205 converter.FillOutput(*composer_, &output);
1206 EXPECT_FALSE(output.has_result());
1207 EXPECT_TRUE(output.has_preedit());
1208 EXPECT_FALSE(output.has_candidates());
1209
1210 const commands::Preedit &conversion = output.preedit();
1211 EXPECT_EQ(1, conversion.segment_size());
1212 EXPECT_EQ("あbc",
1213 conversion.segment(0).value());
1214 }
1215 }
1216
1217 { // From conversion mode
1218 SessionConverter converter(
1219 convertermock_.get(), request_.get(), config_.get());
1220 composer_->EditErase();
1221 composer_->InsertCharacterKeyAndPreedit("ka", "か");
1222 composer_->InsertCharacterKeyAndPreedit("n", "ん");
1223 composer_->InsertCharacterKeyAndPreedit("ji", "じ");
1224
1225 Segments segments;
1226 { // Initialize segments.
1227 Segment *segment = segments.add_segment();
1228 segment->set_key("かんじ");
1229 segment->add_candidate()->value = "漢字";
1230 }
1231 FillT13Ns(&segments, composer_.get());
1232 convertermock_->SetStartConversionForRequest(&segments, true);
1233 EXPECT_TRUE(converter.Convert(*composer_));
1234 std::vector<int> expected_indices;
1235 expected_indices.push_back(0);
1236 EXPECT_FALSE(IsCandidateListVisible(converter));
1237 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1238
1239 { // Make sure the output
1240 commands::Output output;
1241 converter.FillOutput(*composer_, &output);
1242 EXPECT_FALSE(output.has_result());
1243 EXPECT_TRUE(output.has_preedit());
1244 EXPECT_FALSE(output.has_candidates());
1245
1246 const commands::Preedit &conversion = output.preedit();
1247 EXPECT_EQ(1, conversion.segment_size());
1248 EXPECT_EQ("漢字", conversion.segment(0).value());
1249 }
1250
1251 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1252 EXPECT_FALSE(IsCandidateListVisible(converter));
1253 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1254 {
1255 commands::Output output;
1256 converter.FillOutput(*composer_, &output);
1257 EXPECT_FALSE(output.has_result());
1258 EXPECT_TRUE(output.has_preedit());
1259 EXPECT_FALSE(output.has_candidates());
1260
1261 const commands::Preedit &conversion = output.preedit();
1262 EXPECT_EQ(1, conversion.segment_size());
1263 EXPECT_EQ("かんじ", conversion.segment(0).value());
1264 }
1265
1266 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1267 EXPECT_FALSE(IsCandidateListVisible(converter));
1268 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1269 {
1270 commands::Output output;
1271 converter.FillOutput(*composer_, &output);
1272 EXPECT_FALSE(output.has_result());
1273 EXPECT_TRUE(output.has_preedit());
1274 EXPECT_FALSE(output.has_candidates());
1275
1276 const commands::Preedit &conversion = output.preedit();
1277 EXPECT_EQ(1, conversion.segment_size());
1278 EXPECT_EQ("カンジ", conversion.segment(0).value());
1279 }
1280
1281 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1282 EXPECT_FALSE(IsCandidateListVisible(converter));
1283 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1284 {
1285 commands::Output output;
1286 converter.FillOutput(*composer_, &output);
1287 EXPECT_FALSE(output.has_result());
1288 EXPECT_TRUE(output.has_preedit());
1289 EXPECT_FALSE(output.has_candidates());
1290
1291 const commands::Preedit &conversion = output.preedit();
1292 EXPECT_EQ(1, conversion.segment_size());
1293 EXPECT_EQ("カンジ", conversion.segment(0).value());
1294 }
1295
1296 EXPECT_TRUE(converter.SwitchKanaType(*composer_));
1297 EXPECT_FALSE(IsCandidateListVisible(converter));
1298 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1299 {
1300 commands::Output output;
1301 converter.FillOutput(*composer_, &output);
1302 EXPECT_FALSE(output.has_result());
1303 EXPECT_TRUE(output.has_preedit());
1304 EXPECT_FALSE(output.has_candidates());
1305
1306 const commands::Preedit &conversion = output.preedit();
1307 EXPECT_EQ(1, conversion.segment_size());
1308 EXPECT_EQ("かんじ", conversion.segment(0).value());
1309 }
1310 }
1311 }
1312
TEST_F(SessionConverterTest,CommitFirstSegment)1313 TEST_F(SessionConverterTest, CommitFirstSegment) {
1314 SessionConverter converter(
1315 convertermock_.get(), request_.get(), config_.get());
1316 Segments segments;
1317 SetKamaboko(&segments);
1318 FillT13Ns(&segments, composer_.get());
1319 convertermock_->SetStartConversionForRequest(&segments, true);
1320
1321 const string kKamabokono = "かまぼこの";
1322 const string kInbou = "いんぼう";
1323
1324 composer_->InsertCharacterPreedit(kKamabokono + kInbou);
1325 EXPECT_TRUE(converter.Convert(*composer_));
1326 std::vector<int> expected_indices;
1327 expected_indices.push_back(0);
1328 expected_indices.push_back(0);
1329 EXPECT_FALSE(IsCandidateListVisible(converter));
1330 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1331
1332 { // Check the conversion.
1333 commands::Output output;
1334 converter.FillOutput(*composer_, &output);
1335 EXPECT_FALSE(output.has_result());
1336 EXPECT_TRUE(output.has_preedit());
1337 EXPECT_FALSE(output.has_candidates());
1338
1339 const commands::Preedit &conversion = output.preedit();
1340 EXPECT_EQ(kKamabokono, conversion.segment(0).value());
1341 EXPECT_EQ("陰謀", conversion.segment(1).value());
1342 }
1343
1344 converter.CandidateNext(*composer_);
1345 expected_indices[0] += 1;
1346 EXPECT_TRUE(IsCandidateListVisible(converter));
1347 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1348
1349 { // Check the conversion.
1350 commands::Output output;
1351 converter.FillOutput(*composer_, &output);
1352 EXPECT_FALSE(output.has_result());
1353 EXPECT_TRUE(output.has_preedit());
1354 EXPECT_TRUE(output.has_candidates());
1355
1356 const commands::Preedit &conversion = output.preedit();
1357 EXPECT_EQ("カマボコの", conversion.segment(0).value());
1358 EXPECT_EQ("陰謀", conversion.segment(1).value());
1359 }
1360
1361 { // Initialization of SetCommitSegments.
1362 Segments segments_after_submit;
1363 Segment *segment = segments_after_submit.add_segment();
1364 segment->set_key("いんぼう");
1365 segment->add_candidate()->value = "陰謀";
1366 segment->add_candidate()->value = "印房";
1367 convertermock_->SetCommitSegments(&segments_after_submit, true);
1368 }
1369 size_t size;
1370 converter.CommitFirstSegment(*composer_, Context::default_instance(), &size);
1371 expected_indices.erase(expected_indices.begin(),
1372 expected_indices.begin() + 1);
1373 EXPECT_FALSE(IsCandidateListVisible(converter));
1374 EXPECT_EQ(Util::CharsLen(kKamabokono), size);
1375 EXPECT_TRUE(converter.IsActive());
1376 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1377
1378 EXPECT_COUNT_STATS("Commit", 1);
1379 EXPECT_COUNT_STATS("CommitFromConversion", 1);
1380 EXPECT_STATS_NOT_EXIST("ConversionCandidates0");
1381 EXPECT_COUNT_STATS("ConversionCandidates1", 1);
1382 }
1383
TEST_F(SessionConverterTest,CommitHeadToFocusedSegments)1384 TEST_F(SessionConverterTest, CommitHeadToFocusedSegments) {
1385 SessionConverter converter(
1386 convertermock_.get(), request_.get(), config_.get());
1387 const string kIberiko = "いべりこ";
1388 const string kNekowo = "ねこを";
1389 const string kItadaita = "いただいた";
1390 { // Three segments as the result of conversion.
1391 Segments segments;
1392 Segment *segment;
1393 Segment::Candidate *candidate;
1394
1395 segment = segments.add_segment();
1396 segment->set_key(kIberiko);
1397 candidate = segment->add_candidate();
1398 candidate->value = "イベリコ";
1399
1400 segment = segments.add_segment();
1401 segment->set_key(kNekowo);
1402 candidate = segment->add_candidate();
1403 candidate->value = "猫を";
1404
1405 segment = segments.add_segment();
1406 segment->set_key(kItadaita);
1407 candidate = segment->add_candidate();
1408 candidate->value = "頂いた";
1409 convertermock_->SetStartConversionForRequest(&segments, true);
1410 }
1411
1412 composer_->InsertCharacterPreedit(kIberiko + kNekowo + kItadaita);
1413 EXPECT_TRUE(converter.Convert(*composer_));
1414 // Here [イベリコ]|猫を|頂いた
1415
1416 converter.SegmentFocusRight();
1417 // Here イベリコ|[猫を]|頂いた
1418
1419 { // Initialization of SetCommitSegments.
1420 Segments segments;
1421 Segment *segment;
1422 Segment::Candidate *candidate;
1423
1424 segment = segments.add_segment();
1425 segment->set_key(kItadaita);
1426 candidate = segment->add_candidate();
1427 candidate->value = "頂いた";
1428 convertermock_->SetStartConversionForRequest(&segments, true);
1429
1430 convertermock_->SetCommitSegments(&segments, true);
1431 }
1432 size_t size;
1433 converter.CommitHeadToFocusedSegments(*composer_,
1434 Context::default_instance(), &size);
1435 // Here 頂いた
1436 EXPECT_FALSE(IsCandidateListVisible(converter));
1437 EXPECT_EQ(Util::CharsLen(kIberiko + kNekowo), size);
1438 EXPECT_TRUE(converter.IsActive());
1439 }
1440
TEST_F(SessionConverterTest,CommitHeadToFocusedSegments_atLastSegment)1441 TEST_F(SessionConverterTest, CommitHeadToFocusedSegments_atLastSegment) {
1442 SessionConverter converter(
1443 convertermock_.get(), request_.get(), config_.get());
1444 Segments segments;
1445 SetKamaboko(&segments);
1446 convertermock_->SetStartConversionForRequest(&segments, true);
1447
1448 const string kKamabokono = "かまぼこの";
1449 const string kInbou = "いんぼう";
1450
1451 composer_->InsertCharacterPreedit(kKamabokono + kInbou);
1452 EXPECT_TRUE(converter.Convert(*composer_));
1453 // Here [かまぼこの]|陰謀
1454
1455 converter.SegmentFocusRight();
1456 // Here かまぼこの|[陰謀]
1457
1458 { // Initialization of SetCommitSegments.
1459 Segments segments_after_submit;
1460 convertermock_->SetCommitSegments(&segments_after_submit, true);
1461 }
1462 size_t size;
1463 // All the segments should be committed.
1464 converter.CommitHeadToFocusedSegments(*composer_,
1465 Context::default_instance(), &size);
1466 EXPECT_FALSE(IsCandidateListVisible(converter));
1467 EXPECT_EQ(0, size);
1468 EXPECT_FALSE(converter.IsActive());
1469 }
1470
TEST_F(SessionConverterTest,CommitPreedit)1471 TEST_F(SessionConverterTest, CommitPreedit) {
1472 SessionConverter converter(
1473 convertermock_.get(), request_.get(), config_.get());
1474 std::vector<int> expected_indices;
1475 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1476 composer_->InsertCharacterPreedit(kChars_Aiueo);
1477 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1478 converter.CommitPreedit(*composer_, Context::default_instance());
1479 composer_->Reset();
1480 EXPECT_FALSE(IsCandidateListVisible(converter));
1481 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1482
1483 { // Check the result
1484 commands::Output output;
1485 converter.FillOutput(*composer_, &output);
1486 EXPECT_TRUE(output.has_result());
1487 EXPECT_FALSE(output.has_preedit());
1488 EXPECT_FALSE(output.has_candidates());
1489
1490 const commands::Result &result = output.result();
1491 EXPECT_EQ(kChars_Aiueo, result.value());
1492 EXPECT_EQ(kChars_Aiueo, result.key());
1493 }
1494 EXPECT_FALSE(converter.IsActive());
1495
1496 EXPECT_COUNT_STATS("Commit", 1);
1497 EXPECT_COUNT_STATS("CommitFromComposition", 1);
1498 }
1499
TEST_F(SessionConverterTest,CommitSuggestionByIndex)1500 TEST_F(SessionConverterTest, CommitSuggestionByIndex) {
1501 SessionConverter converter(
1502 convertermock_.get(), request_.get(), config_.get());
1503 Segments segments;
1504 { // Initialize mock segments for suggestion
1505 segments.set_request_type(Segments::SUGGESTION);
1506 Segment *segment = segments.add_segment();
1507 Segment::Candidate *candidate;
1508 segment->set_key(kChars_Mo);
1509 candidate = segment->add_candidate();
1510 candidate->value = kChars_Mozukusu;
1511 candidate->key = kChars_Mozukusu;
1512 candidate->content_key = kChars_Mozukusu;
1513 candidate = segment->add_candidate();
1514 candidate->value = kChars_Momonga;
1515 candidate->key = kChars_Momonga;
1516 candidate->content_key = kChars_Momonga;
1517 }
1518 composer_->InsertCharacterPreedit(kChars_Mo);
1519
1520 // Suggestion
1521 convertermock_->SetStartSuggestionForRequest(&segments, true);
1522 EXPECT_TRUE(converter.Suggest(*composer_));
1523 std::vector<int> expected_indices;
1524 expected_indices.push_back(0);
1525 EXPECT_TRUE(IsCandidateListVisible(converter));
1526 EXPECT_TRUE(converter.IsActive());
1527
1528 { // Check the candidate list
1529 commands::Output output;
1530 converter.FillOutput(*composer_, &output);
1531 EXPECT_FALSE(output.has_result());
1532 EXPECT_TRUE(output.has_preedit());
1533 EXPECT_TRUE(output.has_candidates());
1534
1535 const commands::Preedit &preedit = output.preedit();
1536 EXPECT_EQ(1, preedit.segment_size());
1537 EXPECT_EQ(kChars_Mo, preedit.segment(0).value());
1538
1539 const commands::Candidates &candidates = output.candidates();
1540 EXPECT_EQ(2, candidates.size());
1541 EXPECT_EQ(kChars_Mozukusu, candidates.candidate(0).value());
1542 EXPECT_FALSE(candidates.has_focused_index());
1543 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1544 }
1545
1546 // FinishConversion is expected to return empty Segments.
1547 convertermock_->SetFinishConversion(
1548 std::unique_ptr<Segments>(new Segments).get(), true);
1549
1550 size_t committed_key_size = 0;
1551 converter.CommitSuggestionByIndex(1, *composer_.get(),
1552 Context::default_instance(),
1553 &committed_key_size);
1554 expected_indices.clear();
1555 composer_->Reset();
1556 EXPECT_FALSE(IsCandidateListVisible(converter));
1557 EXPECT_FALSE(converter.IsActive());
1558 EXPECT_EQ(SessionConverter::kConsumedAllCharacters, committed_key_size);
1559
1560 { // Check the result
1561 commands::Output output;
1562 converter.FillOutput(*composer_, &output);
1563 EXPECT_TRUE(output.has_result());
1564 EXPECT_FALSE(output.has_preedit());
1565 EXPECT_FALSE(output.has_candidates());
1566
1567 const commands::Result &result = output.result();
1568 EXPECT_EQ(kChars_Momonga, result.value());
1569 EXPECT_EQ(kChars_Momonga, result.key());
1570 EXPECT_EQ(SessionConverterInterface::COMPOSITION, GetState(converter));
1571 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1572 }
1573
1574 EXPECT_COUNT_STATS("Commit", 1);
1575 // Suggestion is counted as Prediction
1576 EXPECT_COUNT_STATS("CommitFromPrediction", 1);
1577 EXPECT_COUNT_STATS("PredictionCandidates1", 1);
1578 }
1579
TEST_F(SessionConverterTest,CommitSuggestionById)1580 TEST_F(SessionConverterTest, CommitSuggestionById) {
1581 SessionConverter converter(
1582 convertermock_.get(), request_.get(), config_.get());
1583 Segments segments;
1584 { // Initialize mock segments for suggestion
1585 segments.set_request_type(Segments::SUGGESTION);
1586 Segment *segment = segments.add_segment();
1587 Segment::Candidate *candidate;
1588 segment->set_key(kChars_Mo);
1589 candidate = segment->add_candidate();
1590 candidate->value = kChars_Mozukusu;
1591 candidate->key = kChars_Mozukusu;
1592 candidate->content_key = kChars_Mozukusu;
1593 candidate = segment->add_candidate();
1594 candidate->value = kChars_Momonga;
1595 candidate->key = kChars_Momonga;
1596 candidate->content_key = kChars_Momonga;
1597 }
1598 composer_->InsertCharacterPreedit(kChars_Mo);
1599
1600 // Suggestion
1601 convertermock_->SetStartSuggestionForRequest(&segments, true);
1602 EXPECT_TRUE(converter.Suggest(*composer_));
1603 std::vector<int> expected_indices;
1604 expected_indices.push_back(0);
1605 EXPECT_TRUE(IsCandidateListVisible(converter));
1606 EXPECT_TRUE(converter.IsActive());
1607 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1608
1609 // FinishConversion is expected to return empty Segments.
1610 convertermock_->SetFinishConversion(
1611 std::unique_ptr<Segments>(new Segments).get(), true);
1612
1613 const int kCandidateIndex = 1;
1614 size_t committed_key_size = 0;
1615 convertermock_->SetCommitSegmentValue(&segments, true);
1616 converter.CommitSuggestionById(kCandidateIndex, *composer_.get(),
1617 Context::default_instance(),
1618 &committed_key_size);
1619 expected_indices.clear();
1620 composer_->Reset();
1621 EXPECT_FALSE(IsCandidateListVisible(converter));
1622 EXPECT_FALSE(converter.IsActive());
1623 EXPECT_EQ(SessionConverter::kConsumedAllCharacters, committed_key_size);
1624 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1625
1626 { // Check the result
1627 commands::Output output;
1628 converter.FillOutput(*composer_, &output);
1629 EXPECT_TRUE(output.has_result());
1630 EXPECT_FALSE(output.has_preedit());
1631 EXPECT_FALSE(output.has_candidates());
1632
1633 const commands::Result &result = output.result();
1634 EXPECT_EQ(kChars_Momonga, result.value());
1635 EXPECT_EQ(kChars_Momonga, result.key());
1636 EXPECT_EQ(SessionConverterInterface::COMPOSITION, GetState(converter));
1637 }
1638 // Check the converter's internal state
1639 Segments committed_segments;
1640 size_t segment_index;
1641 int candidate_index;
1642 convertermock_->GetCommitSegmentValue(
1643 &committed_segments, &segment_index, &candidate_index);
1644 EXPECT_EQ(0, segment_index);
1645 EXPECT_EQ(kCandidateIndex, candidate_index);
1646
1647 EXPECT_COUNT_STATS("Commit", 1);
1648 // Suggestion is counted as Prediction.
1649 EXPECT_COUNT_STATS("CommitFromPrediction", 1);
1650 EXPECT_COUNT_STATS("PredictionCandidates" +
1651 std::to_string(kCandidateIndex),
1652 1);
1653 }
1654
TEST_F(SessionConverterTest,PartialSuggestion)1655 TEST_F(SessionConverterTest, PartialSuggestion) {
1656 RequestForUnitTest::FillMobileRequest(request_.get());
1657 SessionConverter converter(
1658 convertermock_.get(), request_.get(), config_.get());
1659 Segments segments1, segments2;
1660 Segments suggestion_segments;
1661 const string kChars_Kokode = "ここで";
1662 const string kChars_Hakimonowo = "はきものを";
1663
1664 { // Initialize mock segments for partial suggestion
1665 segments1.set_request_type(Segments::PARTIAL_SUGGESTION);
1666 Segment *segment = segments1.add_segment();
1667 Segment::Candidate *candidate;
1668 segment->set_key(kChars_Kokode);
1669 candidate = segment->add_candidate();
1670 candidate->value = "此処では";
1671 candidate->key = kChars_Kokode;
1672 candidate->content_key = kChars_Kokode;
1673 candidate->attributes = Segment::Candidate::PARTIALLY_KEY_CONSUMED;
1674 candidate->consumed_key_size = Util::CharsLen(kChars_Kokode);
1675 }
1676
1677 // Suggestion that matches to the same key by its prefix.
1678 // Should not be used by partial suggestion.
1679 {
1680 suggestion_segments.set_request_type(Segments::SUGGESTION);
1681 Segment *segment = suggestion_segments.add_segment();
1682 Segment::Candidate *candidate;
1683 segment->set_key(kChars_Kokode);
1684 candidate = segment->add_candidate();
1685 candidate->value = "ここでは着物を";
1686 candidate->key = "ここではきものを";
1687 candidate->content_key = candidate->key;
1688 candidate = segment->add_candidate();
1689 }
1690
1691 { // Initialize mock segments for suggestion
1692 segments2.set_request_type(Segments::SUGGESTION);
1693 Segment *segment = segments2.add_segment();
1694 Segment::Candidate *candidate;
1695 segment->set_key(kChars_Hakimonowo);
1696 candidate = segment->add_candidate();
1697 candidate->value = "此処では";
1698 candidate->key = kChars_Hakimonowo;
1699 candidate->content_key = kChars_Hakimonowo;
1700 }
1701
1702 // "ここではきものを|" ("|" is cursor position)
1703 composer_->InsertCharacterPreedit(kChars_Kokode + kChars_Hakimonowo);
1704 composer_->MoveCursorToEnd();
1705 // Suggestion for "ここではきものを". Not partial suggestion.
1706 convertermock_->SetStartSuggestionForRequest(&suggestion_segments, true);
1707 convertermock_->SetStartPartialSuggestion(&suggestion_segments, false);
1708 EXPECT_TRUE(converter.Suggest(*composer_));
1709 std::vector<int> expected_indices;
1710 expected_indices.push_back(0);
1711 EXPECT_TRUE(IsCandidateListVisible(converter));
1712 EXPECT_TRUE(converter.IsActive());
1713 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1714
1715 // "|ここではきものを" ("|" is cursor position)
1716 composer_->MoveCursorTo(0);
1717
1718 // Suggestion for "ここではきものを". Not partial suggestion.
1719 EXPECT_TRUE(converter.Suggest(*composer_));
1720 EXPECT_TRUE(IsCandidateListVisible(converter));
1721 EXPECT_TRUE(converter.IsActive());
1722 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1723
1724 // "ここで|はきものを" ("|" is cursor position)
1725 composer_->MoveCursorTo(3);
1726
1727 // Partial Suggestion for "ここで"
1728 convertermock_->SetStartSuggestionForRequest(&segments1, false);
1729 convertermock_->SetStartPartialSuggestion(&segments1, false);
1730 convertermock_->SetStartPartialSuggestionForRequest(&segments1, true);
1731 EXPECT_TRUE(converter.Suggest(*composer_));
1732 EXPECT_TRUE(IsCandidateListVisible(converter));
1733 EXPECT_TRUE(converter.IsActive());
1734 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1735
1736 // commit partial suggestion
1737 size_t committed_key_size = 0;
1738 convertermock_->SetStartSuggestionForRequest(&segments2, true);
1739 convertermock_->SetStartPartialSuggestion(&segments2, false);
1740 converter.CommitSuggestionById(0, *composer_.get(),
1741 Context::default_instance(),
1742 &committed_key_size);
1743 EXPECT_EQ(Util::CharsLen(kChars_Kokode), committed_key_size);
1744 // Indices should be {0} since there is another segment.
1745 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1746
1747 { // Check the result
1748 commands::Output output;
1749 converter.FillOutput(*composer_, &output);
1750 EXPECT_TRUE(output.has_result());
1751
1752 const commands::Result &result = output.result();
1753 EXPECT_EQ("此処では", result.value());
1754 EXPECT_EQ(kChars_Kokode, result.key());
1755 EXPECT_EQ(SessionConverterInterface::SUGGESTION, GetState(converter));
1756 }
1757 // Check the converter's internal state
1758 Segments committed_segments;
1759 size_t segment_index;
1760 int candidate_index;
1761 string current_segment_key;
1762 string new_segment_key;
1763 convertermock_->GetCommitPartialSuggestionSegmentValue(&committed_segments,
1764 &segment_index,
1765 &candidate_index,
1766 ¤t_segment_key,
1767 &new_segment_key);
1768 EXPECT_EQ(0, segment_index);
1769 EXPECT_EQ(0, candidate_index);
1770 EXPECT_EQ(kChars_Kokode, current_segment_key);
1771 EXPECT_EQ(kChars_Hakimonowo, new_segment_key);
1772
1773 EXPECT_COUNT_STATS("Commit", 1);
1774 // Suggestion is counted as Prediction.
1775 EXPECT_COUNT_STATS("CommitFromPrediction", 1);
1776 EXPECT_COUNT_STATS("PredictionCandidates0", 1);
1777 }
1778
TEST_F(SessionConverterTest,SuggestAndPredict)1779 TEST_F(SessionConverterTest, SuggestAndPredict) {
1780 SessionConverter converter(
1781 convertermock_.get(), request_.get(), config_.get());
1782 Segments segments;
1783 { // Initialize mock segments for suggestion
1784 segments.set_request_type(Segments::SUGGESTION);
1785 Segment *segment = segments.add_segment();
1786 Segment::Candidate *candidate;
1787 segment->set_key(kChars_Mo);
1788 candidate = segment->add_candidate();
1789 candidate->value = kChars_Mozukusu;
1790 candidate->content_key = kChars_Mozukusu;
1791 candidate = segment->add_candidate();
1792 candidate->value = kChars_Momonga;
1793 candidate->content_key = kChars_Momonga;
1794 }
1795 composer_->InsertCharacterPreedit(kChars_Mo);
1796
1797 // Suggestion
1798 convertermock_->SetStartSuggestionForRequest(&segments, true);
1799 EXPECT_TRUE(converter.Suggest(*composer_));
1800 std::vector<int> expected_indices;
1801 expected_indices.push_back(0);
1802 EXPECT_TRUE(IsCandidateListVisible(converter));
1803 EXPECT_TRUE(converter.IsActive());
1804 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1805
1806 { // Check the candidate list
1807 commands::Output output;
1808 converter.FillOutput(*composer_, &output);
1809 EXPECT_FALSE(output.has_result());
1810 EXPECT_TRUE(output.has_preedit());
1811 EXPECT_TRUE(output.has_candidates());
1812 EXPECT_TRUE(output.candidates().has_footer());
1813 #if defined(CHANNEL_DEV) && defined(GOOGLE_JAPANESE_INPUT_BUILD)
1814 EXPECT_FALSE(output.candidates().footer().has_label());
1815 EXPECT_TRUE(output.candidates().footer().has_sub_label());
1816 #else // CHANNEL_DEV && GOOGLE_JAPANESE_INPUT_BUILD
1817 EXPECT_TRUE(output.candidates().footer().has_label());
1818 EXPECT_FALSE(output.candidates().footer().has_sub_label());
1819 #endif // CHANNEL_DEV && GOOGLE_JAPANESE_INPUT_BUILD
1820 EXPECT_FALSE(output.candidates().footer().index_visible());
1821 EXPECT_FALSE(output.candidates().footer().logo_visible());
1822
1823 const commands::Candidates &candidates = output.candidates();
1824 EXPECT_EQ(2, candidates.size());
1825 EXPECT_EQ(kChars_Mozukusu, candidates.candidate(0).value());
1826 EXPECT_FALSE(candidates.has_focused_index());
1827 }
1828
1829 segments.Clear();
1830 { // Initialize mock segments for prediction
1831 segments.set_request_type(Segments::PREDICTION);
1832 Segment *segment = segments.add_segment();
1833 Segment::Candidate *candidate;
1834 segment->set_key(kChars_Mo);
1835 candidate = segment->add_candidate();
1836 candidate->value = kChars_Mozuku;
1837 candidate->content_key = kChars_Mozuku;
1838 candidate = segment->add_candidate();
1839 candidate->value = kChars_Momonga;
1840 candidate->content_key = kChars_Momonga;
1841 candidate = segment->add_candidate();
1842 candidate->value = "モンドリアン";
1843 candidate->content_key = "もんどりあん";
1844 }
1845
1846 // Prediction
1847 convertermock_->SetStartPredictionForRequest(&segments, true);
1848 EXPECT_TRUE(converter.Predict(*composer_));
1849 EXPECT_TRUE(IsCandidateListVisible(converter));
1850 EXPECT_TRUE(converter.IsActive());
1851 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1852
1853 // If there are suggestion results, the Prediction is not triggered.
1854 {
1855 commands::Output output;
1856 converter.FillOutput(*composer_, &output);
1857 EXPECT_FALSE(output.has_result());
1858 EXPECT_TRUE(output.has_preedit());
1859 EXPECT_TRUE(output.has_candidates());
1860 EXPECT_FALSE(output.candidates().footer().has_label());
1861 EXPECT_TRUE(output.candidates().footer().index_visible());
1862 EXPECT_TRUE(output.candidates().footer().logo_visible());
1863
1864 // Check the conversion
1865 const commands::Preedit &conversion = output.preedit();
1866 EXPECT_EQ(1, conversion.segment_size());
1867 EXPECT_EQ(kChars_Mozukusu, conversion.segment(0).value());
1868
1869 // Check the candidate list
1870 const commands::Candidates &candidates = output.candidates();
1871 // Candidates should be the same as suggestion
1872 EXPECT_EQ(2, candidates.size());
1873 EXPECT_EQ(kChars_Mozukusu, candidates.candidate(0).value());
1874 EXPECT_EQ(kChars_Momonga, candidates.candidate(1).value());
1875 EXPECT_TRUE(candidates.has_focused_index());
1876 EXPECT_EQ(0, candidates.focused_index());
1877 }
1878
1879 // Prediction is called
1880 converter.CandidateNext(*composer_);
1881 converter.CandidateNext(*composer_);
1882 expected_indices[0] += 2;
1883 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1884
1885 { // Check the candidate list
1886 commands::Output output;
1887 converter.FillOutput(*composer_, &output);
1888 EXPECT_FALSE(output.has_result());
1889 EXPECT_TRUE(output.has_preedit());
1890 EXPECT_TRUE(output.has_candidates());
1891
1892 const commands::Candidates &candidates = output.candidates();
1893 // Candidates should be merged with the previous suggestions.
1894 EXPECT_EQ(4, candidates.size());
1895 EXPECT_EQ(kChars_Mozukusu, candidates.candidate(0).value());
1896 EXPECT_EQ(kChars_Momonga, candidates.candidate(1).value());
1897 EXPECT_EQ(kChars_Mozuku, candidates.candidate(2).value());
1898 EXPECT_EQ("モンドリアン", candidates.candidate(3).value());
1899 EXPECT_TRUE(candidates.has_focused_index());
1900 }
1901
1902 // Select to "モンドリアン".
1903 converter.CandidateNext(*composer_);
1904 expected_indices[0] += 1;
1905 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1906 converter.Commit(*composer_, Context::default_instance());
1907 composer_->Reset();
1908 expected_indices.clear();
1909 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1910
1911 { // Check the submitted value
1912 commands::Output output;
1913 converter.FillOutput(*composer_, &output);
1914 EXPECT_TRUE(output.has_result());
1915 EXPECT_FALSE(output.has_preedit());
1916 EXPECT_FALSE(output.has_candidates());
1917
1918 const commands::Result &result = output.result();
1919 EXPECT_EQ("モンドリアン", result.value());
1920 EXPECT_EQ("もんどりあん", result.key());
1921 }
1922
1923 segments.Clear();
1924 { // Initialize mock segments for prediction
1925 segments.set_request_type(Segments::PREDICTION);
1926 Segment *segment = segments.add_segment();
1927 Segment::Candidate *candidate;
1928 segment->set_key(kChars_Mo);
1929 candidate = segment->add_candidate();
1930 candidate->value = kChars_Mozuku;
1931 candidate->content_key = kChars_Mozuku;
1932 candidate = segment->add_candidate();
1933 candidate->value = kChars_Momonga;
1934 candidate->content_key = kChars_Momonga;
1935 candidate = segment->add_candidate();
1936 candidate->value = "モンドリアン";
1937 candidate->content_key = "もんどりあん";
1938 }
1939
1940 // Prediction without suggestion.
1941 convertermock_->SetStartPredictionForRequest(&segments, true);
1942 expected_indices.push_back(0);
1943 EXPECT_TRUE(converter.Predict(*composer_));
1944 EXPECT_TRUE(converter.IsActive());
1945 EXPECT_SELECTED_CANDIDATE_INDICES_EQ(converter, expected_indices);
1946
1947 {
1948 commands::Output output;
1949 converter.FillOutput(*composer_, &output);
1950 EXPECT_FALSE(output.has_result());
1951 EXPECT_TRUE(output.has_preedit());
1952 EXPECT_TRUE(output.has_candidates());
1953
1954 // Check the conversion
1955 const commands::Preedit &conversion = output.preedit();
1956 EXPECT_EQ(1, conversion.segment_size());
1957 EXPECT_EQ(kChars_Mozuku, conversion.segment(0).value());
1958
1959 // Check the candidate list
1960 const commands::Candidates &candidates = output.candidates();
1961 // Candidates should NOT be merged with the previous suggestions.
1962 EXPECT_EQ(3, candidates.size());
1963 EXPECT_EQ(kChars_Mozuku, candidates.candidate(0).value());
1964 EXPECT_EQ(kChars_Momonga, candidates.candidate(1).value());
1965 EXPECT_EQ("モンドリアン", candidates.candidate(2).value());
1966 EXPECT_TRUE(candidates.has_focused_index());
1967 }
1968 }
1969
TEST_F(SessionConverterTest,SuppressSuggestionOnPasswordField)1970 TEST_F(SessionConverterTest, SuppressSuggestionOnPasswordField) {
1971 SessionConverter converter(
1972 convertermock_.get(), request_.get(), config_.get());
1973 Segments segments;
1974 { // Initialize mock segments for suggestion
1975 segments.set_request_type(Segments::SUGGESTION);
1976 Segment *segment = segments.add_segment();
1977 Segment::Candidate *candidate;
1978 segment->set_key(kChars_Mo);
1979 candidate = segment->add_candidate();
1980 candidate->value = kChars_Mozukusu;
1981 candidate->content_key = kChars_Mozukusu;
1982 candidate = segment->add_candidate();
1983 candidate->value = kChars_Momonga;
1984 candidate->content_key = kChars_Momonga;
1985 }
1986 composer_->InsertCharacterPreedit(kChars_Mo);
1987
1988 // Suggestion
1989 convertermock_->SetStartSuggestionForRequest(&segments, true);
1990 // No candidates should be visible because we are on password field.
1991
1992 ConversionPreferences conversion_preferences =
1993 converter.conversion_preferences();
1994 conversion_preferences.request_suggestion = false;
1995 EXPECT_FALSE(converter.SuggestWithPreferences(
1996 *composer_, conversion_preferences));
1997 EXPECT_FALSE(IsCandidateListVisible(converter));
1998 EXPECT_FALSE(converter.IsActive());
1999 }
2000
TEST_F(SessionConverterTest,SuppressSuggestion)2001 TEST_F(SessionConverterTest, SuppressSuggestion) {
2002 SessionConverter converter(
2003 convertermock_.get(), request_.get(), config_.get());
2004 Segments segments;
2005 { // Initialize mock segments for suggestion
2006 segments.set_request_type(Segments::SUGGESTION);
2007 Segment *segment = segments.add_segment();
2008 Segment::Candidate *candidate;
2009 segment->set_key(kChars_Mo);
2010 candidate = segment->add_candidate();
2011 candidate->value = kChars_Mozukusu;
2012 candidate->content_key = kChars_Mozukusu;
2013 candidate = segment->add_candidate();
2014 candidate->value = kChars_Momonga;
2015 candidate->content_key = kChars_Momonga;
2016 }
2017 composer_->SetInputFieldType(Context::PASSWORD);
2018 composer_->InsertCharacterPreedit(kChars_Mo);
2019
2020 // Suggestion
2021 convertermock_->SetStartSuggestionForRequest(&segments, true);
2022 // No candidates should be visible because we are on password field.
2023 EXPECT_FALSE(converter.Suggest(*composer_));
2024 EXPECT_FALSE(IsCandidateListVisible(converter));
2025 EXPECT_FALSE(converter.IsActive());
2026 }
2027
TEST_F(SessionConverterTest,ExpandPartialSuggestion)2028 TEST_F(SessionConverterTest, ExpandPartialSuggestion) {
2029 RequestForUnitTest::FillMobileRequest(request_.get());
2030 SessionConverter converter(
2031 convertermock_.get(), request_.get(), config_.get());
2032
2033 const char *kSuggestionValues[] = {
2034 "S0",
2035 "S1",
2036 "S2",
2037 };
2038 const char *kPredictionValues[] = {
2039 "P0",
2040 "P1",
2041 "P2",
2042 // Duplicate entry. Any dupulication should not exist
2043 // in the candidate list.
2044 "S1",
2045 "P3",
2046 };
2047 const char kPredictionKey[] = "left";
2048 const char kSuffixKey[] = "right";
2049 const int kDupulicationIndex = 3;
2050
2051 Segments segments;
2052 { // Initialize mock segments for suggestion
2053 segments.set_request_type(Segments::PARTIAL_SUGGESTION);
2054 Segment *segment = segments.add_segment();
2055 Segment::Candidate *candidate;
2056 segment->set_key(kPredictionKey);
2057 for (size_t i = 0; i < arraysize(kSuggestionValues); ++i) {
2058 candidate = segment->add_candidate();
2059 candidate->value = kSuggestionValues[i];
2060 candidate->content_key = kPredictionKey;
2061 }
2062 }
2063 composer_->InsertCharacterPreedit(
2064 string(kPredictionKey) + string(kSuffixKey));
2065 composer_->MoveCursorTo(Util::CharsLen(string(kPredictionKey)));
2066
2067 // Partial Suggestion
2068 convertermock_->SetStartPartialSuggestionForRequest(&segments, true);
2069 EXPECT_TRUE(converter.Suggest(*composer_));
2070 EXPECT_TRUE(IsCandidateListVisible(converter));
2071 EXPECT_TRUE(converter.IsActive());
2072
2073 segments.Clear();
2074 {
2075 // Initialize mock segments for partial prediction for expanding suggestion.
2076 segments.set_request_type(Segments::PARTIAL_PREDICTION);
2077 Segment *segment = segments.add_segment();
2078 Segment::Candidate *candidate;
2079 segment->set_key(kPredictionKey);
2080 for (size_t i = 0; i < arraysize(kPredictionValues); ++i) {
2081 candidate = segment->add_candidate();
2082 candidate->value = kPredictionValues[i];
2083 candidate->content_key = kPredictionKey;
2084 }
2085 }
2086 // Expand suggestion candidate (cursor == HEAD)
2087 composer_->MoveCursorTo(0);
2088 convertermock_->SetStartPartialPrediction(&segments, false);
2089 convertermock_->SetStartPredictionForRequest(&segments, true);
2090 EXPECT_TRUE(converter.ExpandSuggestion(*composer_));
2091 EXPECT_TRUE(IsCandidateListVisible(converter));
2092 EXPECT_TRUE(converter.IsActive());
2093
2094 // Expand suggestion candidate (cursor == TAIL)
2095 composer_->MoveCursorTo(composer_->GetLength());
2096 convertermock_->SetStartPredictionForRequest(&segments, false);
2097 convertermock_->SetStartPartialPrediction(&segments, false);
2098 convertermock_->SetStartPartialPredictionForRequest(&segments, true);
2099 EXPECT_TRUE(converter.ExpandSuggestion(*composer_));
2100 EXPECT_TRUE(IsCandidateListVisible(converter));
2101 EXPECT_TRUE(converter.IsActive());
2102 { // Check the candidate list
2103 commands::Output output;
2104 converter.FillOutput(*composer_, &output);
2105 const commands::Candidates &candidates = output.candidates();
2106 EXPECT_EQ(commands::SUGGESTION, candidates.category());
2107 EXPECT_EQ(commands::SUGGESTION, output.all_candidate_words().category());
2108 // -1 is for duplicate entry.
2109 EXPECT_EQ(arraysize(kSuggestionValues) + arraysize(kPredictionValues) - 1,
2110 candidates.size());
2111 size_t i;
2112 for (i = 0; i < arraysize(kSuggestionValues); ++i) {
2113 EXPECT_EQ(kSuggestionValues[i], candidates.candidate(i).value());
2114 }
2115
2116 // -1 is for duplicate entry.
2117 for (; i < candidates.size(); ++i) {
2118 size_t index_in_prediction = i - arraysize(kSuggestionValues);
2119 if (index_in_prediction >= kDupulicationIndex) {
2120 ++index_in_prediction;
2121 }
2122 EXPECT_EQ(kPredictionValues[index_in_prediction],
2123 candidates.candidate(i).value());
2124 }
2125 }
2126 }
2127
TEST_F(SessionConverterTest,ExpandSuggestion)2128 TEST_F(SessionConverterTest, ExpandSuggestion) {
2129 RequestForUnitTest::FillMobileRequest(request_.get());
2130 SessionConverter converter(
2131 convertermock_.get(), request_.get(), config_.get());
2132
2133 const char *kSuggestionValues[] = {
2134 "S0",
2135 "S1",
2136 "S2",
2137 };
2138 const char *kPredictionValues[] = {
2139 "P0",
2140 "P1",
2141 "P2",
2142 // Duplicate entry. Any dupulication should not exist
2143 // in the candidate list.
2144 "S1",
2145 "P3",
2146 };
2147 const char kKey[] = "key";
2148 const int kDupulicationIndex = 3;
2149
2150 Segments segments;
2151 { // Initialize mock segments for suggestion
2152 segments.set_request_type(Segments::SUGGESTION);
2153 Segment *segment = segments.add_segment();
2154 Segment::Candidate *candidate;
2155 segment->set_key(kKey);
2156 for (size_t i = 0; i < arraysize(kSuggestionValues); ++i) {
2157 candidate = segment->add_candidate();
2158 candidate->value = kSuggestionValues[i];
2159 candidate->content_key = kKey;
2160 }
2161 }
2162 composer_->InsertCharacterPreedit(kKey);
2163
2164 // Suggestion
2165 convertermock_->SetStartSuggestionForRequest(&segments, true);
2166 EXPECT_TRUE(converter.Suggest(*composer_));
2167 EXPECT_TRUE(IsCandidateListVisible(converter));
2168 EXPECT_TRUE(converter.IsActive());
2169 { // Check the candidate list
2170 commands::Output output;
2171 converter.FillOutput(*composer_, &output);
2172 const commands::Candidates &candidates = output.candidates();
2173 EXPECT_EQ(commands::SUGGESTION, candidates.category());
2174 EXPECT_EQ(commands::SUGGESTION, output.all_candidate_words().category());
2175 EXPECT_EQ(arraysize(kSuggestionValues), candidates.size());
2176 for (size_t i = 0; i < arraysize(kSuggestionValues); ++i) {
2177 EXPECT_EQ(kSuggestionValues[i], candidates.candidate(i).value());
2178 }
2179 }
2180
2181 segments.Clear();
2182 { // Initialize mock segments for prediction (== expanding suggestion)
2183 segments.set_request_type(Segments::PREDICTION);
2184 Segment *segment = segments.add_segment();
2185 Segment::Candidate *candidate;
2186 segment->set_key(kKey);
2187 for (size_t i = 0; i < arraysize(kPredictionValues); ++i) {
2188 candidate = segment->add_candidate();
2189 candidate->value = kPredictionValues[i];
2190 candidate->content_key = kKey;
2191 }
2192 }
2193 // Expand suggestion candidate
2194 convertermock_->SetStartPredictionForRequest(&segments, true);
2195 EXPECT_TRUE(converter.ExpandSuggestion(*composer_));
2196 EXPECT_TRUE(IsCandidateListVisible(converter));
2197 EXPECT_TRUE(converter.IsActive());
2198 { // Check the candidate list
2199 commands::Output output;
2200 converter.FillOutput(*composer_, &output);
2201 const commands::Candidates &candidates = output.candidates();
2202 EXPECT_EQ(commands::SUGGESTION, candidates.category());
2203 EXPECT_EQ(commands::SUGGESTION, output.all_candidate_words().category());
2204 // -1 is for duplicate entry.
2205 EXPECT_EQ(arraysize(kSuggestionValues) + arraysize(kPredictionValues) - 1,
2206 candidates.size());
2207 size_t i;
2208 for (i = 0; i < arraysize(kSuggestionValues); ++i) {
2209 EXPECT_EQ(kSuggestionValues[i], candidates.candidate(i).value());
2210 }
2211
2212 // -1 is for duplicate entry.
2213 for (; i < candidates.size(); ++i) {
2214 size_t index_in_prediction = i - arraysize(kSuggestionValues);
2215 if (index_in_prediction >= kDupulicationIndex) {
2216 ++index_in_prediction;
2217 }
2218 EXPECT_EQ(kPredictionValues[index_in_prediction],
2219 candidates.candidate(i).value());
2220 }
2221 }
2222 }
2223
TEST_F(SessionConverterTest,AppendCandidateList)2224 TEST_F(SessionConverterTest, AppendCandidateList) {
2225 SessionConverter converter(
2226 convertermock_.get(), request_.get(), config_.get());
2227 SetState(SessionConverterInterface::CONVERSION, &converter);
2228 converter.set_use_cascading_window(true);
2229 Segments segments;
2230
2231 {
2232 SetAiueo(&segments);
2233 FillT13Ns(&segments, composer_.get());
2234
2235 SetSegments(segments, &converter);
2236 AppendCandidateList(&converter);
2237 const CandidateList &candidate_list = GetCandidateList(converter);
2238 // 3 == hiragana cand, katakana cand and sub candidate list.
2239 EXPECT_EQ(3, candidate_list.size());
2240 EXPECT_TRUE(candidate_list.focused());
2241 size_t sub_cand_list_count = 0;
2242 for (size_t i = 0; i < candidate_list.size(); ++i) {
2243 if (candidate_list.candidate(i).IsSubcandidateList()) {
2244 ++sub_cand_list_count;
2245 }
2246 }
2247 // Sub candidate list for T13N.
2248 EXPECT_EQ(1, sub_cand_list_count);
2249 }
2250 {
2251 Segment *segment = segments.mutable_conversion_segment(0);
2252 Segment::Candidate *candidate = segment->add_candidate();
2253 candidate->value = "あいうえお_2";
2254 // New meta candidates.
2255 // They should be ignored.
2256 std::vector<Segment::Candidate> *meta_candidates =
2257 segment->mutable_meta_candidates();
2258 meta_candidates->clear();
2259 meta_candidates->resize(1);
2260 meta_candidates->at(0).Init();
2261 meta_candidates->at(0).value = "t13nValue";
2262 meta_candidates->at(0).content_value = "t13nValue";
2263 meta_candidates->at(0).content_key = segment->key();
2264
2265 SetSegments(segments, &converter);
2266 AppendCandidateList(&converter);
2267 const CandidateList &candidate_list = GetCandidateList(converter);
2268 // 4 == hiragana cand, katakana cand, hiragana cand2
2269 // and sub candidate list.
2270 EXPECT_EQ(4, candidate_list.size());
2271 EXPECT_TRUE(candidate_list.focused());
2272 size_t sub_cand_list_count = 0;
2273 std::set<int> id_set;
2274 for (size_t i = 0; i < candidate_list.size(); ++i) {
2275 if (candidate_list.candidate(i).IsSubcandidateList()) {
2276 ++sub_cand_list_count;
2277 } else {
2278 // No duplicate ids are expected.
2279 int id = candidate_list.candidate(i).id();
2280 // EXPECT_EQ(iterator, iterator) might cause compile error in specific
2281 // environment.
2282 EXPECT_TRUE(id_set.end() == id_set.find(id));
2283 id_set.insert(id);
2284 }
2285 }
2286 // Sub candidate list shouldn't be duplicated.
2287 EXPECT_EQ(1, sub_cand_list_count);
2288 }
2289 }
2290
TEST_F(SessionConverterTest,AppendCandidateListForRequestTypes)2291 TEST_F(SessionConverterTest, AppendCandidateListForRequestTypes) {
2292 SessionConverter converter(
2293 convertermock_.get(), request_.get(), config_.get());
2294 SetState(SessionConverterInterface::SUGGESTION, &converter);
2295 Segments segments;
2296
2297 {
2298 SetAiueo(&segments);
2299 FillT13Ns(&segments, composer_.get());
2300 segments.set_request_type(Segments::SUGGESTION);
2301 SetSegments(segments, &converter);
2302 AppendCandidateList(&converter);
2303 const CandidateList &candidate_list = GetCandidateList(converter);
2304 EXPECT_FALSE(candidate_list.focused());
2305 }
2306
2307 segments.Clear();
2308 {
2309 SetAiueo(&segments);
2310 FillT13Ns(&segments, composer_.get());
2311 segments.set_request_type(Segments::PARTIAL_SUGGESTION);
2312 SetSegments(segments, &converter);
2313 AppendCandidateList(&converter);
2314 const CandidateList &candidate_list = GetCandidateList(converter);
2315 EXPECT_FALSE(candidate_list.focused());
2316 }
2317
2318 segments.Clear();
2319 {
2320 SetAiueo(&segments);
2321 FillT13Ns(&segments, composer_.get());
2322 segments.set_request_type(Segments::PARTIAL_PREDICTION);
2323 SetSegments(segments, &converter);
2324 AppendCandidateList(&converter);
2325 const CandidateList &candidate_list = GetCandidateList(converter);
2326 EXPECT_FALSE(candidate_list.focused());
2327 }
2328 }
2329
TEST_F(SessionConverterTest,ReloadConfig)2330 TEST_F(SessionConverterTest, ReloadConfig) {
2331 SessionConverter converter(
2332 convertermock_.get(), request_.get(), config_.get());
2333 Segments segments;
2334 SetAiueo(&segments);
2335 FillT13Ns(&segments, composer_.get());
2336 convertermock_->SetStartConversionForRequest(&segments, true);
2337
2338 composer_->InsertCharacterPreedit("aiueo");
2339 EXPECT_TRUE(converter.Convert(*composer_));
2340 converter.SetCandidateListVisible(true);
2341
2342 { // Set OperationPreferences
2343 converter.set_use_cascading_window(false);
2344 converter.set_selection_shortcut(config::Config::SHORTCUT_123456789);
2345 EXPECT_TRUE(IsCandidateListVisible(converter));
2346 }
2347 { // Check the config update
2348 commands::Output output;
2349 converter.FillOutput(*composer_, &output);
2350 EXPECT_FALSE(output.has_result());
2351 EXPECT_TRUE(output.has_preedit());
2352 EXPECT_TRUE(output.has_candidates());
2353
2354 const commands::Candidates &candidates = output.candidates();
2355 EXPECT_EQ("1", candidates.candidate(0).annotation().shortcut());
2356 EXPECT_EQ("2", candidates.candidate(1).annotation().shortcut());
2357 }
2358
2359 { // Set OperationPreferences #2
2360 converter.set_use_cascading_window(false);
2361 converter.set_selection_shortcut(config::Config::NO_SHORTCUT);
2362 }
2363 { // Check the config update
2364 commands::Output output;
2365 converter.FillOutput(*composer_, &output);
2366 EXPECT_FALSE(output.has_result());
2367 EXPECT_TRUE(output.has_preedit());
2368 EXPECT_TRUE(output.has_candidates());
2369
2370 const commands::Candidates &candidates = output.candidates();
2371 EXPECT_TRUE(candidates.candidate(0).annotation().shortcut().empty());
2372 EXPECT_TRUE(candidates.candidate(1).annotation().shortcut().empty());
2373 }
2374 }
2375
TEST_F(SessionConverterTest,OutputAllCandidateWords)2376 TEST_F(SessionConverterTest, OutputAllCandidateWords) {
2377 SessionConverter converter(
2378 convertermock_.get(), request_.get(), config_.get());
2379 Segments segments;
2380 SetKamaboko(&segments);
2381 const string kKamabokono = "かまぼこの";
2382 const string kInbou = "いんぼう";
2383 composer_->InsertCharacterPreedit(kKamabokono + kInbou);
2384
2385 FillT13Ns(&segments, composer_.get());
2386 convertermock_->SetStartConversionForRequest(&segments, true);
2387
2388 commands::Output output;
2389
2390 EXPECT_TRUE(converter.Convert(*composer_));
2391 {
2392 ASSERT_TRUE(converter.IsActive());
2393 EXPECT_FALSE(IsCandidateListVisible(converter));
2394
2395 output.Clear();
2396 converter.PopOutput(*composer_, &output);
2397 EXPECT_FALSE(output.has_result());
2398 EXPECT_TRUE(output.has_preedit());
2399 EXPECT_FALSE(output.has_candidates());
2400 EXPECT_TRUE(output.has_all_candidate_words());
2401
2402 EXPECT_EQ(0, output.all_candidate_words().focused_index());
2403 EXPECT_EQ(commands::CONVERSION, output.all_candidate_words().category());
2404 // [ "かまぼこの", "カマボコの", "カマボコノ" (t13n), "かまぼこの" (t13n),
2405 // "カマボコノ" (t13n) ]
2406 EXPECT_EQ(5, output.all_candidate_words().candidates_size());
2407 }
2408
2409 converter.CandidateNext(*composer_);
2410 {
2411 ASSERT_TRUE(converter.IsActive());
2412 EXPECT_TRUE(IsCandidateListVisible(converter));
2413
2414 output.Clear();
2415 converter.PopOutput(*composer_, &output);
2416 EXPECT_FALSE(output.has_result());
2417 EXPECT_TRUE(output.has_preedit());
2418 EXPECT_TRUE(output.has_candidates());
2419 EXPECT_TRUE(output.has_all_candidate_words());
2420
2421 EXPECT_EQ(1, output.all_candidate_words().focused_index());
2422 EXPECT_EQ(commands::CONVERSION, output.all_candidate_words().category());
2423 // [ "かまぼこの", "カマボコの", "カマボコノ" (t13n), "かまぼこの" (t13n),
2424 // "カマボコノ" (t13n) ]
2425 EXPECT_EQ(5, output.all_candidate_words().candidates_size());
2426 }
2427
2428 converter.SegmentFocusRight();
2429 {
2430 ASSERT_TRUE(converter.IsActive());
2431 EXPECT_FALSE(IsCandidateListVisible(converter));
2432
2433 output.Clear();
2434 converter.PopOutput(*composer_, &output);
2435 EXPECT_FALSE(output.has_result());
2436 EXPECT_TRUE(output.has_preedit());
2437 EXPECT_FALSE(output.has_candidates());
2438 EXPECT_TRUE(output.has_all_candidate_words());
2439
2440 EXPECT_EQ(0, output.all_candidate_words().focused_index());
2441 EXPECT_EQ(commands::CONVERSION, output.all_candidate_words().category());
2442 // [ "陰謀", "印房", "インボウ" (t13n), "いんぼう" (t13n), "インボウ" (t13n) ]
2443 EXPECT_EQ(5, output.all_candidate_words().candidates_size());
2444 }
2445 }
2446
TEST_F(SessionConverterTest,GetPreeditAndGetConversion)2447 TEST_F(SessionConverterTest, GetPreeditAndGetConversion) {
2448 Segments segments;
2449
2450 Segment *segment;
2451 Segment::Candidate *candidate;
2452
2453 segment = segments.add_segment();
2454 segment->set_segment_type(Segment::HISTORY);
2455 segment->set_key("[key:history1]");
2456 candidate = segment->add_candidate();
2457 candidate->content_key = "[content_key:history1-1]";
2458 candidate = segment->add_candidate();
2459 candidate->content_key = "[content_key:history1-2]";
2460
2461 segment = segments.add_segment();
2462 segment->set_segment_type(Segment::FREE);
2463 segment->set_key("[key:conversion1]");
2464 candidate = segment->add_candidate();
2465 candidate->key = "[key:conversion1-1]";
2466 candidate->content_key = "[content_key:conversion1-1]";
2467 candidate->value = "[value:conversion1-1]";
2468 candidate = segment->add_candidate();
2469 candidate->key = "[key:conversion1-2]";
2470 candidate->content_key = "[content_key:conversion1-2]";
2471 candidate->value = "[value:conversion1-2]";
2472 {
2473 // PREDICTION
2474 SessionConverter converter(
2475 convertermock_.get(), request_.get(), config_.get());
2476 convertermock_->SetStartPredictionForRequest(&segments, true);
2477 converter.Predict(*composer_);
2478 converter.CandidateNext(*composer_);
2479 string preedit;
2480 GetPreedit(converter, 0, 1, &preedit);
2481 EXPECT_EQ("[content_key:conversion1-2]", preedit);
2482 string conversion;
2483 GetConversion(converter, 0, 1, &conversion);
2484 EXPECT_EQ("[value:conversion1-2]", conversion);
2485 }
2486 {
2487 // SUGGESTION
2488 SessionConverter converter(
2489 convertermock_.get(), request_.get(), config_.get());
2490 convertermock_->SetStartSuggestionForRequest(&segments, true);
2491 converter.Suggest(*composer_);
2492 string preedit;
2493 GetPreedit(converter, 0, 1, &preedit);
2494 EXPECT_EQ("[content_key:conversion1-1]", preedit);
2495 string conversion;
2496 GetConversion(converter, 0, 1, &conversion);
2497 EXPECT_EQ("[value:conversion1-1]", conversion);
2498 }
2499 segment = segments.add_segment();
2500 segment->set_segment_type(Segment::FREE);
2501 segment->set_key("[key:conversion2]");
2502 candidate = segment->add_candidate();
2503 candidate->key = "[key:conversion2-1]";
2504 candidate->content_key = "[content_key:conversion2-1]";
2505 candidate->value = "[value:conversion2-1]";
2506 candidate = segment->add_candidate();
2507 candidate->key = "[key:conversion2-2]";
2508 candidate->content_key = "[content_key:conversion2-2]";
2509 candidate->value = "[value:conversion2-2]";
2510 {
2511 // CONVERSION
2512 SessionConverter converter(
2513 convertermock_.get(), request_.get(), config_.get());
2514 convertermock_->SetStartConversionForRequest(&segments, true);
2515 converter.Convert(*composer_);
2516 converter.CandidateNext(*composer_);
2517 string preedit;
2518 GetPreedit(converter, 0, 2, &preedit);
2519 EXPECT_EQ("[key:conversion1][key:conversion2]", preedit);
2520 string conversion;
2521 GetConversion(converter, 0, 2, &conversion);
2522 EXPECT_EQ("[value:conversion1-2][value:conversion2-1]", conversion);
2523 }
2524 }
2525
TEST_F(SessionConverterTest,GetAndSetSegments)2526 TEST_F(SessionConverterTest, GetAndSetSegments) {
2527 SessionConverter converter(
2528 convertermock_.get(), request_.get(), config_.get());
2529 Segments segments;
2530
2531 // Set history segments.
2532 const string kHistoryInput[] = {"車で", "行く"};
2533 for (size_t i = 0; i < arraysize(kHistoryInput); ++i) {
2534 Segment *segment = segments.add_segment();
2535 segment->set_segment_type(Segment::HISTORY);
2536 Segment::Candidate *candidate = segment->add_candidate();
2537 candidate->value = kHistoryInput[i];
2538 }
2539 convertermock_->SetFinishConversion(&segments, true);
2540 converter.CommitPreedit(*composer_, Context::default_instance());
2541
2542 Segments src;
2543 GetSegments(converter, &src);
2544 ASSERT_EQ(2, src.history_segments_size());
2545 EXPECT_EQ("車で", src.history_segment(0).candidate(0).value);
2546 EXPECT_EQ("行く", src.history_segment(1).candidate(0).value);
2547
2548 src.mutable_history_segment(0)->mutable_candidate(0)->value = "歩いて";
2549 Segment *segment = src.add_segment();
2550 segment->set_segment_type(Segment::FREE);
2551 Segment::Candidate *candidate = segment->add_candidate();
2552 candidate->value = "?";
2553
2554 SetSegments(src, &converter);
2555
2556 Segments dest;
2557 GetSegments(converter, &dest);
2558
2559 ASSERT_EQ(2, dest.history_segments_size());
2560 ASSERT_EQ(1, dest.conversion_segments_size());
2561 EXPECT_EQ(src.history_segment(0).candidate(0).value,
2562 dest.history_segment(0).candidate(0).value);
2563 EXPECT_EQ(src.history_segment(1).candidate(0).value,
2564 dest.history_segment(1).candidate(0).value);
2565 EXPECT_EQ(src.history_segment(2).candidate(0).value,
2566 dest.history_segment(2).candidate(0).value);
2567 }
2568
TEST_F(SessionConverterTest,Clone)2569 TEST_F(SessionConverterTest, Clone) {
2570 SessionConverter src(
2571 convertermock_.get(), request_.get(), config_.get());
2572
2573 const string kKamabokono = "かまぼこの";
2574 const string kInbou = "いんぼう";
2575 const string kInbouKanji = "陰謀";
2576
2577 { // create source converter
2578 Segments segments;
2579 SetKamaboko(&segments);
2580
2581 convertermock_->SetStartConversionForRequest(&segments, true);
2582
2583 src.set_use_cascading_window(false);
2584 src.set_selection_shortcut(config::Config::SHORTCUT_123456789);
2585 }
2586
2587 { // validation
2588 // Copy and validate
2589 std::unique_ptr<SessionConverter> dest(src.Clone());
2590 ASSERT_TRUE(dest.get() != NULL);
2591 ExpectSameSessionConverter(src, *dest);
2592
2593 // Convert source
2594 EXPECT_TRUE(src.Convert(*composer_));
2595 EXPECT_TRUE(src.IsActive());
2596
2597 // Convert destination and validate
2598 EXPECT_TRUE(dest->Convert(*composer_));
2599 ExpectSameSessionConverter(src, *dest);
2600
2601 // Copy converted and validate
2602 dest.reset(src.Clone());
2603 ASSERT_TRUE(dest.get() != NULL);
2604 ExpectSameSessionConverter(src, *dest);
2605 }
2606 }
2607
2608 // Suggest() in the suggestion state was not accepted. (http://b/1948334)
TEST_F(SessionConverterTest,Issue1948334)2609 TEST_F(SessionConverterTest, Issue1948334) {
2610 SessionConverter converter(
2611 convertermock_.get(), request_.get(), config_.get());
2612 Segments segments;
2613 { // Initialize mock segments for the first suggestion
2614 segments.set_request_type(Segments::SUGGESTION);
2615 Segment *segment = segments.add_segment();
2616 Segment::Candidate *candidate;
2617 segment->set_key(kChars_Mo);
2618 candidate = segment->add_candidate();
2619 candidate->value = kChars_Mozukusu;
2620 candidate->content_key = kChars_Mozukusu;
2621 candidate = segment->add_candidate();
2622 candidate->value = kChars_Momonga;
2623 candidate->content_key = kChars_Momonga;
2624 }
2625 composer_->InsertCharacterPreedit(kChars_Mo);
2626
2627 // Suggestion
2628 convertermock_->SetStartSuggestionForRequest(&segments, true);
2629 EXPECT_TRUE(converter.Suggest(*composer_));
2630 EXPECT_TRUE(converter.IsActive());
2631
2632 segments.Clear();
2633 { // Initialize mock segments for the second suggestion
2634 segments.set_request_type(Segments::SUGGESTION);
2635 Segment *segment = segments.add_segment();
2636 Segment::Candidate *candidate;
2637 segment->set_key("もず");
2638 candidate = segment->add_candidate();
2639 candidate->value = kChars_Mozukusu;
2640 candidate->content_key = kChars_Mozukusu;
2641 }
2642 composer_->InsertCharacterPreedit("もず");
2643
2644 // Suggestion
2645 convertermock_->SetStartSuggestionForRequest(&segments, true);
2646 EXPECT_TRUE(converter.Suggest(*composer_));
2647 EXPECT_TRUE(converter.IsActive());
2648
2649 { // Check the candidate list
2650 commands::Output output;
2651 converter.FillOutput(*composer_, &output);
2652 EXPECT_FALSE(output.has_result());
2653 EXPECT_TRUE(output.has_preedit());
2654 EXPECT_TRUE(output.has_candidates());
2655
2656 const commands::Candidates &candidates = output.candidates();
2657 // Candidates should be merged with the previous suggestions.
2658 EXPECT_EQ(1, candidates.size());
2659 EXPECT_EQ(kChars_Mozukusu, candidates.candidate(0).value());
2660 EXPECT_FALSE(candidates.has_focused_index());
2661 }
2662 }
2663
TEST_F(SessionConverterTest,Issue1960362)2664 TEST_F(SessionConverterTest, Issue1960362) {
2665 // Testcase against http://b/1960362, a candidate list was not
2666 // updated when ConvertToTransliteration changed the size of segments.
2667
2668 table_->AddRule("zyu", "ZYU", "");
2669 table_->AddRule("jyu", "ZYU", "");
2670 table_->AddRule("tt", "XTU", "t");
2671 table_->AddRule("ta", "TA", "");
2672
2673 composer_->InsertCharacter("j");
2674 composer_->InsertCharacter("y");
2675 composer_->InsertCharacter("u");
2676 composer_->InsertCharacter("t");
2677
2678 SessionConverter converter(
2679 convertermock_.get(), request_.get(), config_.get());
2680
2681 Segments segments;
2682 {
2683 segments.set_request_type(Segments::CONVERSION);
2684 Segment *segment;
2685 Segment::Candidate *candidate;
2686
2687 segment = segments.add_segment();
2688 segment->set_key("ZYU");
2689 candidate = segment->add_candidate();
2690 candidate->value = "[ZYU]";
2691 candidate->content_key = "[ZYU]";
2692
2693 segment = segments.add_segment();
2694 segment->set_key("t");
2695 candidate = segment->add_candidate();
2696 candidate->value = "[t]";
2697 candidate->content_key = "[t]";
2698 }
2699
2700 Segments resized_segments;
2701 {
2702 resized_segments.set_request_type(Segments::CONVERSION);
2703 Segment *segment = resized_segments.add_segment();
2704 Segment::Candidate *candidate;
2705 segment->set_key("ZYUt");
2706 candidate = segment->add_candidate();
2707 candidate->value = "[ZYUt]";
2708 candidate->content_key = "[ZYUt]";
2709 }
2710
2711 FillT13Ns(&segments, composer_.get());
2712 convertermock_->SetStartConversionForRequest(&segments, true);
2713 FillT13Ns(&resized_segments, composer_.get());
2714 convertermock_->SetResizeSegment1(&resized_segments, true);
2715 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
2716 transliteration::HALF_ASCII));
2717 EXPECT_FALSE(IsCandidateListVisible(converter));
2718
2719 commands::Output output;
2720 converter.FillOutput(*composer_, &output);
2721 EXPECT_FALSE(output.has_result());
2722 EXPECT_TRUE(output.has_preedit());
2723 EXPECT_FALSE(output.has_candidates());
2724
2725 const commands::Preedit &conversion = output.preedit();
2726 EXPECT_EQ("jyut", conversion.segment(0).value());
2727 }
2728
TEST_F(SessionConverterTest,Issue1978201)2729 TEST_F(SessionConverterTest, Issue1978201) {
2730 // This is a unittest against http://b/1978201
2731 SessionConverter converter(
2732 convertermock_.get(), request_.get(), config_.get());
2733 Segments segments;
2734 composer_->InsertCharacterPreedit(kChars_Mo);
2735
2736 { // Initialize mock segments for prediction
2737 segments.set_request_type(Segments::PREDICTION);
2738 Segment *segment = segments.add_segment();
2739 Segment::Candidate *candidate;
2740 segment->set_key(kChars_Mo);
2741 candidate = segment->add_candidate();
2742 candidate->value = kChars_Mozuku;
2743 candidate->content_key = kChars_Mozuku;
2744 candidate = segment->add_candidate();
2745 candidate->value = kChars_Momonga;
2746 candidate->content_key = kChars_Momonga;
2747 }
2748
2749 // Prediction
2750 convertermock_->SetStartPredictionForRequest(&segments, true);
2751 EXPECT_TRUE(converter.Predict(*composer_));
2752 EXPECT_TRUE(converter.IsActive());
2753
2754 { // Check the conversion
2755 commands::Output output;
2756 converter.FillOutput(*composer_, &output);
2757 EXPECT_FALSE(output.has_result());
2758 EXPECT_TRUE(output.has_preedit());
2759 EXPECT_TRUE(output.has_candidates());
2760
2761 const commands::Preedit &conversion = output.preedit();
2762 EXPECT_EQ(1, conversion.segment_size());
2763 EXPECT_EQ(kChars_Mozuku, conversion.segment(0).value());
2764 }
2765
2766 // Meaningless segment manipulations.
2767 converter.SegmentWidthShrink(*composer_);
2768 converter.SegmentFocusLeft();
2769 converter.SegmentFocusLast();
2770
2771 { // Check the conversion again
2772 commands::Output output;
2773 converter.FillOutput(*composer_, &output);
2774 EXPECT_FALSE(output.has_result());
2775 EXPECT_TRUE(output.has_preedit());
2776 EXPECT_FALSE(output.has_candidates());
2777
2778 const commands::Preedit &conversion = output.preedit();
2779 EXPECT_EQ(1, conversion.segment_size());
2780 EXPECT_EQ(kChars_Mozuku, conversion.segment(0).value());
2781 }
2782 }
2783
TEST_F(SessionConverterTest,Issue1981020)2784 TEST_F(SessionConverterTest, Issue1981020) {
2785 SessionConverter converter(
2786 convertermock_.get(), request_.get(), config_.get());
2787 // "〜〜〜〜" U+301C * 4
2788 const string wave_dash_301c = "〜〜〜〜";
2789 composer_->InsertCharacterPreedit(wave_dash_301c);
2790 Segments segments;
2791 convertermock_->SetFinishConversion(&segments, true);
2792 converter.CommitPreedit(*composer_, Context::default_instance());
2793 convertermock_->GetFinishConversion(&segments);
2794
2795 #ifdef OS_WIN
2796 // "~~~~" U+FF5E * 4
2797 const string fullwidth_tilde_ff5e = "~~~~";
2798 EXPECT_EQ(fullwidth_tilde_ff5e,
2799 segments.conversion_segment(0).candidate(0).value);
2800 EXPECT_EQ(fullwidth_tilde_ff5e,
2801 segments.conversion_segment(0).candidate(0).content_value);
2802 #else // OS_WIN
2803 EXPECT_EQ(wave_dash_301c,
2804 segments.conversion_segment(0).candidate(0).value);
2805 EXPECT_EQ(wave_dash_301c,
2806 segments.conversion_segment(0).candidate(0).content_value);
2807 #endif // OS_WIN
2808 }
2809
TEST_F(SessionConverterTest,Issue2029557)2810 TEST_F(SessionConverterTest, Issue2029557) {
2811 // Unittest against http://b/2029557
2812 // a<tab><F6> raised a DCHECK error.
2813 SessionConverter converter(
2814 convertermock_.get(), request_.get(), config_.get());
2815 // Composition (as "a")
2816 composer_->InsertCharacterPreedit("a");
2817
2818 // Prediction (as <tab>)
2819 Segments segments;
2820 SetAiueo(&segments);
2821 convertermock_->SetStartPredictionForRequest(&segments, true);
2822 EXPECT_TRUE(converter.Predict(*composer_));
2823 EXPECT_TRUE(converter.IsActive());
2824
2825 // Transliteration (as <F6>)
2826 segments.Clear();
2827 Segment *segment = segments.add_segment();
2828 segment->set_key("a");
2829 Segment::Candidate *candidate = segment->add_candidate();
2830 candidate->value = "a";
2831
2832 FillT13Ns(&segments, composer_.get());
2833 convertermock_->SetStartConversionForRequest(&segments, true);
2834 EXPECT_TRUE(converter.ConvertToTransliteration(*composer_,
2835 transliteration::HIRAGANA));
2836 EXPECT_TRUE(converter.IsActive());
2837 }
2838
TEST_F(SessionConverterTest,Issue2031986)2839 TEST_F(SessionConverterTest, Issue2031986) {
2840 // Unittest against http://b/2031986
2841 // aaaaa<Shift+Enter> raised a CRT error.
2842 SessionConverter converter(
2843 convertermock_.get(), request_.get(), config_.get());
2844
2845 { // Initialize a suggest result triggered by "aaaa".
2846 Segments segments;
2847 Segment *segment = segments.add_segment();
2848 segment->set_key("aaaa");
2849 Segment::Candidate *candidate;
2850 candidate = segment->add_candidate();
2851 candidate->value = "AAAA";
2852 candidate = segment->add_candidate();
2853 candidate->value = "Aaaa";
2854 convertermock_->SetStartSuggestionForRequest(&segments, true);
2855 }
2856 // Get suggestion
2857 composer_->InsertCharacterPreedit("aaaa");
2858 EXPECT_TRUE(converter.Suggest(*composer_));
2859 EXPECT_TRUE(converter.IsActive());
2860
2861 { // Initialize no suggest result triggered by "aaaaa".
2862 Segments segments;
2863 Segment *segment = segments.add_segment();
2864 segment->set_key("aaaaa");
2865 convertermock_->SetStartSuggestionForRequest(&segments, false);
2866 }
2867 // Hide suggestion
2868 composer_->InsertCharacterPreedit("a");
2869 EXPECT_FALSE(converter.Suggest(*composer_));
2870 EXPECT_FALSE(converter.IsActive());
2871 }
2872
TEST_F(SessionConverterTest,Issue2040116)2873 TEST_F(SessionConverterTest, Issue2040116) {
2874 // Unittest against http://b/2040116
2875 //
2876 // It happens when the first Predict returns results but the next
2877 // MaybeExpandPrediction does not return any results. That's a
2878 // trick by GoogleSuggest.
2879 SessionConverter converter(
2880 convertermock_.get(), request_.get(), config_.get());
2881 composer_->InsertCharacterPreedit("G");
2882
2883 {
2884 // Initialize no predict result.
2885 Segments segments;
2886 segments.set_request_type(Segments::PREDICTION);
2887 Segment *segment = segments.add_segment();
2888 segment->set_key("G");
2889 convertermock_->SetStartPredictionForRequest(&segments, false);
2890 }
2891 // Get prediction
2892 EXPECT_FALSE(converter.Predict(*composer_));
2893 EXPECT_FALSE(converter.IsActive());
2894
2895 {
2896 // Initialize a suggest result triggered by "G".
2897 Segments segments;
2898 segments.set_request_type(Segments::PREDICTION);
2899 Segment *segment = segments.add_segment();
2900 segment->set_key("G");
2901 Segment::Candidate *candidate;
2902 candidate = segment->add_candidate();
2903 candidate->value = "GoogleSuggest";
2904 convertermock_->SetStartPredictionForRequest(&segments, true);
2905 }
2906 // Get prediction again
2907 EXPECT_TRUE(converter.Predict(*composer_));
2908 EXPECT_TRUE(converter.IsActive());
2909
2910 { // Check the conversion.
2911 commands::Output output;
2912 converter.FillOutput(*composer_, &output);
2913 EXPECT_FALSE(output.has_result());
2914 EXPECT_TRUE(output.has_preedit());
2915 EXPECT_TRUE(output.has_candidates());
2916
2917 const commands::Preedit &conversion = output.preedit();
2918 EXPECT_EQ(1, conversion.segment_size());
2919 EXPECT_EQ("GoogleSuggest", conversion.segment(0).value());
2920 }
2921
2922 {
2923 // Initialize no predict result triggered by "G". It's possible
2924 // by Google Suggest.
2925 Segments segments;
2926 segments.set_request_type(Segments::PREDICTION);
2927 Segment *segment = segments.add_segment();
2928 segment->set_key("G");
2929 convertermock_->SetStartPredictionForRequest(&segments, false);
2930 }
2931 // Hide prediction
2932 converter.CandidateNext(*composer_);
2933 EXPECT_TRUE(converter.IsActive());
2934
2935 { // Check the conversion.
2936 commands::Output output;
2937 converter.FillOutput(*composer_, &output);
2938 EXPECT_FALSE(output.has_result());
2939 EXPECT_TRUE(output.has_preedit());
2940 EXPECT_TRUE(output.has_candidates());
2941
2942 const commands::Preedit &conversion = output.preedit();
2943 EXPECT_EQ(1, conversion.segment_size());
2944 EXPECT_EQ("GoogleSuggest", conversion.segment(0).value());
2945
2946 const commands::Candidates &candidates = output.candidates();
2947 EXPECT_EQ(1, candidates.candidate_size());
2948 }
2949 }
2950
TEST_F(SessionConverterTest,GetReadingText)2951 TEST_F(SessionConverterTest, GetReadingText) {
2952 SessionConverter converter(
2953 convertermock_.get(), request_.get(), config_.get());
2954
2955 const char *kKanjiAiueo = "阿伊宇江於";
2956 // Set up Segments for reverse conversion.
2957 Segments reverse_segments;
2958 Segment *segment;
2959 segment = reverse_segments.add_segment();
2960 segment->set_key(kKanjiAiueo);
2961 Segment::Candidate *candidate;
2962 candidate = segment->add_candidate();
2963 // For reverse conversion, key is the original kanji string.
2964 candidate->key = kKanjiAiueo;
2965 candidate->value = kChars_Aiueo;
2966 convertermock_->SetStartReverseConversion(&reverse_segments, true);
2967 // Set up Segments for forward conversion.
2968 Segments segments;
2969 segment = segments.add_segment();
2970 segment->set_key(kChars_Aiueo);
2971 candidate = segment->add_candidate();
2972 candidate->key = kChars_Aiueo;
2973 candidate->value = kKanjiAiueo;
2974 convertermock_->SetStartConversionForRequest(&segments, true);
2975
2976 string reading;
2977 EXPECT_TRUE(converter.GetReadingText(kKanjiAiueo, &reading));
2978 EXPECT_EQ(kChars_Aiueo, reading);
2979 }
2980
TEST_F(SessionConverterTest,ZeroQuerySuggestion)2981 TEST_F(SessionConverterTest, ZeroQuerySuggestion) {
2982 SessionConverter converter(
2983 convertermock_.get(), request_.get(), config_.get());
2984
2985 // Set up a mock suggestion result.
2986 Segments segments;
2987 segments.set_request_type(Segments::SUGGESTION);
2988 Segment *segment;
2989 segment = segments.add_segment();
2990 segment->set_key("");
2991 segment->add_candidate()->value = "search";
2992 segment->add_candidate()->value = "input";
2993 convertermock_->SetStartSuggestionForRequest(&segments, true);
2994
2995 EXPECT_TRUE(composer_->Empty());
2996 EXPECT_TRUE(converter.Suggest(*composer_));
2997 EXPECT_TRUE(IsCandidateListVisible(converter));
2998 EXPECT_TRUE(converter.IsActive());
2999
3000 { // Check the output
3001 commands::Output output;
3002 converter.FillOutput(*composer_, &output);
3003 EXPECT_FALSE(output.has_result());
3004 EXPECT_FALSE(output.has_preedit());
3005 EXPECT_TRUE(output.has_candidates());
3006
3007 const commands::Candidates &candidates = output.candidates();
3008 EXPECT_EQ(2, candidates.size());
3009 EXPECT_EQ("search", candidates.candidate(0).value());
3010 EXPECT_EQ("input", candidates.candidate(1).value());
3011 }
3012 }
3013
3014 // since History segments are almost hidden from
3015 namespace {
3016
3017 class ConverterMockForReset : public ConverterMock {
3018 public:
ConverterMockForReset()3019 ConverterMockForReset() : reset_conversion_called_(false) {}
3020
ResetConversion(Segments * segments) const3021 bool ResetConversion(Segments *segments) const override {
3022 reset_conversion_called_ = true;
3023 return true;
3024 }
3025
reset_conversion_called() const3026 bool reset_conversion_called() const {
3027 return reset_conversion_called_;
3028 }
3029
Reset()3030 void Reset() {
3031 reset_conversion_called_ = false;
3032 }
3033
3034 private:
3035 mutable bool reset_conversion_called_;
3036 };
3037
3038 class ConverterMockForRevert : public ConverterMock {
3039 public:
ConverterMockForRevert()3040 ConverterMockForRevert() : revert_conversion_called_(false) {}
3041
RevertConversion(Segments * segments) const3042 bool RevertConversion(Segments *segments) const override {
3043 revert_conversion_called_ = true;
3044 return true;
3045 }
3046
revert_conversion_called() const3047 bool revert_conversion_called() const {
3048 return revert_conversion_called_;
3049 }
3050
Reset()3051 void Reset() {
3052 revert_conversion_called_ = false;
3053 }
3054
3055 private:
3056 mutable bool revert_conversion_called_;
3057 };
3058
3059 class ConverterMockForReconstructHistory : public ConverterMock {
3060 public:
ConverterMockForReconstructHistory()3061 ConverterMockForReconstructHistory() : reconstruct_history_called_(false) {}
3062
ReconstructHistory(Segments * segments,const string & preceding_text) const3063 bool ReconstructHistory(Segments *segments,
3064 const string &preceding_text) const override {
3065 reconstruct_history_called_ = true;
3066 preceding_text_ = preceding_text;
3067 ConverterMock::ReconstructHistory(segments, preceding_text);
3068 return true;
3069 }
3070
reconstruct_history_called() const3071 bool reconstruct_history_called() const {
3072 return reconstruct_history_called_;
3073 }
3074
preceding_text() const3075 const string preceding_text() const {
3076 return preceding_text_;
3077 }
3078
Reset()3079 void Reset() {
3080 reconstruct_history_called_ = false;
3081 preceding_text_.clear();
3082 }
3083
3084 private:
3085 mutable bool reconstruct_history_called_;
3086 mutable string preceding_text_;
3087 };
3088
3089 } // namespace
3090
TEST(SessionConverterResetTest,Reset)3091 TEST(SessionConverterResetTest, Reset) {
3092 ConverterMockForReset convertermock;
3093 Request request;
3094 Config config;
3095 SessionConverter converter(&convertermock, &request, &config);
3096 EXPECT_FALSE(convertermock.reset_conversion_called());
3097 converter.Reset();
3098 EXPECT_TRUE(convertermock.reset_conversion_called());
3099 }
3100
TEST(SessionConverterRevertTest,Revert)3101 TEST(SessionConverterRevertTest, Revert) {
3102 ConverterMockForRevert convertermock;
3103 Request request;
3104 Config config;
3105 SessionConverter converter(&convertermock, &request, &config);
3106 EXPECT_FALSE(convertermock.revert_conversion_called());
3107 converter.Revert();
3108 EXPECT_TRUE(convertermock.revert_conversion_called());
3109 }
3110
TEST_F(SessionConverterTest,CommitHead)3111 TEST_F(SessionConverterTest, CommitHead) {
3112 SessionConverter converter(
3113 convertermock_.get(), request_.get(), config_.get());
3114 composer_->InsertCharacterPreedit(kChars_Aiueo);
3115
3116 size_t committed_size;
3117 converter.CommitHead(1, *composer_, &committed_size);
3118 EXPECT_EQ(1, committed_size);
3119 composer_->DeleteAt(0);
3120
3121 commands::Output output;
3122 converter.FillOutput(*composer_, &output);
3123 EXPECT_TRUE(output.has_result());
3124 EXPECT_FALSE(output.has_candidates());
3125
3126 const commands::Result &result = output.result();
3127 EXPECT_EQ("あ", result.value());
3128 EXPECT_EQ("あ", result.key());
3129 string preedit;
3130 composer_->GetStringForPreedit(&preedit);
3131 EXPECT_EQ("いうえお", preedit);
3132
3133 converter.CommitHead(3, *composer_, &committed_size);
3134 EXPECT_EQ(3, committed_size);
3135 composer_->DeleteAt(0);
3136 composer_->DeleteAt(0);
3137 composer_->DeleteAt(0);
3138 converter.FillOutput(*composer_, &output);
3139 EXPECT_TRUE(output.has_result());
3140 EXPECT_FALSE(output.has_candidates());
3141
3142 const commands::Result &result2 = output.result();
3143 EXPECT_EQ("いうえ", result2.value());
3144 EXPECT_EQ("いうえ", result2.key());
3145 composer_->GetStringForPreedit(&preedit);
3146 EXPECT_EQ("お", preedit);
3147
3148 EXPECT_STATS_NOT_EXIST("Commit");
3149 EXPECT_STATS_NOT_EXIST("CommitFromComposition");
3150 }
3151
TEST_F(SessionConverterTest,CommandCandidate)3152 TEST_F(SessionConverterTest, CommandCandidate) {
3153 SessionConverter converter(
3154 convertermock_.get(), request_.get(), config_.get());
3155 Segments segments;
3156 SetAiueo(&segments);
3157 FillT13Ns(&segments, composer_.get());
3158 // set COMMAND_CANDIDATE.
3159 SetCommandCandidate(&segments, 0, 0, Segment::Candidate::DEFAULT_COMMAND);
3160 convertermock_->SetStartConversionForRequest(&segments, true);
3161
3162 composer_->InsertCharacterPreedit(kChars_Aiueo);
3163 EXPECT_TRUE(converter.Convert(*composer_));
3164
3165 converter.Commit(*composer_, Context::default_instance());
3166 commands::Output output;
3167 converter.FillOutput(*composer_, &output);
3168 EXPECT_FALSE(output.has_result());
3169 }
3170
TEST_F(SessionConverterTest,CommandCandidateWithCommitCommands)3171 TEST_F(SessionConverterTest, CommandCandidateWithCommitCommands) {
3172 const string kKamabokono = "かまぼこの";
3173 const string kInbou = "いんぼう";
3174 composer_->InsertCharacterPreedit(kKamabokono + kInbou);
3175
3176 {
3177 // The first candidate is a command candidate, so
3178 // CommitFirstSegment resets all conversion.
3179 SessionConverter converter(
3180 convertermock_.get(), request_.get(), config_.get());
3181 Segments segments;
3182 SetKamaboko(&segments);
3183 SetCommandCandidate(&segments, 0, 0,
3184 Segment::Candidate::DEFAULT_COMMAND);
3185 convertermock_->SetStartConversionForRequest(&segments, true);
3186 converter.Convert(*composer_);
3187
3188 size_t committed_size = 0;
3189 converter.CommitFirstSegment(*composer_,
3190 Context::default_instance(),
3191 &committed_size);
3192 EXPECT_EQ(0, committed_size);
3193
3194 commands::Output output;
3195 converter.FillOutput(*composer_, &output);
3196 EXPECT_FALSE(converter.IsActive());
3197 EXPECT_FALSE(output.has_result());
3198 }
3199
3200 {
3201 // The second candidate is a command candidate, so
3202 // CommitFirstSegment commits all conversion.
3203 SessionConverter converter(
3204 convertermock_.get(), request_.get(), config_.get());
3205 Segments segments;
3206 SetKamaboko(&segments);
3207 SetCommandCandidate(&segments, 1, 0,
3208 Segment::Candidate::DEFAULT_COMMAND);
3209 convertermock_->SetStartConversionForRequest(&segments, true);
3210 converter.Convert(*composer_);
3211
3212 size_t committed_size = 0;
3213 converter.CommitFirstSegment(*composer_,
3214 Context::default_instance(),
3215 &committed_size);
3216 EXPECT_EQ(Util::CharsLen(kKamabokono), committed_size);
3217
3218 commands::Output output;
3219 converter.FillOutput(*composer_, &output);
3220 EXPECT_TRUE(converter.IsActive());
3221 EXPECT_TRUE(output.has_result());
3222 }
3223
3224 {
3225 // The selected suggestion with Id is a command candidate.
3226 SessionConverter converter(
3227 convertermock_.get(), request_.get(), config_.get());
3228 Segments segments;
3229 SetAiueo(&segments);
3230 SetCommandCandidate(&segments, 0, 0,
3231 Segment::Candidate::DEFAULT_COMMAND);
3232 convertermock_->SetStartSuggestionForRequest(&segments, true);
3233 converter.Suggest(*composer_);
3234
3235 size_t committed_size = 0;
3236 EXPECT_FALSE(converter.CommitSuggestionById(
3237 0, *composer_, Context::default_instance(), &committed_size));
3238 EXPECT_EQ(0, committed_size);
3239 }
3240
3241 {
3242 // The selected suggestion with Index is a command candidate.
3243 SessionConverter converter(
3244 convertermock_.get(), request_.get(), config_.get());
3245 Segments segments;
3246 SetAiueo(&segments);
3247 SetCommandCandidate(&segments, 0, 1,
3248 Segment::Candidate::DEFAULT_COMMAND);
3249 convertermock_->SetStartSuggestionForRequest(&segments, true);
3250 converter.Suggest(*composer_);
3251
3252 size_t committed_size = 0;
3253 EXPECT_FALSE(converter.CommitSuggestionByIndex(
3254 1, *composer_, Context::default_instance(), &committed_size));
3255 EXPECT_EQ(0, committed_size);
3256 }
3257 }
3258
TEST_F(SessionConverterTest,ExecuteCommandCandidate)3259 TEST_F(SessionConverterTest, ExecuteCommandCandidate) {
3260 // Enable Incognito mode
3261 {
3262 config_->set_incognito_mode(false);
3263 SessionConverter converter(
3264 convertermock_.get(), request_.get(), config_.get());
3265 Segments segments;
3266 SetAiueo(&segments);
3267 SetCommandCandidate(&segments, 0, 0,
3268 Segment::Candidate::ENABLE_INCOGNITO_MODE);
3269 convertermock_->SetStartConversionForRequest(&segments, true);
3270
3271 composer_->InsertCharacterPreedit(kChars_Aiueo);
3272 EXPECT_TRUE(converter.Convert(*composer_));
3273
3274 converter.Commit(*composer_, Context::default_instance());
3275 commands::Output output;
3276 converter.FillOutput(*composer_, &output);
3277 EXPECT_FALSE(output.has_result());
3278
3279 // The config in the |output| has the updated value, but |config_| keeps
3280 // the previous value.
3281 EXPECT_TRUE(output.has_config());
3282 EXPECT_TRUE(output.config().incognito_mode());
3283 EXPECT_FALSE(config_->incognito_mode());
3284 }
3285
3286 // Disable Incognito mode
3287 {
3288 config_->set_incognito_mode(false);
3289 SessionConverter converter(
3290 convertermock_.get(), request_.get(), config_.get());
3291 Segments segments;
3292 SetAiueo(&segments);
3293 SetCommandCandidate(&segments, 0, 0,
3294 Segment::Candidate::DISABLE_INCOGNITO_MODE);
3295 convertermock_->SetStartConversionForRequest(&segments, true);
3296
3297 composer_->InsertCharacterPreedit(kChars_Aiueo);
3298 EXPECT_TRUE(converter.Convert(*composer_));
3299
3300 converter.Commit(*composer_, Context::default_instance());
3301 commands::Output output;
3302 converter.FillOutput(*composer_, &output);
3303 EXPECT_FALSE(output.has_result());
3304
3305 // The config in the |output| has the updated value, but |config_| keeps
3306 // the previous value.
3307 EXPECT_TRUE(output.has_config());
3308 EXPECT_FALSE(output.config().incognito_mode());
3309 EXPECT_FALSE(config_->incognito_mode());
3310 }
3311
3312 // Enable Presentation mode
3313 {
3314 config_->set_presentation_mode(false);
3315 SessionConverter converter(
3316 convertermock_.get(), request_.get(), config_.get());
3317
3318 Segments segments;
3319 SetAiueo(&segments);
3320 SetCommandCandidate(&segments, 0, 0,
3321 Segment::Candidate::ENABLE_PRESENTATION_MODE);
3322 convertermock_->SetStartConversionForRequest(&segments, true);
3323
3324 composer_->InsertCharacterPreedit(kChars_Aiueo);
3325 EXPECT_TRUE(converter.Convert(*composer_));
3326
3327 converter.Commit(*composer_, Context::default_instance());
3328 commands::Output output;
3329 converter.FillOutput(*composer_, &output);
3330 EXPECT_FALSE(output.has_result());
3331
3332 // The config in the |output| has the updated value, but |config_| keeps
3333 // the previous value.
3334 EXPECT_TRUE(output.has_config());
3335 EXPECT_TRUE(output.config().presentation_mode());
3336 EXPECT_FALSE(config_->presentation_mode());
3337 }
3338
3339 // Disable Presentation mode
3340 {
3341 config_->set_incognito_mode(true);
3342 SessionConverter converter(
3343 convertermock_.get(), request_.get(), config_.get());
3344
3345 Segments segments;
3346 SetAiueo(&segments);
3347 SetCommandCandidate(&segments, 0, 0,
3348 Segment::Candidate::DISABLE_PRESENTATION_MODE);
3349 convertermock_->SetStartConversionForRequest(&segments, true);
3350
3351 composer_->InsertCharacterPreedit(kChars_Aiueo);
3352 EXPECT_TRUE(converter.Convert(*composer_));
3353
3354 converter.Commit(*composer_, Context::default_instance());
3355 commands::Output output;
3356 converter.FillOutput(*composer_, &output);
3357 EXPECT_FALSE(output.has_result());
3358
3359 // The config in the |output| has the updated value, but |config_| keeps
3360 // the previous value.
3361 EXPECT_TRUE(output.has_config());
3362 EXPECT_FALSE(output.config().presentation_mode());
3363 EXPECT_FALSE(config_->presentation_mode());
3364 }
3365 }
3366
TEST_F(SessionConverterTest,PropageteConfigToRenderer)3367 TEST_F(SessionConverterTest, PropageteConfigToRenderer) {
3368 // Disable information_list_config()
3369 {
3370 SessionConverter converter(
3371 convertermock_.get(), request_.get(), config_.get());
3372 Segments segments;
3373 SetAiueo(&segments);
3374 FillT13Ns(&segments, composer_.get());
3375 convertermock_->SetStartConversionForRequest(&segments, true);
3376
3377 commands::Output output;
3378 composer_->InsertCharacterPreedit(kChars_Aiueo);
3379 converter.Convert(*composer_);
3380
3381 EXPECT_FALSE(IsCandidateListVisible(converter));
3382 output.Clear();
3383 converter.FillOutput(*composer_, &output);
3384 EXPECT_FALSE(output.has_config());
3385
3386 converter.CandidateNext(*composer_);
3387 EXPECT_TRUE(IsCandidateListVisible(converter));
3388 output.Clear();
3389 converter.FillOutput(*composer_, &output);
3390 EXPECT_FALSE(output.has_config());
3391 }
3392 }
3393
TEST_F(SessionConverterTest,ConversionFail)3394 TEST_F(SessionConverterTest, ConversionFail) {
3395 SessionConverter converter(
3396 convertermock_.get(), request_.get(), config_.get());
3397
3398 // Conversion fails.
3399 {
3400 // segments doesn't have any candidates.
3401 Segments segments;
3402 segments.add_segment()->set_key(kChars_Aiueo);
3403 convertermock_->SetStartConversionForRequest(&segments, false);
3404 composer_->InsertCharacterPreedit(kChars_Aiueo);
3405
3406 // Falls back to composition state.
3407 EXPECT_FALSE(converter.Convert(*composer_));
3408 EXPECT_FALSE(IsCandidateListVisible(converter));
3409 EXPECT_TRUE(converter.CheckState(SessionConverterInterface::COMPOSITION));
3410
3411 commands::Output output;
3412 converter.FillOutput(*composer_, &output);
3413 EXPECT_FALSE(output.has_result());
3414 EXPECT_TRUE(output.has_preedit());
3415 EXPECT_FALSE(output.has_candidates());
3416 EXPECT_FALSE(IsCandidateListVisible(converter));
3417 }
3418
3419 composer_->Reset();
3420
3421 // Suggestion succeeds and conversion fails.
3422 {
3423 Segments segments;
3424 SetAiueo(&segments);
3425 convertermock_->SetStartSuggestionForRequest(&segments, true);
3426 composer_->InsertCharacterPreedit(kChars_Aiueo);
3427
3428 EXPECT_TRUE(converter.Suggest(*composer_));
3429 EXPECT_TRUE(IsCandidateListVisible(converter));
3430 EXPECT_TRUE(converter.CheckState(SessionConverterInterface::SUGGESTION));
3431
3432 commands::Output output;
3433 converter.FillOutput(*composer_, &output);
3434 EXPECT_FALSE(output.has_result());
3435 EXPECT_TRUE(output.has_preedit());
3436 EXPECT_TRUE(output.has_candidates());
3437
3438 segments.Clear();
3439 output.Clear();
3440
3441 // segments doesn't have any candidates.
3442 segments.add_segment()->set_key(kChars_Aiueo);
3443 convertermock_->SetStartConversionForRequest(&segments, false);
3444
3445 // Falls back to composition state.
3446 EXPECT_FALSE(converter.Convert(*composer_));
3447 EXPECT_FALSE(IsCandidateListVisible(converter));
3448 EXPECT_TRUE(converter.CheckState(SessionConverterInterface::COMPOSITION));
3449
3450 converter.FillOutput(*composer_, &output);
3451 EXPECT_FALSE(output.has_result());
3452 EXPECT_TRUE(output.has_preedit());
3453 EXPECT_FALSE(output.has_candidates());
3454 }
3455 }
3456
TEST_F(SessionConverterTest,ResetByClientRevision)3457 TEST_F(SessionConverterTest, ResetByClientRevision) {
3458 const int32 kRevision = 0x1234;
3459
3460 ConverterMockForReset convertermock;
3461 SessionConverter converter(&convertermock, request_.get(), config_.get());
3462 Context context;
3463
3464 // Initialize the session converter with given context age.
3465 context.set_revision(kRevision);
3466 converter.OnStartComposition(context);
3467 converter.Revert();
3468
3469 convertermock.Reset();
3470 EXPECT_FALSE(convertermock.reset_conversion_called());
3471
3472 // OnStartComposition with different context age causes Reset()
3473 context.set_revision(kRevision + 1);
3474 converter.OnStartComposition(context);
3475 EXPECT_TRUE(convertermock.reset_conversion_called());
3476 }
3477
TEST_F(SessionConverterTest,ResetByPrecedingText)3478 TEST_F(SessionConverterTest, ResetByPrecedingText) {
3479 ConverterMockForReset convertermock;
3480 SessionConverter converter(&convertermock, request_.get(), config_.get());
3481
3482 // no preceding_text -> Reset should not be called.
3483 {
3484 convertermock.Reset();
3485 Segments segments;
3486 SetAiueo(&segments);
3487 FillT13Ns(&segments, composer_.get());
3488 for (size_t i = 0; i < segments.segments_size(); ++i) {
3489 Segment *segment = segments.mutable_segment(i);
3490 segment->set_segment_type(Segment::HISTORY);
3491 }
3492 SetSegments(segments, &converter);
3493 converter.OnStartComposition(Context::default_instance());
3494 EXPECT_FALSE(convertermock.reset_conversion_called());
3495 converter.Revert();
3496 }
3497
3498 // preceding_text == history_segments -> Reset should not be called.
3499 {
3500 convertermock.Reset();
3501 Segments segments;
3502 SetAiueo(&segments);
3503 FillT13Ns(&segments, composer_.get());
3504 for (size_t i = 0; i < segments.segments_size(); ++i) {
3505 Segment *segment = segments.mutable_segment(i);
3506 segment->set_segment_type(Segment::HISTORY);
3507 }
3508 SetSegments(segments, &converter);
3509 Context context;
3510 context.set_preceding_text(kChars_Aiueo);
3511 converter.OnStartComposition(context);
3512 EXPECT_FALSE(convertermock.reset_conversion_called());
3513 converter.Revert();
3514 }
3515
3516 // preceding_text == "" && history_segments != "" -> Reset should be called.
3517 {
3518 convertermock.Reset();
3519 Segments segments;
3520 SetAiueo(&segments);
3521 FillT13Ns(&segments, composer_.get());
3522 for (size_t i = 0; i < segments.segments_size(); ++i) {
3523 Segment *segment = segments.mutable_segment(i);
3524 segment->set_segment_type(Segment::HISTORY);
3525 }
3526 SetSegments(segments, &converter);
3527 Context context;
3528 context.set_preceding_text("");
3529 converter.OnStartComposition(context);
3530 EXPECT_TRUE(convertermock.reset_conversion_called());
3531 converter.Revert();
3532 }
3533
3534 // preceding_text != "" && preceding_text.EndsWith(history_segments).
3535 // -> Reset should not be called.
3536 {
3537 convertermock.Reset();
3538 Segments segments;
3539 SetAiueo(&segments);
3540 FillT13Ns(&segments, composer_.get());
3541 for (size_t i = 0; i < segments.segments_size(); ++i) {
3542 Segment *segment = segments.mutable_segment(i);
3543 segment->set_segment_type(Segment::HISTORY);
3544 }
3545 SetSegments(segments, &converter);
3546 Context context;
3547 context.set_preceding_text(kChars_Aiueo);
3548 converter.OnStartComposition(context);
3549 EXPECT_FALSE(convertermock.reset_conversion_called());
3550 converter.Revert();
3551 }
3552
3553 // preceding_text != "" && history_segments.EndsWith(preceding_text).
3554 // -> Reset should not be called.
3555 {
3556 convertermock.Reset();
3557 Segments segments;
3558 SetAiueo(&segments);
3559 FillT13Ns(&segments, composer_.get());
3560 for (size_t i = 0; i < segments.segments_size(); ++i) {
3561 Segment *segment = segments.mutable_segment(i);
3562 segment->set_segment_type(Segment::HISTORY);
3563 }
3564 SetSegments(segments, &converter);
3565 Context context;
3566 context.set_preceding_text(kChars_Aiueo);
3567 converter.OnStartComposition(context);
3568 EXPECT_FALSE(convertermock.reset_conversion_called());
3569 converter.Revert();
3570 }
3571 }
3572
TEST_F(SessionConverterTest,ReconstructHistoryByPrecedingText)3573 TEST_F(SessionConverterTest, ReconstructHistoryByPrecedingText) {
3574 ConverterMockForReconstructHistory convertermock;
3575
3576 const uint16 kId = 1234;
3577 const char kKey[] = "1";
3578 const char kValue[] = "1";
3579
3580 // Set up mock
3581 Segments mock_result;
3582 {
3583 Segment *segment = mock_result.add_segment();
3584 segment->set_key(kKey);
3585 segment->set_segment_type(Segment::HISTORY);
3586 Segment::Candidate *candidate = segment->push_back_candidate();
3587 candidate->rid = kId;
3588 candidate->lid = kId;
3589 candidate->content_key = kKey;
3590 candidate->key = kKey;
3591 candidate->content_value = kValue;
3592 candidate->value = kValue;
3593 candidate->attributes = Segment::Candidate::NO_LEARNING;
3594 }
3595 convertermock.SetReconstructHistory(&mock_result, true);
3596
3597 // With revision
3598 {
3599 SessionConverter converter(&convertermock, request_.get(), config_.get());
3600
3601 Context context;
3602 context.set_revision(0);
3603 context.set_preceding_text(kKey);
3604 EXPECT_FALSE(convertermock.reconstruct_history_called());
3605 converter.OnStartComposition(context);
3606 EXPECT_TRUE(convertermock.reconstruct_history_called());
3607 EXPECT_EQ(kKey, convertermock.preceding_text());
3608
3609 // History segments should be reconstructed.
3610 Segments segments;
3611 GetSegments(converter, &segments);
3612 EXPECT_EQ(1, segments.segments_size());
3613 const Segment &segment = segments.segment(0);
3614 EXPECT_EQ(Segment::HISTORY, segment.segment_type());
3615 EXPECT_EQ(kKey, segment.key());
3616 EXPECT_EQ(1, segment.candidates_size());
3617 const Segment::Candidate &candidate = segment.candidate(0);
3618 EXPECT_EQ(Segment::Candidate::NO_LEARNING, candidate.attributes);
3619 EXPECT_EQ(kKey, candidate.content_key);
3620 EXPECT_EQ(kKey, candidate.key);
3621 EXPECT_EQ(kValue, candidate.content_value);
3622 EXPECT_EQ(kValue, candidate.value);
3623 EXPECT_EQ(kId, candidate.lid);
3624 EXPECT_EQ(kId, candidate.rid);
3625
3626 convertermock.Reset();
3627 convertermock.SetReconstructHistory(&mock_result, true);
3628
3629 // Increment revesion
3630 context.set_revision(1);
3631 context.set_preceding_text(kKey);
3632 EXPECT_FALSE(convertermock.reconstruct_history_called());
3633 converter.OnStartComposition(context);
3634 EXPECT_FALSE(convertermock.reconstruct_history_called())
3635 << "ReconstructHistory should not be called as long as the "
3636 << "history segments are consistent with the preceding text "
3637 << "even when the revision number is changed.";
3638 }
3639
3640 convertermock.Reset();
3641 convertermock.SetReconstructHistory(&mock_result, true);
3642
3643 // Without revision
3644 {
3645 SessionConverter converter(&convertermock, request_.get(), config_.get());
3646
3647 Context context;
3648 context.set_preceding_text(kKey);
3649 EXPECT_FALSE(convertermock.reconstruct_history_called());
3650 converter.OnStartComposition(context);
3651 EXPECT_TRUE(convertermock.reconstruct_history_called());
3652 EXPECT_EQ(kKey, convertermock.preceding_text());
3653
3654 // History segments should be reconstructed.
3655 Segments segments;
3656 GetSegments(converter, &segments);
3657 EXPECT_EQ(1, segments.segments_size());
3658 const Segment &segment = segments.segment(0);
3659 EXPECT_EQ(Segment::HISTORY, segment.segment_type());
3660 EXPECT_EQ(kKey, segment.key());
3661 EXPECT_EQ(1, segment.candidates_size());
3662 const Segment::Candidate &candidate = segment.candidate(0);
3663 EXPECT_EQ(Segment::Candidate::NO_LEARNING, candidate.attributes);
3664 EXPECT_EQ(kKey, candidate.content_key);
3665 EXPECT_EQ(kKey, candidate.key);
3666 EXPECT_EQ(kValue, candidate.content_value);
3667 EXPECT_EQ(kValue, candidate.value);
3668 EXPECT_EQ(kId, candidate.lid);
3669 EXPECT_EQ(kId, candidate.rid);
3670
3671 convertermock.Reset();
3672 convertermock.SetReconstructHistory(&mock_result, true);
3673
3674 context.set_preceding_text(kKey);
3675 EXPECT_FALSE(convertermock.reconstruct_history_called());
3676 converter.OnStartComposition(context);
3677 EXPECT_FALSE(convertermock.reconstruct_history_called())
3678 << "ReconstructHistory should not be called as long as the "
3679 << "history segments are consistent with the preceding text "
3680 << "even when the revision number is changed.";
3681 }
3682 }
3683
3684 // Test whether Request::candidate_page_size is correctly propagated to
3685 // CandidateList.page_size in SessionConverter. The tests for the behavior
3686 // of CandidateList.page_size is in session/internal/candidate_list_test.cc
TEST_F(SessionConverterTest,CandidatePageSize)3687 TEST_F(SessionConverterTest, CandidatePageSize) {
3688 const size_t kPageSize = 3;
3689 request_->set_candidate_page_size(kPageSize);
3690 SessionConverter converter(
3691 convertermock_.get(), request_.get(), config_.get());
3692 EXPECT_EQ(kPageSize, GetCandidateList(converter).page_size());
3693 }
3694
3695 } // namespace session
3696 } // namespace mozc
3697