blob: e9c78f2aae5d9891f17a6b0e312acd0d89060cbc [file] [log] [blame]
// Copyright 2017 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 "ash/login/ui/note_action_launch_button.h"
#include <memory>
#include <vector>
#include "ash/login/ui/login_test_base.h"
#include "ash/login/ui/views_utils.h"
#include "ash/public/mojom/tray_action.mojom.h"
#include "ash/shell.h"
#include "ash/tray_action/test_tray_action_client.h"
#include "base/macros.h"
#include "base/time/time.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// The note action button bubble sizes:
constexpr int kLargeButtonRadiusDp = 56;
constexpr int kSmallButtonRadiusDp = 48;
constexpr float kSqrt2 = 1.4142;
} // namespace
class NoteActionLaunchButtonTest : public LoginTestBase {
public:
NoteActionLaunchButtonTest() = default;
~NoteActionLaunchButtonTest() override = default;
void SetUp() override {
LoginTestBase::SetUp();
Shell::Get()->tray_action()->SetClient(
tray_action_client_.CreateInterfacePtrAndBind(),
mojom::TrayActionState::kAvailable);
}
TestTrayActionClient* tray_action_client() { return &tray_action_client_; }
void PerformClick(const gfx::Point& point) {
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseTo(point.x(), point.y());
generator->ClickLeftButton();
Shell::Get()->tray_action()->FlushMojoForTesting();
}
void GestureFling(const gfx::Point& start, const gfx::Point& end) {
ui::test::EventGenerator* generator = GetEventGenerator();
generator->GestureScrollSequence(start, end,
base::TimeDelta::FromMilliseconds(10), 2);
Shell::Get()->tray_action()->FlushMojoForTesting();
}
private:
TestTrayActionClient tray_action_client_;
DISALLOW_COPY_AND_ASSIGN(NoteActionLaunchButtonTest);
};
// Verifies that note action button is not visible if lock screen note taking
// is not enabled.
TEST_F(NoteActionLaunchButtonTest, VisibilityActionNotAvailable) {
auto note_action_button = std::make_unique<NoteActionLaunchButton>(
mojom::TrayActionState::kNotAvailable);
EXPECT_FALSE(note_action_button->GetVisible());
}
// Verifies that note action button is shown and enabled if lock screen note
// taking is available.
TEST_F(NoteActionLaunchButtonTest, VisibilityActionAvailable) {
auto note_action_button = std::make_unique<NoteActionLaunchButton>(
mojom::TrayActionState::kAvailable);
NoteActionLaunchButton::TestApi test_api(note_action_button.get());
EXPECT_TRUE(note_action_button->GetVisible());
EXPECT_TRUE(note_action_button->GetEnabled());
EXPECT_TRUE(test_api.ActionButtonView()->GetVisible());
EXPECT_TRUE(test_api.ActionButtonView()->GetEnabled());
EXPECT_TRUE(test_api.BackgroundView()->GetVisible());
}
// Tests that clicking Enter while lock screen action button is focused requests
// a new note action.
TEST_F(NoteActionLaunchButtonTest, KeyboardTest) {
auto* note_action_button =
new NoteActionLaunchButton(mojom::TrayActionState::kAvailable);
std::unique_ptr<views::Widget> widget =
CreateWidgetWithContent(login_views_utils::WrapViewForPreferredSize(
base::WrapUnique(note_action_button))
.release());
NoteActionLaunchButton::TestApi test_api(note_action_button);
note_action_button->RequestFocus();
// Focusing the whole note action launch button view should give the image
// button sub-view the focus.
EXPECT_TRUE(test_api.ActionButtonView()->HasFocus());
ui::test::EventGenerator* generator = GetEventGenerator();
generator->PressKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
Shell::Get()->tray_action()->FlushMojoForTesting();
EXPECT_EQ(std::vector<mojom::LockScreenNoteOrigin>(
{mojom::LockScreenNoteOrigin::kLockScreenButtonKeyboard}),
tray_action_client()->note_origins());
}
// The button hit area is expected to be a circle centered in the top right
// corner of the view with kSmallButtonRadiusDp (and clipped but the view
// bounds). The test verifies clicking the button within the button's hit area
// requests a new note action.
TEST_F(NoteActionLaunchButtonTest, ClickTest) {
auto* note_action_button =
new NoteActionLaunchButton(mojom::TrayActionState::kAvailable);
std::unique_ptr<views::Widget> widget =
CreateWidgetWithContent(login_views_utils::WrapViewForPreferredSize(
base::WrapUnique(note_action_button))
.release());
const gfx::Size action_size = note_action_button->GetPreferredSize();
EXPECT_EQ(gfx::Size(kLargeButtonRadiusDp, kLargeButtonRadiusDp), action_size);
const gfx::Rect view_bounds = note_action_button->GetBoundsInScreen();
ASSERT_EQ(gfx::Rect(gfx::Point(), action_size), view_bounds);
const std::vector<mojom::LockScreenNoteOrigin> expected_actions = {
mojom::LockScreenNoteOrigin::kLockScreenButtonTap};
// Point near the center of the view, inside the actionable area:
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / kSqrt2 + 2,
kSmallButtonRadiusDp / kSqrt2 - 2));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point near the center of the view, outside the actionable area:
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / kSqrt2 - 2,
kSmallButtonRadiusDp / kSqrt2 + 2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the top right corner:
PerformClick(view_bounds.top_right() + gfx::Vector2d(-2, 2));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point near the bottom left corner:
PerformClick(view_bounds.bottom_left() + gfx::Vector2d(2, -2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the origin:
PerformClick(view_bounds.origin() + gfx::Vector2d(2, 2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the origin of the actionable area bounds (inside the bounds):
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp + 2, 2));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point near the origin of the actionable area bounds (outside the bounds):
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp - 2, 2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the bottom right corner:
PerformClick(view_bounds.bottom_right() + gfx::Vector2d(0, -2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the bottom right corner of the actionable area bounds (inside
// the bounds):
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-2, kSmallButtonRadiusDp - 2));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point near the bottom right corner of the actionable area bounds (outside
// the bounds):
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-2, kSmallButtonRadiusDp + 2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the bottom edge:
PerformClick(view_bounds.bottom_left() +
gfx::Vector2d(kSmallButtonRadiusDp / 2, -1));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the top edge:
PerformClick(view_bounds.origin() +
gfx::Vector2d(kSmallButtonRadiusDp / 2, 1));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point near the left edge:
PerformClick(view_bounds.origin() +
gfx::Vector2d(1, kSmallButtonRadiusDp / 2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
// Point near the right edge:
PerformClick(view_bounds.top_right() +
gfx::Vector2d(-1, kSmallButtonRadiusDp / 2));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point in the center of the actionable area:
PerformClick(
view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / 2, kSmallButtonRadiusDp / 2));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Point outside the view bounds:
PerformClick(view_bounds.top_right() + gfx::Vector2d(2, 2));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
}
// Tests tap gesture in and outside of the note action launch button.
TEST_F(NoteActionLaunchButtonTest, TapTest) {
auto* note_action_button =
new NoteActionLaunchButton(mojom::TrayActionState::kAvailable);
std::unique_ptr<views::Widget> widget =
CreateWidgetWithContent(login_views_utils::WrapViewForPreferredSize(
base::WrapUnique(note_action_button))
.release());
const gfx::Size action_size = note_action_button->GetPreferredSize();
EXPECT_EQ(gfx::Size(kLargeButtonRadiusDp, kLargeButtonRadiusDp), action_size);
const gfx::Rect view_bounds = note_action_button->GetBoundsInScreen();
ASSERT_EQ(gfx::Rect(gfx::Point(), action_size), view_bounds);
const std::vector<mojom::LockScreenNoteOrigin> expected_actions = {
mojom::LockScreenNoteOrigin::kLockScreenButtonTap};
ui::test::EventGenerator* generator = GetEventGenerator();
// Tap in actionable area of the button requests action:
generator->GestureTapAt(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / kSqrt2 + 2,
kSmallButtonRadiusDp / kSqrt2 - 2));
Shell::Get()->tray_action()->FlushMojoForTesting();
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Tap in non-actionable area of the button does not request action:
generator->GestureTapAt(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / kSqrt2 - 2,
kSmallButtonRadiusDp / kSqrt2 + 2));
Shell::Get()->tray_action()->FlushMojoForTesting();
EXPECT_TRUE(tray_action_client()->note_origins().empty());
tray_action_client()->ClearRecordedRequests();
}
// Tests a number of fling gestures that interact with the note action button.
// Verifies that only a fling from the button's actionable area to bottom right
// direction generate an action request.
TEST_F(NoteActionLaunchButtonTest, FlingGesture) {
auto* note_action_button =
new NoteActionLaunchButton(mojom::TrayActionState::kAvailable);
std::unique_ptr<views::Widget> widget =
CreateWidgetWithContent(login_views_utils::WrapViewForPreferredSize(
base::WrapUnique(note_action_button))
.release());
const gfx::Size action_size = note_action_button->GetPreferredSize();
EXPECT_EQ(gfx::Size(kLargeButtonRadiusDp, kLargeButtonRadiusDp), action_size);
// Offset note action button closer to the center of the test widget, to give
// extra space for performing gestures.
gfx::Rect view_bounds = note_action_button->GetBoundsInScreen();
view_bounds.Offset(200, 200);
note_action_button->SetBoundsRect(view_bounds);
ASSERT_EQ(gfx::Rect(gfx::Point(200, 200), action_size),
note_action_button->GetBoundsInScreen());
const std::vector<mojom::LockScreenNoteOrigin> expected_actions = {
mojom::LockScreenNoteOrigin::kLockScreenButtonSwipe};
// Point in the center of the note action element's actionable area:
gfx::Point start =
view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / 2, kSmallButtonRadiusDp / 2);
// Fling from the center of the actionable area to bottom left:
GestureFling(start, view_bounds.bottom_left() + gfx::Vector2d(-50, 50));
EXPECT_EQ(expected_actions, tray_action_client()->note_origins());
tray_action_client()->ClearRecordedRequests();
// Fling from the center of the actionable area to bottom right:
GestureFling(start, view_bounds.bottom_right() + gfx::Vector2d(0, 50));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
// Fling from the center of the actionable area to top left:
GestureFling(start, view_bounds.origin() + gfx::Vector2d(-50, 0));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
// Fling accross the button:
GestureFling(view_bounds.top_right() + gfx::Vector2d(25, -25),
view_bounds.bottom_left() + gfx::Vector2d(-25, 25));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
// Fling from non-actionable area of the button:
GestureFling(view_bounds.top_right() +
gfx::Vector2d(-kSmallButtonRadiusDp / kSqrt2 - 2,
kSmallButtonRadiusDp / kSqrt2 + 2),
view_bounds.bottom_left() + gfx::Vector2d(-25, 25));
EXPECT_TRUE(tray_action_client()->note_origins().empty());
}
// Generates multi-finger fling in the direction that would be accepted for
// single finger fling, and verifies no action is requested.
TEST_F(NoteActionLaunchButtonTest, MultiFingerFling) {
auto* note_action_button =
new NoteActionLaunchButton(mojom::TrayActionState::kAvailable);
std::unique_ptr<views::Widget> widget =
CreateWidgetWithContent(login_views_utils::WrapViewForPreferredSize(
base::WrapUnique(note_action_button))
.release());
const gfx::Size action_size = note_action_button->GetPreferredSize();
EXPECT_EQ(gfx::Size(kLargeButtonRadiusDp, kLargeButtonRadiusDp), action_size);
// Offset note action button closer to the center of the test widget, to give
// extra space for performing gestures:
gfx::Rect view_bounds = note_action_button->GetBoundsInScreen();
view_bounds.Offset(200, 200);
note_action_button->SetBoundsRect(view_bounds);
ASSERT_EQ(gfx::Rect(gfx::Point(200, 200), action_size),
note_action_button->GetBoundsInScreen());
const int kTouchPoints = 3;
const gfx::Point start_points[kTouchPoints] = {
view_bounds.top_right() + gfx::Vector2d(-2, 2),
view_bounds.top_right() + gfx::Vector2d(-20, 15),
view_bounds.top_right() + gfx::Vector2d(-35, 40)};
const gfx::Vector2d deltas[kTouchPoints] = {gfx::Vector2d(-100, 100),
gfx::Vector2d(-100, 100),
gfx::Vector2d(-100, 100)};
int delays_adding_fingers_ms[kTouchPoints] = {0, 4, 8};
int delays_releasing_fingers_ms[kTouchPoints] = {20, 16, 18};
ui::test::EventGenerator* generator = GetEventGenerator();
generator->GestureMultiFingerScrollWithDelays(
kTouchPoints, start_points, deltas, delays_adding_fingers_ms,
delays_releasing_fingers_ms, 4 /* event_separaation_time_ms*/,
5 /*steps*/);
Shell::Get()->tray_action()->FlushMojoForTesting();
EXPECT_TRUE(tray_action_client()->note_origins().empty());
}
} // namespace ash