#ifndef UI_VIEWS_INTERACTION_INTERACTIVE_VIEWS_TEST_H_
#define UI_VIEWS_INTERACTION_INTERACTIVE_VIEWS_TEST_H_
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/stringprintf.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_test_util.h"
#include "ui/base/interaction/interactive_test.h"
#include "ui/base/interaction/interactive_test_internal.h"
#include "ui/views/interaction/element_tracker_views.h"
#include "ui/views/interaction/interaction_test_util_mouse.h"
#include "ui/views/interaction/interactive_views_test_internal.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
namespace views::test {
class InteractiveViewsTestApi : public ui::test::InteractiveTestApi {
public:
InteractiveViewsTestApi();
~InteractiveViewsTestApi() override;
InteractionTestUtilMouse& mouse_util() { return test_impl().mouse_util(); }
template <typename T = View>
static T* AsView(ui::TrackedElement* el);
template <typename T = View>
static const T* AsView(const ui::TrackedElement* el);
template <typename... Args>
bool RunTestSequence(Args&&... steps);
using ViewMatcher = base::RepeatingCallback<bool(const View*)>;
using AbsoluteViewSpecifier = absl::variant<
View*,
std::reference_wrapper<View*>,
base::OnceCallback<View*()>>;
using ChildViewSpecifier = absl::variant<
size_t,
ViewMatcher>;
template <typename C,
typename V = internal::ViewArgType<0, C>,
typename R = std::remove_cv_t<
std::remove_pointer_t<ui::test::internal::ReturnTypeOf<C>>>,
typename = ui::test::internal::RequireSignature<C, R*(V*)>>
[[nodiscard]] static StepBuilder NameViewRelative(
ElementSpecifier relative_to,
base::StringPiece name,
C&& find_callback);
[[nodiscard]] static StepBuilder NameView(base::StringPiece name,
AbsoluteViewSpecifier spec);
[[nodiscard]] static StepBuilder NameChildView(ElementSpecifier parent,
base::StringPiece name,
ChildViewSpecifier spec);
[[nodiscard]] static StepBuilder NameDescendantView(ElementSpecifier ancestor,
base::StringPiece name,
ViewMatcher matcher);
template <typename V>
[[nodiscard]] static StepBuilder NameChildViewByType(ElementSpecifier parent,
base::StringPiece name,
size_t index = 0);
template <typename V>
[[nodiscard]] static StepBuilder NameDescendantViewByType(
ElementSpecifier ancestor,
base::StringPiece name,
size_t index = 0);
template <typename F,
typename V = internal::ViewArgType<0, F>,
typename = ui::test::internal::RequireSignature<F, void(V*)>>
[[nodiscard]] static StepBuilder WithView(ElementSpecifier view,
F&& function);
template <typename F,
typename V = internal::ViewArgType<0, F>,
typename = ui::test::internal::RequireSignature<
F,
bool(V*)>>
[[nodiscard]] static StepBuilder CheckView(ElementSpecifier view, F&& check);
template <typename F,
typename M,
typename R = ui::test::internal::ReturnTypeOf<F>,
typename V = internal::ViewArgType<0, F>,
typename = ui::test::internal::RequireSignature<F, R(V*)>>
[[nodiscard]] static StepBuilder CheckView(ElementSpecifier view,
F&& function,
M&& matcher);
template <typename V, typename R, typename M>
[[nodiscard]] static StepBuilder CheckViewProperty(ElementSpecifier view,
R (V::*property)() const,
M&& matcher);
[[nodiscard]] static StepBuilder ScrollIntoView(ElementSpecifier view);
struct CenterPoint {};
using AbsolutePositionCallback = base::OnceCallback<gfx::Point()>;
using AbsolutePositionSpecifier = absl::variant<
gfx::Point,
gfx::Point*,
AbsolutePositionCallback>;
using RelativePositionCallback =
base::OnceCallback<gfx::Point(ui::TrackedElement* reference_element)>;
using RelativePositionSpecifier = absl::variant<
CenterPoint,
RelativePositionCallback>;
[[nodiscard]] StepBuilder MoveMouseTo(AbsolutePositionSpecifier position);
[[nodiscard]] StepBuilder MoveMouseTo(
ElementSpecifier reference,
RelativePositionSpecifier position = CenterPoint());
[[nodiscard]] StepBuilder ClickMouse(
ui_controls::MouseButton button = ui_controls::LEFT,
bool release = true);
[[nodiscard]] StepBuilder DragMouseTo(AbsolutePositionSpecifier position,
bool release = true);
[[nodiscard]] StepBuilder DragMouseTo(
ElementSpecifier reference,
RelativePositionSpecifier position = CenterPoint(),
bool release = true);
[[nodiscard]] StepBuilder ReleaseMouse(
ui_controls::MouseButton button = ui_controls::LEFT);
template <typename C,
typename T,
typename U = MultiStep,
typename V = internal::ViewArgType<0, C>,
typename = ui::test::internal::RequireSignature<
C,
bool(const V*)>>
[[nodiscard]] static StepBuilder IfView(ElementSpecifier element,
C&& condition,
T&& then_steps,
U&& else_steps = MultiStep());
template <typename F,
typename M,
typename T,
typename U = MultiStep,
typename R = ui::test::internal::ReturnTypeOf<F>,
typename V = internal::ViewArgType<0, F>,
typename = ui::test::internal::RequireSignature<F, R(const V*)>>
[[nodiscard]] static StepBuilder IfViewMatches(ElementSpecifier element,
F&& function,
M&& matcher,
T&& then_steps,
U&& else_steps = MultiStep());
template <typename R,
typename M,
typename V,
typename T,
typename U = MultiStep>
[[nodiscard]] static StepBuilder IfViewPropertyMatches(
ElementSpecifier element,
R (V::*property)() const,
M&& matcher,
T&& then_steps,
U&& else_steps = MultiStep());
void SetContextWidget(Widget* context_widget);
Widget* context_widget() { return context_widget_; }
protected:
explicit InteractiveViewsTestApi(
std::unique_ptr<internal::InteractiveViewsTestPrivate> private_test_impl);
private:
using FindViewCallback = base::OnceCallback<View*(View*)>;
static FindViewCallback GetFindViewCallback(AbsoluteViewSpecifier spec);
static FindViewCallback GetFindViewCallback(ChildViewSpecifier spec);
static views::View* FindMatchingView(const views::View* from,
ViewMatcher& matcher,
bool recursive);
static RelativePositionCallback GetPositionCallback(
AbsolutePositionSpecifier spec);
static RelativePositionCallback GetPositionCallback(
RelativePositionSpecifier spec);
internal::InteractiveViewsTestPrivate& test_impl() {
return static_cast<internal::InteractiveViewsTestPrivate&>(
InteractiveTestApi::private_test_impl());
}
StepBuilder CreateMouseFollowUpStep(const base::StringPiece& description);
base::raw_ptr<Widget, DanglingUntriaged> context_widget_ = nullptr;
};
template <typename T>
class InteractiveViewsTestT : public T, public InteractiveViewsTestApi {
public:
template <typename... Args>
explicit InteractiveViewsTestT(Args&&... args)
: T(std::forward<Args>(args)...) {}
~InteractiveViewsTestT() override = default;
protected:
void SetUp() override {
T::SetUp();
private_test_impl().DoTestSetUp();
}
void TearDown() override {
private_test_impl().DoTestTearDown();
T::TearDown();
}
};
using InteractiveViewsTest = InteractiveViewsTestT<ViewsTestBase>;
template <class T>
T* InteractiveViewsTestApi::AsView(ui::TrackedElement* el) {
auto* const views_el = el->AsA<TrackedElementViews>();
CHECK(views_el);
T* const view = AsViewClass<T>(views_el->view());
CHECK(view);
return view;
}
template <class T>
const T* InteractiveViewsTestApi::AsView(const ui::TrackedElement* el) {
const auto* const views_el = el->AsA<TrackedElementViews>();
CHECK(views_el);
const T* const view = AsViewClass<T>(views_el->view());
CHECK(view);
return view;
}
template <typename... Args>
bool InteractiveViewsTestApi::RunTestSequence(Args&&... steps) {
return RunTestSequenceInContext(
ElementTrackerViews::GetContextForWidget(context_widget()),
std::forward<Args>(steps)...);
}
template <typename C, typename V, typename R, typename>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::NameViewRelative(
ElementSpecifier relative_to,
base::StringPiece name,
C&& find_callback) {
StepBuilder builder;
builder.SetDescription(
base::StringPrintf("NameViewRelative( \"%s\" )", name.data()));
ui::test::internal::SpecifyElement(builder, relative_to);
builder.SetMustBeVisibleAtStart(true);
builder.SetStartCallback(base::BindOnce(
[](base::OnceCallback<R*(V*)> find_callback, std::string name,
ui::InteractionSequence* seq, ui::TrackedElement* el) {
V* relative_to = nullptr;
if (el->identifier() !=
ui::test::internal::kInteractiveTestPivotElementId) {
if (!el->IsA<TrackedElementViews>()) {
LOG(ERROR) << "NameView(): Target element is not a View.";
seq->FailForTesting();
return;
}
View* const view = el->AsA<TrackedElementViews>()->view();
if (!IsViewClass<V>(view)) {
LOG(ERROR) << "NameView(): Target View is of type "
<< view->GetClassName() << " but expected "
<< V::MetaData()->type_name();
seq->FailForTesting();
return;
}
relative_to = AsViewClass<V>(view);
}
View* const result = std::move(find_callback).Run(relative_to);
if (!result) {
LOG(ERROR) << "NameView(): No View found.";
seq->FailForTesting();
return;
}
auto* const target_element =
ElementTrackerViews::GetInstance()->GetElementForView(
result, true);
if (!target_element) {
LOG(ERROR)
<< "NameView(): attempting to name View that is not visible.";
seq->FailForTesting();
return;
}
seq->NameElement(target_element, name);
},
ui::test::internal::MaybeBind(std::forward<C>(find_callback)),
std::string(name)));
return builder;
}
template <typename F, typename V, typename>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::WithView(
ElementSpecifier view,
F&& function) {
StepBuilder builder;
builder.SetDescription("WithView()");
ui::test::internal::SpecifyElement(builder, view);
builder.SetMustBeVisibleAtStart(true);
builder.SetStartCallback(base::BindOnce(
[](base::OnceCallback<void(V*)> function, ui::InteractionSequence* seq,
ui::TrackedElement* el) { std::move(function).Run(AsView<V>(el)); },
ui::test::internal::MaybeBind(std::forward<F>(function))));
return builder;
}
template <typename C, typename T, typename U, typename V, typename>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::IfView(
ElementSpecifier element,
C&& condition,
T&& then_steps,
U&& else_steps) {
return std::move(
IfElement(element,
base::BindOnce(
[](base::OnceCallback<bool(const V*)> condition,
const ui::InteractionSequence* seq,
const ui::TrackedElement* el) {
const V* const view = el ? AsView<V>(el) : nullptr;
return std::move(condition).Run(view);
},
ui::test::internal::MaybeBind(std::forward<C>(condition))),
std::forward<T>(then_steps), std::forward<U>(else_steps))
.SetDescription("IfView()"));
}
template <typename F,
typename M,
typename T,
typename U,
typename R,
typename V,
typename>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::IfViewMatches(
ElementSpecifier element,
F&& function,
M&& matcher,
T&& then_steps,
U&& else_steps) {
return std::move(
IfElementMatches(
element,
base::BindOnce(
[](base::OnceCallback<R(const V*)> condition,
const ui::InteractionSequence* seq,
const ui::TrackedElement* el) {
const V* const view = el ? AsView<V>(el) : nullptr;
return std::move(condition).Run(view);
},
ui::test::internal::MaybeBind(std::forward<F>(function))),
testing::Matcher<R>(std::forward<M>(matcher)),
std::forward<T>(then_steps), std::forward<U>(else_steps))
.SetDescription("IfViewMatches()"));
}
template <typename R, typename M, typename V, typename T, typename U>
ui::InteractionSequence::StepBuilder
InteractiveViewsTestApi::IfViewPropertyMatches(ElementSpecifier element,
R (V::*property)() const,
M&& matcher,
T&& then_steps,
U&& else_steps) {
using Return = std::remove_cvref_t<R>;
base::OnceCallback<Return(const V*)> function = base::BindOnce(
[](R (V::*property)() const, const V* view) -> Return {
return (view->*property)();
},
std::move(property));
return std::move(
IfViewMatches(element, std::move(function), std::forward<M>(matcher),
std::forward<T>(then_steps), std::forward<U>(else_steps))
.SetDescription("IfViewPropertyMatches()"));
}
template <typename V>
ui::InteractionSequence::StepBuilder
InteractiveViewsTestApi::NameChildViewByType(ElementSpecifier parent,
base::StringPiece name,
size_t index) {
return std::move(
NameChildView(parent, name,
base::BindRepeating(
[](size_t& index, const View* view) {
if (IsViewClass<V>(view)) {
if (index == 0) {
return true;
}
--index;
}
return false;
},
base::OwnedRef(index)))
.SetDescription(base::StringPrintf(
"NameChildViewByType<%s>( \"%s\" %zu )",
V::MetaData()->type_name().c_str(), name.data(), index)));
}
template <typename V>
ui::InteractionSequence::StepBuilder
InteractiveViewsTestApi::NameDescendantViewByType(ElementSpecifier ancestor,
base::StringPiece name,
size_t index) {
return std::move(
NameDescendantView(ancestor, name,
base::BindRepeating(
[](size_t& index, const View* view) {
if (IsViewClass<V>(view)) {
if (index == 0) {
return true;
}
--index;
}
return false;
},
base::OwnedRef(index)))
.SetDescription(base::StringPrintf(
"NameDescendantViewByType<%s>( \"%s\" %zu )",
V::MetaData()->type_name().c_str(), name.data(), index)));
}
template <typename F, typename, typename>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::CheckView(
ElementSpecifier view,
F&& check) {
return CheckView(view, std::forward<F>(check), true);
}
template <typename F, typename M, typename R, typename V, typename>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::CheckView(
ElementSpecifier view,
F&& function,
M&& matcher) {
StepBuilder builder;
builder.SetDescription("CheckView()");
ui::test::internal::SpecifyElement(builder, view);
builder.SetStartCallback(base::BindOnce(
[](base::OnceCallback<R(V*)> function, testing::Matcher<R> matcher,
ui::InteractionSequence* seq, ui::TrackedElement* el) {
if (!ui::test::internal::MatchAndExplain(
"CheckView()", matcher,
std::move(function).Run(AsView<V>(el)))) {
seq->FailForTesting();
}
},
ui::test::internal::MaybeBind(std::forward<F>(function)),
testing::Matcher<R>(std::forward<M>(matcher))));
return builder;
}
template <typename V, typename R, typename M>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::CheckViewProperty(
ElementSpecifier view,
R (V::*property)() const,
M&& matcher) {
StepBuilder builder;
builder.SetDescription("CheckViewProperty()");
ui::test::internal::SpecifyElement(builder, view);
builder.SetStartCallback(base::BindOnce(
[](R (V::*property)() const, testing::Matcher<R> matcher,
ui::InteractionSequence* seq, ui::TrackedElement* el) {
if (!ui::test::internal::MatchAndExplain(
"CheckViewProperty()", matcher, (AsView<V>(el)->*property)())) {
seq->FailForTesting();
}
},
property, testing::Matcher<R>(std::forward<M>(matcher))));
return builder;
}
}
#endif