Home | History | Annotate | Download | only in input_method
      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