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
16namespace ash {
17namespace ime {
18
19namespace {
20const char* kSampleCandidate[] = {
21  "Sample Candidate 1",
22  "Sample Candidate 2",
23  "Sample Candidate 3"
24};
25const char* kSampleAnnotation[] = {
26  "Sample Annotation 1",
27  "Sample Annotation 2",
28  "Sample Annotation 3"
29};
30const char* kSampleDescriptionTitle[] = {
31  "Sample Description Title 1",
32  "Sample Description Title 2",
33  "Sample Description Title 3",
34};
35const char* kSampleDescriptionBody[] = {
36  "Sample Description Body 1",
37  "Sample Description Body 2",
38  "Sample Description Body 3",
39};
40
41void 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
49void 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
65class 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
118TEST_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
143TEST_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
167TEST_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
292TEST_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