blob: 98f2c9e025e64ace74287e12056dad8061627d49 [file] [log] [blame]
// Copyright (c) 2020 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/assistive_window_controller.h"
#include "ash/public/cpp/ash_pref_names.h"
#include "base/test/task_environment.h"
#include "chrome/browser/chromeos/input_method/assistive_window_controller_delegate.h"
#include "chrome/browser/chromeos/input_method/ui/suggestion_details.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chrome/test/base/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/base/ime/chromeos/ime_assistive_window_handler_interface.h"
#include "ui/base/ime/chromeos/ime_bridge.h"
#include "ui/views/test/test_views_delegate.h"
#include "ui/wm/core/window_util.h"
namespace {
const char kAnnounceString[] = "announce string";
} // namespace
namespace chromeos {
namespace input_method {
class MockDelegate : public AssistiveWindowControllerDelegate {
public:
~MockDelegate() override = default;
void AssistiveWindowButtonClicked(
const ui::ime::AssistiveWindowButton& button) const override {}
};
class TestTtsHandler : public TtsHandler {
public:
explicit TestTtsHandler(Profile* profile) : TtsHandler(profile) {}
void VerifyAnnouncement(const std::string& expected_text) {
EXPECT_EQ(text_, expected_text);
}
private:
void Speak(const std::string& text) override { text_ = text; }
std::string text_ = "";
};
class AssistiveWindowControllerTest : public ChromeAshTestBase {
protected:
AssistiveWindowControllerTest() { ui::IMEBridge::Initialize(); }
~AssistiveWindowControllerTest() override { ui::IMEBridge::Shutdown(); }
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
auto tts_handler = std::make_unique<TestTtsHandler>(profile_.get());
tts_handler_ = tts_handler.get();
controller_ = std::make_unique<AssistiveWindowController>(
delegate_.get(), profile_.get(), std::move(tts_handler));
ui::IMEBridge::Get()->SetAssistiveWindowHandler(controller_.get());
ChromeAshTestBase::SetUp();
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(1));
wm::ActivateWindow(window.get());
// TODO(crbug/1102283): Create MockSuggestionWindowView to be independent of
// SuggestionWindowView's implementation.
static_cast<views::TestViewsDelegate*>(views::ViewsDelegate::GetInstance())
->set_layout_provider(ChromeLayoutProvider::CreateLayoutProvider());
}
std::vector<std::u16string> Candidates() {
std::vector<std::u16string> candidates;
for (int i = 0; i < 3; i++) {
std::string candidate = base::NumberToString(i);
candidates.push_back(base::UTF8ToUTF16(candidate));
}
return candidates;
}
void InitEmojiSuggestionWindow() {
emoji_window_.type = ui::ime::AssistiveWindowType::kEmojiSuggestion;
emoji_window_.visible = true;
emoji_window_.candidates = Candidates();
}
void InitEmojiButton() {
emoji_button_.window_type = ui::ime::AssistiveWindowType::kEmojiSuggestion;
emoji_button_.announce_string = kAnnounceString;
}
std::unique_ptr<AssistiveWindowController> controller_;
std::unique_ptr<MockDelegate> delegate_ = std::make_unique<MockDelegate>();
std::unique_ptr<TestingProfile> profile_;
const std::u16string suggestion_ = u"test";
ui::ime::AssistiveWindowButton emoji_button_;
chromeos::AssistiveWindowProperties emoji_window_;
TestTtsHandler* tts_handler_;
void TearDown() override {
controller_.reset();
ChromeAshTestBase::TearDown();
}
};
TEST_F(AssistiveWindowControllerTest, ConfirmedLength0SetsBoundsToCaretBounds) {
// Sets up suggestion_view with confirmed_length = 0.
ui::ime::SuggestionDetails details;
details.text = suggestion_;
details.confirmed_length = 0;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->ShowSuggestion(details);
ui::ime::SuggestionWindowView* suggestion_view =
controller_->GetSuggestionWindowViewForTesting();
EXPECT_EQ(
0u,
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->GetConfirmedLength());
gfx::Rect current_bounds = suggestion_view->GetAnchorRect();
gfx::Rect new_caret_bounds(current_bounds.width() + 1,
current_bounds.height());
Bounds bounds;
bounds.caret = new_caret_bounds;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetBounds(bounds);
EXPECT_EQ(new_caret_bounds, suggestion_view->GetAnchorRect());
}
TEST_F(AssistiveWindowControllerTest,
ConfirmedLengthNSetsBoundsToCompositionTextBounds) {
// Sets up suggestion_view with confirmed_length = 1.
ui::ime::SuggestionDetails details;
details.text = suggestion_;
details.confirmed_length = 1;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->ShowSuggestion(details);
ui::ime::SuggestionWindowView* suggestion_view =
controller_->GetSuggestionWindowViewForTesting();
EXPECT_EQ(
1u,
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->GetConfirmedLength());
gfx::Rect current_bounds = suggestion_view->GetAnchorRect();
gfx::Rect composition_text_bounds(current_bounds.width() - 5,
current_bounds.height());
Bounds bounds;
bounds.composition_text = composition_text_bounds;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetBounds(bounds);
EXPECT_EQ(composition_text_bounds, suggestion_view->GetAnchorRect());
}
TEST_F(AssistiveWindowControllerTest,
ShowingSuggestionForFirstTimeShouldRepositionWindow) {
ui::ime::SuggestionDetails details;
details.text = suggestion_;
details.confirmed_length = 0;
IMEAssistiveWindowHandlerInterface* assistive_window =
ui::IMEBridge::Get()->GetAssistiveWindowHandler();
assistive_window->ShowSuggestion(details);
ui::ime::SuggestionWindowView* suggestion_view =
controller_->GetSuggestionWindowViewForTesting();
gfx::Rect current_bounds = suggestion_view->GetAnchorRect();
gfx::Rect caret_bounds_after_one_key(current_bounds.width() + 1,
current_bounds.height());
gfx::Rect caret_bounds_after_two_key(current_bounds.width() + 2,
current_bounds.height());
// One char entered
assistive_window->SetBounds(Bounds{.caret = caret_bounds_after_one_key});
// Mimic tracking the last suggestion
details.confirmed_length = 1;
assistive_window->ShowSuggestion(details);
// Second char entered to text input
assistive_window->SetBounds(Bounds{.caret = caret_bounds_after_two_key});
// Second `SetBounds` should be ignored
EXPECT_EQ(caret_bounds_after_one_key, suggestion_view->GetAnchorRect());
}
TEST_F(AssistiveWindowControllerTest,
SuggestionViewBoundIsResetAfterHideSuggestionThenShowAgain) {
// Sets up suggestion_view with confirmed_length = 1.
ui::ime::SuggestionDetails details;
details.text = suggestion_;
details.confirmed_length = 1;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->ShowSuggestion(details);
EXPECT_EQ(
1u,
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->GetConfirmedLength());
gfx::Rect current_bounds =
controller_->GetSuggestionWindowViewForTesting()->GetAnchorRect();
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->HideSuggestion();
// Create new suggestion window.
AssistiveWindowProperties properties;
properties.type = ui::ime::AssistiveWindowType::kEmojiSuggestion;
properties.visible = true;
properties.candidates = std::vector<std::u16string>({u"candidate"});
ui::IMEBridge::Get()
->GetAssistiveWindowHandler()
->SetAssistiveWindowProperties(properties);
gfx::Rect new_caret_bounds(current_bounds.width() + 1,
current_bounds.height());
Bounds bounds;
bounds.caret = new_caret_bounds;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetBounds(bounds);
EXPECT_EQ(new_caret_bounds,
controller_->GetSuggestionWindowViewForTesting()->GetAnchorRect());
}
TEST_F(AssistiveWindowControllerTest, SetsUndoWindowAnchorRectCorrectly) {
gfx::Rect autocorrect_bounds(1, 1);
gfx::Rect caret_bounds(2, 2);
Bounds bounds;
bounds.caret = caret_bounds;
bounds.autocorrect = autocorrect_bounds;
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetBounds(bounds);
AssistiveWindowProperties window;
window.type = ui::ime::AssistiveWindowType::kUndoWindow;
window.visible = true;
ui::IMEBridge::Get()
->GetAssistiveWindowHandler()
->SetAssistiveWindowProperties(window);
ASSERT_TRUE(controller_->GetUndoWindowForTesting() != nullptr);
EXPECT_EQ(autocorrect_bounds,
controller_->GetUndoWindowForTesting()->GetAnchorRect());
}
TEST_F(AssistiveWindowControllerTest,
AnnouncesWhenSetButtonHighlightedInEmojiWindowHasAnnounceString) {
profile_->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
InitEmojiSuggestionWindow();
InitEmojiButton();
ui::IMEBridge::Get()
->GetAssistiveWindowHandler()
->SetAssistiveWindowProperties(emoji_window_);
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetButtonHighlighted(
emoji_button_, true);
task_environment()->RunUntilIdle();
tts_handler_->VerifyAnnouncement(kAnnounceString);
}
TEST_F(AssistiveWindowControllerTest,
DoesNotAnnounceWhenSetButtonHighlightedAndChromeVoxIsOff) {
profile_->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilitySpokenFeedbackEnabled, false);
InitEmojiSuggestionWindow();
InitEmojiButton();
ui::IMEBridge::Get()
->GetAssistiveWindowHandler()
->SetAssistiveWindowProperties(emoji_window_);
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetButtonHighlighted(
emoji_button_, true);
task_environment()->RunUntilIdle();
tts_handler_->VerifyAnnouncement(base::EmptyString());
}
TEST_F(
AssistiveWindowControllerTest,
DoesNotAnnounceWhenSetButtonHighlightedInEmojiWindowDoesNotHaveAnnounceString) {
profile_->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
InitEmojiSuggestionWindow();
InitEmojiButton();
emoji_button_.announce_string = base::EmptyString();
ui::IMEBridge::Get()
->GetAssistiveWindowHandler()
->SetAssistiveWindowProperties(emoji_window_);
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetButtonHighlighted(
emoji_button_, true);
task_environment()->RunUntilIdle();
tts_handler_->VerifyAnnouncement(base::EmptyString());
}
TEST_F(AssistiveWindowControllerTest,
AnnouncesWhenSetButtonHighlightedInUndoWindowHasAnnounceString) {
profile_->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
AssistiveWindowProperties window;
window.type = ui::ime::AssistiveWindowType::kUndoWindow;
window.visible = true;
ui::ime::AssistiveWindowButton button;
button.window_type = ui::ime::AssistiveWindowType::kUndoWindow;
button.announce_string = kAnnounceString;
ui::IMEBridge::Get()
->GetAssistiveWindowHandler()
->SetAssistiveWindowProperties(window);
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetButtonHighlighted(
button, true);
task_environment()->RunUntilIdle();
tts_handler_->VerifyAnnouncement(kAnnounceString);
}
TEST_F(
AssistiveWindowControllerTest,
DoesNotAnnounceWhenSetButtonHighlightedAndSuggestionWindowViewIsNotActive) {
profile_->GetPrefs()->SetBoolean(
ash::prefs::kAccessibilitySpokenFeedbackEnabled, true);
InitEmojiButton();
ui::IMEBridge::Get()->GetAssistiveWindowHandler()->SetButtonHighlighted(
emoji_button_, true);
task_environment()->RunUntilIdle();
std::string expected_announcement = base::EmptyString();
tts_handler_->VerifyAnnouncement(base::EmptyString());
}
} // namespace input_method
} // namespace chromeos