Merge lp:~veebers/ubuntu-keyboard/adding_autopilot_tests_and_emulators into lp:ubuntu-keyboard

Proposed by Christopher Lee
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
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.

To post a comment you must log in.
Revision history for this message
Michael Terry (mterry) wrote :

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. :-/

Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

> As an aside, I'm bummed that autopilot is python2, instead of python3. :-/

man, me too!

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Thomi Richards (thomir-deactivatedaccount) wrote :

Python side LGTM

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
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.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+ )

Subscribers

People subscribed via source and target branches