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