1
#  Copyright (c) 2005-2020, Enthought, Inc.
2
#  All rights reserved.
3
#
4
#  This software is provided without warranty under the terms of the BSD
5
#  license included in LICENSE.txt and may be redistributed only
6
#  under the conditions described in the aforementioned license.  The license
7
#  is also available online at http://www.enthought.com/licenses/BSD.txt
8
#
9
#  Thanks for using Enthought open source!
10
#
11

12 11
from traitsui.testing.tester.exceptions import (
13
    InteractionNotSupported,
14
    LocationNotSupported,
15
)
16

17

18 11
class _TargetToKeyRegistry:
19
    """ Perform the mapping from target to a key to a callable.
20

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

25 11
    def __init__(self, exception_maker):
26
        """ Initializer
27

28
        Parameters
29
        ----------
30
        exception_maker : callable(target_class, key, available_keys)
31
            A callable that return an exception for when no values are referred
32
            to by a given pair of target_class and key.
33
        """
34 11
        self._target_to_key_to_value = {}
35 11
        self.exception_maker = exception_maker
36

37 11
    def register(self, target_class, key, value):
38 11
        action_to_handler = self._target_to_key_to_value.setdefault(
39
            target_class, {}
40
        )
41 11
        if key in action_to_handler:
42 11
            raise ValueError(
43
                "A value for target {!r} and key {!r} already "
44
                "exists.".format(target_class, key)
45
            )
46 11
        action_to_handler[key] = value
47

48 11
    def get_value(self, target_class, key):
49 11
        action_to_handler = self._target_to_key_to_value.get(target_class, [])
50 11
        if key not in action_to_handler:
51 11
            raise self.exception_maker(
52
                target_class=target_class,
53
                key=key,
54
                available_keys=list(action_to_handler),
55
            )
56 11
        return action_to_handler[key]
57

58

59 11
class TargetRegistry:
60
    """ An object for registering interaction and location resolution logic
61
    for different UI target types.
62

63
    Registering interaction handler (register_handler)
64
    --------------------------------------------------
65
    The interaction type can be a subclass of any type. There are a few
66
    pre-defined interaction types in
67
    - ``traitsui.testing.tester.command``
68
    - ``traitsui.testing.tester.query``
69

70
    For example, to simulate clicking a button in TraitsUI's ButtonEditor, the
71
    implementation for Qt may look like this::
72

73
        def mouse_click_qt_button(wrapper, interaction):
74
            # wrapper is an instance of UIWrapper
75
            wrapper.target.control.click()
76

77
    The function can then be registered with the target type and an interaction
78
    type::
79

80
        registry = TargetRegistry()
81
        registry.register_handler(
82
            target_class=traitsui.qt4.button_editor.SimpleEditor,
83
            interaction_class=traitsui.testing.tester.command.MouseClick,
84
            handler=mouse_click_qt_button,
85
        )
86

87
    Similarly, a wx implementation of clicking a button can be registered
88
    to the registry (the content of ``mouse_click_wx_button`` is not shown)::
89

90
        registry.register_handler(
91
            target_class=traitsui.wx.button_editor.SimpleEditor,
92
            interaction_class=traitsui.testing.tester.command.MouseClick,
93
            handler=mouse_click_wx_button,
94
        )
95

96
    Then this registry can be used with the ``UITester`` and ``UIWrapper`` to
97
    support ``UIWrapper.perform`` and ``UIWrapper.inspect``.
98

99
    Registering location solver (register_solver)
100
    ---------------------------------------------
101

102
    Resolving a location on a UI target is logically similar to making a query
103
    for a nested UI target. This query is separated out to support the
104
    ``UIWrapper.locate`` method independently of the query method
105
    ``UIWrapper.inspect``.
106

107
    The locator type can be any subclass of ``type``. There are predefined
108
    locators in ``traitsui.testing.tester.locator``.
109

110
    For example, suppose a UI target called ``MyUIContainer`` has some buttons,
111
    and the objective of a test is to click a specific button with a given
112
    label. We will therefore need to locate the button with the given label
113
    before a mouse click can be performed.
114

115
    The test code we wanted to achieve looks like this::
116

117
        button_wrapper = container.locate(Button("OK"))
118

119
    Where we want ``button_wrapper`` to be an instance of``UIWrapper``
120
    wrapping a button.
121

122
    The ``Button`` is a new locator type::
123

124
        class Button:
125
            ''' Locator for locating a push button by label.'''
126
            def __init__(self, label):
127
                self.label = label
128

129
    Say ``MyUIContainer`` is keeping track of the buttons by names with a
130
    dictionary called ``_buttons``::
131

132
        def get_button(wrapper, location):
133
            return wrapper.target._buttons[location.label]
134

135
    The solvers can then be registered for the container UI target::
136

137
        registry = TargetRegistry()
138
        registry.register_solver(
139
            target_class=MyUIContainer,
140
            locator_class=Button,
141
            solver=get_button,
142
        )
143

144
    When the solver is registered this way, the ``wrapper`` argument will be
145
    an instance of ``UIWrapper`` whose ``target`` attribute is an instance of
146
    ``MyUIContainer``, and ``location`` will be an instance of ``Button``, as
147
    is provided via ``UIWrapper.locate``.
148
    """
149

150 11
    def __init__(self):
151 11
        self._interaction_registry = _TargetToKeyRegistry(
152
            exception_maker=(
153
                lambda target_class, key, available_keys: (
154
                    InteractionNotSupported(
155
                        target_class=target_class,
156
                        interaction_class=key,
157
                        supported=available_keys,
158
                    )
159
                )
160
            ),
161
        )
162 11
        self._location_registry = _TargetToKeyRegistry(
163
            exception_maker=(
164
                lambda target_class, key, available_keys: LocationNotSupported(
165
                    target_class=target_class,
166
                    locator_class=key,
167
                    supported=available_keys,
168
                )
169
            ),
170
        )
171

172 11
    def register_handler(self, target_class, interaction_class, handler):
173
        """ Register a handler for a given target type and interaction type.
174

175
        Parameters
176
        ----------
177
        target_class : subclass of type
178
            The type of a UI target being operated on.
179
        interaction_class : subclass of type
180
            Any class.
181
        handler : callable(UIWrapper, interaction) -> any
182
            The function to handle the particular interaction on a target.
183
            ``interaction`` should be an instance of ``interaction_class``.
184

185
        Raises
186
        ------
187
        ValueError
188
            If a handler has already be registered for the same target type
189
            and interaction class.
190
        """
191 11
        self._interaction_registry.register(
192
            target_class=target_class,
193
            key=interaction_class,
194
            value=handler,
195
        )
196

197 11
    def get_handler(self, target_class, interaction_class):
198
        """ Return a callable for handling an interaction for a given target
199
        type.
200

201
        Parameters
202
        ----------
203
        target_class : subclass of type
204
            The type of a UI target being operated on.
205
        interaction_class : subclass of type
206
            Any class.
207

208
        Returns
209
        -------
210
        handler : callable(UIWrapper, interaction) -> any
211
            The function to handle the particular interaction on a target.
212
            ``interaction`` should be an instance of ``interaction_class``.
213
        """
214 11
        return self._interaction_registry.get_value(
215
            target_class=target_class,
216
            key=interaction_class,
217
        )
218

219 11
    def register_solver(self, target_class, locator_class, solver):
220
        """ Register a solver for resolving the next UI target for the given
221
        target type and locator type.
222

223
        Parameters
224
        ----------
225
        target_class : subclass of type
226
            The type of a UI target being operated on.
227
        locator_class : subclass of type
228
            Any class.
229
        solver : callable(UIWrapper, location) -> any
230
            A callable for resolving a location into a new target.
231
            The location argument will be an instance of locator_class.
232

233
        Raises
234
        ------
235
        ValueError
236
            If a solver has already been registered for the given target
237
            type and locator type.
238
        """
239 11
        self._location_registry.register(
240
            target_class=target_class,
241
            key=locator_class,
242
            value=solver,
243
        )
244

245 11
    def get_solver(self, target_class, locator_class):
246
        """ Return a callable registered for resolving a location for the
247
        given target type and locator type.
248

249
        Parameters
250
        ----------
251
        target_class : subclass of type
252
            The type of a UI target being operated on.
253
        locator_class : subclass of type
254
            Any class.
255

256
        Raises
257
        ------
258
        LocationNotSupported
259
        """
260 11
        return self._location_registry.get_value(
261
            target_class=target_class,
262
            key=locator_class,
263
        )

Read our documentation on viewing source code .

Loading