blob: e99573deab2eedd7ced340dbb7b65154360c860f [file] [log] [blame]
// Copyright (c) 2012 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 "ui/views/controls/button/button.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/layout.h"
#include "ui/display/screen.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/animation/ink_drop_host_view.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/test/ink_drop_host_view_test_api.h"
#include "ui/views/animation/test/test_ink_drop.h"
#include "ui/views/animation/test/test_ink_drop_host.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/button/button_controller.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/link.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"
#if defined(USE_AURA)
#include "ui/aura/test/test_cursor_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#endif
namespace views {
using test::InkDropHostViewTestApi;
using test::TestInkDrop;
namespace {
// No-op test double of a ContextMenuController.
class TestContextMenuController : public ContextMenuController {
public:
TestContextMenuController() = default;
~TestContextMenuController() override = default;
// ContextMenuController:
void ShowContextMenuForViewImpl(View* source,
const gfx::Point& point,
ui::MenuSourceType source_type) override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestContextMenuController);
};
class TestButton : public Button {
public:
explicit TestButton(bool has_ink_drop_action_on_click)
: Button(base::BindRepeating([](bool* pressed) { *pressed = true; },
&pressed_)) {
SetHasInkDropActionOnClick(has_ink_drop_action_on_click);
}
~TestButton() override = default;
KeyClickAction GetKeyClickActionForEvent(const ui::KeyEvent& event) override {
if (custom_key_click_action_ == KeyClickAction::kNone)
return Button::GetKeyClickActionForEvent(event);
return custom_key_click_action_;
}
void OnClickCanceled(const ui::Event& event) override { canceled_ = true; }
// Button:
void AddInkDropLayer(ui::Layer* ink_drop_layer) override {
++ink_drop_layer_add_count_;
Button::AddInkDropLayer(ink_drop_layer);
}
void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override {
++ink_drop_layer_remove_count_;
Button::RemoveInkDropLayer(ink_drop_layer);
}
bool pressed() const { return pressed_; }
bool canceled() const { return canceled_; }
int ink_drop_layer_add_count() const { return ink_drop_layer_add_count_; }
int ink_drop_layer_remove_count() const {
return ink_drop_layer_remove_count_;
}
void set_custom_key_click_action(KeyClickAction custom_key_click_action) {
custom_key_click_action_ = custom_key_click_action;
}
void Reset() {
pressed_ = false;
canceled_ = false;
}
// Raised visibility of OnFocus() to public
void OnFocus() override { Button::OnFocus(); }
private:
bool pressed_ = false;
bool canceled_ = false;
int ink_drop_layer_add_count_ = 0;
int ink_drop_layer_remove_count_ = 0;
KeyClickAction custom_key_click_action_ = KeyClickAction::kNone;
DISALLOW_COPY_AND_ASSIGN(TestButton);
};
class TestButtonObserver {
public:
explicit TestButtonObserver(Button* button) {
highlighted_changed_subscription_ =
button->AddHighlightedChangedCallback(base::BindRepeating(
[](TestButtonObserver* obs) { obs->highlighted_changed_ = true; },
base::Unretained(this)));
state_changed_subscription_ =
button->AddStateChangedCallback(base::BindRepeating(
[](TestButtonObserver* obs) { obs->state_changed_ = true; },
base::Unretained(this)));
}
~TestButtonObserver() = default;
void Reset() {
highlighted_changed_ = false;
state_changed_ = false;
}
bool highlighted_changed() const { return highlighted_changed_; }
bool state_changed() const { return state_changed_; }
private:
bool highlighted_changed_ = false;
bool state_changed_ = false;
base::CallbackListSubscription highlighted_changed_subscription_;
base::CallbackListSubscription state_changed_subscription_;
DISALLOW_COPY_AND_ASSIGN(TestButtonObserver);
};
TestInkDrop* AddTestInkDrop(TestButton* button) {
auto owned_ink_drop = std::make_unique<TestInkDrop>();
TestInkDrop* ink_drop = owned_ink_drop.get();
button->SetInkDropMode(InkDropHostView::InkDropMode::ON);
InkDropHostViewTestApi(button).SetInkDrop(std::move(owned_ink_drop));
return ink_drop;
}
} // namespace
class ButtonTest : public ViewsTestBase {
public:
ButtonTest() = default;
~ButtonTest() override = default;
void SetUp() override {
ViewsTestBase::SetUp();
// Create a widget so that the Button can query the hover state
// correctly.
widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(0, 0, 650, 650);
widget_->Init(std::move(params));
widget_->Show();
button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
event_generator_ =
std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget()));
event_generator_->set_assume_window_at_origin(false);
}
void TearDown() override {
widget_.reset();
ViewsTestBase::TearDown();
}
TestInkDrop* CreateButtonWithInkDrop(bool has_ink_drop_action_on_click) {
button_ = widget()->SetContentsView(
std::make_unique<TestButton>(has_ink_drop_action_on_click));
return AddTestInkDrop(button_);
}
void CreateButtonWithObserver() {
button_ = widget()->SetContentsView(std::make_unique<TestButton>(false));
button_->SetInkDropMode(InkDropHostView::InkDropMode::ON);
button_observer_ = std::make_unique<TestButtonObserver>(button_);
}
protected:
Widget* widget() { return widget_.get(); }
TestButton* button() { return button_; }
TestButtonObserver* button_observer() { return button_observer_.get(); }
ui::test::EventGenerator* event_generator() { return event_generator_.get(); }
void SetDraggedView(View* dragged_view) {
widget_->dragged_view_ = dragged_view;
}
private:
std::unique_ptr<Widget> widget_;
TestButton* button_;
std::unique_ptr<TestButtonObserver> button_observer_;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
DISALLOW_COPY_AND_ASSIGN(ButtonTest);
};
// Iterate through the metadata for Button to ensure it all works.
TEST_F(ButtonTest, MetadataTest) {
test::TestViewMetadata(button());
}
// Tests that hover state changes correctly when visibility/enableness changes.
TEST_F(ButtonTest, HoverStateOnVisibilityChange) {
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
event_generator()->ReleaseLeftButton();
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button()->SetEnabled(false);
EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
button()->SetEnabled(true);
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button()->SetVisible(false);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(true);
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
#if defined(USE_AURA)
{
// If another widget has capture, the button should ignore mouse position
// and not enter hovered state.
Widget second_widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(700, 700, 10, 10);
second_widget.Init(std::move(params));
second_widget.Show();
second_widget.GetNativeWindow()->SetCapture();
button()->SetEnabled(false);
EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
button()->SetEnabled(true);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(false);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(true);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
#endif
// Disabling cursor events occurs for touch events and the Ash magnifier. There
// is no touch on desktop Mac. Tracked in http://crbug.com/445520.
#if !defined(OS_APPLE) || defined(USE_AURA)
aura::test::TestCursorClient cursor_client(GetRootWindow(widget()));
// In Aura views, no new hover effects are invoked if mouse events
// are disabled.
cursor_client.DisableMouseEvents();
button()->SetEnabled(false);
EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
button()->SetEnabled(true);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(false);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetVisible(true);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
#endif // !defined(OS_APPLE) || defined(USE_AURA)
}
// Tests that the hover state is preserved during a view hierarchy update of a
// button's child View.
TEST_F(ButtonTest, HoverStatePreservedOnDescendantViewHierarchyChange) {
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
Label* child = new Label(std::u16string());
button()->AddChildView(child);
delete child;
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
}
// Tests the different types of NotifyActions.
TEST_F(ButtonTest, NotifyAction) {
gfx::Point center(10, 10);
// By default the button should notify the callback on mouse release.
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_FALSE(button()->pressed());
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
EXPECT_TRUE(button()->pressed());
// Set the notify action to happen on mouse press.
button()->Reset();
button()->button_controller()->set_notify_action(
ButtonController::NotifyAction::kOnPress);
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_TRUE(button()->pressed());
// The button should no longer notify on mouse release.
button()->Reset();
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
EXPECT_FALSE(button()->pressed());
}
// Tests that OnClickCanceled gets called when NotifyClick is not expected
// anymore.
TEST_F(ButtonTest, NotifyActionNoClick) {
gfx::Point center(10, 10);
// By default the button should notify the callback on mouse release.
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON));
EXPECT_FALSE(button()->canceled());
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON));
EXPECT_TRUE(button()->canceled());
// Set the notify action to happen on mouse press.
button()->Reset();
button()->button_controller()->set_notify_action(
ButtonController::NotifyAction::kOnPress);
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON));
// OnClickCanceled is only sent on mouse release.
EXPECT_FALSE(button()->canceled());
// The button should no longer notify on mouse release.
button()->Reset();
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON));
EXPECT_FALSE(button()->canceled());
}
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if !defined(OS_APPLE) || defined(USE_AURA)
namespace {
void PerformGesture(Button* button, ui::EventType event_type) {
ui::GestureEventDetails gesture_details(event_type);
ui::GestureEvent gesture_event(0, 0, 0, base::TimeTicks(), gesture_details);
button->OnGestureEvent(&gesture_event);
}
} // namespace
// Tests that gesture events correctly change the button state.
TEST_F(ButtonTest, GestureEventsSetState) {
aura::test::TestCursorClient cursor_client(GetRootWindow(widget()));
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
PerformGesture(button(), ui::ET_GESTURE_TAP_DOWN);
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
PerformGesture(button(), ui::ET_GESTURE_SHOW_PRESS);
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
PerformGesture(button(), ui::ET_GESTURE_TAP_CANCEL);
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
// Tests that if the button was disabled in its button press handler, gesture
// events will not revert the disabled state back to normal.
// https://crbug.com/1084241.
TEST_F(ButtonTest, GestureEventsRespectDisabledState) {
button()->SetCallback(base::BindRepeating(
[](TestButton* button) { button->SetEnabled(false); }, button()));
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
event_generator()->GestureTapAt(button()->GetBoundsInScreen().CenterPoint());
EXPECT_EQ(Button::STATE_DISABLED, button()->GetState());
}
#endif // !defined(OS_APPLE) || defined(USE_AURA)
// Ensure subclasses of Button are correctly recognized as Button.
TEST_F(ButtonTest, AsButton) {
std::u16string text;
LabelButton label_button(Button::PressedCallback(), text);
EXPECT_TRUE(Button::AsButton(&label_button));
ImageButton image_button;
EXPECT_TRUE(Button::AsButton(&image_button));
Checkbox checkbox(text);
EXPECT_TRUE(Button::AsButton(&checkbox));
RadioButton radio_button(text, 0);
EXPECT_TRUE(Button::AsButton(&radio_button));
MenuButton menu_button(Button::PressedCallback(), text);
EXPECT_TRUE(Button::AsButton(&menu_button));
ToggleButton toggle_button;
EXPECT_TRUE(Button::AsButton(&toggle_button));
Label label;
EXPECT_FALSE(Button::AsButton(&label));
Link link;
EXPECT_FALSE(Button::AsButton(&link));
Textfield textfield;
EXPECT_FALSE(Button::AsButton(&textfield));
}
// Tests that pressing a button shows the ink drop and releasing the button
// does not hide the ink drop.
// Note: Ink drop is not hidden upon release because Button descendants
// may enter a different ink drop state.
TEST_F(ButtonTest, ButtonClickTogglesInkDrop) {
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
event_generator()->ReleaseLeftButton();
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
}
// Tests that pressing a button shows and releasing capture hides ink drop.
// Releasing capture should also reset PRESSED button state to NORMAL.
TEST_F(ButtonTest, CaptureLossHidesInkDrop) {
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
EXPECT_EQ(Button::ButtonState::STATE_PRESSED, button()->GetState());
SetDraggedView(button());
widget()->SetCapture(button());
widget()->ReleaseCapture();
SetDraggedView(nullptr);
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
EXPECT_EQ(Button::ButtonState::STATE_NORMAL, button()->GetState());
}
TEST_F(ButtonTest, HideInkDropWhenShowingContextMenu) {
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
TestContextMenuController context_menu_controller;
button()->set_context_menu_controller(&context_menu_controller);
button()->SetHideInkDropWhenShowingContextMenu(true);
ink_drop->SetHovered(true);
ink_drop->AnimateToState(InkDropState::ACTION_PENDING);
button()->ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE);
EXPECT_FALSE(ink_drop->is_hovered());
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
}
TEST_F(ButtonTest, DontHideInkDropWhenShowingContextMenu) {
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
TestContextMenuController context_menu_controller;
button()->set_context_menu_controller(&context_menu_controller);
button()->SetHideInkDropWhenShowingContextMenu(false);
ink_drop->SetHovered(true);
ink_drop->AnimateToState(InkDropState::ACTION_PENDING);
button()->ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE);
EXPECT_TRUE(ink_drop->is_hovered());
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
}
TEST_F(ButtonTest, HideInkDropOnBlur) {
gfx::Point center(10, 10);
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
button()->OnFocus();
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
button()->OnBlur();
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_TRUE(button()->pressed());
}
TEST_F(ButtonTest, HideInkDropHighlightOnDisable) {
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(ink_drop->is_hovered());
button()->SetEnabled(false);
EXPECT_FALSE(ink_drop->is_hovered());
button()->SetEnabled(true);
EXPECT_TRUE(ink_drop->is_hovered());
}
TEST_F(ButtonTest, InkDropAfterTryingToShowContextMenu) {
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
button()->set_context_menu_controller(nullptr);
ink_drop->SetHovered(true);
ink_drop->AnimateToState(InkDropState::ACTION_PENDING);
button()->ShowContextMenu(gfx::Point(), ui::MENU_SOURCE_MOUSE);
EXPECT_TRUE(ink_drop->is_hovered());
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
}
TEST_F(ButtonTest, HideInkDropHighlightWhenRemoved) {
View* contents_view = widget()->SetContentsView(std::make_unique<View>());
TestButton* button =
contents_view->AddChildView(std::make_unique<TestButton>(false));
button->SetBounds(0, 0, 200, 200);
TestInkDrop* ink_drop = AddTestInkDrop(button);
// Make sure that the button ink drop is hidden after the button gets removed.
event_generator()->MoveMouseTo(button->GetBoundsInScreen().origin());
event_generator()->MoveMouseBy(2, 2);
EXPECT_TRUE(ink_drop->is_hovered());
// Set ink-drop state to ACTIVATED to make sure that removing the container
// sets it back to HIDDEN.
ink_drop->AnimateToState(InkDropState::ACTIVATED);
auto owned_button = contents_view->RemoveChildViewT(button);
button = nullptr;
EXPECT_FALSE(ink_drop->is_hovered());
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
// Make sure hiding the ink drop happens even if the button is indirectly
// being removed.
View* parent_view = contents_view->AddChildView(std::make_unique<View>());
parent_view->SetBounds(0, 0, 400, 400);
button = parent_view->AddChildView(std::move(owned_button));
// Trigger hovering and then remove from the indirect parent. This should
// propagate down to Button which should remove the highlight effect.
EXPECT_FALSE(ink_drop->is_hovered());
event_generator()->MoveMouseBy(8, 8);
EXPECT_TRUE(ink_drop->is_hovered());
// Set ink-drop state to ACTIVATED to make sure that removing the container
// sets it back to HIDDEN.
ink_drop->AnimateToState(InkDropState::ACTIVATED);
auto owned_parent = contents_view->RemoveChildViewT(parent_view);
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
EXPECT_FALSE(ink_drop->is_hovered());
}
// Tests that when button is set to notify on release, dragging mouse out and
// back transitions ink drop states correctly.
TEST_F(ButtonTest, InkDropShowHideOnMouseDraggedNotifyOnRelease) {
gfx::Point center(10, 10);
gfx::Point oob(-1, -1);
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
button()->button_controller()->set_notify_action(
ButtonController::NotifyAction::kOnRelease);
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
button()->OnMouseReleased(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_FALSE(button()->pressed());
}
// Tests that when button is set to notify on press, dragging mouse out and back
// does not change the ink drop state.
TEST_F(ButtonTest, InkDropShowHideOnMouseDraggedNotifyOnPress) {
gfx::Point center(10, 10);
gfx::Point oob(-1, -1);
TestInkDrop* ink_drop = CreateButtonWithInkDrop(true);
button()->button_controller()->set_notify_action(
ButtonController::NotifyAction::kOnPress);
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_TRIGGERED, ink_drop->GetTargetInkDropState());
EXPECT_TRUE(button()->pressed());
button()->OnMouseDragged(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_TRIGGERED, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_TRIGGERED, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_TRIGGERED, ink_drop->GetTargetInkDropState());
button()->OnMouseReleased(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_TRIGGERED, ink_drop->GetTargetInkDropState());
}
TEST_F(ButtonTest, InkDropStaysHiddenWhileDragging) {
gfx::Point center(10, 10);
gfx::Point oob(-1, -1);
TestInkDrop* ink_drop = CreateButtonWithInkDrop(false);
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::ACTION_PENDING, ink_drop->GetTargetInkDropState());
SetDraggedView(button());
widget()->SetCapture(button());
widget()->ReleaseCapture();
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(
ui::MouseEvent(ui::ET_MOUSE_PRESSED, oob, oob, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
button()->OnMouseDragged(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
SetDraggedView(nullptr);
}
// Ensure PressedCallback is dynamically settable.
TEST_F(ButtonTest, SetCallback) {
bool pressed = false;
button()->SetCallback(
base::BindRepeating([](bool* pressed) { *pressed = true; }, &pressed));
const gfx::Point center(10, 10);
button()->OnMousePressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
// Default button controller notifies callback at mouse release.
button()->OnMouseReleased(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_TRUE(pressed);
}
// VisibilityTestButton tests to see if an ink drop or a layer has been added to
// the button at any point during the visibility state changes of its Widget.
class VisibilityTestButton : public TestButton {
public:
VisibilityTestButton() : TestButton(false) {}
~VisibilityTestButton() override {
if (layer())
ADD_FAILURE();
}
// TestButton:
void AddInkDropLayer(ui::Layer* ink_drop_layer) override {
ADD_FAILURE();
TestButton::AddInkDropLayer(ink_drop_layer);
}
void RemoveInkDropLayer(ui::Layer* ink_drop_layer) override {
ADD_FAILURE();
TestButton::RemoveInkDropLayer(ink_drop_layer);
}
};
// Test that hiding or closing a Widget doesn't attempt to add a layer due to
// changed visibility states.
TEST_F(ButtonTest, NoLayerAddedForWidgetVisibilityChanges) {
VisibilityTestButton* button =
widget()->SetContentsView(std::make_unique<VisibilityTestButton>());
// Ensure no layers are created during construction.
EXPECT_TRUE(button->GetVisible());
EXPECT_FALSE(button->layer());
// Ensure no layers are created when hiding the widget.
widget()->Hide();
EXPECT_FALSE(button->layer());
// Ensure no layers are created when the widget is reshown.
widget()->Show();
EXPECT_FALSE(button->layer());
// Ensure no layers are created during the closing of the Widget.
widget()->Close(); // Start an asynchronous close.
EXPECT_FALSE(button->layer());
// Ensure no layers are created following the Widget's destruction.
base::RunLoop().RunUntilIdle(); // Complete the Close().
}
// Verify that the Space key clicks the button on key-press on Mac, and
// key-release on other platforms.
TEST_F(ButtonTest, ActionOnSpace) {
// Give focus to the button.
button()->RequestFocus();
EXPECT_TRUE(button()->HasFocus());
ui::KeyEvent space_press(ui::ET_KEY_PRESSED, ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_TRUE(button()->OnKeyPressed(space_press));
#if defined(OS_APPLE)
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
#else
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
EXPECT_FALSE(button()->pressed());
#endif
ui::KeyEvent space_release(ui::ET_KEY_RELEASED, ui::VKEY_SPACE, ui::EF_NONE);
#if defined(OS_APPLE)
EXPECT_FALSE(button()->OnKeyReleased(space_release));
#else
EXPECT_TRUE(button()->OnKeyReleased(space_release));
#endif
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
}
// Verify that the Return key clicks the button on key-press on all platforms
// except Mac. On Mac, the Return key performs the default action associated
// with a dialog, even if a button has focus.
TEST_F(ButtonTest, ActionOnReturn) {
// Give focus to the button.
button()->RequestFocus();
EXPECT_TRUE(button()->HasFocus());
ui::KeyEvent return_press(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
#if defined(OS_APPLE)
EXPECT_FALSE(button()->OnKeyPressed(return_press));
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_FALSE(button()->pressed());
#else
EXPECT_TRUE(button()->OnKeyPressed(return_press));
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
#endif
ui::KeyEvent return_release(ui::ET_KEY_RELEASED, ui::VKEY_RETURN,
ui::EF_NONE);
EXPECT_FALSE(button()->OnKeyReleased(return_release));
}
// Verify that a subclass may customize the action for a key pressed event.
TEST_F(ButtonTest, CustomActionOnKeyPressedEvent) {
// Give focus to the button.
button()->RequestFocus();
EXPECT_TRUE(button()->HasFocus());
// Set the button to handle any key pressed event as kOnKeyPress.
button()->set_custom_key_click_action(Button::KeyClickAction::kOnKeyPress);
ui::KeyEvent control_press(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL, ui::EF_NONE);
EXPECT_TRUE(button()->OnKeyPressed(control_press));
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
EXPECT_TRUE(button()->pressed());
ui::KeyEvent control_release(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL,
ui::EF_NONE);
EXPECT_FALSE(button()->OnKeyReleased(control_release));
}
// Verifies that button activation highlight state changes trigger property
// change callbacks.
TEST_F(ButtonTest, ChangingHighlightStateNotifiesCallback) {
CreateButtonWithObserver();
EXPECT_FALSE(button_observer()->highlighted_changed());
EXPECT_FALSE(button()->GetHighlighted());
button()->SetHighlighted(/*bubble_visible=*/true);
EXPECT_TRUE(button_observer()->highlighted_changed());
EXPECT_TRUE(button()->GetHighlighted());
button_observer()->Reset();
EXPECT_FALSE(button_observer()->highlighted_changed());
EXPECT_TRUE(button()->GetHighlighted());
button()->SetHighlighted(/*bubble_visible=*/false);
EXPECT_TRUE(button_observer()->highlighted_changed());
EXPECT_FALSE(button()->GetHighlighted());
}
// Verifies that button state changes trigger property change callbacks.
TEST_F(ButtonTest, ClickingButtonNotifiesObserverOfStateChanges) {
CreateButtonWithObserver();
EXPECT_FALSE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
event_generator()->MoveMouseTo(button()->GetBoundsInScreen().CenterPoint());
event_generator()->PressLeftButton();
EXPECT_TRUE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
button_observer()->Reset();
EXPECT_FALSE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_PRESSED, button()->GetState());
event_generator()->ReleaseLeftButton();
EXPECT_TRUE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
}
// Verifies that direct calls to Button::SetState() trigger property change
// callbacks.
TEST_F(ButtonTest, SetStateNotifiesObserver) {
CreateButtonWithObserver();
EXPECT_FALSE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
button()->SetState(Button::STATE_HOVERED);
EXPECT_TRUE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button_observer()->Reset();
EXPECT_FALSE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_HOVERED, button()->GetState());
button()->SetState(Button::STATE_NORMAL);
EXPECT_TRUE(button_observer()->state_changed());
EXPECT_EQ(Button::STATE_NORMAL, button()->GetState());
}
// Verifies setting the tooltip text will call NotifyAccessibilityEvent.
TEST_F(ButtonTest, SetTooltipTextNotifiesAccessibilityEvent) {
std::u16string test_tooltip_text = u"Test Tooltip Text";
test::AXEventCounter counter(views::AXEventManager::Get());
EXPECT_EQ(0, counter.GetCount(ax::mojom::Event::kTextChanged));
button()->SetTooltipText(test_tooltip_text);
EXPECT_EQ(1, counter.GetCount(ax::mojom::Event::kTextChanged));
EXPECT_EQ(test_tooltip_text, button()->GetTooltipText(gfx::Point()));
ui::AXNodeData data;
button()->GetAccessibleNodeData(&data);
const std::string& name =
data.GetStringAttribute(ax::mojom::StringAttribute::kName);
EXPECT_EQ(test_tooltip_text, base::ASCIIToUTF16(name));
}
} // namespace views