blob: d4fa4cfb3c5beb247aaf29ea7a281d365607a918 [file] [log] [blame]
// Copyright 2023 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_STATE_OBSERVER_H_
#define UI_BASE_INTERACTION_STATE_OBSERVER_H_
#include <algorithm>
#include <concepts>
#include <ostream>
#include <utility>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/interactive_test_definitions.h"
#include "ui/base/interaction/typed_identifier.h"
namespace ui::test {
// Base class for all state change observers. Observes some state of the system,
// which we can then wait on.
//
// Used with `InteractiveTestApi::ObserveState()` and `WaitForState()`.
//
// Value type `T` must be default-constructible and copy-assignable.
template <typename T>
requires ::ui::test::internal::IsValidMatcherType<T>
class StateObserver {
public:
using StateChangedCallback = base::RepeatingCallback<void(T)>;
using ValueType = T;
StateObserver() = default;
virtual ~StateObserver() { OnStateObserverStateChanged(T()); }
// Copy and assignment are not allowed.
StateObserver(const StateObserver&) = delete;
void operator=(const StateObserver&) = delete;
// Returns the initial state. Override to provide your own logic; returns
// `T()` by default.
virtual T GetStateObserverInitialState() const { return T(); }
// Used by the owning test to set the state change callback. Do not call
// directly. The caller should ensure the the new callback is issued with the
// current state.
void SetStateObserverStateChangedCallback(StateChangedCallback callback) {
CHECK(!state_changed_callback_);
state_changed_callback_ = std::move(callback);
}
protected:
// Call to update the state.
void OnStateObserverStateChanged(T state) {
if (state_changed_callback_) {
state_changed_callback_.Run(state);
}
}
private:
StateChangedCallback state_changed_callback_;
};
template <typename T>
concept IsStateObserver = requires {
typename T::ValueType;
} && std::derived_from<T, StateObserver<typename T::ValueType>>;
// State observer that uses a `ScopedObservation<T, Source, Observer>` to watch
// for state changes using an observer pattern.
//
// You will still need to override the specific observer methods to detect:
// - The actual state change
// - call `OnStateObserverStateChanged()`
// - The "source destroyed" message (optional)
// - call `OnObservationStateObserverSourceDestroyed()`
//
// If the initial state may vary, you can also override
// `GetStateObserverInitialState()`. Use `source()` to extract the relevant
// information.
//
// An example can be found in chrome/test/interaction/README.md.
template <typename T, typename Source, typename Observer>
class ObservationStateObserver : public StateObserver<T>, public Observer {
public:
explicit ObservationStateObserver(Source* source_object)
: source_(source_object) {
observation_.Observe(source_object);
}
~ObservationStateObserver() override = default;
Source* source() const { return source_; }
protected:
// Call to indicate that the `source` object is going away. If the object will
// never go away during the scope of this object, or there is no callback to
// detect destruction, you can ignore this method.
//
// Resets the state value to `T()`.
void OnObservationStateObserverSourceDestroyed() {
StateObserver<T>::OnStateObserverStateChanged(T());
observation_.Reset();
source_ = nullptr;
}
private:
raw_ptr<Source> source_;
base::ScopedObservation<Source, Observer> observation_{this};
};
template <typename T>
using StateIdentifier = TypedIdentifier<T>;
} // namespace ui::test
// The following macros create a state identifier value for use in tests.
//
// The associated type of observer should be specified along with the unique
// identifier name:
// ```
// DECLARE_STATE_IDENTIFIER_VALUE(MyObserverType, kMyState);
// ```
//
// `DECLARE_STATE_IDENTIFIER_VALUE()` and `DEFINE_STATE_IDENTIFIER_VALUE()` are
// for use in .h and .cc files, respectively, as with declaring
// `ElementIdentifier`s.
//
// To declare a `StateIdentifier` local to a .cc file or method body, use
// DEFINE_LOCAL_STATE_IDENTIFIER_VALUE() instead. This will create a file- and
// line-mangled name that will not suffer name collisions with other
// identifiers.
#define DECLARE_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DECLARE_TYPED_IDENTIFIER_VALUE(ObserverType, Name)
#define DEFINE_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DEFINE_TYPED_IDENTIFIER_VALUE(ObserverType, Name)
#define DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ObserverType, Name) \
DEFINE_MACRO_TYPED_IDENTIFIER_VALUE(__FILE__, __LINE__, ObserverType, Name)
#endif // UI_BASE_INTERACTION_STATE_OBSERVER_H_