1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/input_method/candidate_window_view.h" 6 7 #include <string> 8 9 #include "base/strings/stringprintf.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "chrome/browser/chromeos/input_method/candidate_view.h" 12 #include "testing/gtest/include/gtest/gtest.h" 13 #include "ui/views/test/views_test_base.h" 14 #include "ui/views/widget/widget.h" 15 16 namespace chromeos { 17 namespace input_method { 18 19 namespace { 20 const char* kSampleCandidate[] = { 21 "Sample Candidate 1", 22 "Sample Candidate 2", 23 "Sample Candidate 3" 24 }; 25 const char* kSampleAnnotation[] = { 26 "Sample Annotation 1", 27 "Sample Annotation 2", 28 "Sample Annotation 3" 29 }; 30 const char* kSampleDescriptionTitle[] = { 31 "Sample Description Title 1", 32 "Sample Description Title 2", 33 "Sample Description Title 3", 34 }; 35 const char* kSampleDescriptionBody[] = { 36 "Sample Description Body 1", 37 "Sample Description Body 2", 38 "Sample Description Body 3", 39 }; 40 41 void InitIBusLookupTable(size_t page_size, 42 IBusLookupTable* table) { 43 table->set_cursor_position(0); 44 table->set_page_size(page_size); 45 table->mutable_candidates()->clear(); 46 table->set_orientation(IBusLookupTable::VERTICAL); 47 } 48 49 void InitIBusLookupTableWithCandidatesFilled(size_t page_size, 50 IBusLookupTable* table) { 51 InitIBusLookupTable(page_size, table); 52 for (size_t i = 0; i < page_size; ++i) { 53 IBusLookupTable::Entry entry; 54 entry.value = base::StringPrintf("value %lld", 55 static_cast<unsigned long long>(i)); 56 entry.label = base::StringPrintf("%lld", 57 static_cast<unsigned long long>(i)); 58 table->mutable_candidates()->push_back(entry); 59 } 60 } 61 62 } // namespace 63 64 class CandidateWindowViewTest : public views::ViewsTestBase { 65 protected: 66 void ExpectLabels(const std::string& shortcut, 67 const std::string& candidate, 68 const std::string& annotation, 69 const CandidateView* row) { 70 EXPECT_EQ(shortcut, UTF16ToUTF8(row->shortcut_label_->text())); 71 EXPECT_EQ(candidate, UTF16ToUTF8(row->candidate_label_->text())); 72 EXPECT_EQ(annotation, UTF16ToUTF8(row->annotation_label_->text())); 73 } 74 }; 75 76 TEST_F(CandidateWindowViewTest, UpdateCandidatesTest_CursorVisibility) { 77 views::Widget* widget = new views::Widget; 78 views::Widget::InitParams params = 79 CreateParams(views::Widget::InitParams::TYPE_WINDOW); 80 widget->Init(params); 81 82 CandidateWindowView candidate_window_view(widget); 83 candidate_window_view.Init(); 84 85 // Visible (by default) cursor. 86 IBusLookupTable table; 87 const int table_size = 9; 88 InitIBusLookupTableWithCandidatesFilled(table_size, &table); 89 candidate_window_view.UpdateCandidates(table); 90 EXPECT_EQ(0, candidate_window_view.selected_candidate_index_in_page_); 91 92 // Invisible cursor. 93 table.set_is_cursor_visible(false); 94 candidate_window_view.UpdateCandidates(table); 95 EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); 96 97 // Move the cursor to the end. 98 table.set_cursor_position(table_size - 1); 99 candidate_window_view.UpdateCandidates(table); 100 EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); 101 102 // Change the cursor to visible. The cursor must be at the end. 103 table.set_is_cursor_visible(true); 104 candidate_window_view.UpdateCandidates(table); 105 EXPECT_EQ(table_size - 1, 106 candidate_window_view.selected_candidate_index_in_page_); 107 } 108 109 TEST_F(CandidateWindowViewTest, SelectCandidateAtTest) { 110 views::Widget* widget = new views::Widget; 111 views::Widget::InitParams params = 112 CreateParams(views::Widget::InitParams::TYPE_WINDOW); 113 widget->Init(params); 114 115 CandidateWindowView candidate_window_view(widget); 116 candidate_window_view.Init(); 117 118 // Set 9 candidates. 119 IBusLookupTable table_large; 120 const int table_large_size = 9; 121 InitIBusLookupTableWithCandidatesFilled(table_large_size, &table_large); 122 table_large.set_cursor_position(table_large_size - 1); 123 candidate_window_view.UpdateCandidates(table_large); 124 // Select the last candidate. 125 candidate_window_view.SelectCandidateAt(table_large_size - 1); 126 127 // Reduce the number of candidates to 3. 128 IBusLookupTable table_small; 129 const int table_small_size = 3; 130 InitIBusLookupTableWithCandidatesFilled(table_small_size, &table_small); 131 table_small.set_cursor_position(table_small_size - 1); 132 // Make sure the test doesn't crash if the candidate table reduced its size. 133 // (crbug.com/174163) 134 candidate_window_view.UpdateCandidates(table_small); 135 candidate_window_view.SelectCandidateAt(table_small_size - 1); 136 } 137 138 TEST_F(CandidateWindowViewTest, ShortcutSettingTest) { 139 const char* kEmptyLabel = ""; 140 const char* kCustomizedLabel[] = { "a", "s", "d" }; 141 const char* kExpectedHorizontalCustomizedLabel[] = { "a.", "s.", "d." }; 142 143 views::Widget* widget = new views::Widget; 144 views::Widget::InitParams params = 145 CreateParams(views::Widget::InitParams::TYPE_WINDOW); 146 widget->Init(params); 147 148 CandidateWindowView candidate_window_view(widget); 149 candidate_window_view.Init(); 150 151 { 152 SCOPED_TRACE("candidate_views allocation test"); 153 const size_t kMaxPageSize = 16; 154 for (size_t i = 1; i < kMaxPageSize; ++i) { 155 IBusLookupTable table; 156 InitIBusLookupTable(i, &table); 157 candidate_window_view.UpdateCandidates(table); 158 EXPECT_EQ(i, candidate_window_view.candidate_views_.size()); 159 } 160 } 161 { 162 SCOPED_TRACE("Empty string for each labels expects empty labels(vertical)"); 163 const size_t kPageSize = 3; 164 IBusLookupTable table; 165 InitIBusLookupTable(kPageSize, &table); 166 167 table.set_orientation(IBusLookupTable::VERTICAL); 168 for (size_t i = 0; i < kPageSize; ++i) { 169 IBusLookupTable::Entry entry; 170 entry.value = kSampleCandidate[i]; 171 entry.annotation = kSampleAnnotation[i]; 172 entry.description_title = kSampleDescriptionTitle[i]; 173 entry.description_body = kSampleDescriptionBody[i]; 174 entry.label = kEmptyLabel; 175 table.mutable_candidates()->push_back(entry); 176 } 177 178 candidate_window_view.UpdateCandidates(table); 179 180 ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); 181 for (size_t i = 0; i < kPageSize; ++i) { 182 ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], 183 candidate_window_view.candidate_views_[i]); 184 } 185 } 186 { 187 SCOPED_TRACE( 188 "Empty string for each labels expect empty labels(horizontal)"); 189 const size_t kPageSize = 3; 190 IBusLookupTable table; 191 InitIBusLookupTable(kPageSize, &table); 192 193 table.set_orientation(IBusLookupTable::HORIZONTAL); 194 for (size_t i = 0; i < kPageSize; ++i) { 195 IBusLookupTable::Entry entry; 196 entry.value = kSampleCandidate[i]; 197 entry.annotation = kSampleAnnotation[i]; 198 entry.description_title = kSampleDescriptionTitle[i]; 199 entry.description_body = kSampleDescriptionBody[i]; 200 entry.label = kEmptyLabel; 201 table.mutable_candidates()->push_back(entry); 202 } 203 204 candidate_window_view.UpdateCandidates(table); 205 206 ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); 207 // Confirm actual labels not containing ".". 208 for (size_t i = 0; i < kPageSize; ++i) { 209 ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i], 210 candidate_window_view.candidate_views_[i]); 211 } 212 } 213 { 214 SCOPED_TRACE("Vertical customized label case"); 215 const size_t kPageSize = 3; 216 IBusLookupTable table; 217 InitIBusLookupTable(kPageSize, &table); 218 219 table.set_orientation(IBusLookupTable::VERTICAL); 220 for (size_t i = 0; i < kPageSize; ++i) { 221 IBusLookupTable::Entry entry; 222 entry.value = kSampleCandidate[i]; 223 entry.annotation = kSampleAnnotation[i]; 224 entry.description_title = kSampleDescriptionTitle[i]; 225 entry.description_body = kSampleDescriptionBody[i]; 226 entry.label = kCustomizedLabel[i]; 227 table.mutable_candidates()->push_back(entry); 228 } 229 230 candidate_window_view.UpdateCandidates(table); 231 232 ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); 233 // Confirm actual labels not containing ".". 234 for (size_t i = 0; i < kPageSize; ++i) { 235 ExpectLabels(kCustomizedLabel[i], 236 kSampleCandidate[i], 237 kSampleAnnotation[i], 238 candidate_window_view.candidate_views_[i]); 239 } 240 } 241 { 242 SCOPED_TRACE("Horizontal customized label case"); 243 const size_t kPageSize = 3; 244 IBusLookupTable table; 245 InitIBusLookupTable(kPageSize, &table); 246 247 table.set_orientation(IBusLookupTable::HORIZONTAL); 248 for (size_t i = 0; i < kPageSize; ++i) { 249 IBusLookupTable::Entry entry; 250 entry.value = kSampleCandidate[i]; 251 entry.annotation = kSampleAnnotation[i]; 252 entry.description_title = kSampleDescriptionTitle[i]; 253 entry.description_body = kSampleDescriptionBody[i]; 254 entry.label = kCustomizedLabel[i]; 255 table.mutable_candidates()->push_back(entry); 256 } 257 258 candidate_window_view.UpdateCandidates(table); 259 260 ASSERT_EQ(kPageSize, candidate_window_view.candidate_views_.size()); 261 // Confirm actual labels not containing ".". 262 for (size_t i = 0; i < kPageSize; ++i) { 263 ExpectLabels(kExpectedHorizontalCustomizedLabel[i], 264 kSampleCandidate[i], 265 kSampleAnnotation[i], 266 candidate_window_view.candidate_views_[i]); 267 } 268 } 269 270 // We should call CloseNow method, otherwise this test will leak memory. 271 widget->CloseNow(); 272 } 273 274 TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest) { 275 const size_t kPageSize = 10; 276 IBusLookupTable table; 277 IBusLookupTable no_shortcut_table; 278 279 const char kSampleCandidate1[] = "Sample String 1"; 280 const char kSampleCandidate2[] = "\xE3\x81\x82"; // multi byte string. 281 const char kSampleCandidate3[] = "....."; 282 283 const char kSampleShortcut1[] = "1"; 284 const char kSampleShortcut2[] = "b"; 285 const char kSampleShortcut3[] = "C"; 286 287 const char kSampleAnnotation1[] = "Sample Annotation 1"; 288 const char kSampleAnnotation2[] = "\xE3\x81\x82"; // multi byte string. 289 const char kSampleAnnotation3[] = "......"; 290 291 // For testing, we have to prepare empty widget. 292 // We should NOT manually free widget by default, otherwise double free will 293 // be occurred. So, we should instantiate widget class with "new" operation. 294 views::Widget* widget = new views::Widget; 295 views::Widget::InitParams params = 296 CreateParams(views::Widget::InitParams::TYPE_WINDOW); 297 widget->Init(params); 298 299 CandidateWindowView candidate_window_view(widget); 300 candidate_window_view.Init(); 301 302 // Create LookupTable object. 303 InitIBusLookupTable(kPageSize, &table); 304 305 table.set_cursor_position(0); 306 table.set_page_size(3); 307 table.mutable_candidates()->clear(); 308 table.set_orientation(IBusLookupTable::VERTICAL); 309 no_shortcut_table.CopyFrom(table); 310 311 IBusLookupTable::Entry entry; 312 entry.value = kSampleCandidate1; 313 entry.annotation = kSampleAnnotation1; 314 table.mutable_candidates()->push_back(entry); 315 entry.label = kSampleShortcut1; 316 no_shortcut_table.mutable_candidates()->push_back(entry); 317 318 entry.value = kSampleCandidate2; 319 entry.annotation = kSampleAnnotation2; 320 table.mutable_candidates()->push_back(entry); 321 entry.label = kSampleShortcut2; 322 no_shortcut_table.mutable_candidates()->push_back(entry); 323 324 entry.value = kSampleCandidate3; 325 entry.annotation = kSampleAnnotation3; 326 table.mutable_candidates()->push_back(entry); 327 entry.label = kSampleShortcut3; 328 no_shortcut_table.mutable_candidates()->push_back(entry); 329 330 int before_height = 0; 331 332 // Test for shortcut mode to no-shortcut mode. 333 // Initialize with a shortcut mode lookup table. 334 candidate_window_view.MaybeInitializeCandidateViews(table); 335 ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); 336 // Check the selected index is invalidated. 337 EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); 338 before_height = 339 candidate_window_view.candidate_views_[0]->GetContentsBounds().height(); 340 // Checks all entry have same row height. 341 for (size_t i = 1; i < candidate_window_view.candidate_views_.size(); ++i) { 342 const CandidateView* view = candidate_window_view.candidate_views_[i]; 343 EXPECT_EQ(before_height, view->GetContentsBounds().height()); 344 } 345 346 // Initialize with a no shortcut mode lookup table. 347 candidate_window_view.MaybeInitializeCandidateViews(no_shortcut_table); 348 ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); 349 // Check the selected index is invalidated. 350 EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); 351 EXPECT_EQ(before_height, 352 candidate_window_view.candidate_views_[0]->GetContentsBounds() 353 .height()); 354 // Checks all entry have same row height. 355 for (size_t i = 1; i < candidate_window_view.candidate_views_.size(); ++i) { 356 const CandidateView* view = candidate_window_view.candidate_views_[i]; 357 EXPECT_EQ(before_height, view->GetContentsBounds().height()); 358 } 359 360 // Test for no-shortcut mode to shortcut mode. 361 // Initialize with a no shortcut mode lookup table. 362 candidate_window_view.MaybeInitializeCandidateViews(no_shortcut_table); 363 ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); 364 // Check the selected index is invalidated. 365 EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); 366 before_height = 367 candidate_window_view.candidate_views_[0]->GetContentsBounds().height(); 368 // Checks all entry have same row height. 369 for (size_t i = 1; i < candidate_window_view.candidate_views_.size(); ++i) { 370 const CandidateView* view = candidate_window_view.candidate_views_[i]; 371 EXPECT_EQ(before_height, view->GetContentsBounds().height()); 372 } 373 374 // Initialize with a shortcut mode lookup table. 375 candidate_window_view.MaybeInitializeCandidateViews(table); 376 ASSERT_EQ(3UL, candidate_window_view.candidate_views_.size()); 377 // Check the selected index is invalidated. 378 EXPECT_EQ(-1, candidate_window_view.selected_candidate_index_in_page_); 379 EXPECT_EQ(before_height, 380 candidate_window_view.candidate_views_[0]->GetContentsBounds() 381 .height()); 382 // Checks all entry have same row height. 383 for (size_t i = 1; i < candidate_window_view.candidate_views_.size(); ++i) { 384 const CandidateView* view = candidate_window_view.candidate_views_[i]; 385 EXPECT_EQ(before_height, view->GetContentsBounds().height()); 386 } 387 388 // We should call CloseNow method, otherwise this test will leak memory. 389 widget->CloseNow(); 390 } 391 } // namespace input_method 392 } // namespace chromeos 393