blob: d253cb377cff0ff42d6225804b93358a11f3517c [file] [log] [blame]
// Copyright 2021 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/interaction/interaction_sequence_views.h"
// This suite contains tests which integrate the functionality of
// ui::InteractionSequence with Views elements like Widgets and buttons.
// Similar suites should be created for other platforms.
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/expect_call_in_scope.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/interaction/interaction_sequence_test_util.h"
#include "ui/base/interaction/interaction_test_util.h"
#include "ui/base/ui_base_types.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/interaction/interaction_test_util_views.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kContentsElementID);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestElementID);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestElementID2);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestElementID3);
const char kElementName[] = "ElementName";
} // namespace
class InteractionSequenceViewsTest
: public ViewsTestBase,
public testing::WithParamInterface<
ui::InteractionSequence::StepStartMode> {
public:
InteractionSequenceViewsTest() = default;
~InteractionSequenceViewsTest() override = default;
static View* ElementToView(ui::TrackedElement* element) {
return element ? element->AsA<TrackedElementViews>()->view() : nullptr;
}
static ui::TrackedElement* ViewToElement(View* view) {
return view ? ElementTrackerViews::GetInstance()->GetElementForView(view)
: nullptr;
}
std::unique_ptr<Widget> CreateWidget() {
auto 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));
auto* contents = widget->SetContentsView(std::make_unique<View>());
auto* layout = contents->SetLayoutManager(std::make_unique<FlexLayout>());
layout->SetOrientation(LayoutOrientation::kHorizontal);
layout->SetDefault(kFlexBehaviorKey,
FlexSpecification(MinimumFlexSizeRule::kPreferred,
MaximumFlexSizeRule::kUnbounded));
test::WidgetVisibleWaiter visible_waiter(widget.get());
widget->Show();
visible_waiter.Wait();
return widget;
}
void ShowBubble(ui::ElementIdentifier id) {
auto delegate = std::make_unique<BubbleDialogDelegateView>(
BubbleDialogDelegateView::CreatePassKey(), contents(),
BubbleBorder::Arrow::TOP_LEFT);
label_button_ = delegate->AddChildView(std::make_unique<LabelButton>());
label_button_->SetProperty(kElementIdentifierKey, id);
no_id_view_ = delegate->AddChildView(std::make_unique<LabelButton>());
bubble_widget_ =
BubbleDialogDelegateView::CreateBubble(std::move(delegate));
test::WidgetVisibleWaiter visible_waiter(bubble_widget_);
bubble_widget_->Show();
visible_waiter.Wait();
}
void CloseBubble() {
DCHECK(bubble_widget_);
label_button_ = nullptr;
no_id_view_ = nullptr;
bubble_widget_.ExtractAsDangling()->CloseNow();
}
void Activate(View* view) {
ui::ElementTracker::GetFrameworkDelegate()->NotifyElementActivated(
ElementTrackerViews::GetInstance()->GetElementForView(view));
}
void SetUp() override {
ViewsTestBase::SetUp();
widget_ = CreateWidget();
contents()->SetProperty(kElementIdentifierKey, kContentsElementID);
}
void TearDown() override {
if (bubble_widget_) {
CloseBubble();
}
widget_.reset();
ViewsTestBase::TearDown();
}
ui::InteractionSequence::Builder Builder() const {
return std::move(
ui::InteractionSequence::Builder().SetDefaultStepStartMode(GetParam()));
}
protected:
ui::ElementContext context() const {
return ElementTrackerViews::GetContextForWidget(widget_.get());
}
View* contents() { return widget_->GetContentsView(); }
std::unique_ptr<Widget> widget_;
raw_ptr<Widget> bubble_widget_ = nullptr;
raw_ptr<LabelButton> label_button_ = nullptr;
raw_ptr<LabelButton> no_id_view_ = nullptr;
};
INSTANTIATE_TEST_SUITE_P(
,
InteractionSequenceViewsTest,
testing::Values(ui::InteractionSequence::StepStartMode::kImmediate,
ui::InteractionSequence::StepStartMode::kAsynchronous),
[](const testing::TestParamInfo<ui::InteractionSequence::StepStartMode>&
mode) {
std::ostringstream oss;
oss << mode.param;
return oss.str();
});
TEST_P(InteractionSequenceViewsTest, DestructWithInitialViewAborts) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
auto* const starting_view =
contents()->AddChildView(std::make_unique<View>());
starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(starting_view))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.Build())
.Build();
sequence->Start();
EXPECT_ASYNC_CALL_IN_SCOPE(aborted, Run,
contents()->RemoveChildViewT(starting_view));
}
TEST_P(InteractionSequenceViewsTest, DestructWithInitialViewBeforeStartAborts) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
auto* const starting_view =
contents()->AddChildView(std::make_unique<View>());
starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(starting_view))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.Build())
.Build();
contents()->RemoveChildViewT(starting_view);
EXPECT_ASYNC_CALL_IN_SCOPE(aborted, Run, sequence->Start());
}
TEST_P(InteractionSequenceViewsTest, WrongWithInitialViewDoesNotStartSequence) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
auto* const starting_view =
contents()->AddChildView(std::make_unique<View>());
starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
auto* const other_view = contents()->AddChildView(std::make_unique<View>());
other_view->SetProperty(kElementIdentifierKey, kTestElementID);
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(starting_view))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.Build())
.Build();
starting_view->SetVisible(false);
EXPECT_ASYNC_CALL_IN_SCOPE(aborted, Run, sequence->Start());
}
TEST_P(InteractionSequenceViewsTest,
SequenceNotCanceledDueToViewDestroyedIfRequirementChanged) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback,
step2_start);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepEndCallback, step2_end);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback,
step3_start);
auto* const starting_view =
contents()->AddChildView(std::make_unique<View>());
starting_view->SetProperty(kElementIdentifierKey, kTestElementID);
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(starting_view))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kShown)
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kActivated)
// Specify that this step doesn't abort on the view
// becoming hidden.
.SetMustRemainVisible(false)
.SetStartCallback(step2_start.Get())
.SetEndCallback(step2_end.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID3)
.SetType(ui::InteractionSequence::StepType::kShown)
.SetStartCallback(step3_start.Get())
.Build())
.Build();
sequence->Start();
auto* const second_view = contents()->AddChildView(std::make_unique<View>());
second_view->SetProperty(kElementIdentifierKey, kTestElementID2);
auto* const third_view = contents()->AddChildView(std::make_unique<View>());
third_view->SetProperty(kElementIdentifierKey, kTestElementID3);
third_view->SetVisible(false);
// Simulate the view being activated to do the second step.
EXPECT_ASYNC_CALL_IN_SCOPE(step2_start,
Run(sequence.get(), ViewToElement(second_view)),
Activate(second_view));
// Destroying the second view should NOT break the sequence.
contents()->RemoveChildViewT(second_view);
// Showing the third view at this point continues the sequence.
EXPECT_ASYNC_CALLS_IN_SCOPE_3(step2_end, Run, step3_start, Run, completed,
Run, third_view->SetVisible(true));
}
// The tests are failing on debug swiftshader on arm64, see
// https://ci.chromium.org/ui/p/chromium/builders/ci/fuchsia-fyi-arm64-dbg/9234/overview
// TODO(crbug.com/42050042): Re-enable the tests once we get rid of swiftshader.
#if BUILDFLAG(IS_FUCHSIA) && !defined(NDEBUG) && defined(ARCH_CPU_ARM64)
#define MAYBE_TransitionToBubble DISABLED_TransitionToBubble
#else
#define MAYBE_TransitionToBubble TransitionToBubble
#endif
TEST_P(InteractionSequenceViewsTest, MAYBE_TransitionToBubble) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step2);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step3);
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(contents()))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kShown)
.SetStartCallback(step2.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step3.Get())
.Build())
.Build();
auto* const button = contents()->AddChildView(
std::make_unique<LabelButton>(Button::PressedCallback(
base::BindRepeating(&InteractionSequenceViewsTest::ShowBubble,
base::Unretained(this), kTestElementID2))));
button->SetProperty(kElementIdentifierKey, kTestElementID);
button->SetAccessibleName(u"Button");
sequence->Start();
EXPECT_ASYNC_CALLS_IN_SCOPE_2(
step, Run, step2, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(button));
EXPECT_ASYNC_CALLS_IN_SCOPE_2(
step3, Run, completed, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(
label_button_));
}
// The tests are failing on debug swiftshader on arm64, see
// https://ci.chromium.org/ui/p/chromium/builders/ci/fuchsia-fyi-arm64-dbg/9234/overview
// TODO(crbug.com/42050042): Re-enable the tests once we get rid of swiftshader.
#if BUILDFLAG(IS_FUCHSIA) && !defined(NDEBUG) && defined(ARCH_CPU_ARM64)
#define MAYBE_TransitionToBubbleThenAbort DISABLED_TransitionToBubbleThenAbort
#else
#define MAYBE_TransitionToBubbleThenAbort TransitionToBubbleThenAbort
#endif
TEST_P(InteractionSequenceViewsTest, MAYBE_TransitionToBubbleThenAbort) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step2);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step3);
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(contents()))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kShown)
.SetStartCallback(step2.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step3.Get())
.Build())
.Build();
auto* const button = contents()->AddChildView(
std::make_unique<LabelButton>(Button::PressedCallback(
base::BindRepeating(&InteractionSequenceViewsTest::ShowBubble,
base::Unretained(this), kTestElementID2))));
button->SetProperty(kElementIdentifierKey, kTestElementID);
button->SetAccessibleName(u"Button");
sequence->Start();
EXPECT_ASYNC_CALLS_IN_SCOPE_2(
step, Run, step2, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(button));
EXPECT_ASYNC_CALL_IN_SCOPE(aborted, Run, CloseBubble());
}
// NameView tests:
// The tests are failing on debug swiftshader on arm64, see
// https://ci.chromium.org/ui/p/chromium/builders/ci/fuchsia-fyi-arm64-dbg/9234/overview
// TODO(crbug.com/42050042): Re-enable the tests once we get rid of swiftshader.
#if BUILDFLAG(IS_FUCHSIA) && !defined(NDEBUG) && defined(ARCH_CPU_ARM64)
#define MAYBE_NameView_NameViewWithIdentifier \
DISABLED_NameView_NameViewWithIdentifier
#else
#define MAYBE_NameView_NameViewWithIdentifier NameView_NameViewWithIdentifier
#endif
TEST_P(InteractionSequenceViewsTest, MAYBE_NameView_NameViewWithIdentifier) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step3);
auto step2 = base::BindLambdaForTesting([&](ui::InteractionSequence* sequence,
ui::TrackedElement* element) {
EXPECT_EQ(label_button_, element->AsA<TrackedElementViews>()->view());
InteractionSequenceViews::NameView(sequence, label_button_, kElementName);
});
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(contents()))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kShown)
.SetStartCallback(std::move(step2))
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementName(kElementName)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step3.Get())
.Build())
.Build();
auto* const button = contents()->AddChildView(
std::make_unique<LabelButton>(Button::PressedCallback(
base::BindRepeating(&InteractionSequenceViewsTest::ShowBubble,
base::Unretained(this), kTestElementID2))));
button->SetProperty(kElementIdentifierKey, kTestElementID);
button->SetAccessibleName(u"Button");
sequence->Start();
EXPECT_ASYNC_CALL_IN_SCOPE(
step, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(button));
EXPECT_ASYNC_CALLS_IN_SCOPE_2(
step3, Run, completed, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(
label_button_));
}
// The tests are failing on debug swiftshader on arm64, see
// https://ci.chromium.org/ui/p/chromium/builders/ci/fuchsia-fyi-arm64-dbg/9234/overview
// TODO(crbug.com/42050042): Re-enable the tests once we get rid of swiftshader.
#if BUILDFLAG(IS_FUCHSIA) && !defined(NDEBUG) && defined(ARCH_CPU_ARM64)
#define MAYBE_NameView_NameViewWithNoIdentifier \
DISABLED_NameView_NameViewWithNoIdentifier
#else
#define MAYBE_NameView_NameViewWithNoIdentifier \
NameView_NameViewWithNoIdentifier
#endif
TEST_P(InteractionSequenceViewsTest, MAYBE_NameView_NameViewWithNoIdentifier) {
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step);
UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::StepStartCallback, step3);
auto step2 = base::BindLambdaForTesting(
[&](ui::InteractionSequence* sequence, ui::TrackedElement* element) {
EXPECT_EQ(label_button_, element->AsA<TrackedElementViews>()->view());
InteractionSequenceViews::NameView(sequence, no_id_view_, kElementName);
});
auto sequence =
Builder()
.SetAbortedCallback(aborted.Get())
.SetCompletedCallback(completed.Get())
.AddStep(InteractionSequenceViews::WithInitialView(contents()))
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step.Get())
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementID(kTestElementID2)
.SetType(ui::InteractionSequence::StepType::kShown)
.SetStartCallback(std::move(step2))
.Build())
.AddStep(ui::InteractionSequence::StepBuilder()
.SetElementName(kElementName)
.SetType(ui::InteractionSequence::StepType::kActivated)
.SetStartCallback(step3.Get())
.Build())
.Build();
auto* const button = contents()->AddChildView(
std::make_unique<LabelButton>(Button::PressedCallback(
base::BindRepeating(&InteractionSequenceViewsTest::ShowBubble,
base::Unretained(this), kTestElementID2))));
button->SetProperty(kElementIdentifierKey, kTestElementID);
button->SetAccessibleName(u"Button");
sequence->Start();
EXPECT_ASYNC_CALL_IN_SCOPE(
step, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(button));
EXPECT_ASYNC_CALLS_IN_SCOPE_2(
step3, Run, completed, Run,
views::test::InteractionTestUtilSimulatorViews::PressButton(no_id_view_));
}
} // namespace views