Source code for traitsui.testing.tester.target_registry

# (C) Copyright 2004-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

""" Define the registry object responsible for collecting and reporting
implementations for testing various GUI elements.
"""

import inspect


from traitsui.testing.tester._abstract_target_registry import (
    AbstractTargetRegistry,
)
from traitsui.testing.tester.exceptions import (
    InteractionNotSupported,
    LocationNotSupported,
)


class _TargetToKeyRegistry:
    """Perform the mapping from target to a key to a callable.

    Internally this is a dict(type, dict(type, callable)), but expose a few
    methods for better error reporting.
    """

    def __init__(self, exception_maker):
        """Initializer

        Parameters
        ----------
        exception_maker : callable(target_class, key, available_keys)
            A callable that return an exception for when no values are referred
            to by a given pair of target_class and key.
        """
        self._target_to_key_to_value = {}
        self.exception_maker = exception_maker

    def register(self, target_class, key, value):
        action_to_handler = self._target_to_key_to_value.setdefault(
            target_class, {}
        )
        if key in action_to_handler:
            raise ValueError(
                "A value for target {!r} and key {!r} already "
                "exists.".format(target_class, key)
            )
        action_to_handler[key] = value

    def get_value(self, target_class, key):
        action_to_handler = self._target_to_key_to_value.get(target_class, [])
        if key not in action_to_handler:
            raise self.exception_maker(
                target_class=target_class,
                key=key,
                available_keys=list(action_to_handler),
            )
        return action_to_handler[key]

    def get_keys(self, target_class):
        """Return all the keys for the given target.

        Parameters
        ----------
        target_class : subclass of type
            The type of a UI target being operated on.

        Returns
        -------
        keys : set
        """
        return set(self._target_to_key_to_value.get(target_class, []))


[docs]class TargetRegistry(AbstractTargetRegistry): """An object for registering interaction and location resolution logic for different UI target types. ``register_interaction`` supports extending ``UIWrapper.perform`` and ``UIWrapper.inspect`` for a given UI target type and interaction type. ``register_location`` supports extending ``UIWrapper.locate`` for a given UI target type and location type. See :ref:`testing-how-extension-works` in the User Manual for further details. """ def __init__(self): self._interaction_registry = _TargetToKeyRegistry( exception_maker=( lambda target_class, key, available_keys: ( InteractionNotSupported( target_class=target_class, interaction_class=key, supported=available_keys, ) ) ), ) self._location_registry = _TargetToKeyRegistry( exception_maker=( lambda target_class, key, available_keys: LocationNotSupported( target_class=target_class, locator_class=key, supported=available_keys, ) ), )
[docs] def register_interaction(self, target_class, interaction_class, handler): """Register a handler for a given target type and interaction type. Parameters ---------- target_class : subclass of type The type of a UI target being operated on. interaction_class : subclass of type Any class. handler : callable(UIWrapper, interaction) -> any The function to handle the particular interaction on a target. ``interaction`` should be an instance of ``interaction_class``. Raises ------ ValueError If a handler has already be registered for the same target type and interaction class. """ self._interaction_registry.register( target_class=target_class, key=interaction_class, value=handler, )
def _get_handler(self, target, interaction): """Return a callable for handling an interaction for a given target. Parameters ---------- target : any The UI target being operated on. interaction : any Any interaction object. Returns ------- handler : callable(UIWrapper, interaction) -> any The function to handle the given interaction on a target. Raises ------ InteractionNotSupported If the given target and interaction types are not supported by this registry. """ return self._interaction_registry.get_value( target_class=target.__class__, key=interaction.__class__, ) def _get_interactions(self, target): """Returns all the interactions supported for the given target. Parameters ---------- target : any The UI target for which supported interactions are queried. Returns ------- interaction_classes : set Supported interaction types for the given target type. """ return self._interaction_registry.get_keys( target_class=target.__class__ ) def _get_interaction_doc(self, target, interaction_class): """Return the documentation for the given target and interaction type. Parameters ---------- target : any The UI target for which the interaction will be applied. interaction_class : subclass of type Any class. Returns ------- doc : str Raises ------ InteractionNotSupported If the given target and interaction types are not supported by this registry. """ self._interaction_registry.get_value( target_class=target.__class__, key=interaction_class, ) # This maybe configurable in the future via register_interaction return inspect.getdoc(interaction_class)
[docs] def register_location(self, target_class, locator_class, solver): """Register a solver for resolving the next UI target for the given target type and locator type. Parameters ---------- target_class : subclass of type The type of a UI target being operated on. locator_class : subclass of type Any class. solver : callable(UIWrapper, location) -> any A callable for resolving a location into a new target. The location argument will be an instance of locator_class. Raises ------ ValueError If a solver has already been registered for the given target type and locator type. """ self._location_registry.register( target_class=target_class, key=locator_class, value=solver, )
def _get_solver(self, target, location): """Return a callable registered for resolving a location for the given target and location. Parameters ---------- target : any The UI target being operated on. location : subclass of type The location to be resolved on the target. Raises ------ LocationNotSupported If the given locator and target types are not supported. """ return self._location_registry.get_value( target_class=target.__class__, key=location.__class__, ) def _get_locations(self, target): """Returns all the location types supported for the given target. Parameters ---------- target : any The UI target for which supported location types are queried. Returns ------- locators_classes : set Supported locator types for the given target type. """ return self._location_registry.get_keys(target_class=target.__class__) def _get_location_doc(self, target, locator_class): """Return the documentation for the given target and locator type. Parameters ---------- target : any The UI target being operated on. locator_class : subclass of type Any class. Returns ------- doc : str Raises ------ LocationNotSupported If the given locator and target types are not supported. """ self._location_registry.get_value( target_class=target.__class__, key=locator_class, ) # This maybe configurable in the future via register_location return inspect.getdoc(locator_class)