1 // Copyright 2020 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/emoji_suggester.h"
6
7 #include "base/strings/utf_string_conversions.h"
8 #include "base/test/metrics/histogram_tester.h"
9 #include "chrome/browser/chromeos/input_method/input_method_engine.h"
10 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
11 #include "chrome/test/base/testing_profile.h"
12 #include "content/public/test/browser_task_environment.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14
15 namespace chromeos {
16
17 const char kEmojiData[] = "happy,;;";
18
19 class TestSuggestionHandler : public SuggestionHandlerInterface {
20 public:
SetButtonHighlighted(int context_id,const ui::ime::AssistiveWindowButton & button,bool highlighted,std::string * error)21 bool SetButtonHighlighted(int context_id,
22 const ui::ime::AssistiveWindowButton& button,
23 bool highlighted,
24 std::string* error) override {
25 switch (button.id) {
26 case ui::ime::ButtonId::kLearnMore:
27 learn_more_button_highlighted_ = highlighted;
28 return true;
29 case ui::ime::ButtonId::kSuggestion:
30 // If highlighted, needs to unhighlight previously highlighted button.
31 if (currently_highlighted_index_ != INT_MAX && highlighted) {
32 candidate_highlighted_[currently_highlighted_index_] = 0;
33 }
34 currently_highlighted_index_ = highlighted ? button.index : INT_MAX;
35 candidate_highlighted_[button.index] = highlighted ? 1 : 0;
36 return true;
37 default:
38 return false;
39 }
40 }
41
SetAssistiveWindowProperties(int context_id,const AssistiveWindowProperties & assistive_window,std::string * error)42 bool SetAssistiveWindowProperties(
43 int context_id,
44 const AssistiveWindowProperties& assistive_window,
45 std::string* error) override {
46 candidate_highlighted_.clear();
47 for (size_t i = 0; i < assistive_window.candidates.size(); i++) {
48 candidate_highlighted_.push_back(0);
49 }
50 show_indices_ = assistive_window.show_indices;
51 show_setting_link_ = assistive_window.show_setting_link;
52 return true;
53 }
54
VerifyShowIndices(bool show_indices)55 void VerifyShowIndices(bool show_indices) {
56 EXPECT_EQ(show_indices_, show_indices);
57 }
58
VerifyLearnMoreButtonHighlighted(const bool highlighted)59 void VerifyLearnMoreButtonHighlighted(const bool highlighted) {
60 EXPECT_EQ(learn_more_button_highlighted_, highlighted);
61 }
62
VerifyCandidateHighlighted(const int index,const bool highlighted)63 void VerifyCandidateHighlighted(const int index, const bool highlighted) {
64 int expect = highlighted ? 1 : 0;
65 EXPECT_EQ(candidate_highlighted_[index], expect);
66 }
67
VerifyShowSettingLink(const bool show_setting_link)68 void VerifyShowSettingLink(const bool show_setting_link) {
69 EXPECT_EQ(show_setting_link_, show_setting_link);
70 }
71
DismissSuggestion(int context_id,std::string * error)72 bool DismissSuggestion(int context_id, std::string* error) override {
73 return false;
74 }
75
AcceptSuggestion(int context_id,std::string * error)76 bool AcceptSuggestion(int context_id, std::string* error) override {
77 return false;
78 }
79
OnSuggestionsChanged(const std::vector<std::string> & suggestions)80 void OnSuggestionsChanged(
81 const std::vector<std::string>& suggestions) override {}
82
ClickButton(const ui::ime::AssistiveWindowButton & button)83 void ClickButton(const ui::ime::AssistiveWindowButton& button) override {}
84
AcceptSuggestionCandidate(int context_id,const base::string16 & candidate,std::string * error)85 bool AcceptSuggestionCandidate(int context_id,
86 const base::string16& candidate,
87 std::string* error) override {
88 return false;
89 }
90
SetSuggestion(int context_id,const ui::ime::SuggestionDetails & details,std::string * error)91 bool SetSuggestion(int context_id,
92 const ui::ime::SuggestionDetails& details,
93 std::string* error) override {
94 return false;
95 }
96
97 private:
98 bool show_indices_ = false;
99 bool show_setting_link_ = false;
100 bool learn_more_button_highlighted_ = false;
101 std::vector<int> candidate_highlighted_;
102 size_t currently_highlighted_index_ = INT_MAX;
103 };
104
105 class EmojiSuggesterTest : public testing::Test {
106 protected:
SetUp()107 void SetUp() override {
108 engine_ = std::make_unique<TestSuggestionHandler>();
109 profile_ = std::make_unique<TestingProfile>();
110 emoji_suggester_ =
111 std::make_unique<EmojiSuggester>(engine_.get(), profile_.get());
112 emoji_suggester_->LoadEmojiMapForTesting(kEmojiData);
113 chrome_keyboard_controller_client_ =
114 ChromeKeyboardControllerClient::CreateForTest();
115 chrome_keyboard_controller_client_->set_keyboard_visible_for_test(false);
116 }
117
Press(std::string event_key)118 SuggestionStatus Press(std::string event_key) {
119 InputMethodEngineBase::KeyboardEvent event;
120 event.key = event_key;
121 return emoji_suggester_->HandleKeyEvent(event);
122 }
123
124 content::BrowserTaskEnvironment task_environment_;
125 std::unique_ptr<EmojiSuggester> emoji_suggester_;
126 std::unique_ptr<TestSuggestionHandler> engine_;
127 std::unique_ptr<TestingProfile> profile_;
128 std::unique_ptr<ChromeKeyboardControllerClient>
129 chrome_keyboard_controller_client_;
130 };
131
TEST_F(EmojiSuggesterTest,SuggestWhenStringEndsWithSpace)132 TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpace) {
133 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
134 }
135
TEST_F(EmojiSuggesterTest,SuggestWhenStringStartsWithOpenBracket)136 TEST_F(EmojiSuggesterTest, SuggestWhenStringStartsWithOpenBracket) {
137 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("(happy ")));
138 }
139
TEST_F(EmojiSuggesterTest,SuggestWhenStringEndsWithSpaceAndIsUppercase)140 TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpaceAndIsUppercase) {
141 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("HAPPY ")));
142 }
143
TEST_F(EmojiSuggesterTest,DoNotSuggestWhenStringEndsWithNewLine)144 TEST_F(EmojiSuggesterTest, DoNotSuggestWhenStringEndsWithNewLine) {
145 EXPECT_FALSE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy\n")));
146 }
147
TEST_F(EmojiSuggesterTest,DoNotSuggestWhenStringDoesNotEndWithSpace)148 TEST_F(EmojiSuggesterTest, DoNotSuggestWhenStringDoesNotEndWithSpace) {
149 EXPECT_FALSE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy")));
150 }
151
TEST_F(EmojiSuggesterTest,DoNotSuggestWhenWordNotInMap)152 TEST_F(EmojiSuggesterTest, DoNotSuggestWhenWordNotInMap) {
153 EXPECT_FALSE(emoji_suggester_->Suggest(base::UTF8ToUTF16("hapy ")));
154 }
155
TEST_F(EmojiSuggesterTest,DoNotShowSuggestionWhenVirtualKeyboardEnabled)156 TEST_F(EmojiSuggesterTest, DoNotShowSuggestionWhenVirtualKeyboardEnabled) {
157 chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true);
158 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
159 EXPECT_FALSE(emoji_suggester_->GetSuggestionShownForTesting());
160 }
161
TEST_F(EmojiSuggesterTest,ReturnkBrowsingWhenPressingDown)162 TEST_F(EmojiSuggesterTest, ReturnkBrowsingWhenPressingDown) {
163 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
164 InputMethodEngineBase::KeyboardEvent event;
165 event.key = "Down";
166 EXPECT_EQ(SuggestionStatus::kBrowsing,
167 emoji_suggester_->HandleKeyEvent(event));
168 }
169
TEST_F(EmojiSuggesterTest,ReturnkBrowsingWhenPressingUp)170 TEST_F(EmojiSuggesterTest, ReturnkBrowsingWhenPressingUp) {
171 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
172 InputMethodEngineBase::KeyboardEvent event;
173 event.key = "Up";
174 EXPECT_EQ(SuggestionStatus::kBrowsing,
175 emoji_suggester_->HandleKeyEvent(event));
176 }
177
TEST_F(EmojiSuggesterTest,ReturnkDismissWhenPressingEsc)178 TEST_F(EmojiSuggesterTest, ReturnkDismissWhenPressingEsc) {
179 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
180 InputMethodEngineBase::KeyboardEvent event;
181 event.key = "Esc";
182 EXPECT_EQ(SuggestionStatus::kDismiss,
183 emoji_suggester_->HandleKeyEvent(event));
184 }
185
TEST_F(EmojiSuggesterTest,ReturnkNotHandledWhenPressDownThenValidNumber)186 TEST_F(EmojiSuggesterTest, ReturnkNotHandledWhenPressDownThenValidNumber) {
187 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
188 InputMethodEngineBase::KeyboardEvent event1;
189 event1.key = "Down";
190 emoji_suggester_->HandleKeyEvent(event1);
191 InputMethodEngineBase::KeyboardEvent event2;
192 event2.key = "1";
193 EXPECT_EQ(SuggestionStatus::kNotHandled,
194 emoji_suggester_->HandleKeyEvent(event2));
195 }
196
TEST_F(EmojiSuggesterTest,ReturnkNotHandledWhenPressDownThenNotANumber)197 TEST_F(EmojiSuggesterTest, ReturnkNotHandledWhenPressDownThenNotANumber) {
198 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
199 InputMethodEngineBase::KeyboardEvent event1;
200 event1.key = "Down";
201 emoji_suggester_->HandleKeyEvent(event1);
202 InputMethodEngineBase::KeyboardEvent event2;
203 event2.key = "a";
204 EXPECT_EQ(SuggestionStatus::kNotHandled,
205 emoji_suggester_->HandleKeyEvent(event2));
206 }
207
TEST_F(EmojiSuggesterTest,ReturnkNotHandledWhenPressingEnterAndACandidateHasNotBeenChosen)208 TEST_F(EmojiSuggesterTest,
209 ReturnkNotHandledWhenPressingEnterAndACandidateHasNotBeenChosen) {
210 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
211 InputMethodEngineBase::KeyboardEvent event;
212 event.key = "Enter";
213 EXPECT_EQ(SuggestionStatus::kNotHandled,
214 emoji_suggester_->HandleKeyEvent(event));
215 }
216
TEST_F(EmojiSuggesterTest,ReturnkAcceptWhenPressingEnterAndACandidateHasBeenChosenByPressingDown)217 TEST_F(EmojiSuggesterTest,
218 ReturnkAcceptWhenPressingEnterAndACandidateHasBeenChosenByPressingDown) {
219 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
220 // Press "Down" to choose a candidate.
221 InputMethodEngineBase::KeyboardEvent event1;
222 event1.key = "Down";
223 emoji_suggester_->HandleKeyEvent(event1);
224 InputMethodEngineBase::KeyboardEvent event2;
225 event2.key = "Enter";
226 EXPECT_EQ(SuggestionStatus::kAccept,
227 emoji_suggester_->HandleKeyEvent(event2));
228 }
229
TEST_F(EmojiSuggesterTest,HighlightFirstCandidateWhenPressingDown)230 TEST_F(EmojiSuggesterTest, HighlightFirstCandidateWhenPressingDown) {
231 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
232 Press("Down");
233 engine_->VerifyCandidateHighlighted(0, true);
234 }
235
TEST_F(EmojiSuggesterTest,HighlightButtonCorrectlyWhenPressingUp)236 TEST_F(EmojiSuggesterTest, HighlightButtonCorrectlyWhenPressingUp) {
237 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
238
239 // Go into the window.
240 Press("Down");
241
242 // Press "Up" to choose learn more button.
243 Press("Up");
244 engine_->VerifyLearnMoreButtonHighlighted(true);
245
246 // Press "Up" to go through candidates;
247 for (size_t i = emoji_suggester_->GetCandidatesSizeForTesting(); i > 0; i--) {
248 Press("Up");
249 engine_->VerifyCandidateHighlighted(i - 1, true);
250 engine_->VerifyLearnMoreButtonHighlighted(false);
251 if (i != emoji_suggester_->GetCandidatesSizeForTesting()) {
252 engine_->VerifyCandidateHighlighted(i, false);
253 }
254 }
255
256 // Press "Up" to go to learn more button from first candidate.
257 Press("Up");
258 engine_->VerifyLearnMoreButtonHighlighted(true);
259 }
260
TEST_F(EmojiSuggesterTest,HighlightButtonCorrectlyWhenPressingDown)261 TEST_F(EmojiSuggesterTest, HighlightButtonCorrectlyWhenPressingDown) {
262 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
263
264 // Press "Down" to go through candidates.
265 for (size_t i = 0; i < emoji_suggester_->GetCandidatesSizeForTesting(); i++) {
266 Press("Down");
267 engine_->VerifyCandidateHighlighted(i, true);
268 engine_->VerifyLearnMoreButtonHighlighted(false);
269 if (i != 0) {
270 engine_->VerifyCandidateHighlighted(i - 1, false);
271 }
272 }
273
274 // Go to LearnMore Button
275 Press("Down");
276 engine_->VerifyLearnMoreButtonHighlighted(true);
277 engine_->VerifyCandidateHighlighted(
278 emoji_suggester_->GetCandidatesSizeForTesting() - 1, false);
279
280 // Go to first candidate
281 Press("Down");
282 engine_->VerifyLearnMoreButtonHighlighted(false);
283 engine_->VerifyCandidateHighlighted(0, true);
284 }
285
TEST_F(EmojiSuggesterTest,OpenSettingWhenPressingEnterAndLearnMoreButtonIsChosen)286 TEST_F(EmojiSuggesterTest,
287 OpenSettingWhenPressingEnterAndLearnMoreButtonIsChosen) {
288 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
289
290 // Go into the window.
291 Press("Down");
292 // Choose Learn More Button.
293 Press("Up");
294 engine_->VerifyLearnMoreButtonHighlighted(true);
295
296 EXPECT_EQ(Press("Enter"), SuggestionStatus::kOpenSettings);
297 }
298
TEST_F(EmojiSuggesterTest,DoesNotShowIndicesWhenFirstSuggesting)299 TEST_F(EmojiSuggesterTest, DoesNotShowIndicesWhenFirstSuggesting) {
300 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
301
302 engine_->VerifyShowIndices(false);
303 }
304
TEST_F(EmojiSuggesterTest,DoesNotShowIndexAfterPressingDown)305 TEST_F(EmojiSuggesterTest, DoesNotShowIndexAfterPressingDown) {
306 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
307 Press("Down");
308
309 engine_->VerifyShowIndices(false);
310 }
311
TEST_F(EmojiSuggesterTest,DoesNotShowIndicesAfterGettingSuggestionsTwice)312 TEST_F(EmojiSuggesterTest, DoesNotShowIndicesAfterGettingSuggestionsTwice) {
313 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
314 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
315
316 engine_->VerifyShowIndices(false);
317 }
318
TEST_F(EmojiSuggesterTest,DoesNotShowIndicesAfterPressingDownThenGetNewSuggestions)319 TEST_F(EmojiSuggesterTest,
320 DoesNotShowIndicesAfterPressingDownThenGetNewSuggestions) {
321 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
322 Press("Down");
323 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
324
325 engine_->VerifyShowIndices(false);
326 }
327
TEST_F(EmojiSuggesterTest,ShowSettingLinkCorrectly)328 TEST_F(EmojiSuggesterTest, ShowSettingLinkCorrectly) {
329 for (int i = 0; i < kEmojiSuggesterShowSettingMaxCount; i++) {
330 emoji_suggester_->Suggest(base::UTF8ToUTF16("happy "));
331 // Dismiss suggestion.
332 Press("Esc");
333 engine_->VerifyShowSettingLink(true);
334 }
335 emoji_suggester_->Suggest(base::UTF8ToUTF16("happy "));
336 engine_->VerifyShowSettingLink(false);
337 }
338
TEST_F(EmojiSuggesterTest,RecordsTimeToAccept)339 TEST_F(EmojiSuggesterTest, RecordsTimeToAccept) {
340 base::HistogramTester histogram_tester;
341 histogram_tester.ExpectTotalCount("InputMethod.Assistive.TimeToAccept.Emoji",
342 0);
343 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
344 // Press "Down" to choose and accept a candidate.
345 Press("Down");
346 Press("Enter");
347 histogram_tester.ExpectTotalCount("InputMethod.Assistive.TimeToAccept.Emoji",
348 1);
349 }
350
TEST_F(EmojiSuggesterTest,RecordsTimeToDismiss)351 TEST_F(EmojiSuggesterTest, RecordsTimeToDismiss) {
352 base::HistogramTester histogram_tester;
353 histogram_tester.ExpectTotalCount("InputMethod.Assistive.TimeToDismiss.Emoji",
354 0);
355 EXPECT_TRUE(emoji_suggester_->Suggest(base::UTF8ToUTF16("happy ")));
356 // Press "Esc" to dismiss.
357 Press("Esc");
358 histogram_tester.ExpectTotalCount("InputMethod.Assistive.TimeToDismiss.Emoji",
359 1);
360 }
361
362 } // namespace chromeos
363