blob: 9ece8dc9a729d5cdbc2b208429e29dd4ae349f02 [file] [log] [blame]
// Copyright (c) 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/input_method/grammar_manager.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/chromeos/input_method/assistive_window_properties.h"
#include "chrome/browser/chromeos/input_method/grammar_service_client.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/services/machine_learning/public/cpp/fake_service_connection.h"
#include "components/spellcheck/common/spellcheck_result.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/chromeos/ime_bridge.h"
#include "ui/base/ime/chromeos/mock_ime_input_context_handler.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace chromeos {
namespace {
using ::testing::_;
class TestGrammarServiceClient : public GrammarServiceClient {
public:
TestGrammarServiceClient() {}
~TestGrammarServiceClient() override = default;
bool RequestTextCheck(Profile* profile,
const std::u16string& text,
TextCheckCompleteCallback callback) const override {
std::vector<ui::GrammarFragment> grammar_results;
for (int i = 0; i < text.size(); i++) {
if (text.substr(i, 5) == u"error") {
grammar_results.emplace_back(gfx::Range(i, i + 5), "correct");
}
}
std::move(callback).Run(true, grammar_results);
return true;
}
};
ui::KeyEvent CreateKeyEvent(const ui::DomCode& code) {
return ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, code, ui::EF_NONE,
ui::DomKey::NONE, ui::EventTimeForNow());
}
class MockSuggestionHandler : public SuggestionHandlerInterface {
public:
MOCK_METHOD(bool,
DismissSuggestion,
(int context_id, std::string* error),
(override));
MOCK_METHOD(bool,
SetSuggestion,
(int context_id,
const ui::ime::SuggestionDetails& details,
std::string* error),
(override));
MOCK_METHOD(bool,
AcceptSuggestion,
(int context_id, std::string* error),
(override));
MOCK_METHOD(void,
OnSuggestionsChanged,
(const std::vector<std::string>& suggestions),
(override));
MOCK_METHOD(bool,
SetButtonHighlighted,
(int context_id,
const ui::ime::AssistiveWindowButton& button,
bool highlighted,
std::string* error),
(override));
MOCK_METHOD(void,
ClickButton,
(const ui::ime::AssistiveWindowButton& button),
(override));
MOCK_METHOD(bool,
AcceptSuggestionCandidate,
(int context_id,
const std::u16string& candidate,
std::string* error),
(override));
MOCK_METHOD(bool,
SetAssistiveWindowProperties,
(int context_id,
const AssistiveWindowProperties& assistive_window,
std::string* error),
(override));
};
class GrammarManagerTest : public testing::Test {
protected:
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
ui::IMEBridge::Initialize();
ui::IMEBridge::Get()->SetInputContextHandler(
&mock_ime_input_context_handler_);
machine_learning::ServiceConnection::UseFakeServiceConnectionForTesting(
&fake_service_connection_);
machine_learning::ServiceConnection::GetInstance()->Initialize();
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<TestingProfile> profile_;
machine_learning::FakeServiceConnectionImpl fake_service_connection_;
ui::MockIMEInputContextHandler mock_ime_input_context_handler_;
};
TEST_F(GrammarManagerTest, HandlesSingleGrammarCheckResult) {
MockSuggestionHandler mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
base::HistogramTester histogram_tester;
manager.OnFocus(1, /*text_input_flags=*/0);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
auto grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(grammar_fragments.size(), 1);
EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
histogram_tester.ExpectUniqueSample("InputMethod.Assistive.Grammar.Actions",
0 /*GrammarAction::kUnderlined*/, 1);
}
TEST_F(GrammarManagerTest, DoesNotRunGrammarCheckOnTextFieldWithSpellcheckOff) {
MockSuggestionHandler mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
base::HistogramTester histogram_tester;
manager.OnFocus(1, ui::TEXT_INPUT_FLAG_SPELLCHECK_OFF);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
auto grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(grammar_fragments.size(), 0);
histogram_tester.ExpectUniqueSample("InputMethod.Assistive.Grammar.Actions",
0 /*GrammarAction::kUnderlined*/, 0);
}
TEST_F(GrammarManagerTest, ChecksLastSentenceImmediately) {
MockSuggestionHandler mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error. And another error.", 20,
20);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(100));
auto grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(grammar_fragments.size(), 1);
EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
}
TEST_F(GrammarManagerTest, ChecksBothLastAndCurrentSentence) {
MockSuggestionHandler mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error. And another error.", 20,
20);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
auto grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(grammar_fragments.size(), 2);
EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
EXPECT_EQ(grammar_fragments[1].range, gfx::Range(28, 33));
EXPECT_EQ(grammar_fragments[1].suggestion, "correct");
}
TEST_F(GrammarManagerTest, HandlesMultipleGrammarCheckResults) {
MockSuggestionHandler mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
auto grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(grammar_fragments.size(), 2);
EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
EXPECT_EQ(grammar_fragments[1].range, gfx::Range(15, 20));
EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
}
TEST_F(GrammarManagerTest, ClearsPreviousMarkersUponGettingNewResults) {
MockSuggestionHandler mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
auto grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(grammar_fragments.size(), 1);
EXPECT_EQ(grammar_fragments[0].range, gfx::Range(9, 14));
EXPECT_EQ(grammar_fragments[0].suggestion, "correct");
manager.OnSurroundingTextChanged(u"There is a new error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
auto updated_grammar_fragments =
mock_ime_input_context_handler_.get_grammar_fragments();
EXPECT_EQ(updated_grammar_fragments.size(), 1);
EXPECT_EQ(updated_grammar_fragments[0].range, gfx::Range(15, 20));
EXPECT_EQ(updated_grammar_fragments[0].suggestion, "correct");
}
TEST_F(GrammarManagerTest, ShowsAndDismissesGrammarSuggestion) {
::testing::StrictMock<MockSuggestionHandler> mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
base::HistogramTester histogram_tester;
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
AssistiveWindowProperties expected_properties;
expected_properties.type = ui::ime::AssistiveWindowType::kGrammarSuggestion;
expected_properties.candidates = {u"correct"};
expected_properties.visible = true;
EXPECT_CALL(mock_suggestion_handler,
SetAssistiveWindowProperties(1, expected_properties, _));
manager.OnSurroundingTextChanged(u"There is error.", 10, 10);
histogram_tester.ExpectBucketCount("InputMethod.Assistive.Grammar.Actions",
1 /*GrammarAction::kWindowShown*/, 1);
EXPECT_CALL(mock_suggestion_handler, DismissSuggestion(1, _));
manager.OnSurroundingTextChanged(u"There is error.", 3, 3);
}
TEST_F(GrammarManagerTest, DismissesSuggestionWhenSelectingARange) {
::testing::StrictMock<MockSuggestionHandler> mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
AssistiveWindowProperties expected_properties;
expected_properties.type = ui::ime::AssistiveWindowType::kGrammarSuggestion;
expected_properties.candidates = {u"correct"};
expected_properties.visible = true;
EXPECT_CALL(mock_suggestion_handler,
SetAssistiveWindowProperties(1, expected_properties, _));
manager.OnSurroundingTextChanged(u"There is error.", 10, 10);
EXPECT_CALL(mock_suggestion_handler, DismissSuggestion(1, _));
manager.OnSurroundingTextChanged(u"There is error.", 9, 10);
}
TEST_F(GrammarManagerTest, HighlightsAndCommitsGrammarSuggestion) {
::testing::StrictMock<MockSuggestionHandler> mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
base::HistogramTester histogram_tester;
mock_ime_input_context_handler_.Reset();
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
EXPECT_CALL(mock_suggestion_handler, SetAssistiveWindowProperties(1, _, _));
manager.OnSurroundingTextChanged(u"There is error.", 10, 10);
ui::ime::AssistiveWindowButton suggestion_button{
.id = ui::ime::ButtonId::kSuggestion,
.window_type = ui::ime::AssistiveWindowType::kGrammarSuggestion,
};
EXPECT_CALL(mock_suggestion_handler,
SetButtonHighlighted(1, suggestion_button, true, _));
manager.OnKeyEvent(CreateKeyEvent(ui::DomCode::TAB));
EXPECT_CALL(mock_suggestion_handler, DismissSuggestion(1, _));
manager.OnKeyEvent(CreateKeyEvent(ui::DomCode::ENTER));
EXPECT_EQ(
mock_ime_input_context_handler_.delete_surrounding_text_call_count(), 1);
auto deleteSurroundingTextArg =
mock_ime_input_context_handler_.last_delete_surrounding_text_arg();
EXPECT_EQ(deleteSurroundingTextArg.offset, 9);
EXPECT_EQ(deleteSurroundingTextArg.length, 5);
EXPECT_EQ(mock_ime_input_context_handler_.commit_text_call_count(), 1);
EXPECT_EQ(mock_ime_input_context_handler_.last_commit_text(), u"correct");
histogram_tester.ExpectBucketCount("InputMethod.Assistive.Grammar.Actions",
2 /*GrammarAction::kAccepted*/, 1);
}
TEST_F(GrammarManagerTest, IgnoresGrammarSuggestion) {
::testing::StrictMock<MockSuggestionHandler> mock_suggestion_handler;
GrammarManager manager(profile_.get(),
std::make_unique<TestGrammarServiceClient>(),
&mock_suggestion_handler);
base::HistogramTester histogram_tester;
mock_ime_input_context_handler_.Reset();
manager.OnFocus(1);
manager.OnSurroundingTextChanged(u"There is error.", 0, 0);
task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1000));
EXPECT_EQ(mock_ime_input_context_handler_.get_grammar_fragments().size(), 1);
EXPECT_CALL(mock_suggestion_handler, SetAssistiveWindowProperties(1, _, _));
manager.OnSurroundingTextChanged(u"There is error.", 10, 10);
ui::ime::AssistiveWindowButton suggestion_button{
.id = ui::ime::ButtonId::kSuggestion,
.window_type = ui::ime::AssistiveWindowType::kGrammarSuggestion,
};
ui::ime::AssistiveWindowButton ignore_button{
.id = ui::ime::ButtonId::kIgnoreSuggestion,
.window_type = ui::ime::AssistiveWindowType::kGrammarSuggestion,
};
EXPECT_CALL(mock_suggestion_handler,
SetButtonHighlighted(1, suggestion_button, true, _));
manager.OnKeyEvent(CreateKeyEvent(ui::DomCode::TAB));
EXPECT_CALL(mock_suggestion_handler,
SetButtonHighlighted(1, ignore_button, true, _));
manager.OnKeyEvent(CreateKeyEvent(ui::DomCode::TAB));
EXPECT_CALL(mock_suggestion_handler, DismissSuggestion(1, _));
manager.OnKeyEvent(CreateKeyEvent(ui::DomCode::ENTER));
EXPECT_EQ(mock_ime_input_context_handler_.get_grammar_fragments().size(), 0);
EXPECT_EQ(
mock_ime_input_context_handler_.delete_surrounding_text_call_count(), 0);
EXPECT_EQ(mock_ime_input_context_handler_.commit_text_call_count(), 0);
histogram_tester.ExpectBucketCount("InputMethod.Assistive.Grammar.Actions",
3 /*GrammarAction::kIngored*/, 1);
}
} // namespace
} // namespace chromeos