| // Copyright 2025 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_BASE_INTERACTION_INTERACTIVE_TEST_DEFINITIONS_H_ |
| #define UI_BASE_INTERACTION_INTERACTIVE_TEST_DEFINITIONS_H_ |
| |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/test/bind.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "ui/base/interaction/element_tracker.h" |
| #include "ui/base/interaction/interaction_sequence.h" |
| |
| namespace ui::test::internal { |
| |
| // Specifies a sequence of steps. |
| using MultiStep = std::vector<InteractionSequence::StepBuilder>; |
| |
| // Similar to `std::invocable<T, Args...>`, but does not put constraints on the |
| // parameters passed to the invocation method. |
| template <typename T> |
| concept IsCallable = requires { &std::decay_t<T>::operator(); }; |
| |
| // Applies if `T` has bound state (such as a lambda expression with captures). |
| template <typename T> |
| concept HasState = !std::is_empty_v<std::remove_reference_t<T>>; |
| |
| // Helper for matching a function pointer. |
| template <typename T> |
| inline constexpr bool IsFunctionPointerValue = false; |
| |
| template <typename R, typename... Args> |
| inline constexpr bool IsFunctionPointerValue<R (*)(Args...)> = true; |
| |
| // Applies if `T` is a function pointer (but not a pointer to an instance |
| // member function). |
| template <typename T> |
| concept IsFunctionPointer = IsFunctionPointerValue<T>; |
| |
| // Optionally converts `function` to something that is compatible with a |
| // base::OnceCallback. |
| template <typename F> |
| auto MaybeBind(F&& function) { |
| if constexpr (base::IsBaseCallback<F>) { |
| // Callbacks are already callbacks, so can be returned as-is. |
| return std::forward<F>(function); |
| } else if constexpr (IsCallable<F> && HasState<F>) { |
| // Callable objects with state can only be bound with |
| // `base::BindLambdaForTesting`. |
| return base::BindLambdaForTesting(std::forward<F>(function)); |
| } else if constexpr ((IsCallable<F> && !HasState<F>) || |
| IsFunctionPointer<F>) { |
| // Function pointers and empty callable objects can be bound using |
| // `base::BindOnce`. |
| return base::BindOnce(std::forward<F>(function)); |
| } else if constexpr (std::same_as<F, decltype(base::DoNothing())>) { |
| // base::DoNothing() is compatible with callbacks, so return it as-is. |
| return function; |
| } else { |
| static_assert(false, "Can only bind callable objects."); |
| } |
| } |
| |
| // Helper struct that captures information about what signature a function-like |
| // object would have if it were bound. |
| 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; |
| }; |
| |
| // DoNothing always has a void return type but no defined signature. |
| template <> |
| struct MaybeBindTypeHelper<decltype(base::DoNothing())> { |
| using ReturnType = void; |
| }; |
| |
| // Optionally converts `function` to something that is compatible with a |
| // base::RepeatingCallback, or returns it as-is if it's already a callback. |
| template <typename F> |
| base::RepeatingCallback<typename MaybeBindTypeHelper<F>::Signature> |
| MaybeBindRepeating(F&& function) { |
| if constexpr (IsCallable<F> && !HasState<F> && |
| std::copy_constructible<std::decay_t<F>>) { |
| return base::BindRepeating(std::forward<F>(function)); |
| } else { |
| return MaybeBind(std::forward<F>(function)); |
| } |
| } |
| |
| template <typename T> |
| struct ArgsExtractor; |
| |
| template <typename R, typename... Args> |
| struct ArgsExtractor<R(Args...)> { |
| using Holder = std::tuple<Args...>; |
| }; |
| |
| template <typename F> |
| using ReturnTypeOf = MaybeBindTypeHelper<F>::ReturnType; |
| |
| template <size_t N, typename F> |
| using NthArgumentOf = std::tuple_element_t< |
| N, |
| typename ArgsExtractor<typename MaybeBindTypeHelper<F>::Signature>::Holder>; |
| |
| // Requires that `F` resolves to some kind of callable object with call |
| // signature `S`. |
| template <typename F, typename S> |
| concept HasSignature = |
| std::same_as<typename MaybeBindTypeHelper<F>::Signature, S> || |
| std::same_as<F, decltype(base::DoNothing())>; |
| |
| // Helper for `HasCompatibleSignature`; see recursive implementation below. |
| template <typename F, typename S> |
| inline constexpr bool HasCompatibleSignatureValue = false; |
| |
| // Requires that `F` resolves to some kind of callable object whose signature |
| // can be rectified to `S`; see `base::RectifyCallback` for more information. |
| // (Basically, `F` can omit arguments from the left of `S`; these arguments |
| // will be ignored.) |
| template <typename F, typename S> |
| concept HasCompatibleSignature = HasCompatibleSignatureValue<F, S>; |
| |
| // This is the leaf state for the recursive compatibility computation; see |
| // below. |
| template <typename F, typename R> |
| requires HasSignature<F, R()> |
| inline constexpr bool HasCompatibleSignatureValue<F, R()> = true; |
| |
| // Implementation for `HasCompatibleSignature`. |
| // |
| // This removes arguments one by one from the left of the target signature `S` |
| // to see if `F` has that signature. The recursion stops when one matches, or |
| // when the arg list is empty (in which case the leaf state is hit, above). |
| template <typename F, typename R, typename A, typename... Args> |
| requires HasSignature<F, R(A, Args...)> || |
| HasCompatibleSignature<F, R(Args...)> |
| inline constexpr bool HasCompatibleSignatureValue<F, R(A, Args...)> = true; |
| |
| // Checks that `T` is a reference wrapper around any type. |
| template <typename T> |
| concept IsReferenceWrapper = base::is_instantiation<T, std::reference_wrapper>; |
| |
| // Helper to determine the type used to match a value. The default is to just |
| // use the decayed value type. |
| template <typename T> |
| struct MatcherTypeHelper { |
| using ActualType = T; |
| }; |
| |
| // Specialization for string types used in Chrome. For any representation of a |
| // string using character type, the type used for matching is the corresponding |
| // `std::basic_string`. |
| // |
| // Add to this template if different character formats become supported (e.g. |
| // char8_t, char32_t, wchar_t, etc.) |
| template <typename C> |
| requires(std::same_as<std::remove_const_t<C>, char> || |
| std::same_as<std::remove_const_t<C>, char16_t>) |
| struct MatcherTypeHelper<C*> { |
| using ActualType = std::basic_string<std::remove_const_t<C>>; |
| }; |
| |
| // Map string_view-like types to strings to avoid storing dangling references in |
| // matchers. |
| template <typename CharT, typename Traits> |
| struct MatcherTypeHelper<std::basic_string_view<CharT, Traits>> { |
| using ActualType = std::basic_string<CharT, Traits>; |
| }; |
| |
| // Gets the appropriate matchable type for `T`. This affects string-like types |
| // (e.g. `const char*`) as the corresponding `Matcher` should match a |
| // `std::string` or `std::u16string`. |
| template <typename T> |
| using MatcherTypeFor = MatcherTypeHelper<std::decay_t<T>>::ActualType; |
| |
| // Determines if `T` is a valid type to be used in a matcher. This precludes |
| // string-like types (const char*, constexpr char16_t[], etc.) in favor of |
| // `std::string` and `std::u16string`. |
| template <typename T> |
| concept IsValidMatcherType = std::same_as<T, MatcherTypeFor<T>>; |
| |
| template <typename T> |
| concept IsGtestMatcher = requires { typename T::is_gtest_matcher; }; |
| |
| template <typename T> |
| concept HasMatchAndExplain = requires { &T::MatchAndExplain; }; |
| |
| template <typename T> |
| concept IsMatcher = IsGtestMatcher<T> || HasMatchAndExplain<T> || |
| base::is_instantiation<T, testing::PolymorphicMatcher>; |
| |
| // Accepts any function-like object that is compatible with |
| // `InteractionSequence::StepCallback`. |
| template <typename F> |
| concept IsStepCallback = internal:: |
| HasCompatibleSignature<F, void(InteractionSequence*, TrackedElement*)>; |
| |
| // Accepts any function-like object that can be used with `Check()` and |
| // `CheckResult()`. |
| template <typename F, typename R> |
| concept IsCheckCallback = |
| internal::HasCompatibleSignature<F, |
| R(const InteractionSequence*, |
| const TrackedElement*)>; |
| |
| std::string DescribeElement(ElementSpecifier spec); |
| |
| InteractionSequence::Builder BuildSubsequence(MultiStep steps); |
| |
| // Takes an argument expected to be a literal value and retrieves the literal |
| // value by either calling the object (if it's callable), unwrapping it (if it's |
| // a `std::reference_wrapper`) or just returning it otherwise. |
| // |
| // This allows e.g. passing deferred or computed values to the `Log()` verb. |
| template <typename Arg> |
| auto UnwrapArgument(Arg arg) { |
| if constexpr (base::IsBaseCallback<Arg>) { |
| return std::move(arg).Run(); |
| } else if constexpr (internal::IsFunctionPointer<Arg>) { |
| return (*arg)(); |
| } else if constexpr (internal::IsCallable<Arg>) { |
| return arg(); |
| } else { |
| return arg; |
| } |
| } |
| |
| // Converts value of type U to testing::Matcher<T>. |
| template <typename T, typename U> |
| testing::Matcher<T> CreateMatcherFromValue(U value) { |
| if constexpr (internal::IsReferenceWrapper<U>) { |
| return testing::Matcher<T>(T(value.get())); |
| } else if constexpr (std::derived_from<U, testing::Matcher<T>> || |
| std::convertible_to<U, testing::Matcher<T>>) { |
| // Note that a Matcher<T> is actually a wrapper around a "matcher" |
| // object, not a matcher itself. |
| return value; |
| } else if constexpr (internal::IsMatcher<U>) { |
| // Need to wrap the "matcher" in a Matcher<T> for it to be used. |
| return testing::Matcher<T>(value); |
| } else { |
| return testing::Matcher<T>( |
| T(internal::UnwrapArgument<U>(std::move(value)))); |
| } |
| } |
| |
| // Serves as a strongly-typed wrapper around a MultiStep that carries additional |
| // semantic or syntactic information; for example the "Then" and "Else" |
| // sequences of an `If()`. |
| // |
| // This object is move-constructible and move-assignable, but only to blocks of |
| // the same type `T`. This prevents a user from writing `Else()` where a |
| // `ThenBlock` is expected. |
| // |
| // Do not use directly. To create a block type for use in InteractiveTest |
| // primitives, see `DeclareStepBlockFactory()`, which also creates an |
| // appropriately-named factory method. |
| template <typename T> |
| class StepBlock { |
| public: |
| StepBlock(); |
| explicit StepBlock(MultiStep steps) : steps_(std::move(steps)) {} |
| StepBlock(StepBlock<T>&& other) noexcept = default; |
| StepBlock& operator=(StepBlock<T>&& other) noexcept = default; |
| ~StepBlock() = default; |
| |
| MultiStep& steps() { return steps_; } |
| const MultiStep& steps() const { return steps_; } |
| |
| private: |
| MultiStep steps_; |
| }; |
| |
| // Explicitly exclude lvalue references when calling certain methods. These |
| // methods would not compile anyway, but this concept is provided as a courtesy |
| // to generate better compile errors. |
| template <typename T> |
| concept IsValueOrRvalue = !std::is_lvalue_reference_v<T>; |
| |
| } // namespace ui::test::internal |
| |
| #endif // UI_BASE_INTERACTION_INTERACTIVE_TEST_DEFINITIONS_H_ |