blob: acd1bfd2b6016678513fbb6f838e7576cb1d1ff1 [file]
// 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.
#include "ui/views/interaction/interactive_views_test.h"
#include <functional>
#include <optional>
#include <string_view>
#include <variant>
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/interaction/interaction_test_util.h"
#include "ui/base/test/ui_controls.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/interaction/interaction_test_util_views.h"
#include "ui/views/interaction/interactive_views_test_internal.h"
#include "ui/views/view_tracker.h"
namespace views::test {
using ui::test::internal::kInteractiveTestPivotElementId;
InteractiveViewsTestApi::InteractiveViewsTestApi()
: test_impl_(private_test_impl()
.MaybeRegisterFrameworkImpl<
internal::InteractiveViewsTestPrivate>()) {}
InteractiveViewsTestApi::~InteractiveViewsTestApi() = default;
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::NameView(
std::string_view name,
AbsoluteViewSpecifier spec) {
return NameViewRelative(kInteractiveTestPivotElementId, name,
GetFindViewCallback(std::move(spec)));
}
// static
ui::InteractionSequence::StepBuilder InteractiveViewsTestApi::NameChildView(
ElementSpecifier parent,
std::string_view name,
ChildViewSpecifier spec) {
return std::move(
NameViewRelative(parent, name, GetFindViewCallback(std::move(spec)))
.SetDescription(
base::StringPrintf("NameChildView( \"%s\" )", name.data())));
}
// static
ui::InteractionSequence::StepBuilder
InteractiveViewsTestApi::NameDescendantView(ElementSpecifier parent,
std::string_view name,
ViewMatcher matcher) {
return std::move(
NameViewRelative(
parent, name,
base::BindOnce(
[](ViewMatcher matcher, View* ancestor) -> View* {
auto* const result =
FindMatchingView(ancestor, matcher, /* recursive =*/true);
if (!result) {
LOG(ERROR)
<< "NameDescendantView(): No descendant matches matcher.";
}
return result;
},
matcher))
.SetDescription(
base::StringPrintf("NameDescendantView( \"%s\" )", name.data())));
}
InteractiveViewsTestApi::StepBuilder InteractiveViewsTestApi::ScrollIntoView(
ElementSpecifier view) {
return std::move(WithView(view, [](View* v) {
v->ScrollViewToVisible();
}).SetDescription("ScrollIntoView()"));
}
// static
InteractiveViewsTestApi::FindViewCallback
InteractiveViewsTestApi::GetFindViewCallback(AbsoluteViewSpecifier spec) {
return std::visit(
absl::Overload{
[](View* view) {
CHECK(view) << "NameView(View*): view must be set.";
return base::BindOnce(
[](const std::unique_ptr<ViewTracker>& ref, View*) {
LOG_IF(ERROR, !ref->view())
<< "NameView(View*): view ceased to be "
"valid before step was executed.";
return ref->view();
},
std::make_unique<ViewTracker>(view));
},
[](std::reference_wrapper<View*> ref) {
return base::BindOnce(
[](std::reference_wrapper<View*> ref, View*) {
LOG_IF(ERROR, !ref.get())
<< "NameView(View*): view ceased to be "
"valid before step was executed.";
return ref.get();
},
ref);
},
[](base::OnceCallback<View*()>& callback) {
return base::RectifyCallback<FindViewCallback>(std::move(callback));
}},
spec);
}
// static
InteractiveViewsTestApi::FindViewCallback
InteractiveViewsTestApi::GetFindViewCallback(ChildViewSpecifier spec) {
return std::visit(
absl::Overload{
[](size_t index) {
return base::BindOnce(
[](size_t index, View* parent) -> View* {
if (index >= parent->children().size()) {
LOG(ERROR)
<< "NameChildView(int): Child index out of bounds; got "
<< index << " but only " << parent->children().size()
<< " children.";
return nullptr;
}
return parent->children()[index];
},
index);
},
[](ViewMatcher& matcher) {
return base::BindOnce(
[](ViewMatcher matcher, View* parent) -> View* {
auto* const result =
FindMatchingView(parent, matcher, /*recursive =*/false);
LOG_IF(ERROR, !result) << "NameChildView(ViewMatcher): No "
"child matches matcher.";
return result;
},
std::move(matcher));
}},
spec);
}
// static
View* InteractiveViewsTestApi::FindMatchingView(const View* from,
ViewMatcher& matcher,
bool recursive) {
for (views::View* const child : from->children()) {
if (matcher.Run(child)) {
return child;
}
if (recursive) {
auto* const result = FindMatchingView(child, matcher, true);
if (result) {
return result;
}
}
}
return nullptr;
}
void InteractiveViewsTestApi::SetContextWidget(Widget* widget) {
CHECK(!widget || !private_test_impl().default_context())
<< "Changing the context widget during a test is not supported.";
if (widget) {
context_widget_ = widget->GetWeakPtr();
private_test_impl().SetDefaultContext(
ElementTrackerViews::GetContextForWidget(widget),
widget->GetNativeWindow());
} else {
context_widget_.reset();
private_test_impl().SetDefaultContext(ui::ElementContext(),
gfx::NativeWindow());
}
}
} // namespace views::test