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