Merge lp:~veebers/ubuntu-keyboard/update_autopilot_emulators into lp:ubuntu-keyboard
- update_autopilot_emulators
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Christopher Lee |
Approved revision: | 79 |
Merged at revision: | 77 |
Proposed branch: | lp:~veebers/ubuntu-keyboard/update_autopilot_emulators |
Merge into: | lp:ubuntu-keyboard |
Diff against target: |
594 lines (+188/-230) 3 files modified
tests/autopilot/ubuntu_keyboard/emulators/keyboard.py (+127/-92) tests/autopilot/ubuntu_keyboard/emulators/keypad.py (+16/-124) tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py (+45/-14) |
To merge this branch: | bzr merge lp:~veebers/ubuntu-keyboard/update_autopilot_emulators |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Paul Larson (community) | Approve | ||
Ubuntu Phablet Team | Pending | ||
Review via email: mp+190319@code.launchpad.net |
Commit message
Update to the ubuntu-keyboard emulator so that they work after the recent changes to the loader.
Description of the change
Update to the ubuntu-keyboard emulator so that they work after the recent changes to the loader.
PS Jenkins bot (ps-jenkins) wrote : | # |
Paul Larson (pwlars) wrote : | # |
I still get lots of test failures on mir with this, but the tests at least run now. +1
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:78
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 79. By Christopher Lee
-
Update so test app can be run while shell is running.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:79
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'tests/autopilot/ubuntu_keyboard/emulators/keyboard.py' | |||
2 | --- tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-10-09 22:19:09 +0000 | |||
3 | +++ tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-10-14 04:55:18 +0000 | |||
4 | @@ -17,6 +17,8 @@ | |||
5 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
6 | 18 | # | 18 | # |
7 | 19 | 19 | ||
8 | 20 | from collections import defaultdict | ||
9 | 21 | |||
10 | 20 | from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase | 22 | from ubuntu_keyboard.emulators import UbuntuKeyboardEmulatorBase |
11 | 21 | from ubuntu_keyboard.emulators.keypad import KeyPad | 23 | from ubuntu_keyboard.emulators.keypad import KeyPad |
12 | 22 | 24 | ||
13 | @@ -33,11 +35,13 @@ | |||
14 | 33 | logger = logging.getLogger(__name__) | 35 | logger = logging.getLogger(__name__) |
15 | 34 | 36 | ||
16 | 35 | 37 | ||
19 | 36 | class KeyPadNotLoaded(Exception): | 38 | class KeyboardState: |
20 | 37 | pass | 39 | character = "CHARACTERS" |
21 | 40 | symbol = "SYMBOLS" | ||
22 | 38 | 41 | ||
23 | 39 | 42 | ||
24 | 40 | class Keyboard(object): | 43 | class Keyboard(object): |
25 | 44 | """Emulator that provides the OSK as an input backend.""" | ||
26 | 41 | 45 | ||
27 | 42 | _action_to_label = { | 46 | _action_to_label = { |
28 | 43 | 'SHIFT': 'shift', | 47 | 'SHIFT': 'shift', |
29 | @@ -81,8 +85,15 @@ | |||
30 | 81 | ) | 85 | ) |
31 | 82 | raise | 86 | raise |
32 | 83 | 87 | ||
35 | 84 | self._character_keypad = None | 88 | self._keyboard_container = self.keyboard.select_single( |
36 | 85 | self._symbol_keypad = None | 89 | "KeyboardContainer" |
37 | 90 | ) | ||
38 | 91 | |||
39 | 92 | self._stored_active_keypad_name = None | ||
40 | 93 | self._active_keypad = None | ||
41 | 94 | |||
42 | 95 | self._keys_position = defaultdict(dict) | ||
43 | 96 | self._keys_contained = defaultdict(dict) | ||
44 | 86 | 97 | ||
45 | 87 | self._store_current_orientation() | 98 | self._store_current_orientation() |
46 | 88 | self._store_current_language_id() | 99 | self._store_current_language_id() |
47 | @@ -115,43 +126,6 @@ | |||
48 | 115 | raise | 126 | raise |
49 | 116 | return Keyboard.__maliit | 127 | return Keyboard.__maliit |
50 | 117 | 128 | ||
51 | 118 | def _keyboard_details_changed(self): | ||
52 | 119 | return self._language_changed() or self._orientation_changed() | ||
53 | 120 | |||
54 | 121 | @property | ||
55 | 122 | def character_keypad(self): | ||
56 | 123 | if self._character_keypad is None or self._keyboard_details_changed(): | ||
57 | 124 | self._character_keypad = self._get_keypad("character") | ||
58 | 125 | return self._character_keypad | ||
59 | 126 | |||
60 | 127 | @property | ||
61 | 128 | def symbol_keypad(self): | ||
62 | 129 | if (self._symbol_keypad is None | ||
63 | 130 | or (self._language_changed() or self._orientation_changed())): | ||
64 | 131 | self._symbol_keypad = self._get_keypad("symbol") | ||
65 | 132 | |||
66 | 133 | return self._symbol_keypad | ||
67 | 134 | |||
68 | 135 | def _get_keypad(self, name): | ||
69 | 136 | """Attempt to retrieve KeyPad object of either 'character' or 'symbol' | ||
70 | 137 | |||
71 | 138 | *name* must be either 'character' or 'symbol'. | ||
72 | 139 | |||
73 | 140 | Raises KeyPadNotLoaded exception if none or more than one keypad is | ||
74 | 141 | found. | ||
75 | 142 | |||
76 | 143 | """ | ||
77 | 144 | objectName = "{name}KeyPadLoader".format(name=name) | ||
78 | 145 | loader = self.maliit.select_single( | ||
79 | 146 | "QQuickLoader", | ||
80 | 147 | objectName=objectName | ||
81 | 148 | ) | ||
82 | 149 | keypad = loader.select_single(KeyPad) | ||
83 | 150 | if keypad is None: | ||
84 | 151 | raise KeyPadNotLoaded("{name} keypad is not currently loaded.") | ||
85 | 152 | |||
86 | 153 | return keypad | ||
87 | 154 | |||
88 | 155 | def dismiss(self): | 129 | def dismiss(self): |
89 | 156 | """Swipe the keyboard down to hide it. | 130 | """Swipe the keyboard down to hide it. |
90 | 157 | 131 | ||
91 | @@ -160,10 +134,10 @@ | |||
92 | 160 | 134 | ||
93 | 161 | """ | 135 | """ |
94 | 162 | if self.is_available(): | 136 | if self.is_available(): |
96 | 163 | x, y, h, w = self.keyboard.globalRect | 137 | x, y, h, w = self._keyboard_container.globalRect |
97 | 164 | x_pos = int(w / 2) | 138 | x_pos = int(w / 2) |
98 | 165 | # start_y: just inside the keyboard, must be a better way than +1px | 139 | # start_y: just inside the keyboard, must be a better way than +1px |
100 | 166 | start_y = y + 1 | 140 | start_y = y - 10 |
101 | 167 | end_y = y + int(h / 2) | 141 | end_y = y + int(h / 2) |
102 | 168 | self.pointer.drag(x_pos, start_y, x_pos, end_y) | 142 | self.pointer.drag(x_pos, start_y, x_pos, end_y) |
103 | 169 | 143 | ||
104 | @@ -173,19 +147,6 @@ | |||
105 | 173 | """Returns true if the keyboard is shown and ready to use.""" | 147 | """Returns true if the keyboard is shown and ready to use.""" |
106 | 174 | return (self.keyboard.state == "SHOWN") | 148 | return (self.keyboard.state == "SHOWN") |
107 | 175 | 149 | ||
108 | 176 | @property | ||
109 | 177 | def current_state(self): | ||
110 | 178 | return self.keyboard.state | ||
111 | 179 | |||
112 | 180 | @property | ||
113 | 181 | def active_keypad(self): | ||
114 | 182 | if self.character_keypad.enabled: | ||
115 | 183 | return self.character_keypad | ||
116 | 184 | elif self.symbol_keypad.enabled: | ||
117 | 185 | return self.symbol_keypad | ||
118 | 186 | else: | ||
119 | 187 | raise RuntimeError("There are no currently active KeyPads.") | ||
120 | 188 | |||
121 | 189 | # Much like is_available, but attempts to wait for the keyboard to be | 150 | # Much like is_available, but attempts to wait for the keyboard to be |
122 | 190 | # ready. | 151 | # ready. |
123 | 191 | def wait_for_keyboard_ready(self, timeout=10): | 152 | def wait_for_keyboard_ready(self, timeout=10): |
124 | @@ -218,24 +179,21 @@ | |||
125 | 218 | raise RuntimeError("Keyboard is not on screen") | 179 | raise RuntimeError("Keyboard is not on screen") |
126 | 219 | 180 | ||
127 | 220 | key = self._translate_key(key) | 181 | key = self._translate_key(key) |
146 | 221 | active_keypad = None | 182 | |
147 | 222 | 183 | req_keypad = KeyboardState.character | |
148 | 223 | try: | 184 | req_key_state = self._keypad_contains_key(req_keypad, key) |
149 | 224 | if self.character_keypad.contains_key(key): | 185 | if req_key_state is None: |
150 | 225 | self._show_character_keypad() | 186 | req_keypad = KeyboardState.symbol |
151 | 226 | active_keypad = self.character_keypad | 187 | req_key_state = self._keypad_contains_key(req_keypad, key) |
152 | 227 | elif self.symbol_keypad.contains_key(key): | 188 | |
153 | 228 | self._show_symbol_keypad() | 189 | if req_key_state is None: |
154 | 229 | active_keypad = self.symbol_keypad | 190 | raise ValueError("Key '%s' was not found on the keyboard" % key) |
155 | 230 | except KeyPadNotLoaded: | 191 | |
156 | 231 | pass | 192 | key_pos = self._get_key_pos_from_keypad(req_keypad, key) |
157 | 232 | 193 | self._show_keypad(req_keypad) | |
158 | 233 | if active_keypad is None: | 194 | self._change_keypad_to_state(req_key_state) |
159 | 234 | raise ValueError( | 195 | |
160 | 235 | "Key '%s' was not found on the keyboard." % key | 196 | self._tap_key(key_pos) |
143 | 236 | ) | ||
144 | 237 | |||
145 | 238 | active_keypad.press_key(key) | ||
161 | 239 | 197 | ||
162 | 240 | def type(self, string, delay=0.1): | 198 | def type(self, string, delay=0.1): |
163 | 241 | """Type the string *string* with a delay of *delay* between each key | 199 | """Type the string *string* with a delay of *delay* between each key |
164 | @@ -254,6 +212,101 @@ | |||
165 | 254 | self.press_key(char) | 212 | self.press_key(char) |
166 | 255 | sleep(delay) | 213 | sleep(delay) |
167 | 256 | 214 | ||
168 | 215 | @property | ||
169 | 216 | def current_state(self): | ||
170 | 217 | return self.keyboard.state | ||
171 | 218 | |||
172 | 219 | @property | ||
173 | 220 | def active_keypad_state(self): | ||
174 | 221 | return self._keyboard_container.activeKeypadState | ||
175 | 222 | |||
176 | 223 | @property | ||
177 | 224 | def active_keypad(self): | ||
178 | 225 | if ( | ||
179 | 226 | self._stored_active_keypad_name != self._current_keypad_name | ||
180 | 227 | or self._keyboard_details_changed() | ||
181 | 228 | ): | ||
182 | 229 | self._stored_active_keypad_name = self._current_keypad_name | ||
183 | 230 | loader = self.maliit.select_single( | ||
184 | 231 | "QQuickLoader", | ||
185 | 232 | objectName='characterKeyPadLoader' | ||
186 | 233 | ) | ||
187 | 234 | logger.debug("Keypad lookup") | ||
188 | 235 | self._active_keypad = loader.select_single(KeyPad) | ||
189 | 236 | return self._active_keypad | ||
190 | 237 | |||
191 | 238 | @property | ||
192 | 239 | def _current_keypad_name(self): | ||
193 | 240 | return self._keyboard_container.state | ||
194 | 241 | |||
195 | 242 | def _update_details_for_keypad(self, keypad_name): | ||
196 | 243 | self._show_keypad(keypad_name) | ||
197 | 244 | |||
198 | 245 | contained, positions = self.active_keypad.get_key_details() | ||
199 | 246 | self._keys_contained[self._keyboard_container.state] = contained | ||
200 | 247 | self._keys_position[self._keyboard_container.state] = positions | ||
201 | 248 | |||
202 | 249 | def _keypad_contains_key(self, keypad_name, key): | ||
203 | 250 | """Returns the keypad state that key is found in. | ||
204 | 251 | |||
205 | 252 | Returns either a KeyPadState if the key is found on the provided keypad | ||
206 | 253 | or None if not found. | ||
207 | 254 | |||
208 | 255 | """ | ||
209 | 256 | if self._keypad_details_expired(keypad_name): | ||
210 | 257 | self._update_details_for_keypad(keypad_name) | ||
211 | 258 | |||
212 | 259 | return self._keys_contained[keypad_name].get(key, None) | ||
213 | 260 | |||
214 | 261 | def _get_key_pos_from_keypad(self, keypad_name, key): | ||
215 | 262 | """Returns the position of the key if it is found on that keypad or | ||
216 | 263 | None if it is not. | ||
217 | 264 | |||
218 | 265 | """ | ||
219 | 266 | if self._keypad_details_expired(keypad_name): | ||
220 | 267 | self._update_details_for_keypad(keypad_name) | ||
221 | 268 | |||
222 | 269 | return self._keys_position[keypad_name].get(key, None) | ||
223 | 270 | |||
224 | 271 | def _show_keypad(self, keypad_name): | ||
225 | 272 | if self._current_keypad_name == keypad_name: | ||
226 | 273 | return | ||
227 | 274 | |||
228 | 275 | key_pos = self._get_key_pos_from_keypad( | ||
229 | 276 | self._current_keypad_name, | ||
230 | 277 | "symbols" | ||
231 | 278 | ) | ||
232 | 279 | self._tap_key(key_pos) | ||
233 | 280 | # Can I do the property thing here? | ||
234 | 281 | self._keyboard_container.state.wait_for(keypad_name) | ||
235 | 282 | self.active_keypad.opacity.wait_for(1.0) | ||
236 | 283 | |||
237 | 284 | def _change_keypad_to_state(self, state): | ||
238 | 285 | if self._keyboard_container.activeKeypadState == state: | ||
239 | 286 | return | ||
240 | 287 | |||
241 | 288 | key_pos = self._get_key_pos_from_keypad( | ||
242 | 289 | self._current_keypad_name, | ||
243 | 290 | "shift" | ||
244 | 291 | ) | ||
245 | 292 | self._tap_key(key_pos) | ||
246 | 293 | self._keyboard_container.activeKeypadState.wait_for(state) | ||
247 | 294 | self.active_keypad.opacity.wait_for(1.0) | ||
248 | 295 | |||
249 | 296 | def _tap_key(self, key_rect, pointer=None): | ||
250 | 297 | if pointer is None: | ||
251 | 298 | pointer = Pointer(Touch.create()) | ||
252 | 299 | pointer.click_object(key_rect) | ||
253 | 300 | |||
254 | 301 | def _keyboard_details_changed(self): | ||
255 | 302 | return self._language_changed() or self._orientation_changed() | ||
256 | 303 | |||
257 | 304 | def _keypad_details_expired(self, keypad_name): | ||
258 | 305 | return ( | ||
259 | 306 | self._keys_contained.get(keypad_name) is None | ||
260 | 307 | or self._keyboard_details_changed() | ||
261 | 308 | ) | ||
262 | 309 | |||
263 | 257 | def _orientation_changed(self): | 310 | def _orientation_changed(self): |
264 | 258 | if self._stored_orientation != self.orientation.orientationAngle: | 311 | if self._stored_orientation != self.orientation.orientationAngle: |
265 | 259 | self._store_current_orientation() | 312 | self._store_current_orientation() |
266 | @@ -274,24 +327,6 @@ | |||
267 | 274 | def _store_current_language_id(self): | 327 | def _store_current_language_id(self): |
268 | 275 | self._stored_language_id = self.keyboard.layoutId | 328 | self._stored_language_id = self.keyboard.layoutId |
269 | 276 | 329 | ||
270 | 277 | def _show_character_keypad(self): | ||
271 | 278 | """Brings the characters KeyPad to the forefront.""" | ||
272 | 279 | if not self.character_keypad.enabled: | ||
273 | 280 | # If the character keypad isn't enabled than the symbol keypad must | ||
274 | 281 | # be active | ||
275 | 282 | self.symbol_keypad.press_key("symbols") | ||
276 | 283 | self.character_keypad.enabled.wait_for(True) | ||
277 | 284 | self.character_keypad.opacity.wait_for(1.0) | ||
278 | 285 | |||
279 | 286 | def _show_symbol_keypad(self): | ||
280 | 287 | """Brings the symbol KeyPad to the forefront.""" | ||
281 | 288 | if not self.symbol_keypad.enabled: | ||
282 | 289 | # If the symbol keypad isn't enabled than the character keypad must | ||
283 | 290 | # be active | ||
284 | 291 | self.character_keypad.press_key("symbols") | ||
285 | 292 | self.symbol_keypad.enabled.wait_for(True) | ||
286 | 293 | self.symbol_keypad.opacity.wait_for(1.0) | ||
287 | 294 | |||
288 | 295 | def _translate_key(self, label): | 330 | def _translate_key(self, label): |
289 | 296 | """Get the label for a 'special key' (i.e. space) so that it can be | 331 | """Get the label for a 'special key' (i.e. space) so that it can be |
290 | 297 | addressed and clicked. | 332 | addressed and clicked. |
291 | 298 | 333 | ||
292 | === modified file 'tests/autopilot/ubuntu_keyboard/emulators/keypad.py' | |||
293 | --- tests/autopilot/ubuntu_keyboard/emulators/keypad.py 2013-10-09 22:19:09 +0000 | |||
294 | +++ tests/autopilot/ubuntu_keyboard/emulators/keypad.py 2013-10-14 04:55:18 +0000 | |||
295 | @@ -22,145 +22,37 @@ | |||
296 | 22 | 22 | ||
297 | 23 | import logging | 23 | import logging |
298 | 24 | 24 | ||
299 | 25 | from autopilot.input import Pointer, Touch | ||
300 | 26 | from time import sleep | ||
301 | 27 | |||
302 | 28 | logger = logging.getLogger(__name__) | 25 | logger = logging.getLogger(__name__) |
303 | 29 | 26 | ||
304 | 30 | 27 | ||
305 | 28 | class KeyPadState: | ||
306 | 29 | NORMAL = "NORMAL" | ||
307 | 30 | SHIFTED = "SHIFTED" | ||
308 | 31 | CAPSLOCK = "CAPSLOCK" | ||
309 | 32 | |||
310 | 33 | |||
311 | 31 | class KeyPad(UbuntuKeyboardEmulatorBase): | 34 | class KeyPad(UbuntuKeyboardEmulatorBase): |
318 | 32 | """An emulator that understands the KeyPad and its internals and how to | 35 | """A basic emulator that provides the details of the keys contained within. |
313 | 33 | interact with it. | ||
314 | 34 | |||
315 | 35 | - Which keys are displayed within it | ||
316 | 36 | - The positions of these keys | ||
317 | 37 | - The state (NORMAL/SHIFTED) the KeyPad is in | ||
319 | 38 | 36 | ||
320 | 39 | """ | 37 | """ |
321 | 40 | 38 | ||
344 | 41 | class State: | 39 | def get_key_details(self): |
345 | 42 | NORMAL = "NORMAL" | 40 | contained_keys = {} |
346 | 43 | SHIFTED = "SHIFTED" | 41 | key_positions = {} |
347 | 44 | CAPSLOCK = "CAPSLOCK" | 42 | |
326 | 45 | |||
327 | 46 | def __init__(self, *args): | ||
328 | 47 | super(KeyPad, self).__init__(*args) | ||
329 | 48 | self._key_pos = dict() | ||
330 | 49 | self._contained_keys = [] | ||
331 | 50 | self._contained_shifted_keys = [] | ||
332 | 51 | |||
333 | 52 | self.update_key_details() | ||
334 | 53 | |||
335 | 54 | def contains_key(self, label): | ||
336 | 55 | """Returns true if a key with the label *label* is contained within | ||
337 | 56 | this KeyPad. | ||
338 | 57 | |||
339 | 58 | """ | ||
340 | 59 | return (label in self._contained_keys | ||
341 | 60 | or label in self._contained_shifted_keys) | ||
342 | 61 | |||
343 | 62 | def update_key_details(self): | ||
348 | 63 | def _iter_keys(key_type, label_fn): | 43 | def _iter_keys(key_type, label_fn): |
349 | 64 | for key in self.select_many(key_type): | 44 | for key in self.select_many(key_type): |
350 | 65 | with key.no_automatic_refreshing(): | 45 | with key.no_automatic_refreshing(): |
351 | 66 | key_pos = Key.Pos(*key.globalRect) | 46 | key_pos = Key.Pos(*key.globalRect) |
352 | 67 | label = label_fn(key) | 47 | label = label_fn(key) |
353 | 68 | if label != '': | 48 | if label != '': |
356 | 69 | self._contained_keys.append(label) | 49 | contained_keys[label] = KeyPadState.NORMAL |
357 | 70 | self._key_pos[label] = key_pos | 50 | key_positions[label] = key_pos |
358 | 71 | if key.shifted != '': | 51 | if key.shifted != '': |
361 | 72 | self._contained_shifted_keys.append(key.shifted) | 52 | contained_keys[key.shifted] = KeyPadState.SHIFTED |
362 | 73 | self._key_pos[key.shifted] = key_pos | 53 | key_positions[key.shifted] = key_pos |
363 | 74 | 54 | ||
364 | 75 | _iter_keys("CharKey", lambda x: x.label) | 55 | _iter_keys("CharKey", lambda x: x.label) |
365 | 76 | _iter_keys("ActionKey", lambda x: x.action) | 56 | _iter_keys("ActionKey", lambda x: x.action) |
366 | 77 | 57 | ||
456 | 78 | def press_key(self, key, pointer=None): | 58 | return (contained_keys, key_positions) |
368 | 79 | """Taps key *key* with *pointer* | ||
369 | 80 | |||
370 | 81 | If no pointer is passed in one is created for this purpose | ||
371 | 82 | |||
372 | 83 | raises *ValueError* if *key* is not contained within this KeyPad. | ||
373 | 84 | raises *RuntimeError* if this KeyPad is not visible. | ||
374 | 85 | |||
375 | 86 | """ | ||
376 | 87 | if not self.contains_key(key): | ||
377 | 88 | raise ValueError( | ||
378 | 89 | "Key '%s' is not contained by this KeyPad (%s)," | ||
379 | 90 | " cannot press it." % (key, self.objectName) | ||
380 | 91 | ) | ||
381 | 92 | |||
382 | 93 | if not self.visible: | ||
383 | 94 | raise RuntimeError( | ||
384 | 95 | "This Keypad (%s) is not visible" % self.objectName | ||
385 | 96 | ) | ||
386 | 97 | |||
387 | 98 | required_state = self._get_keys_required_keypad_state(key) | ||
388 | 99 | |||
389 | 100 | self._switch_to_state(required_state, pointer) | ||
390 | 101 | |||
391 | 102 | key_rect = self.get_key_position(key) | ||
392 | 103 | self._tap_key(key_rect, pointer) | ||
393 | 104 | |||
394 | 105 | def get_key_position(self, key): | ||
395 | 106 | """Returns Key.Pos for the key with *key*. | ||
396 | 107 | |||
397 | 108 | raises *ValueError* if unable to find the stored position for *key* | ||
398 | 109 | (i.e. not contained within this KeyPad.) | ||
399 | 110 | |||
400 | 111 | """ | ||
401 | 112 | key_rect = self._key_pos.get(key) | ||
402 | 113 | |||
403 | 114 | if key_rect is None: | ||
404 | 115 | raise ValueError("Unknown position for key '%s'" % key) | ||
405 | 116 | |||
406 | 117 | return key_rect | ||
407 | 118 | |||
408 | 119 | def _get_keys_required_keypad_state(self, key): | ||
409 | 120 | if key in ('shift', 'backspace', 'symbols', 'space', 'return'): | ||
410 | 121 | return self.state | ||
411 | 122 | elif key in self._contained_keys: | ||
412 | 123 | return KeyPad.State.NORMAL | ||
413 | 124 | elif key in self._contained_shifted_keys: | ||
414 | 125 | return KeyPad.State.SHIFTED | ||
415 | 126 | else: | ||
416 | 127 | raise ValueError( | ||
417 | 128 | "Don't know which state key '%s' requires." % key | ||
418 | 129 | ) | ||
419 | 130 | |||
420 | 131 | def _switch_to_state(self, state, pointer): | ||
421 | 132 | """Move from one state to the other. | ||
422 | 133 | |||
423 | 134 | i.e. move from NORMAL to SHIFTED | ||
424 | 135 | |||
425 | 136 | """ | ||
426 | 137 | if state == self.state: | ||
427 | 138 | return | ||
428 | 139 | |||
429 | 140 | # If shifted is needed and we're in CAPSLOCK that's fine. | ||
430 | 141 | if (state == KeyPad.State.SHIFTED | ||
431 | 142 | and self.state == KeyPad.State.CAPSLOCK): | ||
432 | 143 | logger.debug( | ||
433 | 144 | "Ignoring request to switch to SHIFTED, already in CAPSLOCK." | ||
434 | 145 | ) | ||
435 | 146 | return | ||
436 | 147 | |||
437 | 148 | logger.debug("Switching from %s to %s" % (self.state, state)) | ||
438 | 149 | |||
439 | 150 | if self.state == KeyPad.State.NORMAL: | ||
440 | 151 | expected_state = KeyPad.State.SHIFTED | ||
441 | 152 | else: | ||
442 | 153 | expected_state = KeyPad.State.NORMAL | ||
443 | 154 | |||
444 | 155 | key_rect = self.get_key_position("shift") | ||
445 | 156 | |||
446 | 157 | # Hack as we cannot tell when the shift key is ready to be pressed | ||
447 | 158 | # bug lp:1229003 and lp:1229001 | ||
448 | 159 | sleep(.2) | ||
449 | 160 | self._tap_key(key_rect, pointer) | ||
450 | 161 | self.state.wait_for(expected_state) | ||
451 | 162 | |||
452 | 163 | def _tap_key(self, key_rect, pointer): | ||
453 | 164 | if pointer is None: | ||
454 | 165 | pointer = Pointer(Touch.create()) | ||
455 | 166 | pointer.click_object(key_rect) | ||
457 | 167 | 59 | ||
458 | === modified file 'tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py' | |||
459 | --- tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-09-26 16:02:12 +0000 | |||
460 | +++ tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-10-14 04:55:18 +0000 | |||
461 | @@ -20,7 +20,7 @@ | |||
462 | 20 | import os | 20 | import os |
463 | 21 | 21 | ||
464 | 22 | from testtools.matchers import Equals | 22 | from testtools.matchers import Equals |
466 | 23 | from tempfile import mktemp | 23 | import tempfile |
467 | 24 | from textwrap import dedent | 24 | from textwrap import dedent |
468 | 25 | from time import sleep | 25 | from time import sleep |
469 | 26 | 26 | ||
470 | @@ -29,7 +29,7 @@ | |||
471 | 29 | from autopilot.matchers import Eventually | 29 | from autopilot.matchers import Eventually |
472 | 30 | 30 | ||
473 | 31 | from ubuntu_keyboard.emulators.keyboard import Keyboard | 31 | from ubuntu_keyboard.emulators.keyboard import Keyboard |
475 | 32 | from ubuntu_keyboard.emulators.keypad import KeyPad | 32 | from ubuntu_keyboard.emulators.keypad import KeyPadState |
476 | 33 | 33 | ||
477 | 34 | 34 | ||
478 | 35 | class UbuntuKeyboardTests(AutopilotTestCase): | 35 | class UbuntuKeyboardTests(AutopilotTestCase): |
479 | @@ -51,16 +51,46 @@ | |||
480 | 51 | 51 | ||
481 | 52 | def _start_qml_script(self, script_contents): | 52 | def _start_qml_script(self, script_contents): |
482 | 53 | """Launch a qml script.""" | 53 | """Launch a qml script.""" |
484 | 54 | qml_path = mktemp(suffix='.qml') | 54 | qml_path = tempfile.mktemp(suffix='.qml') |
485 | 55 | open(qml_path, 'w').write(script_contents) | 55 | open(qml_path, 'w').write(script_contents) |
486 | 56 | self.addCleanup(os.remove, qml_path) | 56 | self.addCleanup(os.remove, qml_path) |
487 | 57 | 57 | ||
488 | 58 | desktop_file = self._write_test_desktop_file() | ||
489 | 58 | return self.launch_test_application( | 59 | return self.launch_test_application( |
490 | 59 | "qmlscene", | 60 | "qmlscene", |
491 | 60 | qml_path, | 61 | qml_path, |
492 | 62 | '--desktop_file_hint=%s' % desktop_file, | ||
493 | 61 | app_type='qt', | 63 | app_type='qt', |
494 | 62 | ) | 64 | ) |
495 | 63 | 65 | ||
496 | 66 | def _write_test_desktop_file(self): | ||
497 | 67 | desktop_file_dir = self.get_local_desktop_file_directory() | ||
498 | 68 | if not os.path.exists(desktop_file_dir): | ||
499 | 69 | os.makedirs(desktop_file_dir) | ||
500 | 70 | with tempfile.NamedTemporaryFile( | ||
501 | 71 | suffix='.desktop', | ||
502 | 72 | dir=desktop_file_dir, | ||
503 | 73 | delete=False | ||
504 | 74 | ) as desktop_file: | ||
505 | 75 | desktop_file.write( | ||
506 | 76 | "[Desktop Entry]\n" | ||
507 | 77 | "Type=Application\n" | ||
508 | 78 | "Exec=Not important\n" | ||
509 | 79 | "Path=Not important\n" | ||
510 | 80 | "Name=Test app\n" | ||
511 | 81 | "Icon=Not important" | ||
512 | 82 | ) | ||
513 | 83 | self.addCleanup(os.remove, desktop_file.name) | ||
514 | 84 | return desktop_file.name | ||
515 | 85 | |||
516 | 86 | def get_local_desktop_file_directory(self): | ||
517 | 87 | return os.path.join( | ||
518 | 88 | os.getenv('HOME'), | ||
519 | 89 | '.local', | ||
520 | 90 | 'share', | ||
521 | 91 | 'applications' | ||
522 | 92 | ) | ||
523 | 93 | |||
524 | 64 | def _launch_simple_input(self, label="", input_hints=None): | 94 | def _launch_simple_input(self, label="", input_hints=None): |
525 | 65 | if input_hints is None: | 95 | if input_hints is None: |
526 | 66 | extra_script = "Qt.ImhNoPredictiveText" | 96 | extra_script = "Qt.ImhNoPredictiveText" |
527 | @@ -160,7 +190,6 @@ | |||
528 | 160 | self.addCleanup(keyboard.dismiss) | 190 | self.addCleanup(keyboard.dismiss) |
529 | 161 | 191 | ||
530 | 162 | keyboard.type(self.input) | 192 | keyboard.type(self.input) |
531 | 163 | |||
532 | 164 | self.assertThat(text_area.text, Eventually(Equals(self.input))) | 193 | self.assertThat(text_area.text, Eventually(Equals(self.input))) |
533 | 165 | 194 | ||
534 | 166 | 195 | ||
535 | @@ -183,8 +212,8 @@ | |||
536 | 183 | self.addCleanup(keyboard.dismiss) | 212 | self.addCleanup(keyboard.dismiss) |
537 | 184 | 213 | ||
538 | 185 | self.assertThat( | 214 | self.assertThat( |
541 | 186 | keyboard.active_keypad.state, | 215 | keyboard.active_keypad_state, |
542 | 187 | Eventually(Equals(KeyPad.State.SHIFTED)) | 216 | Eventually(Equals(KeyPadState.SHIFTED)) |
543 | 188 | ) | 217 | ) |
544 | 189 | 218 | ||
545 | 190 | def test_shift_latch(self): | 219 | def test_shift_latch(self): |
546 | @@ -196,6 +225,10 @@ | |||
547 | 196 | until the shift key is clicked again. | 225 | until the shift key is clicked again. |
548 | 197 | 226 | ||
549 | 198 | """ | 227 | """ |
550 | 228 | self.skip( | ||
551 | 229 | "Skipping due to bug in emulator: lp:1237846" | ||
552 | 230 | ) | ||
553 | 231 | |||
554 | 199 | text_area = self.launch_test_input_area() | 232 | text_area = self.launch_test_input_area() |
555 | 200 | self.ensure_focus_on_input(text_area) | 233 | self.ensure_focus_on_input(text_area) |
556 | 201 | keyboard = Keyboard() | 234 | keyboard = Keyboard() |
557 | @@ -206,13 +239,11 @@ | |||
558 | 206 | sleep(.2) | 239 | sleep(.2) |
559 | 207 | keyboard.press_key('shift') | 240 | keyboard.press_key('shift') |
560 | 208 | keyboard.press_key('shift') | 241 | keyboard.press_key('shift') |
561 | 209 | keyboard.type('S') | ||
562 | 210 | 242 | ||
563 | 211 | self.assertThat( | 243 | self.assertThat( |
566 | 212 | keyboard.active_keypad.state, | 244 | keyboard.active_keypad_state, |
567 | 213 | Eventually(Equals(KeyPad.State.CAPSLOCK)) | 245 | Eventually(Equals(KeyPadState.CAPSLOCK)) |
568 | 214 | ) | 246 | ) |
569 | 215 | self.assertThat(text_area.text, Eventually(Equals('abcS'))) | ||
570 | 216 | 247 | ||
571 | 217 | # Note: based on UX design doc | 248 | # Note: based on UX design doc |
572 | 218 | def test_shift_state_returns_to_default_after_letter_typed(self): | 249 | def test_shift_state_returns_to_default_after_letter_typed(self): |
573 | @@ -234,8 +265,8 @@ | |||
574 | 234 | # Once the capital letter has been typed, we must be able to access the | 265 | # Once the capital letter has been typed, we must be able to access the |
575 | 235 | # lowercase letters, otherwise it's not in the correct state. | 266 | # lowercase letters, otherwise it's not in the correct state. |
576 | 236 | self.assertThat( | 267 | self.assertThat( |
579 | 237 | keyboard.active_keypad.state, | 268 | keyboard.active_keypad_state, |
580 | 238 | Eventually(Equals(KeyPad.State.NORMAL)) | 269 | Eventually(Equals(KeyPadState.NORMAL)) |
581 | 239 | ) | 270 | ) |
582 | 240 | 271 | ||
583 | 241 | self.assertThat(text_area.text, Eventually(Equals('abcA'))) | 272 | self.assertThat(text_area.text, Eventually(Equals('abcA'))) |
584 | @@ -263,8 +294,8 @@ | |||
585 | 263 | ) | 294 | ) |
586 | 264 | 295 | ||
587 | 265 | self.assertThat( | 296 | self.assertThat( |
590 | 266 | keyboard.active_keypad.state, | 297 | keyboard.active_keypad_state, |
591 | 267 | Eventually(Equals(KeyPad.State.SHIFTED)) | 298 | Eventually(Equals(KeyPadState.SHIFTED)) |
592 | 268 | ) | 299 | ) |
593 | 269 | 300 | ||
594 | 270 | def test_switching_between_states(self): | 301 | def test_switching_between_states(self): |
FAILED: Continuous integration, rev:78 jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- ci/145/ jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-amd64- ci/146 jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-armhf- ci/145/ console jenkins. qa.ubuntu. com/job/ ubuntu- keyboard- saucy-i386- ci/145
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild: 10.97.0. 26:8080/ job/ubuntu- keyboard- ci/145/ rebuild
http://