| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/input_method/emoji_suggester.h" |
| |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/ash/input_method/input_method_engine.h" |
| #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| |
| namespace ash { |
| namespace input_method { |
| namespace { |
| |
| using AssistiveSuggestion = ime::AssistiveSuggestion; |
| using AssistiveSuggestionMode = ime::AssistiveSuggestionMode; |
| using AssistiveSuggestionType = ime::AssistiveSuggestionType; |
| |
| } // namespace |
| |
| ui::KeyEvent CreateKeyEventFromCode(const ui::DomCode& code) { |
| return ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, code, ui::EF_NONE, |
| ui::DomKey::NONE, ui::EventTimeForNow()); |
| } |
| |
| const char kEmojiData[] = "happy,😀;😃;😄"; |
| const int kContextId = 24601; |
| |
| class TestSuggestionHandler : public SuggestionHandlerInterface { |
| public: |
| bool SetButtonHighlighted(int context_id, |
| const ui::ime::AssistiveWindowButton& button, |
| bool highlighted, |
| std::string* error) override { |
| switch (button.id) { |
| case ui::ime::ButtonId::kLearnMore: |
| learn_more_button_highlighted_ = highlighted; |
| return true; |
| case ui::ime::ButtonId::kSuggestion: |
| // If highlighted, needs to unhighlight previously highlighted button. |
| if (currently_highlighted_index_ != INT_MAX && highlighted) { |
| candidate_highlighted_[currently_highlighted_index_] = 0; |
| } |
| currently_highlighted_index_ = highlighted ? button.index : INT_MAX; |
| candidate_highlighted_[button.index] = highlighted ? 1 : 0; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool SetAssistiveWindowProperties( |
| int context_id, |
| const AssistiveWindowProperties& assistive_window, |
| std::string* error) override { |
| context_id_ = context_id; |
| candidate_highlighted_.clear(); |
| for (size_t i = 0; i < assistive_window.candidates.size(); i++) { |
| candidate_highlighted_.push_back(0); |
| } |
| show_indices_ = assistive_window.show_indices; |
| show_setting_link_ = assistive_window.show_setting_link; |
| return true; |
| } |
| |
| void VerifyShowIndices(bool show_indices) { |
| EXPECT_EQ(show_indices_, show_indices); |
| } |
| |
| void VerifyLearnMoreButtonHighlighted(const bool highlighted) { |
| EXPECT_EQ(learn_more_button_highlighted_, highlighted); |
| } |
| |
| void VerifyCandidateHighlighted(const int index, const bool highlighted) { |
| int expect = highlighted ? 1 : 0; |
| EXPECT_EQ(candidate_highlighted_[index], expect); |
| } |
| |
| void VerifyShowSettingLink(const bool show_setting_link) { |
| EXPECT_EQ(show_setting_link_, show_setting_link); |
| } |
| |
| void VerifyContextId(const int context_id) { |
| EXPECT_EQ(context_id_, context_id); |
| } |
| |
| bool DismissSuggestion(int context_id, std::string* error) override { |
| return false; |
| } |
| |
| bool AcceptSuggestion(int context_id, std::string* error) override { |
| return false; |
| } |
| |
| void OnSuggestionsChanged( |
| const std::vector<std::string>& suggestions) override {} |
| |
| void ClickButton(const ui::ime::AssistiveWindowButton& button) override {} |
| |
| bool AcceptSuggestionCandidate(int context_id, |
| const std::u16string& candidate, |
| size_t delete_previous_utf16_len, |
| bool use_replace_surrounding_text, |
| std::string* error) override { |
| return false; |
| } |
| |
| bool SetSuggestion(int context_id, |
| const ui::ime::SuggestionDetails& details, |
| std::string* error) override { |
| return false; |
| } |
| |
| void Announce(const std::u16string& message) override {} |
| |
| private: |
| bool show_indices_ = false; |
| bool show_setting_link_ = false; |
| bool learn_more_button_highlighted_ = false; |
| std::vector<int> candidate_highlighted_; |
| size_t currently_highlighted_index_ = INT_MAX; |
| int context_id_ = -1; |
| }; |
| |
| class EmojiSuggesterTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| engine_ = std::make_unique<TestSuggestionHandler>(); |
| profile_ = std::make_unique<TestingProfile>(); |
| emoji_suggester_ = |
| std::make_unique<EmojiSuggester>(engine_.get(), profile_.get()); |
| emoji_suggester_->LoadEmojiMapForTesting(kEmojiData); |
| chrome_keyboard_controller_client_ = |
| ChromeKeyboardControllerClient::CreateForTest(); |
| chrome_keyboard_controller_client_->set_keyboard_visible_for_test(false); |
| emoji_suggester_->OnFocus(kContextId); |
| } |
| |
| SuggestionStatus Press(ui::DomCode code) { |
| return emoji_suggester_->HandleKeyEvent(CreateKeyEventFromCode(code)); |
| } |
| |
| content::BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<EmojiSuggester> emoji_suggester_; |
| std::unique_ptr<TestSuggestionHandler> engine_; |
| std::unique_ptr<TestingProfile> profile_; |
| std::unique_ptr<ChromeKeyboardControllerClient> |
| chrome_keyboard_controller_client_; |
| }; |
| |
| TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpace) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpaceInNewLine) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText( |
| u"oldline\nhappy ", gfx::Range(14))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, PassesContextIdToHandlerOnSuggestion) { |
| emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", gfx::Range(6)); |
| engine_->VerifyContextId(kContextId); |
| } |
| |
| TEST_F(EmojiSuggesterTest, SuggestWhenStringStartsWithOpenBracket) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"(happy ", |
| gfx::Range(7))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, SuggestWhenStringEndsWithSpaceAndIsUppercase) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"HAPPY ", |
| gfx::Range(6))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotSuggestWhenStringEndsWithNewLine) { |
| EXPECT_FALSE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy\n", |
| gfx::Range(6))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotSuggestWhenStringDoesNotEndWithSpace) { |
| EXPECT_FALSE( |
| emoji_suggester_->TrySuggestWithSurroundingText(u"happy", gfx::Range(5))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotSuggestOnWhenContainsCursorSelection) { |
| EXPECT_FALSE(emoji_suggester_->TrySuggestWithSurroundingText( |
| u"happy ", gfx::Range(6, 2))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotSuggestOnWhenNotAtEndOfText) { |
| EXPECT_FALSE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(3))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotSuggestWhenWordNotInMap) { |
| EXPECT_FALSE( |
| emoji_suggester_->TrySuggestWithSurroundingText(u"hapy ", gfx::Range(5))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotSuggestAfterBlur) { |
| emoji_suggester_->OnBlur(); |
| EXPECT_FALSE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoNotShowSuggestionWhenVirtualKeyboardEnabled) { |
| chrome_keyboard_controller_client_->set_keyboard_visible_for_test(true); |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| EXPECT_FALSE(emoji_suggester_->HasSuggestions()); |
| } |
| |
| TEST_F(EmojiSuggesterTest, ReturnkBrowsingWhenPressingDown) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN); |
| EXPECT_EQ(SuggestionStatus::kBrowsing, |
| emoji_suggester_->HandleKeyEvent(event)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, ReturnkBrowsingWhenPressingUp) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ARROW_UP); |
| EXPECT_EQ(SuggestionStatus::kBrowsing, |
| emoji_suggester_->HandleKeyEvent(event)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, ReturnkDismissWhenPressingEsc) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ESCAPE); |
| EXPECT_EQ(SuggestionStatus::kDismiss, |
| emoji_suggester_->HandleKeyEvent(event)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, ReturnkNotHandledWhenPressDownThenValidNumber) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| ui::KeyEvent event1 = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN); |
| emoji_suggester_->HandleKeyEvent(event1); |
| ui::KeyEvent event2 = CreateKeyEventFromCode(ui::DomCode::DIGIT1); |
| EXPECT_EQ(SuggestionStatus::kNotHandled, |
| emoji_suggester_->HandleKeyEvent(event2)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, ReturnkNotHandledWhenPressDownThenNotANumber) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| ui::KeyEvent event1 = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN); |
| emoji_suggester_->HandleKeyEvent(event1); |
| ui::KeyEvent event2 = CreateKeyEventFromCode(ui::DomCode::US_A); |
| EXPECT_EQ(SuggestionStatus::kNotHandled, |
| emoji_suggester_->HandleKeyEvent(event2)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, |
| ReturnkNotHandledWhenPressingEnterAndACandidateHasNotBeenChosen) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| ui::KeyEvent event = CreateKeyEventFromCode(ui::DomCode::ENTER); |
| EXPECT_EQ(SuggestionStatus::kNotHandled, |
| emoji_suggester_->HandleKeyEvent(event)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, |
| ReturnkAcceptWhenPressingEnterAndACandidateHasBeenChosenByPressingDown) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| // Press ui::DomCode::ARROW_DOWN to choose a candidate. |
| ui::KeyEvent event1 = CreateKeyEventFromCode(ui::DomCode::ARROW_DOWN); |
| emoji_suggester_->HandleKeyEvent(event1); |
| ui::KeyEvent event2 = CreateKeyEventFromCode(ui::DomCode::ENTER); |
| EXPECT_EQ(SuggestionStatus::kAccept, |
| emoji_suggester_->HandleKeyEvent(event2)); |
| } |
| |
| TEST_F(EmojiSuggesterTest, HighlightFirstCandidateWhenPressingDown) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| Press(ui::DomCode::ARROW_DOWN); |
| engine_->VerifyCandidateHighlighted(0, true); |
| } |
| |
| TEST_F(EmojiSuggesterTest, HighlightButtonCorrectlyWhenPressingUp) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| |
| // Go into the window. |
| Press(ui::DomCode::ARROW_DOWN); |
| |
| // Press ui::DomCode::ARROW_UP to choose learn more button. |
| Press(ui::DomCode::ARROW_UP); |
| engine_->VerifyLearnMoreButtonHighlighted(true); |
| |
| // Press ui::DomCode::ARROW_UP to go through candidates; |
| for (size_t i = emoji_suggester_->GetCandidatesSizeForTesting(); i > 0; i--) { |
| Press(ui::DomCode::ARROW_UP); |
| engine_->VerifyCandidateHighlighted(i - 1, true); |
| engine_->VerifyLearnMoreButtonHighlighted(false); |
| if (i != emoji_suggester_->GetCandidatesSizeForTesting()) { |
| engine_->VerifyCandidateHighlighted(i, false); |
| } |
| } |
| |
| // Press ui::DomCode::ARROW_UP to go to learn more button from first |
| // candidate. |
| Press(ui::DomCode::ARROW_UP); |
| engine_->VerifyLearnMoreButtonHighlighted(true); |
| } |
| |
| TEST_F(EmojiSuggesterTest, HighlightButtonCorrectlyWhenPressingDown) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| |
| // Press ui::DomCode::ARROW_DOWN to go through candidates. |
| for (size_t i = 0; i < emoji_suggester_->GetCandidatesSizeForTesting(); i++) { |
| Press(ui::DomCode::ARROW_DOWN); |
| engine_->VerifyCandidateHighlighted(i, true); |
| engine_->VerifyLearnMoreButtonHighlighted(false); |
| if (i != 0) { |
| engine_->VerifyCandidateHighlighted(i - 1, false); |
| } |
| } |
| |
| // Go to LearnMore Button |
| Press(ui::DomCode::ARROW_DOWN); |
| engine_->VerifyLearnMoreButtonHighlighted(true); |
| engine_->VerifyCandidateHighlighted( |
| emoji_suggester_->GetCandidatesSizeForTesting() - 1, false); |
| |
| // Go to first candidate |
| Press(ui::DomCode::ARROW_DOWN); |
| engine_->VerifyLearnMoreButtonHighlighted(false); |
| engine_->VerifyCandidateHighlighted(0, true); |
| } |
| |
| TEST_F(EmojiSuggesterTest, |
| OpenSettingWhenPressingEnterAndLearnMoreButtonIsChosen) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| |
| // Go into the window. |
| Press(ui::DomCode::ARROW_DOWN); |
| // Choose Learn More Button. |
| Press(ui::DomCode::ARROW_UP); |
| engine_->VerifyLearnMoreButtonHighlighted(true); |
| |
| EXPECT_EQ(Press(ui::DomCode::ENTER), SuggestionStatus::kOpenSettings); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoesNotShowIndicesWhenFirstSuggesting) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| |
| engine_->VerifyShowIndices(false); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoesNotShowIndexAfterPressingDown) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| Press(ui::DomCode::ARROW_DOWN); |
| |
| engine_->VerifyShowIndices(false); |
| } |
| |
| TEST_F(EmojiSuggesterTest, DoesNotShowIndicesAfterGettingSuggestionsTwice) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| |
| engine_->VerifyShowIndices(false); |
| } |
| |
| TEST_F(EmojiSuggesterTest, |
| DoesNotShowIndicesAfterPressingDownThenGetNewSuggestions) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| Press(ui::DomCode::ARROW_DOWN); |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| |
| engine_->VerifyShowIndices(false); |
| } |
| |
| TEST_F(EmojiSuggesterTest, ShowSettingLinkCorrectly) { |
| for (int i = 0; i < kEmojiSuggesterShowSettingMaxCount; i++) { |
| emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", gfx::Range(6)); |
| // Dismiss suggestion. |
| Press(ui::DomCode::ESCAPE); |
| engine_->VerifyShowSettingLink(true); |
| } |
| emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", gfx::Range(6)); |
| engine_->VerifyShowSettingLink(false); |
| } |
| |
| TEST_F(EmojiSuggesterTest, IsShowingSuggestionTrueWhenCandidatesAvailable) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| EXPECT_TRUE(emoji_suggester_->HasSuggestions()); |
| } |
| |
| TEST_F(EmojiSuggesterTest, IsShowingSuggestionFalseWhenCandidatesUnavailable) { |
| EXPECT_FALSE( |
| emoji_suggester_->TrySuggestWithSurroundingText(u"hapy", gfx::Range(4))); |
| EXPECT_FALSE(emoji_suggester_->HasSuggestions()); |
| } |
| |
| TEST_F(EmojiSuggesterTest, GetSuggestionReturnsCandidatesWhenAvailable) { |
| EXPECT_TRUE(emoji_suggester_->TrySuggestWithSurroundingText(u"happy ", |
| gfx::Range(6))); |
| EXPECT_EQ( |
| emoji_suggester_->GetSuggestions(), |
| (std::vector<AssistiveSuggestion>{ |
| AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction, |
| .type = AssistiveSuggestionType::kAssistiveEmoji, |
| .text = "😀"}, |
| AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction, |
| .type = AssistiveSuggestionType::kAssistiveEmoji, |
| .text = "😃"}, |
| AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction, |
| .type = AssistiveSuggestionType::kAssistiveEmoji, |
| .text = "😄"}, |
| })); |
| } |
| |
| TEST_F(EmojiSuggesterTest, |
| GetSuggestionDoesNotReturnCandidatesWhenUnavailable) { |
| EXPECT_FALSE( |
| emoji_suggester_->TrySuggestWithSurroundingText(u"hapy", gfx::Range(4))); |
| EXPECT_TRUE(emoji_suggester_->GetSuggestions().empty()); |
| } |
| } // namespace input_method |
| } // namespace ash |