Merge lp:~veebers/ubuntu-keyboard/adding_autopilot_tests_and_emulators into lp:ubuntu-keyboard
- adding_autopilot_tests_and_emulators
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~veebers/ubuntu-keyboard/adding_autopilot_tests_and_emulators |
Merge into: | lp:ubuntu-keyboard |
Diff against target: |
1124 lines (+859/-4) 16 files modified
debian/control (+11/-0) debian/rules (+9/-3) debian/ubuntu-keyboard-autopilot.install (+1/-0) qml/Keyboard.qml (+10/-0) qml/WordRibbon.qml (+3/-0) src/lib/logic/layouthelper.h (+2/-0) src/lib/logic/layoutupdater.cpp (+11/-0) src/lib/models/layout.cpp (+18/-0) src/lib/models/layout.h (+17/-1) src/plugin/inputmethod.cpp (+3/-0) tests/autopilot/setup.py (+32/-0) tests/autopilot/ubuntu_keyboard/__init__.py (+18/-0) tests/autopilot/ubuntu_keyboard/emulators/__init__.py (+18/-0) tests/autopilot/ubuntu_keyboard/emulators/keyboard.py (+346/-0) tests/autopilot/ubuntu_keyboard/tests/__init__.py (+18/-0) tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py (+342/-0) |
To merge this branch: | bzr merge lp:~veebers/ubuntu-keyboard/adding_autopilot_tests_and_emulators |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Bill Filler (community) | Approve | ||
Thomi Richards (community) | Approve | ||
Review via email: mp+180480@code.launchpad.net |
This proposal has been superseded by a proposal from 2013-08-23.
Commit message
Adding autopilot emulators and tests to ubuntu-keyboard.
Description of the change
Adding autopilot emulators and tests to ubuntu-keyboard.
The tests cover expectations in the design docs as well as other general tests.
The Emulators will allow us to add a backend to autopilot so that we can use the OSK during tests to enter input.
Michael Terry (mterry) wrote : | # |
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
> As an aside, I'm bummed that autopilot is python2, instead of python3. :-/
man, me too!
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:31
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Thomi Richards (thomir-deactivatedaccount) wrote : | # |
Python side LGTM
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:32
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:33
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:34
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Bill Filler (bfiller) wrote : | # |
approve, there are known failures of the majority of tests due to reported bugs, but those bugs will get fixed as soon as possible. daily image testing of ubuntu-keyboard should not be enabled until that point.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
Unapproved changes made after approval.
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Unmerged revisions
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2013-08-22 02:03:04 +0000 |
3 | +++ debian/control 2013-08-23 04:29:04 +0000 |
4 | @@ -14,6 +14,8 @@ |
5 | libubuntu-platform-api1-dev, |
6 | maliit-framework-dev (>= 0.99.0+git20130615+97e8335-0ubuntu3), |
7 | pkg-config, |
8 | + python:any (>= 2.7), |
9 | + python-setuptools, |
10 | qt5-default, |
11 | qtbase5-dev, |
12 | qtdeclarative5-dev, |
13 | @@ -55,3 +57,12 @@ |
14 | ${shlibs:Depends}, |
15 | Description: Ubuntu on-screen keyboard tests |
16 | Tests for the Ubuntu virtual keyboard |
17 | + |
18 | +Package: ubuntu-keyboard-autopilot |
19 | +Architecture: all |
20 | +Depends: libautopilot-qt, |
21 | + ${misc:Depends}, |
22 | + ${python:Depends}, |
23 | + ubuntu-keyboard (>= ${source:Version}), |
24 | +Description: Tests and emulators package for ubuntu-keyboard |
25 | + Autopilot tests for the ubuntu-keyboard package |
26 | \ No newline at end of file |
27 | |
28 | === modified file 'debian/rules' |
29 | --- debian/rules 2013-08-15 21:07:50 +0000 |
30 | +++ debian/rules 2013-08-23 04:29:04 +0000 |
31 | @@ -4,7 +4,7 @@ |
32 | export DPKG_GENSYMBOLS_CHECK_LEVEL=4 |
33 | |
34 | %: |
35 | - dh $@ --fail-missing |
36 | + dh $@ --fail-missing --with python2 |
37 | |
38 | override_dh_auto_configure: |
39 | dh_auto_configure -- -recursive \ |
40 | @@ -19,10 +19,16 @@ |
41 | CONFIG+=enable-qt-mobility |
42 | |
43 | override_dh_install: |
44 | - # Don't install the tests |
45 | + # install autopilot tests |
46 | + cd tests/autopilot; \ |
47 | + set -ex; for python in $(shell pyversions -r); do \ |
48 | + $$python setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb; \ |
49 | + done; \ |
50 | + cd $(CURDIR) |
51 | + # Don't install the other tests |
52 | rm debian/tmp/usr/bin/language-layout-loading |
53 | rm debian/tmp/usr/bin/language-layout-switching |
54 | rm debian/tmp/usr/bin/repeat-backspace |
55 | rm debian/tmp/usr/bin/ut_editor |
56 | rm debian/tmp/usr/bin/word-candidates |
57 | - dh_install |
58 | + dh_install -X'*.pyc' --fail-missing |
59 | |
60 | === added file 'debian/ubuntu-keyboard-autopilot.install' |
61 | --- debian/ubuntu-keyboard-autopilot.install 1970-01-01 00:00:00 +0000 |
62 | +++ debian/ubuntu-keyboard-autopilot.install 2013-08-23 04:29:04 +0000 |
63 | @@ -0,0 +1,1 @@ |
64 | +usr/lib/python*/*/ubuntu_keyboard* |
65 | |
66 | === modified file 'qml/Keyboard.qml' |
67 | --- qml/Keyboard.qml 2013-08-20 21:43:23 +0000 |
68 | +++ qml/Keyboard.qml 2013-08-23 04:29:04 +0000 |
69 | @@ -33,6 +33,7 @@ |
70 | |
71 | Item { |
72 | id: canvas |
73 | + objectName: "ubuntuKeyboard" // Allow us to specify a specific keyboard within autopilot. |
74 | property alias layout: keyRepeater.model |
75 | property variant event_handler |
76 | property bool area_enabled // MouseArea has no id property so we cannot alias its enabled property. |
77 | @@ -40,6 +41,10 @@ |
78 | |
79 | visible: layout.visible |
80 | |
81 | + // Expose details for use with Autopilot. |
82 | + readonly property var layoutState: layout.keyboard_state |
83 | + readonly property string activeView: layout.activeView |
84 | + |
85 | property int contentOrientation: Qt.PrimaryOrientation |
86 | |
87 | property bool shown: false; |
88 | @@ -77,6 +82,7 @@ |
89 | |
90 | WordRibbon { |
91 | id: wordRibbon |
92 | + objectName: "wordRibbon" |
93 | |
94 | anchors.bottom: keypadMouseArea.top |
95 | width: parent.width; |
96 | @@ -125,6 +131,7 @@ |
97 | } |
98 | |
99 | Item { |
100 | + objectName: "keyboardKeypad" |
101 | id: keyPad |
102 | |
103 | anchors.top: borderTop.bottom |
104 | @@ -160,6 +167,9 @@ |
105 | Text { |
106 | id: key_text_item |
107 | |
108 | + // Expose detail for use within Autopilot |
109 | + property var action_type: key_action_type |
110 | + |
111 | anchors.fill: parent |
112 | text: key_text |
113 | font.family: key_font |
114 | |
115 | === modified file 'qml/WordRibbon.qml' |
116 | --- qml/WordRibbon.qml 2013-07-19 12:05:07 +0000 |
117 | +++ qml/WordRibbon.qml 2013-08-23 04:29:04 +0000 |
118 | @@ -20,6 +20,7 @@ |
119 | Rectangle { |
120 | |
121 | id: wordRibbonCanvas |
122 | + objectName: "wordRibbenCanvas" |
123 | state: "NORMAL" |
124 | |
125 | Rectangle { |
126 | @@ -29,6 +30,7 @@ |
127 | |
128 | ListView { |
129 | id: listView |
130 | + objectName: "wordListView" |
131 | anchors.fill: parent; |
132 | |
133 | model: maliit_wordribbon |
134 | @@ -44,6 +46,7 @@ |
135 | id: wordCandidateItem |
136 | width: wordItem.width + units.gu(2); |
137 | height: wordRibbonCanvas.height |
138 | + property alias word_text: wordItem // For testing in Autopilot |
139 | |
140 | Item { |
141 | anchors.fill: parent |
142 | |
143 | === modified file 'src/lib/logic/layouthelper.h' |
144 | --- src/lib/logic/layouthelper.h 2013-07-19 12:05:07 +0000 |
145 | +++ src/lib/logic/layouthelper.h 2013-08-23 04:29:04 +0000 |
146 | @@ -35,6 +35,7 @@ |
147 | #include "models/key.h" |
148 | #include "models/keyarea.h" |
149 | #include "models/wordribbon.h" |
150 | +#include "models/layout.h" |
151 | |
152 | #include <QtCore> |
153 | |
154 | @@ -143,6 +144,7 @@ |
155 | Q_SLOT void onKeysOverriden(const Logic::KeyOverrides &overriden_keys, |
156 | bool update); |
157 | |
158 | + Q_SIGNAL void stateChanged(Model::Layout::State state); |
159 | private: |
160 | const QScopedPointer<LayoutHelperPrivate> d_ptr; |
161 | }; |
162 | |
163 | === modified file 'src/lib/logic/layoutupdater.cpp' |
164 | --- src/lib/logic/layoutupdater.cpp 2013-08-09 15:22:29 +0000 |
165 | +++ src/lib/logic/layoutupdater.cpp 2013-08-23 04:29:04 +0000 |
166 | @@ -733,6 +733,13 @@ |
167 | converter.setLayoutOrientation(orientation); |
168 | d->layout->setCenterPanel(d->inShiftedState() ? converter.shiftedKeyArea() |
169 | : converter.keyArea()); |
170 | + |
171 | + if (d->inShiftedState()) |
172 | + Q_EMIT d->layout->stateChanged(Model::Layout::ShiftedState); |
173 | + else if (d->inDeadkeyState()) |
174 | + Q_EMIT d->layout->stateChanged(Model::Layout::DeadkeyState); |
175 | + else |
176 | + Q_EMIT d->layout->stateChanged(Model::Layout::DefaultState); |
177 | } |
178 | |
179 | void LayoutUpdater::switchToPrimarySymView() |
180 | @@ -750,6 +757,8 @@ |
181 | |
182 | // Reset shift state machine, also see switchToMainView. |
183 | d->shift_machine.restart(); |
184 | + |
185 | + Q_EMIT d->layout->stateChanged(Model::Layout::PrimarySymbolState); |
186 | } |
187 | |
188 | void LayoutUpdater::switchToSecondarySymView() |
189 | @@ -764,6 +773,8 @@ |
190 | KeyAreaConverter converter(d->style->attributes(), &d->loader); |
191 | converter.setLayoutOrientation(orientation); |
192 | d->layout->setCenterPanel(converter.symbolsKeyArea(1)); |
193 | + |
194 | + Q_EMIT d->layout->stateChanged(Model::Layout::SecondarySymbolState); |
195 | } |
196 | |
197 | void LayoutUpdater::switchToAccentedView() |
198 | |
199 | === modified file 'src/lib/models/layout.cpp' |
200 | --- src/lib/models/layout.cpp 2013-08-09 15:22:29 +0000 |
201 | +++ src/lib/models/layout.cpp 2013-08-23 04:29:04 +0000 |
202 | @@ -63,6 +63,7 @@ |
203 | KeyArea key_area; |
204 | QString image_directory; |
205 | QHash<int, QByteArray> roles; |
206 | + Layout::State state; |
207 | QString activeViewId; |
208 | |
209 | explicit LayoutPrivate(); |
210 | @@ -74,6 +75,7 @@ |
211 | , key_area() |
212 | , image_directory() |
213 | , roles() |
214 | + , state(Layout::DefaultState) |
215 | { |
216 | // Model roles are used as variables in QML, hence the under_score naming |
217 | // convention: |
218 | @@ -88,6 +90,7 @@ |
219 | roles[Layout::RoleKeyFontStretch] = "key_font_stretch"; |
220 | roles[Layout::RoleKeyIcon] = "key_icon"; |
221 | roles[Layout::RoleKeyActionInsert] = "key_action_insert"; |
222 | + roles[Layout::RoleKeyAction] = "key_action_type"; |
223 | } |
224 | |
225 | |
226 | @@ -230,6 +233,18 @@ |
227 | qGuiApp->primaryScreen()->orientation()) ); |
228 | } |
229 | |
230 | +Layout::State Layout::state() const |
231 | +{ |
232 | + Q_D(const Layout); |
233 | + return d->state; |
234 | +} |
235 | + |
236 | +void Layout::setState(Model::Layout::State state) |
237 | +{ |
238 | + Q_D(Layout); |
239 | + d->state = state; |
240 | + Q_EMIT stateChanged(state); |
241 | +} |
242 | |
243 | QString Layout::activeView() const |
244 | { |
245 | @@ -321,6 +336,9 @@ |
246 | |
247 | case RoleKeyActionInsert: |
248 | return QVariant(key.action() == Key::ActionInsert); |
249 | + |
250 | + case RoleKeyAction: |
251 | + return QVariant(key.action()); |
252 | } |
253 | |
254 | qWarning() << __PRETTY_FUNCTION__ |
255 | |
256 | === modified file 'src/lib/models/layout.h' |
257 | --- src/lib/models/layout.h 2013-08-09 15:22:29 +0000 |
258 | +++ src/lib/models/layout.h 2013-08-23 04:29:04 +0000 |
259 | @@ -78,11 +78,22 @@ |
260 | Q_PROPERTY(int invisible_toucharea_height READ invisibleTouchAreaHeight |
261 | NOTIFY invisibleTouchAreaHeightChanged) |
262 | |
263 | + Q_PROPERTY(State keyboard_state READ state WRITE setState |
264 | + NOTIFY stateChanged) |
265 | Q_PROPERTY(QString activeView READ activeView WRITE setActiveView |
266 | NOTIFY activeViewChanged) |
267 | |
268 | + Q_ENUMS(State) |
269 | |
270 | public: |
271 | + enum State { |
272 | + DefaultState, |
273 | + ShiftedState, |
274 | + PrimarySymbolState, |
275 | + SecondarySymbolState, |
276 | + DeadkeyState |
277 | + }; |
278 | + |
279 | enum Roles { |
280 | RoleKeyRectangle = Qt::UserRole + 1, |
281 | RoleKeyReactiveArea, |
282 | @@ -94,7 +105,8 @@ |
283 | RoleKeyFontSize, |
284 | RoleKeyFontStretch, |
285 | RoleKeyIcon, |
286 | - RoleKeyActionInsert |
287 | + RoleKeyActionInsert, |
288 | + RoleKeyAction // Extra introspection detail for testing. |
289 | }; |
290 | |
291 | explicit Layout(QObject *parent = 0); |
292 | @@ -137,6 +149,10 @@ |
293 | Q_SLOT int invisibleTouchAreaHeight() const; |
294 | Q_SIGNAL void invisibleTouchAreaHeightChanged(int &changed); |
295 | |
296 | + Q_SLOT State state() const; |
297 | + Q_SLOT void setState(Model::Layout::State state); |
298 | + Q_SIGNAL void stateChanged(Model::Layout::State state); |
299 | + |
300 | Q_SLOT QString activeView() const; |
301 | Q_SLOT void setActiveView(const QString& activeViewId); |
302 | Q_SIGNAL void activeViewChanged(const QString &activeViewId); |
303 | |
304 | === modified file 'src/plugin/inputmethod.cpp' |
305 | --- src/plugin/inputmethod.cpp 2013-08-22 17:05:44 +0000 |
306 | +++ src/plugin/inputmethod.cpp 2013-08-23 04:29:04 +0000 |
307 | @@ -244,6 +244,9 @@ |
308 | QObject::connect(&layout.updater, SIGNAL(languageChanged(QString)), |
309 | &editor, SLOT(onLanguageChanged(const QString&))); |
310 | |
311 | + QObject::connect(&layout.helper, SIGNAL(stateChanged(Model::Layout::State)), |
312 | + &layout.model, SLOT(setState(Model::Layout::State))); |
313 | + |
314 | #ifdef EXTENDED_SURFACE_TEMP_DISABLED |
315 | QObject::connect(&layout.event_handler, SIGNAL(extendedKeysShown(Key)), |
316 | &extended_layout.event_handler, SLOT(onExtendedKeysShown(Key))); |
317 | |
318 | === added directory 'tests/autopilot' |
319 | === added file 'tests/autopilot/setup.py' |
320 | --- tests/autopilot/setup.py 1970-01-01 00:00:00 +0000 |
321 | +++ tests/autopilot/setup.py 2013-08-23 04:29:04 +0000 |
322 | @@ -0,0 +1,32 @@ |
323 | +#!/usr/bin/python |
324 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
325 | +# |
326 | +# Ubuntu Keyboard Autopilot Test Suite |
327 | +# Copyright (C) 2013 Canonical |
328 | +# |
329 | +# This program is free software: you can redistribute it and/or modify |
330 | +# it under the terms of the GNU General Public License as published by |
331 | +# the Free Software Foundation, either version 3 of the License, or |
332 | +# (at your option) any later version. |
333 | +# |
334 | +# This program is distributed in the hope that it will be useful, |
335 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
336 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
337 | +# GNU General Public License for more details. |
338 | +# |
339 | +# You should have received a copy of the GNU General Public License |
340 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
341 | +# |
342 | + |
343 | + |
344 | +from distutils.core import setup |
345 | +from setuptools import find_packages |
346 | + |
347 | +setup( |
348 | + name='ubuntu_keyboard', |
349 | + version='1.0', |
350 | + description='Ubuntu Keyboard autopilot tests and emulators.', |
351 | + url='https://launchpad.net/ubuntu-keyboard', |
352 | + license='GPLv3', |
353 | + packages=find_packages(), |
354 | +) |
355 | |
356 | === added directory 'tests/autopilot/ubuntu_keyboard' |
357 | === added file 'tests/autopilot/ubuntu_keyboard/__init__.py' |
358 | --- tests/autopilot/ubuntu_keyboard/__init__.py 1970-01-01 00:00:00 +0000 |
359 | +++ tests/autopilot/ubuntu_keyboard/__init__.py 2013-08-23 04:29:04 +0000 |
360 | @@ -0,0 +1,18 @@ |
361 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
362 | +# |
363 | +# Ubuntu Keyboard Test Suite |
364 | +# Copyright (C) 2013 Canonical |
365 | +# |
366 | +# This program is free software: you can redistribute it and/or modify |
367 | +# it under the terms of the GNU General Public License as published by |
368 | +# the Free Software Foundation, either version 3 of the License, or |
369 | +# (at your option) any later version. |
370 | +# |
371 | +# This program is distributed in the hope that it will be useful, |
372 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
373 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
374 | +# GNU General Public License for more details. |
375 | +# |
376 | +# You should have received a copy of the GNU General Public License |
377 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
378 | +# |
379 | |
380 | === added directory 'tests/autopilot/ubuntu_keyboard/emulators' |
381 | === added file 'tests/autopilot/ubuntu_keyboard/emulators/__init__.py' |
382 | --- tests/autopilot/ubuntu_keyboard/emulators/__init__.py 1970-01-01 00:00:00 +0000 |
383 | +++ tests/autopilot/ubuntu_keyboard/emulators/__init__.py 2013-08-23 04:29:04 +0000 |
384 | @@ -0,0 +1,18 @@ |
385 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
386 | +# |
387 | +# Ubuntu Keyboard Test Suite |
388 | +# Copyright (C) 2013 Canonical |
389 | +# |
390 | +# This program is free software: you can redistribute it and/or modify |
391 | +# it under the terms of the GNU General Public License as published by |
392 | +# the Free Software Foundation, either version 3 of the License, or |
393 | +# (at your option) any later version. |
394 | +# |
395 | +# This program is distributed in the hope that it will be useful, |
396 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
397 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
398 | +# GNU General Public License for more details. |
399 | +# |
400 | +# You should have received a copy of the GNU General Public License |
401 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
402 | +# |
403 | |
404 | === added file 'tests/autopilot/ubuntu_keyboard/emulators/keyboard.py' |
405 | --- tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 1970-01-01 00:00:00 +0000 |
406 | +++ tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-08-23 04:29:04 +0000 |
407 | @@ -0,0 +1,346 @@ |
408 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
409 | +# |
410 | +# Ubuntu Keyboard Test Suite |
411 | +# Copyright (C) 2013 Canonical |
412 | +# |
413 | +# This program is free software: you can redistribute it and/or modify |
414 | +# it under the terms of the GNU General Public License as published by |
415 | +# the Free Software Foundation, either version 3 of the License, or |
416 | +# (at your option) any later version. |
417 | +# |
418 | +# This program is distributed in the hope that it will be useful, |
419 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
420 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
421 | +# GNU General Public License for more details. |
422 | +# |
423 | +# You should have received a copy of the GNU General Public License |
424 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
425 | +# |
426 | + |
427 | +from collections import defaultdict, namedtuple |
428 | +from time import sleep |
429 | +import logging |
430 | + |
431 | +from autopilot.input import Pointer, Touch |
432 | +from autopilot.introspection import ( |
433 | + get_proxy_object_for_existing_process, |
434 | + ProcessSearchError |
435 | +) |
436 | + |
437 | + |
438 | +logger = logging.getLogger(__name__) |
439 | + |
440 | + |
441 | +# Definitions of enums used within the cpp source code. |
442 | +class KeyboardState: |
443 | + DEFAULT = 0 |
444 | + SHIFTED = 1 |
445 | + SYMBOL_1 = 2 |
446 | + SYMBOL_2 = 3 |
447 | + |
448 | + |
449 | +class KeyAction: |
450 | + INSERT = 0 |
451 | + SHIFT = 1 |
452 | + BACKSPACE = 2 |
453 | + SPACE = 3 |
454 | + SYM = 6 |
455 | + RETURN = 7 |
456 | + SWITCH = 11 |
457 | + |
458 | + |
459 | +class UnsupportedKey(RuntimeError): |
460 | + pass |
461 | + |
462 | + |
463 | +class Keyboard(object): |
464 | + |
465 | + KeyPos = namedtuple("KeyPos", ['x', 'y', 'h', 'w']) |
466 | + |
467 | + # Note (veebers 19-aug-13): this hardcoded right now, but will be reading |
468 | + # data from the keyboard itself in the very near future. Moved '/' to |
469 | + # primary symbol, default layout can have a .com instead. |
470 | + default_keys = "qwertyuiopasdfghjklzxcvbnm." |
471 | + shifted_keys = "QWERTYUIOPASDFGHJKLZXCVBNM." |
472 | + primary_symbol = "1234567890*#+-=()!?@~/\\';:,." |
473 | + secondary_symbol = u"$%<>[]`^|_{}\"&,.\u20ac\xa3\xa5\u20b9\xa7\xa1\xbf" \ |
474 | + u"\xab\xbb\u201c\u201d\u201e" |
475 | + |
476 | + # The ability to name the non-text keys. |
477 | + _action_id_to_text = { |
478 | + KeyAction.SHIFT: 'SHIFT', |
479 | + KeyAction.BACKSPACE: '\b', |
480 | + KeyAction.SPACE: ' ', |
481 | + KeyAction.RETURN: '\n' |
482 | + } |
483 | + |
484 | + def __init__(self, pointer=None): |
485 | + try: |
486 | + maliit = get_proxy_object_for_existing_process( |
487 | + connection_name='org.maliit.server' |
488 | + ) |
489 | + except ProcessSearchError as e: |
490 | + e.args += ( |
491 | + "Unable to find maliit-server dbus object. Has it been " |
492 | + "started with introspection enabled?", |
493 | + ) |
494 | + raise |
495 | + |
496 | + try: |
497 | + self.keyboard = maliit.select_single( |
498 | + "Keyboard", |
499 | + objectName="ubuntuKeyboard" |
500 | + ) |
501 | + if self.keyboard is None: |
502 | + raise RuntimeError( |
503 | + "Unable to find the Ubuntu Keyboard object within the " |
504 | + "maliit server" |
505 | + ) |
506 | + except ValueError as e: |
507 | + e.args += ( |
508 | + "There was more than one Keyboard object found, aborting.", |
509 | + ) |
510 | + raise |
511 | + |
512 | + try: |
513 | + self.keypad = self.keyboard.select_single( |
514 | + "QQuickItem", |
515 | + objectName="keyboardKeypad" |
516 | + ) |
517 | + |
518 | + if self.keypad is None: |
519 | + raise RuntimeError( |
520 | + "Unable to find the keypad object within the " |
521 | + "maliit server" |
522 | + ) |
523 | + except ValueError as e: |
524 | + e.args += ( |
525 | + "There was more than one keyboard keypad object found, " |
526 | + "aborting.", |
527 | + ) |
528 | + raise |
529 | + |
530 | + # Contains instructions on how to move the keyboard into a specific |
531 | + # state/layout so that we can successfully press the required key. |
532 | + self._state_lookup_table = self._generate_state_lookup_table() |
533 | + # Cache the position of the keys |
534 | + self._key_pos_table = defaultdict(dict) |
535 | + |
536 | + if pointer is None: |
537 | + self.pointer = Pointer(Touch.create()) |
538 | + else: |
539 | + self.pointer = pointer |
540 | + |
541 | + def dismiss(self): |
542 | + """Swipe the keyboard down to hide it. |
543 | + |
544 | + :raises: *AssertionError* if the state.wait_for fails meaning that the |
545 | + keyboard failed to hide. |
546 | + |
547 | + """ |
548 | + if self.is_available(): |
549 | + x, y, h, w = self.keyboard.globalRect |
550 | + x_pos = int(w / 2) |
551 | + # start_y: just inside the keyboard, must be a better way than +1px |
552 | + start_y = y + 1 |
553 | + end_y = y + int(h / 2) |
554 | + self.pointer.drag(x_pos, start_y, x_pos, end_y) |
555 | + |
556 | + self.keyboard.state.wait_for("HIDDEN") |
557 | + |
558 | + def is_available(self): |
559 | + """Returns true if the keyboard is shown and ready to use.""" |
560 | + return ( |
561 | + self.keyboard.state == "SHOWN" |
562 | + and not self.keyboard.hideAnimationFinished |
563 | + ) |
564 | + |
565 | + @property |
566 | + def current_state(self): |
567 | + return self.keyboard.layoutState |
568 | + |
569 | + # Much like is_available, but attempts to wait for the keyboard to be |
570 | + # ready. |
571 | + def wait_for_keyboard_ready(self, timeout=10): |
572 | + """Waits for *timeout* for the keyboard to be ready and returns |
573 | + true. Returns False if the keyboard fails to be considered ready within |
574 | + the alloted time. |
575 | + |
576 | + """ |
577 | + try: |
578 | + self.keyboard.state.wait_for("SHOWN", timeout=timeout) |
579 | + self.keyboard.hideAnimationFinished.wait_for( |
580 | + False, |
581 | + timeout=timeout |
582 | + ) |
583 | + return True |
584 | + except RuntimeError: |
585 | + return False |
586 | + |
587 | + def get_key_position(self, key): |
588 | + """Returns the global rect of the given key. |
589 | + |
590 | + It may need to do a lookup to update the table of positions. |
591 | + |
592 | + """ |
593 | + current_state = self.keyboard.layoutState |
594 | + if self._key_pos_table.get(current_state) is None: |
595 | + self._update_pos_table_for_current_state() |
596 | + |
597 | + return self._key_pos_table[current_state][key] |
598 | + |
599 | + def _update_pos_table_for_current_state(self): |
600 | + all_keys = self.keypad.select_many('QQuickText') |
601 | + current_state = self.keyboard.layoutState |
602 | + labeled_keys = (KeyAction.INSERT, KeyAction.SWITCH, KeyAction.SYM) |
603 | + for key in all_keys: |
604 | + with key.no_automatic_refreshing(): |
605 | + key_pos = Keyboard.KeyPos(*key.globalRect) |
606 | + if key.action_type in labeled_keys: |
607 | + self._key_pos_table[current_state][key.text] = key_pos |
608 | + else: |
609 | + key_text = Keyboard._action_id_to_text[key.action_type] |
610 | + self._key_pos_table[current_state][key_text] = key_pos |
611 | + |
612 | + def press_key(self, key): |
613 | + """Tap on the key with the internal pointer |
614 | + |
615 | + :params key: String containing the text of the key to tap. |
616 | + |
617 | + :raises: *RuntimeError* if the keyboard is not available and thus not |
618 | + ready to be used. |
619 | + :raises: *UnsupportedKey* if the supplied key cannot be found on any of |
620 | + the the current keyboards layouts. |
621 | + """ |
622 | + if not self.is_available(): |
623 | + raise RuntimeError("Keyboard is not on screen") |
624 | + |
625 | + if not self._is_special_key(key): |
626 | + required_state_for_key = self._get_keys_required_state(key) |
627 | + self._switch_keyboard_to_state(required_state_for_key) |
628 | + |
629 | + key_rect = self.get_key_position(key) |
630 | + self.pointer.click_object(key_rect) |
631 | + |
632 | + def type(self, string, delay=0.1): |
633 | + """Type the string *string* with a delay of *delay* between each key |
634 | + press |
635 | + |
636 | + .. note:: The delay provides a minimum delay, it may take longer |
637 | + between each press as the keyboard shifts between states etc. |
638 | + |
639 | + Only 'normal' or single characters can be typed this way. |
640 | + |
641 | + :raises: *UnsupportedKey* if one of the the supplied keys cannot be |
642 | + found on any of the the current keyboards layouts. |
643 | + |
644 | + """ |
645 | + for char in string: |
646 | + self.press_key(char) |
647 | + sleep(delay) |
648 | + |
649 | + def _get_keys_required_state(self, char): |
650 | + """Given a character determine which state the keyboard needs to be in |
651 | + so that it is visible and can be clicked. |
652 | + |
653 | + """ |
654 | + |
655 | + if char in Keyboard.default_keys: |
656 | + return KeyboardState.DEFAULT |
657 | + elif char in Keyboard.shifted_keys: |
658 | + return KeyboardState.SHIFTED |
659 | + elif char in Keyboard.primary_symbol: |
660 | + return KeyboardState.SYMBOL_1 |
661 | + elif char in Keyboard.secondary_symbol: |
662 | + return KeyboardState.SYMBOL_2 |
663 | + else: |
664 | + raise UnsupportedKey( |
665 | + "Don't know which state key '%s' requires" % char |
666 | + ) |
667 | + |
668 | + def _switch_keyboard_to_state(self, target_state): |
669 | + """Given a target_state, presses the required keys to bring the |
670 | + keyboard into the correct state. |
671 | + |
672 | + :raises: *RuntimeError* if unable to change the keyboard into the |
673 | + expected state. |
674 | + |
675 | + """ |
676 | + current_state = self.keyboard.layoutState |
677 | + |
678 | + if target_state == current_state: |
679 | + return |
680 | + |
681 | + instructions = self._state_lookup_table[target_state].get( |
682 | + current_state, |
683 | + None |
684 | + ) |
685 | + if instructions is None: |
686 | + raise RuntimeError( |
687 | + "Don't know how to get to state %d from current state (%d)" |
688 | + % (target_state, current_state) |
689 | + ) |
690 | + |
691 | + for step in instructions: |
692 | + key, expected_state = step |
693 | + self.press_key(key) |
694 | + self.keyboard.layoutState.wait_for(expected_state) |
695 | + |
696 | + def _is_special_key(self, key): |
697 | + return key in ["\n", "\b", " ", "SHIFT", "?123", "ABC", "1/2", "2/2"] |
698 | + |
699 | + # Give the state that you want and the current state, get instructions on |
700 | + # how to move to that state. |
701 | + # lookup_table[REQUESTED_STATE][CURRENT_STATE] -> Instructions(Key to |
702 | + # press, Expected state after key press.) |
703 | + def _generate_state_lookup_table(self): |
704 | + return { |
705 | + KeyboardState.DEFAULT: { |
706 | + KeyboardState.SHIFTED: [ |
707 | + ("SHIFT", KeyboardState.DEFAULT) |
708 | + ], |
709 | + KeyboardState.SYMBOL_1: [ |
710 | + ("ABC", KeyboardState.DEFAULT) |
711 | + ], |
712 | + KeyboardState.SYMBOL_2: [ |
713 | + ("ABC", KeyboardState.DEFAULT) |
714 | + ], |
715 | + }, |
716 | + KeyboardState.SHIFTED: { |
717 | + KeyboardState.DEFAULT: [ |
718 | + ("SHIFT", KeyboardState.SHIFTED) |
719 | + ], |
720 | + KeyboardState.SYMBOL_1: [ |
721 | + ("ABC", KeyboardState.DEFAULT), |
722 | + ("SHIFT", KeyboardState.SHIFTED) |
723 | + ], |
724 | + KeyboardState.SYMBOL_2: [ |
725 | + ("ABC", KeyboardState.DEFAULT), |
726 | + ("SHIFT", KeyboardState.SHIFTED) |
727 | + ], |
728 | + }, |
729 | + KeyboardState.SYMBOL_1: { |
730 | + KeyboardState.DEFAULT: [ |
731 | + ("?123", KeyboardState.SYMBOL_1) |
732 | + ], |
733 | + KeyboardState.SHIFTED: [ |
734 | + ("?123", KeyboardState.SYMBOL_1) |
735 | + ], |
736 | + KeyboardState.SYMBOL_2: [ |
737 | + ("2/2", KeyboardState.SYMBOL_1) |
738 | + ], |
739 | + }, |
740 | + KeyboardState.SYMBOL_2: { |
741 | + KeyboardState.DEFAULT: [ |
742 | + ("?123", KeyboardState.SYMBOL_1), |
743 | + ("1/2", KeyboardState.SYMBOL_2) |
744 | + ], |
745 | + KeyboardState.SHIFTED: [ |
746 | + ("?123", KeyboardState.SYMBOL_1), |
747 | + ("1/2", KeyboardState.SYMBOL_2) |
748 | + ], |
749 | + KeyboardState.SYMBOL_1: [ |
750 | + ("1/2", KeyboardState.SYMBOL_2) |
751 | + ], |
752 | + }, |
753 | + } |
754 | |
755 | === added directory 'tests/autopilot/ubuntu_keyboard/tests' |
756 | === added file 'tests/autopilot/ubuntu_keyboard/tests/__init__.py' |
757 | --- tests/autopilot/ubuntu_keyboard/tests/__init__.py 1970-01-01 00:00:00 +0000 |
758 | +++ tests/autopilot/ubuntu_keyboard/tests/__init__.py 2013-08-23 04:29:04 +0000 |
759 | @@ -0,0 +1,18 @@ |
760 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
761 | +# |
762 | +# Ubuntu Keyboard Test Suite |
763 | +# Copyright (C) 2013 Canonical |
764 | +# |
765 | +# This program is free software: you can redistribute it and/or modify |
766 | +# it under the terms of the GNU General Public License as published by |
767 | +# the Free Software Foundation, either version 3 of the License, or |
768 | +# (at your option) any later version. |
769 | +# |
770 | +# This program is distributed in the hope that it will be useful, |
771 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
772 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
773 | +# GNU General Public License for more details. |
774 | +# |
775 | +# You should have received a copy of the GNU General Public License |
776 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
777 | +# |
778 | |
779 | === added file 'tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py' |
780 | --- tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 1970-01-01 00:00:00 +0000 |
781 | +++ tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-08-23 04:29:04 +0000 |
782 | @@ -0,0 +1,342 @@ |
783 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
784 | +# |
785 | +# Ubuntu Keyboard Test Suite |
786 | +# Copyright (C) 2013 Canonical |
787 | +# |
788 | +# This program is free software: you can redistribute it and/or modify |
789 | +# it under the terms of the GNU General Public License as published by |
790 | +# the Free Software Foundation, either version 3 of the License, or |
791 | +# (at your option) any later version. |
792 | +# |
793 | +# This program is distributed in the hope that it will be useful, |
794 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
795 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
796 | +# GNU General Public License for more details. |
797 | +# |
798 | +# You should have received a copy of the GNU General Public License |
799 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
800 | +# |
801 | + |
802 | +import os |
803 | + |
804 | +from testtools.matchers import Equals |
805 | +from tempfile import mktemp |
806 | +from textwrap import dedent |
807 | + |
808 | +from autopilot.testcase import AutopilotTestCase |
809 | +from autopilot.input import Pointer, Touch |
810 | +from autopilot.matchers import Eventually |
811 | + |
812 | +from ubuntu_keyboard.emulators.keyboard import Keyboard, KeyboardState |
813 | + |
814 | + |
815 | +class UbuntuKeyboardTests(AutopilotTestCase): |
816 | + def setUp(self): |
817 | + super(UbuntuKeyboardTests, self).setUp() |
818 | + self.pointer = Pointer(Touch.create()) |
819 | + |
820 | + def launch_test_input_area(self, label="", input_hints=None): |
821 | + self.app = self._launch_simple_input(label, input_hints) |
822 | + text_area = self.app.select_single("QQuickTextInput") |
823 | + |
824 | + return text_area |
825 | + |
826 | + def ensure_focus_on_input(self, input_area): |
827 | + self.pointer.click_object(input_area) |
828 | + keyboard = Keyboard() |
829 | + self.addCleanup(keyboard.dismiss) |
830 | + self.assertThat(keyboard.is_available, Eventually(Equals(True))) |
831 | + |
832 | + def _start_qml_script(self, script_contents): |
833 | + """Launch a qml script.""" |
834 | + qml_path = mktemp(suffix='.qml') |
835 | + open(qml_path, 'w').write(script_contents) |
836 | + self.addCleanup(os.remove, qml_path) |
837 | + |
838 | + return self.launch_test_application( |
839 | + "qmlscene", |
840 | + qml_path, |
841 | + app_type='qt', |
842 | + ) |
843 | + |
844 | + def _launch_simple_input(self, label="", input_hints=None): |
845 | + if input_hints is None: |
846 | + extra_script = "Qt.ImhNoPredictiveText" |
847 | + else: |
848 | + extra_script = "|".join(input_hints) |
849 | + |
850 | + simple_script = dedent(""" |
851 | + import QtQuick 2.0 |
852 | + import Ubuntu.Components 0.1 |
853 | + |
854 | + Rectangle { |
855 | + id: window |
856 | + objectName: "windowRectangle" |
857 | + color: "lightgrey" |
858 | + |
859 | + Text { |
860 | + id: inputLabel |
861 | + text: "%(label)s" |
862 | + font.pixelSize: units.gu(3) |
863 | + anchors { |
864 | + left: input.left |
865 | + top: parent.top |
866 | + topMargin: 25 |
867 | + bottomMargin: 25 |
868 | + } |
869 | + } |
870 | + |
871 | + TextField { |
872 | + id: input; |
873 | + objectName: "input" |
874 | + anchors { |
875 | + top: inputLabel.bottom |
876 | + horizontalCenter: parent.horizontalCenter |
877 | + topMargin: 10 |
878 | + } |
879 | + inputMethodHints: %(input_method)s |
880 | + } |
881 | + } |
882 | + |
883 | + """ % {'label': label, 'input_method': extra_script}) |
884 | + |
885 | + return self._start_qml_script(simple_script) |
886 | + |
887 | + |
888 | +class UbuntuKeyboardTestsAccess(UbuntuKeyboardTests): |
889 | + |
890 | + def test_keyboard_is_available(self): |
891 | + keyboard = Keyboard() |
892 | + self.addCleanup(keyboard.dismiss) |
893 | + app = self._launch_simple_input() |
894 | + text_rectangle = app.select_single("QQuickTextInput") |
895 | + |
896 | + self.pointer.click_object(text_rectangle) |
897 | + |
898 | + self.assertThat(keyboard.is_available, Eventually(Equals(True))) |
899 | + |
900 | + |
901 | +class UbuntuKeyboardTypingTests(UbuntuKeyboardTests): |
902 | + |
903 | + scenarios = [ |
904 | + ( |
905 | + 'lower_alpha', |
906 | + dict( |
907 | + label="Lowercase", |
908 | + input='abcdefghijklmnopqrstuvwxyz' |
909 | + ) |
910 | + ), |
911 | + ( |
912 | + 'upper_alpha', |
913 | + dict( |
914 | + label="Uppercase", |
915 | + input='ABCDEFGHIJKLMNOPQRSTUVWXYZ' |
916 | + ) |
917 | + ), |
918 | + ( |
919 | + 'numeric', |
920 | + dict( |
921 | + label="Numeric", |
922 | + input='0123456789' |
923 | + ) |
924 | + ), |
925 | + ( |
926 | + 'punctuation', |
927 | + dict( |
928 | + label="Puncuation", |
929 | + input='`~!@#$%^&*()_-+={}[]|\\:;"\'<>,.?/' |
930 | + ) |
931 | + ) |
932 | + ] |
933 | + |
934 | + def test_can_type_string(self): |
935 | + text_area = self.launch_test_input_area(label=self.label) |
936 | + self.ensure_focus_on_input(text_area) |
937 | + keyboard = Keyboard() |
938 | + self.addCleanup(keyboard.dismiss) |
939 | + |
940 | + keyboard.type(self.input) |
941 | + |
942 | + self.assertThat(text_area.text, Eventually(Equals(self.input))) |
943 | + |
944 | + |
945 | +class UbuntuKeyboardStateChanges(UbuntuKeyboardTests): |
946 | + |
947 | + # Note: this is a failing test due to bug lp:1214695 |
948 | + # Note: based on UX design doc |
949 | + def test_keyboard_layout_starts_shifted(self): |
950 | + """When first launched the keyboard state must be |
951 | + shifted/capitalised. |
952 | + |
953 | + """ |
954 | + text_area = self.launch_test_input_area() |
955 | + self.ensure_focus_on_input(text_area) |
956 | + keyboard = Keyboard() |
957 | + self.addCleanup(keyboard.dismiss) |
958 | + |
959 | + self.assertThat( |
960 | + keyboard.keyboard.layoutState, |
961 | + Eventually(Equals(KeyboardState.SHIFTED)) |
962 | + ) |
963 | + |
964 | + def test_shift_latch(self): |
965 | + """Double tap of the shift key must lock it 'On' until the shift key |
966 | + tapped again. |
967 | + |
968 | + Normally hitting shift then a letter reverts from the shifted state |
969 | + back to the default. If double clicked it should stay in the shifted |
970 | + until the shift key is clicked again. |
971 | + |
972 | + """ |
973 | + text_area = self.launch_test_input_area() |
974 | + self.ensure_focus_on_input(text_area) |
975 | + keyboard = Keyboard() |
976 | + self.addCleanup(keyboard.dismiss) |
977 | + |
978 | + keyboard.type('abc') |
979 | + keyboard.press_key('SHIFT') |
980 | + keyboard.press_key('SHIFT') |
981 | + keyboard.type('S') |
982 | + |
983 | + self.assertThat( |
984 | + keyboard.keyboard.layoutState, |
985 | + Eventually(Equals(KeyboardState.SHIFTED)) |
986 | + ) |
987 | + self.assertThat(text_area.text, Eventually(Equals('abcS'))) |
988 | + |
989 | + # Note: based on UX design doc |
990 | + def test_shift_state_returns_to_default_after_letter_typed(self): |
991 | + """Pushing shift and then typing an uppercase letter must automatically |
992 | + shift the keyboard back into the default state. |
993 | + |
994 | + """ |
995 | + text_area = self.launch_test_input_area() |
996 | + self.ensure_focus_on_input(text_area) |
997 | + keyboard = Keyboard() |
998 | + self.addCleanup(keyboard.dismiss) |
999 | + |
1000 | + # Normally, type and (press_key) take care of shifting into the correct |
1001 | + # state, we do it manually here as that's what we're testing. |
1002 | + keyboard.type('abc') |
1003 | + keyboard.press_key('SHIFT') |
1004 | + keyboard.type('A') |
1005 | + |
1006 | + # Once the capital letter has been typed, we must be able to access the |
1007 | + # lowercase letters, otherwise it's not in the correct state. |
1008 | + self.assertThat( |
1009 | + keyboard.keyboard.layoutState, |
1010 | + Eventually(Equals(KeyboardState.DEFAULT)) |
1011 | + ) |
1012 | + |
1013 | + self.assertThat(text_area.text, Eventually(Equals('abcA'))) |
1014 | + |
1015 | + # Note: this is a failing test due to bug lp:1214695 |
1016 | + # Note: Based on UX design doc. |
1017 | + def test_shift_state_entered_after_fullstop(self): |
1018 | + """After typing a fullstop the keyboard state must automatically |
1019 | + enter the shifted state. |
1020 | + |
1021 | + """ |
1022 | + text_area = self.launch_test_input_area() |
1023 | + self.ensure_focus_on_input(text_area) |
1024 | + keyboard = Keyboard() |
1025 | + self.addCleanup(keyboard.dismiss) |
1026 | + |
1027 | + keyboard.type("abc.") |
1028 | + |
1029 | + self.assertThat( |
1030 | + text_area.text, |
1031 | + Eventually(Equals("abc.")) |
1032 | + ) |
1033 | + |
1034 | + self.assertThat( |
1035 | + keyboard.keyboard.layoutState, |
1036 | + Eventually(Equals(KeyboardState.SHIFTED)) |
1037 | + ) |
1038 | + |
1039 | + def test_switching_between_states(self): |
1040 | + """The user must be able to type many different characters including |
1041 | + spaces and backspaces. |
1042 | + |
1043 | + """ |
1044 | + text_area = self.launch_test_input_area() |
1045 | + self.ensure_focus_on_input(text_area) |
1046 | + keyboard = Keyboard() |
1047 | + self.addCleanup(keyboard.dismiss) |
1048 | + |
1049 | + keyboard.type( |
1050 | + 'abc gone\b\b & \bABC (123)' |
1051 | + ) |
1052 | + |
1053 | + expected = "abc go & ABC (123)" |
1054 | + self.assertThat( |
1055 | + text_area.text, |
1056 | + Eventually(Equals(expected)) |
1057 | + ) |
1058 | + |
1059 | + |
1060 | +class UbuntuKeyboardInputTypeStateChange(UbuntuKeyboardTests): |
1061 | + """Note: these tests are currently failing due to bug lp:1214694 (the |
1062 | + activeView detail isn't exposed correctly nor is it updated as expected |
1063 | + (i.e. when the view changes.)) |
1064 | + |
1065 | + """ |
1066 | + |
1067 | + scenarios = [ |
1068 | + ( |
1069 | + "Url", |
1070 | + dict( |
1071 | + label="Url", |
1072 | + hints=['Qt.ImhUrlCharactersOnly'], |
1073 | + expected_activeview="url" |
1074 | + ) |
1075 | + ), |
1076 | + ( |
1077 | + "Password", |
1078 | + dict( |
1079 | + label="Password", |
1080 | + hints=['Qt.ImhHiddenText', 'Qt.ImhSensitiveData'], |
1081 | + expected_activeview="password" |
1082 | + ) |
1083 | + ), |
1084 | + ( |
1085 | + "Email", |
1086 | + dict( |
1087 | + label="Email", |
1088 | + hints=['Qt.ImhEmailCharactersOnly'], |
1089 | + expected_activeview="email" |
1090 | + ) |
1091 | + ), |
1092 | + ( |
1093 | + "Number", |
1094 | + dict( |
1095 | + label="Number", |
1096 | + hints=['Qt.ImhFormattedNumbersOnly'], |
1097 | + expected_activeview="number" |
1098 | + ) |
1099 | + ), |
1100 | + ( |
1101 | + "Telephone", |
1102 | + dict( |
1103 | + label="Telephone", |
1104 | + hints=['Qt.ImhDigitsOnly'], |
1105 | + expected_activeview="phonenumber" |
1106 | + ) |
1107 | + ), |
1108 | + ] |
1109 | + |
1110 | + # Note: based on UX design doc |
1111 | + def test_keyboard_layout(self): |
1112 | + """The Keyboard must respond to the input type and change to be the |
1113 | + correct state. |
1114 | + |
1115 | + """ |
1116 | + text_area = self.launch_test_input_area(self.label, self.hints) |
1117 | + self.ensure_focus_on_input(text_area) |
1118 | + keyboard = Keyboard() |
1119 | + self.addCleanup(keyboard.dismiss) |
1120 | + |
1121 | + self.assertThat( |
1122 | + keyboard.keyboard.activeView, |
1123 | + Eventually(Equals(self.expected_activeview)) |
1124 | + ) |
Looking at the debian/* changes, the only change I would make is a small pet peeve of mine, but can you keep the the Depends lines sorted? python* should be after pkg-config, and the lines for the new package too.
As an aside, I'm bummed that autopilot is python2, instead of python3. :-/