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
InitCandidateWindow(size_t page_size,CandidateWindow * candidate_window)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
InitCandidateWindowWithCandidatesFilled(size_t page_size,CandidateWindow * candidate_window)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:
ExpectLabels(const std::string & shortcut,const std::string & candidate,const std::string & annotation,const CandidateView * row)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
TEST_F(CandidateWindowViewTest,UpdateCandidatesTest_CursorVisibility)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
TEST_F(CandidateWindowViewTest,SelectCandidateAtTest)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
TEST_F(CandidateWindowViewTest,ShortcutSettingTest)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
TEST_F(CandidateWindowViewTest,DoNotChangeRowHeightWithLabelSwitchTest)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