blob: ae2e174b77229e720f93611918e79dbc6a9f8cd3 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// 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/toggle_button.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget_utils.h"
namespace views {
class TestToggleButton : public ToggleButton {
public:
explicit TestToggleButton(int* counter) : counter_(counter) {}
TestToggleButton(const TestToggleButton&) = delete;
TestToggleButton& operator=(const TestToggleButton&) = delete;
~TestToggleButton() override {
// TODO(pbos): Revisit explicit removal of InkDrop for classes that override
// Add/RemoveLayerFromRegions(). This is done so that the InkDrop doesn't
// access the non-override versions in ~View.
views::InkDrop::Remove(this);
}
void AddLayerToRegion(ui::Layer* layer, views::LayerRegion region) override {
++(*counter_);
ToggleButton::AddLayerToRegion(layer, region);
}
void RemoveLayerFromRegions(ui::Layer* layer) override {
--(*counter_);
ToggleButton::RemoveLayerFromRegions(layer);
}
using View::Focus;
private:
const raw_ptr<int> counter_;
};
class ToggleButtonTest : public ViewsTestBase {
public:
ToggleButtonTest() = default;
ToggleButtonTest(const ToggleButtonTest&) = delete;
ToggleButtonTest& operator=(const ToggleButtonTest&) = delete;
~ToggleButtonTest() override = default;
void SetUp() override {
ViewsTestBase::SetUp();
// Create a widget so that the ToggleButton can query the hover state
// correctly.
widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = gfx::Rect(0, 0, 650, 650);
widget_->Init(std::move(params));
widget_->Show();
widget_->SetContentsView(std::make_unique<TestToggleButton>(&counter_));
}
void TearDown() override {
widget_.reset();
ViewsTestBase::TearDown();
}
protected:
int counter() const { return counter_; }
Widget* widget() { return widget_.get(); }
TestToggleButton* button() {
return static_cast<TestToggleButton*>(widget_->GetContentsView());
}
private:
std::unique_ptr<Widget> widget_;
int counter_ = 0;
};
// Starts ink drop animation on a ToggleButton and destroys the button.
// The test verifies that the ink drop layer is removed properly when the
// ToggleButton gets destroyed.
TEST_F(ToggleButtonTest, ToggleButtonDestroyed) {
gfx::Point center(10, 10);
button()->OnMousePressed(ui::MouseEvent(
ui::EventType::kMousePressed, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
EXPECT_EQ(1, counter());
delete button();
EXPECT_EQ(0, counter());
}
// Make sure nothing bad happens when the widget is destroyed while the
// ToggleButton has focus (and is showing a ripple).
TEST_F(ToggleButtonTest, ShutdownWithFocus) {
button()->RequestFocus();
}
// Verify that ToggleButton::accepts_events_ works as expected.
TEST_F(ToggleButtonTest, AcceptEvents) {
EXPECT_FALSE(button()->GetIsOn());
ui::test::EventGenerator generator(GetRootWindow(widget()));
generator.MoveMouseTo(widget()->GetClientAreaBoundsInScreen().CenterPoint());
// Clicking toggles.
generator.ClickLeftButton();
EXPECT_TRUE(button()->GetIsOn());
generator.ClickLeftButton();
EXPECT_FALSE(button()->GetIsOn());
// Spacebar toggles.
button()->RequestFocus();
generator.PressKey(ui::VKEY_SPACE, ui::EF_NONE);
generator.ReleaseKey(ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_TRUE(button()->GetIsOn());
generator.PressKey(ui::VKEY_SPACE, ui::EF_NONE);
generator.ReleaseKey(ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_FALSE(button()->GetIsOn());
// Spacebar and clicking do nothing when not accepting events, but focus is
// not affected.
button()->SetAcceptsEvents(false);
EXPECT_TRUE(button()->HasFocus());
generator.PressKey(ui::VKEY_SPACE, ui::EF_NONE);
generator.ReleaseKey(ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_FALSE(button()->GetIsOn());
generator.ClickLeftButton();
EXPECT_FALSE(button()->GetIsOn());
// Re-enable events and clicking and spacebar resume working.
button()->SetAcceptsEvents(true);
generator.PressKey(ui::VKEY_SPACE, ui::EF_NONE);
generator.ReleaseKey(ui::VKEY_SPACE, ui::EF_NONE);
EXPECT_TRUE(button()->GetIsOn());
generator.ClickLeftButton();
EXPECT_FALSE(button()->GetIsOn());
}
TEST_F(ToggleButtonTest, AccessibleCheckedStateChange) {
views::test::AXEventCounter ax_counter(views::AXUpdateNotifier::Get());
ui::AXNodeData data;
EXPECT_EQ(
ax_counter.GetCount(ax::mojom::Event::kCheckedStateChanged, button()), 0);
button()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kFalse);
EXPECT_EQ(
ax_counter.GetCount(ax::mojom::Event::kCheckedStateChanged, button()), 0);
button()->SetIsOn(true);
button()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(
ax_counter.GetCount(ax::mojom::Event::kCheckedStateChanged, button()), 1);
EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kTrue);
data = ui::AXNodeData();
button()->SetIsOn(false);
button()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetCheckedState(), ax::mojom::CheckedState::kFalse);
EXPECT_EQ(
ax_counter.GetCount(ax::mojom::Event::kCheckedStateChanged, button()), 2);
}
} // namespace views