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