blob: aac4800ed4521adce2facc2112b83a3debac966e [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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/callback_list.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interactive_test.h"
#include "ui/base/interaction/interactive_test_internal.h"
#include "ui/base/metadata/metadata_types.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/interaction/polling_view_observer.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
namespace views::test {
// Provides interactive test functionality for Views.
//
// Interactive tests use InteractionSequence, ElementTracker, and
// InteractionTestUtil to provide a common library of concise test methods. This
// convenience API is nicknamed "Kombucha" (see
// //chrome/test/interaction/README.md for more information).
//
// This class is not a test fixture; it is a mixin that can be added to existing
// test classes using `InteractiveViewsTestT<T>` - or just use
// `InteractiveViewsTest`, which *is* a test fixture (preferred; see below).
//
// To use Kombucha for in-process browser tests, instead see:
// //chrome/test/interaction/interactive_browser_test.h
class InteractiveViewsTestApi : public ui::test::InteractiveTestApi {
public:
InteractiveViewsTestApi();
~InteractiveViewsTestApi() override;
// Returns an object that can be used to inject mouse inputs. Generally,
// prefer to use methods like MoveMouseTo, MouseClick, and DragMouseTo.
InteractionTestUtilMouse& mouse_util() { return test_impl().mouse_util(); }
// Shorthand to convert a tracked element into a View. The element should be
// a views::TrackedElementViews and of type `T`.
template <typename T = View>
static T* AsView(ui::TrackedElement* el);
template <typename T = View>
static const T* AsView(const ui::TrackedElement* el);
// Runs a test InteractionSequence from a series of Steps or StepBuilders with
// RunSynchronouslyForTesting(). Hooks both the completed and aborted
// callbacks to ensure completion, and prints an error on failure. The context
// will be pulled from `context_widget()`.
template <typename... Args>
bool RunTestSequence(Args&&... steps);
// Naming views:
//
// The following methods name a view (to be referred to later in the test
// sequence by name) based on some kind of rule or relationship. The View need
// not have an ElementIdentifier assigned ahead of time, so this is useful for
// finding random or dynamically-created views.
//
// For example:
//
// RunTestSequence(
// ...
// NameView(kThirdTabName,
// base::BindLambdaForTesting([&](){
// return browser_view->tabstrip()->tab_at(3);
// }))
// WithElement(kThirdTabName, ...)
// ...
// );
//
// How the view is named will depend on which version of the method you use;
// the
// Determines if a view matches some predicate.
using ViewMatcher = base::RepeatingCallback<bool(const View*)>;
// Specifies a View not relative to any particular other View.
using AbsoluteViewSpecifier = absl::variant<
// Specify a view that is known at the time the sequence is created. The
// View must persist until the step executes.
View*,
// Specify a view pointer that will be valid by the time the step
// executes. Use `std::ref()` to wrap the pointer that will receive the
// value.
std::reference_wrapper<View*>,
// Find and return a view based on an arbitrary rule.
base::OnceCallback<View*()>>;
// Specifies a view relative to its parent.
using ChildViewSpecifier = absl::variant<
// The index of the child in the parent view. An out of bounds index will
// generate an error.
size_t,
// Specifies a filter that is applied to the children; the first child
// view to satisfy the filter (i.e. return true) is named.
ViewMatcher>;
// Methods that name views.
// Names a view relative to another view `relative_to` based on an arbitrary
// rule. The resulting view does not need to be a descendant (or even an
// ancestor) of `relative_to`.
//
// Your `find_callback` should take a pointer to a View or a derived type and
// return a pointer to a View or derived type.
template <typename C,
typename V = internal::ViewArgType<0, C>,
typename R = std::remove_cv_t<
std::remove_pointer_t<ui::test::internal::ReturnTypeOf<C>>>>
requires ui::test::internal::HasSignature<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);
// Names the `index` (0-indexed) child view of `parent` that is of type `V`.
template <typename V>
requires internal::IsView<V>
[[nodiscard]] static StepBuilder NameChildViewByType(ElementSpecifier parent,
base::StringPiece name,
size_t index = 0);
// Names the `index` (0-indexed) descendant view of `parent` in depth-first
// traversal order that is of type `V`.
template <typename V>
requires internal::IsView<V>
[[nodiscard]] static StepBuilder NameDescendantViewByType(
ElementSpecifier ancestor,
base::StringPiece name,
size_t index = 0);
// As WithElement(), but `view` should resolve to a TrackedElementViews
// wrapping a view of type `V`.
template <typename F, typename V = internal::ViewArgType<0, F>>
requires ui::test::internal::HasSignature<F, void(V*)>
[[nodiscard]] static StepBuilder WithView(ElementSpecifier view,
F&& function);
// As CheckElement(), but `view` should resolve to a TrackedElementViews
// wrapping a view of type `V`.
template <typename F, typename V = internal::ViewArgType<0, F>>
// NOLINTNEXTLINE(readability/casting)
requires ui::test::internal::HasSignature<F, bool(V*)>
[[nodiscard]] static StepBuilder CheckView(ElementSpecifier view, F&& check);
// As CheckView(), but checks that the result of calling `function` on `view`
// matches `matcher`. If not, the mismatch is printed and the test fails.
//
// `matcher` can be any type `M` that resolves or converts to type
// `Matcher<R>`.
template <typename F,
typename M,
typename R = ui::test::internal::ReturnTypeOf<F>,
typename V = internal::ViewArgType<0, F>>
requires ui::test::internal::HasSignature<F, R(V*)>
[[nodiscard]] static StepBuilder CheckView(ElementSpecifier view,
F&& function,
M&& matcher);
// As CheckView() but checks that `matcher` matches the value returned by
// calling `property` on `view`. On failure, logs the matcher error and fails
// the test.
//
// `V` is the View class, `R` is the type of the property being checked, and
// `M` is the type of the matcher, or the constant that will be converted to
// an equality matcher (e.g. `1` would be converted to `testing::Eq(1)`).
//
// `matcher` must resolve or convert to type `Matcher<R>`.
template <typename V, typename R, typename M>
requires internal::IsView<V>
[[nodiscard]] static StepBuilder CheckViewProperty(ElementSpecifier view,
R (V::*property)() const,
M&& matcher);
// Adds a step that waits for `property` to match `matcher` on `view`. The
// `add_listener` method must be specified in this version of the function.
//
// `V` is the View class, `R` is the type of the property being checked, and
// `M` is the type of the matcher, or the constant that will be converted to
// an equality matcher (e.g. `1` would be converted to `testing::Eq(1)`).
//
// NOTE: Prefer using the `WaitForViewProperty` macro as it is more concise
// and creates its own unique event (so you don't have to specify one). For
// example, the following are equivalent:
// ```
// WaitForViewPropertyCallback(
// kMyViewId,
// &View::GetEnabled,
// &View::AddEnabledChangedListener,
// true,
// kMyCustomEventType)
// ```
// and:
// ```
// WaitForViewProperty(kMyViewId, View, Enabled, true)
// ```
//
// Usage notes:
// - Do not use with the "Visible" property; use `WaitForShow()` and
// `WaitForHide()` instead.
// - Specify a unique `event` to avoid collisions between parallel or
// subsequent wait steps.
template <typename V, typename R, typename M>
requires internal::IsView<V>
[[nodiscard]] static MultiStep WaitForViewPropertyCallback(
ElementSpecifier view,
R (V::*property)() const,
base::CallbackListSubscription (V::*add_listener)(
ui::metadata::PropertyChangedCallback),
M&& matcher,
ui::CustomElementEventType event);
// Creates a state observer with `id` which polls the view in the current
// context with `view_id`. If the view is present, uses `callback` to update
// the state value; otherwise the value is `std::nullopt` (the actual state
// value is of type `std::optional<T>`).
//
// The element, if present, must resolve to a View of the correct type, or the
// test will fail.
//
// See `PollState()` and `PollElement()` for usage details and caveats.
// Specifically be aware that polling may miss a transient state; prefer to
// send a custom event or use `WaitForViewPropertyCallback()` if possible.
template <typename T, typename V, typename C>
requires internal::IsView<V> &&
ui::test::internal::HasSignature<C, T(const V*)>
[[nodiscard]] StepBuilder PollView(
ui::test::StateIdentifier<PollingViewObserver<T, V>> id,
ui::ElementIdentifier view_id,
C&& callback,
base::TimeDelta polling_interval = ui::test::PollingStateObserver<
std::optional<T>>::kDefaultPollingInterval);
// Creates a state observer with `id` which polls `property` on the view in
// the current context with `view_id`. If the view is not present, the state
// value will be set to `std::nullopt` (the actual state value is of type
// `std::optional<T>`).
//
// The element, if present, must resolve to a View of the correct type, or the
// test will fail.
//
// See `PollState()` and `PollElement()` for usage details and caveats.
// Specifically be aware that polling may miss a transient state; prefer to
// send a custom event or use `WaitForViewPropertyCallback()` if possible.
template <typename R, typename V, typename T = std::remove_cvref_t<R>>
requires internal::IsView<V>
[[nodiscard]] StepBuilder PollViewProperty(
ui::test::StateIdentifier<PollingViewPropertyObserver<T, V>> id,
ui::ElementIdentifier view_id,
R (V::*property)() const,
base::TimeDelta polling_interval = ui::test::PollingStateObserver<
std::optional<T>>::kDefaultPollingInterval);
// Scrolls `view` into the visible viewport if it is currently scrolled
// outside its container. The view must be otherwise present and visible.
// Has no effect if the view is not in a scroll container.
[[nodiscard]] static StepBuilder ScrollIntoView(ElementSpecifier view);
// Indicates that the center point of the target element should be used for a
// mouse move.
struct CenterPoint {};
// Function that returns a destination for a move or drag.
using AbsolutePositionCallback = base::OnceCallback<gfx::Point()>;
// Specifies an absolute position for a mouse move or drag that does not need
// a reference element.
using AbsolutePositionSpecifier = absl::variant<
// Use this specific position. This value is stored when the sequence is
// created; use gfx::Point* if you want to capture a point during sequence
// execution.
gfx::Point,
// As above, but the position is read from the reference on execution
// instead of copied when the test sequence is constructed. Use this when
// you want to calculate and cache a point during test execution for later
// use. The pointer must remain valid through the end of the test.
std::reference_wrapper<gfx::Point>,
// Use the return value of the supplied callback
AbsolutePositionCallback>;
// Specifies how the `reference_element` should be used (or not) to generate a
// target point for a mouse move.
using RelativePositionCallback =
base::OnceCallback<gfx::Point(ui::TrackedElement* reference_element)>;
// Specifies how the target position of a mouse operation (in screen
// coordinates) will be determined.
using RelativePositionSpecifier = absl::variant<
// Default to the centerpoint of the reference element, which should be a
// views::View.
CenterPoint,
// Use the return value of the supplied callback.
RelativePositionCallback>;
// Move the mouse to the specified `position` in screen coordinates. The
// `reference` element will be used based on how `position` is specified.
[[nodiscard]] StepBuilder MoveMouseTo(AbsolutePositionSpecifier position);
[[nodiscard]] StepBuilder MoveMouseTo(
ElementSpecifier reference,
RelativePositionSpecifier position = CenterPoint());
// Clicks mouse button `button` at the current cursor position.
[[nodiscard]] StepBuilder ClickMouse(
ui_controls::MouseButton button = ui_controls::LEFT,
bool release = true);
// Depresses the left mouse button at the current cursor position and drags to
// the target `position`. The `reference` element will be used based on how
// `position` is specified.
[[nodiscard]] StepBuilder DragMouseTo(AbsolutePositionSpecifier position,
bool release = true);
[[nodiscard]] StepBuilder DragMouseTo(
ElementSpecifier reference,
RelativePositionSpecifier position = CenterPoint(),
bool release = true);
// Releases the specified mouse button. Use when you previously called
// ClickMouse() or DragMouseTo() with `release` = false.
[[nodiscard]] StepBuilder ReleaseMouse(
ui_controls::MouseButton button = ui_controls::LEFT);
// As IfElement(), but `condition` takes a single argument that is a const
// View pointer. If `element` is not a view of type V, then the test will
// fail.
template <typename C,
typename T,
typename U = MultiStep,
typename V = internal::ViewArgType<0, C>>
requires ui::test::internal::HasSignature<
C,
bool(const V*)> // NOLINT(readability/casting)
[[nodiscard]] static StepBuilder IfView(ElementSpecifier element,
C&& condition,
T&& then_steps,
U&& else_steps = MultiStep());
// As IfElementMatches(), but `function` takes a single argument that is a
// const View pointer. If `element` is not a view of type V, then the test
// will fail.
template <typename F,
typename M,
typename T,
typename U = MultiStep,
typename R = ui::test::internal::ReturnTypeOf<F>,
typename V = internal::ViewArgType<0, F>>
requires ui::test::internal::HasSignature<F, R(const V*)>
[[nodiscard]] static StepBuilder IfViewMatches(ElementSpecifier element,
F&& function,
M&& matcher,
T&& then_steps,
U&& else_steps = MultiStep());
// Executes `then_steps` if `property` of the view `element` (which must be of
// the correct View type) matches `matcher`, otherwise executes `else_steps`.
//
// Note that bare literal strings cannot be passed as `matcher` for properties
// with string values, you will need to either explicitly pass a
// std::[u16]string or explicitly construct a testing::Eq matcher.
template <typename R,
typename M,
typename V,
typename T,
typename U = MultiStep>
requires internal::IsView<V>
[[nodiscard]] static StepBuilder IfViewPropertyMatches(
ElementSpecifier element,
R (V::*property)() const,
M&& matcher,
T&& then_steps,
U&& else_steps = MultiStep());
// Sets the context widget. Must be called before RunTestSequence() or any of
// the mouse functions.
void SetContextWidget(Widget* context_widget);
Widget* context_widget() { return context_widget_.get(); }
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);
// Recursively finds an element that matches `matcher` starting with (but
// not including) `from`. If `recursive` is true, searches all descendants,
// otherwise searches children.
static views::View* FindMatchingView(const views::View* from,
ViewMatcher& matcher,
bool recursive);
// Converts a *PositionSpecifier to an appropriate *PositionCallback.
static RelativePositionCallback GetPositionCallback(
AbsolutePositionSpecifier spec);
static RelativePositionCallback GetPositionCallback(
RelativePositionSpecifier spec);
internal::InteractiveViewsTestPrivate& test_impl() {
return static_cast<internal::InteractiveViewsTestPrivate&>(
InteractiveTestApi::private_test_impl());
}
// Creates the follow-up step for a mouse action.
StepBuilder CreateMouseFollowUpStep(const base::StringPiece& description);
base::WeakPtr<Widget> context_widget_;
};
// Template that adds InteractiveViewsTestApi to any test fixture. Prefer to use
// InteractiveViewsTest unless you specifically need to inherit from another
// test class.
//
// You must call SetContextWidget() before using RunTestSequence() or any of the
// mouse actions.
//
// See //chrome/test/interaction/README.md for usage.
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();
}
};
// Convenience test fixture for Views tests that supports
// InteractiveViewsTestApi.
//
// You must call SetContextWidget() before using RunTestSequence() or any of the
// mouse actions.
//
// See //chrome/test/interaction/README.md for usage.
using InteractiveViewsTest = InteractiveViewsTestT<ViewsTestBase>;
// Template definitions:
// static
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;
}
// static
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)...);
}
// static
template <typename C, typename V, typename R>
requires ui::test::internal::HasSignature<C, R*(V*)>
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, /* assign_temporary_id =*/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;
}
// static
template <typename F, typename V>
requires ui::test::internal::HasSignature<F, void(V*)>
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;
}
// static
template <typename C, typename T, typename U, typename V>
requires ui::test::internal::HasSignature<
C,
bool(const V*)> // NOLINT(readability/casting)
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()"));
}
// static
template <typename F,
typename M,
typename T,
typename U,
typename R,
typename V>
requires ui::test::internal::HasSignature<F, R(const V*)>
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()"));
}
// static
template <typename R, typename M, typename V, typename T, typename U>
requires internal::IsView<V>
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()"));
}
// static
template <typename V>
requires internal::IsView<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(), name.data(), index)));
}
// static
template <typename V>
requires internal::IsView<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(), name.data(), index)));
}
// static
template <typename F, typename V>
// NOLINTNEXTLINE(readability/casting)
requires ui::test::internal::HasSignature<F, bool(V*)>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::CheckView(
ElementSpecifier view,
F&& check) {
return CheckView(view, std::forward<F>(check), true);
}
// static
template <typename F, typename M, typename R, typename V>
requires ui::test::internal::HasSignature<F, R(V*)>
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;
}
// static
template <typename V, typename R, typename M>
requires internal::IsView<V>
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;
}
// static
template <typename V, typename R, typename M>
requires internal::IsView<V>
ui::test::InteractiveTestApi::MultiStep
InteractiveViewsTestApi::WaitForViewPropertyCallback(
ElementSpecifier view,
R (V::*property)() const,
base::CallbackListSubscription (V::*add_listener)(
ui::metadata::PropertyChangedCallback),
M&& matcher,
ui::CustomElementEventType event_type) {
// Need to make this ref-counted to ensure it lives long enough to actually
// listen for the state change.
using RefCountedSubscription =
scoped_refptr<base::RefCountedData<base::CallbackListSubscription>>;
RefCountedSubscription subscription =
base::MakeRefCounted<RefCountedSubscription::element_type>();
const std::string format_string = base::StringPrintf(
"WaitForProperty( %%s, \"%s\" )", event_type.GetName().c_str());
// The first step will check the property, and either immediately send the
// event or install the observer that will send the event when the state
// achieves the correct value.
auto observe_property = base::BindOnce(
[](RefCountedSubscription subscription, R (V::*property)() const,
base::CallbackListSubscription (V::*add_listener)(
ui::metadata::PropertyChangedCallback),
ui::CustomElementEventType event_type, testing::Matcher<R> matcher,
ui::TrackedElement* el) {
auto* const view = AsView<V>(el);
if (matcher.Matches((view->*property)())) {
// Property is already in the desired state, send event immediately.
ui::ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(
el, event_type);
} else {
// Watch the property for a value that satisfies the matcher.
subscription->data = (view->*add_listener)(base::BindRepeating(
[](V* view, R (V::*property)() const,
ui::CustomElementEventType event_type,
testing::Matcher<R> matcher) {
if (matcher.Matches((view->*property)())) {
ElementTrackerViews::GetInstance()->NotifyCustomEvent(
event_type, view);
}
},
view, property, event_type, std::move(matcher)));
}
},
subscription, property, add_listener, event_type,
testing::Matcher<R>(std::forward<M>(matcher)));
return Steps(std::move(AfterShow(view, std::move(observe_property))
.SetMustRemainVisible(true)
.FormatDescription(format_string)),
std::move(AfterEvent(view, event_type, [subscription]() {
// Need to reference subscription by value so that it
// is not discarded until this step runs or the
// sequence fails.
subscription->data =
base::CallbackListSubscription();
}).FormatDescription(format_string)));
}
// Waits for a property named `Property` to have a value that matches `matcher`
// on View `view` of View class `Class`. Convenience method for
// `InteractiveViewsTestApi::WaitForClassPropertyCallback`.
//
// Do not use with the "Visible" property; use `WaitForShow()` or
// `WaitForHide()` instead.
#define WaitForViewProperty(view, Class, Property, matcher) \
[]() { \
DEFINE_MACRO_CUSTOM_ELEMENT_EVENT_TYPE(__FILE__, __LINE__, \
kWaitFor##Property##Event); \
return WaitForViewPropertyCallback((view), &Class::Get##Property, \
&Class::Add##Property##ChangedCallback, \
(matcher), kWaitFor##Property##Event); \
}()
template <typename T, typename V, typename C>
requires internal::IsView<V> &&
ui::test::internal::HasSignature<C, T(const V*)>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::PollView(
ui::test::StateIdentifier<PollingViewObserver<T, V>> id,
ui::ElementIdentifier view_id,
C&& callback,
base::TimeDelta polling_interval) {
using Cb = PollingViewObserver<T, V>::PollViewCallback;
Cb cb = ui::test::internal::MaybeBindRepeating(std::forward<C>(callback));
auto step =
WithElement(ui::test::internal::kInteractiveTestPivotElementId,
base::BindOnce(
[](InteractiveViewsTestApi* api, ui::ElementIdentifier id,
ui::ElementIdentifier view_id, Cb callback,
base::TimeDelta polling_interval,
ui::InteractionSequence* seq, ui::TrackedElement* el) {
api->test_impl().AddStateObserver(
id, el->context(),
std::make_unique<PollingViewObserver<T, V>>(
view_id,
seq->IsCurrentStepInAnyContextForTesting()
? std::nullopt
: std::make_optional(el->context()),
std::move(callback), polling_interval));
},
base::Unretained(this), id.identifier(), view_id, cb,
polling_interval));
step.SetDescription(
base::StringPrintf("PollView(%s)", view_id.GetName().c_str()));
return step;
}
template <typename R, typename V, typename T>
requires internal::IsView<V>
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::PollViewProperty(
ui::test::StateIdentifier<PollingViewPropertyObserver<T, V>> id,
ui::ElementIdentifier view_id,
R (V::*property)() const,
base::TimeDelta polling_interval) {
auto step = WithElement(
ui::test::internal::kInteractiveTestPivotElementId,
base::BindOnce(
[](InteractiveViewsTestApi* api, ui::ElementIdentifier id,
ui::ElementIdentifier view_id, R (V::*property)() const,
base::TimeDelta polling_interval, ui::InteractionSequence* seq,
ui::TrackedElement* el) {
api->test_impl().AddStateObserver(
id, el->context(),
std::make_unique<PollingViewPropertyObserver<T, V>>(
view_id,
seq->IsCurrentStepInAnyContextForTesting()
? std::nullopt
: std::make_optional(el->context()),
property, polling_interval));
},
base::Unretained(this), id.identifier(), view_id, property,
polling_interval));
step.SetDescription(
base::StringPrintf("PollViewProperty(%s)", view_id.GetName().c_str()));
return step;
}
} // namespace views::test
#endif // UI_VIEWS_INTERACTION_INTERACTIVE_VIEWS_TEST_H_