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
InitCandidateWindow(size_t page_size,ui::CandidateWindow * candidate_window)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
InitCandidateWindowWithCandidatesFilled(size_t page_size,ui::CandidateWindow * candidate_window)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:
CandidateWindowViewTest()67 CandidateWindowViewTest() {}
~CandidateWindowViewTest()68 virtual ~CandidateWindowViewTest() {}
69
70 protected:
SetUp()71 virtual void SetUp() {
72 views::ViewsTestBase::SetUp();
73 candidate_window_view_ = new CandidateWindowView(GetContext());
74 candidate_window_view_->InitWidget();
75 }
76
candidate_window_view()77 CandidateWindowView* candidate_window_view() {
78 return candidate_window_view_;
79 }
80
selected_candidate_index_in_page()81 int selected_candidate_index_in_page() {
82 return candidate_window_view_->selected_candidate_index_in_page_;
83 }
84
GetCandidatesSize() const85 size_t GetCandidatesSize() const {
86 return candidate_window_view_->candidate_views_.size();
87 }
88
GetCandidateAt(size_t i)89 CandidateView* GetCandidateAt(size_t i) {
90 return candidate_window_view_->candidate_views_[i];
91 }
92
SelectCandidateAt(int index_in_page)93 void SelectCandidateAt(int index_in_page) {
94 candidate_window_view_->SelectCandidateAt(index_in_page);
95 }
96
MaybeInitializeCandidateViews(const ui::CandidateWindow & candidate_window)97 void MaybeInitializeCandidateViews(
98 const ui::CandidateWindow& candidate_window) {
99 candidate_window_view_->MaybeInitializeCandidateViews(candidate_window);
100 }
101
ExpectLabels(const std::string & shortcut,const std::string & candidate,const std::string & annotation,const CandidateView * row)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
TEST_F(CandidateWindowViewTest,UpdateCandidatesTest_CursorVisibility)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
TEST_F(CandidateWindowViewTest,SelectCandidateAtTest)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
TEST_F(CandidateWindowViewTest,ShortcutSettingTest)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
TEST_F(CandidateWindowViewTest,DoNotChangeRowHeightWithLabelSwitchTest)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