#ifndef UI_BASE_INTERACTION_INTERACTIVE_TEST_INTERNAL_H_
#define UI_BASE_INTERACTION_INTERACTIVE_TEST_INTERNAL_H_
#include <memory>
#include <tuple>
#include <type_traits>
#include "base/callback_list.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/rectify_callback.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/interaction_sequence.h"
#include "ui/base/interaction/interaction_test_util.h"
namespace ui::test {
class InteractiveTestApi;
namespace internal {
DECLARE_ELEMENT_IDENTIFIER_VALUE(kInteractiveTestPivotElementId);
DECLARE_CUSTOM_ELEMENT_EVENT_TYPE(kInteractiveTestPivotEventType);
class InteractiveTestPrivate {
public:
using MultiStep = std::vector<InteractionSequence::StepBuilder>;
enum class OnIncompatibleAction {
kFailTest,
kSkipTest,
kHaltTest,
kIgnoreAndContinue,
};
explicit InteractiveTestPrivate(
std::unique_ptr<InteractionTestUtil> test_util);
virtual ~InteractiveTestPrivate();
InteractiveTestPrivate(const InteractiveTestPrivate&) = delete;
void operator=(const InteractiveTestPrivate&) = delete;
InteractionTestUtil& test_util() { return *test_util_; }
OnIncompatibleAction on_incompatible_action() const {
return on_incompatible_action_;
}
bool sequence_skipped() const { return sequence_skipped_; }
void HandleActionResult(InteractionSequence* seq,
const TrackedElement* el,
const std::string& operation_name,
ActionResult result);
TrackedElement* GetPivotElement(ElementContext context) const;
virtual void DoTestSetUp();
virtual void DoTestTearDown();
virtual void OnSequenceComplete();
virtual void OnSequenceAborted(const InteractionSequence::AbortedData& data);
void set_aborted_callback_for_testing(
InteractionSequence::AbortedCallback aborted_callback_for_testing) {
aborted_callback_for_testing_ = std::move(aborted_callback_for_testing);
}
template <typename T>
static MultiStep PostTask(const base::StringPiece& description, T&& task);
private:
friend class ui::test::InteractiveTestApi;
void Init(ElementContext initial_context);
void Cleanup();
void OnElementAdded(TrackedElement* el);
void MaybeAddPivotElement(ElementContext context);
bool success_ = false;
OnIncompatibleAction on_incompatible_action_ =
OnIncompatibleAction::kFailTest;
std::string on_incompatible_action_reason_;
bool sequence_skipped_ = false;
std::unique_ptr<InteractionTestUtil> test_util_;
base::CallbackListSubscription context_subscription_;
std::map<ElementContext, std::unique_ptr<TrackedElement>> pivot_elements_;
InteractionSequence::AbortedCallback aborted_callback_for_testing_;
};
using ElementSpecifier = absl::variant<ElementIdentifier, base::StringPiece>;
template <typename T>
bool MatchAndExplain(const base::StringPiece& test_name,
testing::Matcher<T>& matcher,
T&& value) {
if (matcher.Matches(value))
return true;
std::ostringstream oss;
oss << test_name << " failed.\nExpected: ";
matcher.DescribeTo(&oss);
oss << "\nActual: " << testing::PrintToString(value);
LOG(ERROR) << oss.str();
return false;
}
template <typename T>
InteractiveTestPrivate::MultiStep InteractiveTestPrivate::PostTask(
const base::StringPiece& description,
T&& task) {
MultiStep result;
result.emplace_back(std::move(
InteractionSequence::StepBuilder()
.SetDescription(base::StrCat({description, ": PostTask()"}))
.SetElementID(kInteractiveTestPivotElementId)
.SetStartCallback(base::BindOnce([](ui::TrackedElement* el) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
[](ElementIdentifier id, ElementContext context) {
auto* const el =
ui::ElementTracker::GetElementTracker()
->GetFirstMatchingElement(id, context);
if (el) {
ui::ElementTracker::GetFrameworkDelegate()
->NotifyCustomEvent(el,
kInteractiveTestPivotEventType);
}
},
el->identifier(), el->context()));
}))));
result.emplace_back(std::move(
InteractionSequence::StepBuilder()
.SetDescription(base::StrCat({description, ": WaitForComplete()"}))
.SetElementID(kInteractiveTestPivotElementId)
.SetContext(InteractionSequence::ContextMode::kFromPreviousStep)
.SetType(InteractionSequence::StepType::kCustomEvent,
kInteractiveTestPivotEventType)
.SetStartCallback(
base::RectifyCallback<InteractionSequence::StepStartCallback>(
std::move(task)))));
return result;
}
template <typename T>
constexpr bool IsCallbackValue = base::IsBaseCallback<T>::value;
template <typename T, typename SFINAE = void>
struct IsCallable {
static constexpr bool value = false;
};
template <typename T>
struct IsCallable<T, std::void_t<decltype(&T::operator())>> {
static constexpr bool value = true;
};
template <typename T>
constexpr bool IsCallableValue = IsCallable<std::remove_reference_t<T>>::value;
template <typename T, typename SFINAE = void>
struct IsFunctionPointer {
static constexpr bool value = false;
};
template <typename R, typename... Args>
struct IsFunctionPointer<R (*)(Args...), void> {
static constexpr bool value = true;
};
template <typename T>
constexpr bool IsFunctionPointerValue = IsFunctionPointer<T>::value;
template <typename F, typename SFINAE = void>
struct MaybeBindHelper;
template <typename F>
struct MaybeBindHelper<F, std::enable_if_t<IsCallbackValue<F>>> {
template <class G>
static auto MaybeBind(G&& function) {
return std::forward<G>(function);
}
};
template <typename F>
struct MaybeBindHelper<
F,
std::enable_if_t<IsCallableValue<F> && !std::is_empty_v<F>>> {
template <class G>
static auto MaybeBind(G&& function) {
return base::BindLambdaForTesting(std::forward<G>(function));
}
};
template <typename F>
struct MaybeBindHelper<
F,
std::enable_if_t<(IsCallableValue<F> && std::is_empty_v<F>) ||
IsFunctionPointerValue<F>>> {
template <class G>
static auto MaybeBind(G&& function) {
return base::BindOnce(std::forward<G>(function));
}
};
template <>
struct MaybeBindHelper<decltype(base::DoNothing()), void> {
static auto MaybeBind(decltype(base::DoNothing()) function) {
return function;
}
};
template <typename F>
auto MaybeBind(F&& function) {
return MaybeBindHelper<F>::MaybeBind(std::forward<F>(function));
}
template <typename F>
struct MaybeBindTypeHelper {
using CallbackType = std::invoke_result_t<decltype(&MaybeBind<F>), F>;
using ReturnType = typename CallbackType::ResultType;
using Signature = typename CallbackType::RunType;
};
template <>
struct MaybeBindTypeHelper<decltype(base::DoNothing())> {
using ReturnType = void;
};
template <typename T>
struct ArgsExtractor;
template <typename R, typename... Args>
struct ArgsExtractor<R(Args...)> {
using holder = std::tuple<Args...>;
};
template <typename F>
using ReturnTypeOf = typename MaybeBindTypeHelper<F>::ReturnType;
template <size_t N, typename F>
using NthArgumentOf = std::tuple_element_t<
N,
typename ArgsExtractor<typename MaybeBindTypeHelper<F>::Signature>::holder>;
template <typename F, typename S>
struct HasSignatureHelper {
static constexpr bool value =
std::is_same_v<typename MaybeBindTypeHelper<F>::Signature, S>;
};
template <typename... Args>
struct HasSignatureHelper<decltype(base::DoNothing()), void(Args...)> {
static constexpr bool value = true;
};
template <typename F, typename S>
constexpr bool HasSignature = HasSignatureHelper<F, S>::value;
template <typename F, typename S>
using RequireSignature = std::enable_if_t<HasSignature<F, S>>;
template <typename F, typename S>
struct HasCompatibleSignatureHelper;
template <typename F, typename R>
struct HasCompatibleSignatureHelper<F, R()> {
static constexpr bool value = HasSignature<F, R()>;
};
template <typename F, typename R, typename A, typename... Args>
struct HasCompatibleSignatureHelper<F, R(A, Args...)> {
static constexpr bool value =
HasSignature<F, R(A, Args...)> ||
HasCompatibleSignatureHelper<F, R(Args...)>::value;
};
template <typename F, typename S>
constexpr bool HasCompatibleSignature =
HasCompatibleSignatureHelper<F, S>::value;
template <typename F, typename S>
using RequireCompatibleSignature =
std::enable_if_t<HasCompatibleSignature<F, S>>;
void SpecifyElement(ui::InteractionSequence::StepBuilder& builder,
ElementSpecifier element);
std::string DescribeElement(ElementSpecifier spec);
InteractionSequence::Builder BuildSubsequence(
InteractiveTestPrivate::MultiStep steps);
}
}
#endif