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
=== modified file 'debian/control'
--- debian/control 2013-08-22 02:03:04 +0000
+++ debian/control 2013-08-23 04:29:04 +0000
@@ -14,6 +14,8 @@
14 libubuntu-platform-api1-dev,14 libubuntu-platform-api1-dev,
15 maliit-framework-dev (>= 0.99.0+git20130615+97e8335-0ubuntu3),15 maliit-framework-dev (>= 0.99.0+git20130615+97e8335-0ubuntu3),
16 pkg-config,16 pkg-config,
17 python:any (>= 2.7),
18 python-setuptools,
17 qt5-default,19 qt5-default,
18 qtbase5-dev,20 qtbase5-dev,
19 qtdeclarative5-dev,21 qtdeclarative5-dev,
@@ -55,3 +57,12 @@
55 ${shlibs:Depends},57 ${shlibs:Depends},
56Description: Ubuntu on-screen keyboard tests58Description: Ubuntu on-screen keyboard tests
57 Tests for the Ubuntu virtual keyboard59 Tests for the Ubuntu virtual keyboard
60
61Package: ubuntu-keyboard-autopilot
62Architecture: all
63Depends: libautopilot-qt,
64 ${misc:Depends},
65 ${python:Depends},
66 ubuntu-keyboard (>= ${source:Version}),
67Description: Tests and emulators package for ubuntu-keyboard
68 Autopilot tests for the ubuntu-keyboard package
58\ No newline at end of file69\ No newline at end of file
5970
=== modified file 'debian/rules'
--- debian/rules 2013-08-15 21:07:50 +0000
+++ debian/rules 2013-08-23 04:29:04 +0000
@@ -4,7 +4,7 @@
4export DPKG_GENSYMBOLS_CHECK_LEVEL=44export DPKG_GENSYMBOLS_CHECK_LEVEL=4
55
6%:6%:
7 dh $@ --fail-missing7 dh $@ --fail-missing --with python2
88
9override_dh_auto_configure:9override_dh_auto_configure:
10 dh_auto_configure -- -recursive \10 dh_auto_configure -- -recursive \
@@ -19,10 +19,16 @@
19 CONFIG+=enable-qt-mobility19 CONFIG+=enable-qt-mobility
2020
21override_dh_install:21override_dh_install:
22 # Don't install the tests22 # install autopilot tests
23 cd tests/autopilot; \
24 set -ex; for python in $(shell pyversions -r); do \
25 $$python setup.py install --root=$(CURDIR)/debian/tmp --install-layout=deb; \
26 done; \
27 cd $(CURDIR)
28 # Don't install the other tests
23 rm debian/tmp/usr/bin/language-layout-loading29 rm debian/tmp/usr/bin/language-layout-loading
24 rm debian/tmp/usr/bin/language-layout-switching30 rm debian/tmp/usr/bin/language-layout-switching
25 rm debian/tmp/usr/bin/repeat-backspace31 rm debian/tmp/usr/bin/repeat-backspace
26 rm debian/tmp/usr/bin/ut_editor32 rm debian/tmp/usr/bin/ut_editor
27 rm debian/tmp/usr/bin/word-candidates33 rm debian/tmp/usr/bin/word-candidates
28 dh_install34 dh_install -X'*.pyc' --fail-missing
2935
=== added file 'debian/ubuntu-keyboard-autopilot.install'
--- debian/ubuntu-keyboard-autopilot.install 1970-01-01 00:00:00 +0000
+++ debian/ubuntu-keyboard-autopilot.install 2013-08-23 04:29:04 +0000
@@ -0,0 +1,1 @@
1usr/lib/python*/*/ubuntu_keyboard*
02
=== modified file 'qml/Keyboard.qml'
--- qml/Keyboard.qml 2013-08-20 21:43:23 +0000
+++ qml/Keyboard.qml 2013-08-23 04:29:04 +0000
@@ -33,6 +33,7 @@
3333
34Item {34Item {
35 id: canvas35 id: canvas
36 objectName: "ubuntuKeyboard" // Allow us to specify a specific keyboard within autopilot.
36 property alias layout: keyRepeater.model37 property alias layout: keyRepeater.model
37 property variant event_handler38 property variant event_handler
38 property bool area_enabled // MouseArea has no id property so we cannot alias its enabled property.39 property bool area_enabled // MouseArea has no id property so we cannot alias its enabled property.
@@ -40,6 +41,10 @@
4041
41 visible: layout.visible42 visible: layout.visible
4243
44 // Expose details for use with Autopilot.
45 readonly property var layoutState: layout.keyboard_state
46 readonly property string activeView: layout.activeView
47
43 property int contentOrientation: Qt.PrimaryOrientation48 property int contentOrientation: Qt.PrimaryOrientation
4449
45 property bool shown: false;50 property bool shown: false;
@@ -77,6 +82,7 @@
7782
78 WordRibbon {83 WordRibbon {
79 id: wordRibbon84 id: wordRibbon
85 objectName: "wordRibbon"
8086
81 anchors.bottom: keypadMouseArea.top87 anchors.bottom: keypadMouseArea.top
82 width: parent.width;88 width: parent.width;
@@ -125,6 +131,7 @@
125 }131 }
126132
127 Item {133 Item {
134 objectName: "keyboardKeypad"
128 id: keyPad135 id: keyPad
129136
130 anchors.top: borderTop.bottom137 anchors.top: borderTop.bottom
@@ -160,6 +167,9 @@
160 Text {167 Text {
161 id: key_text_item168 id: key_text_item
162169
170 // Expose detail for use within Autopilot
171 property var action_type: key_action_type
172
163 anchors.fill: parent173 anchors.fill: parent
164 text: key_text174 text: key_text
165 font.family: key_font175 font.family: key_font
166176
=== modified file 'qml/WordRibbon.qml'
--- qml/WordRibbon.qml 2013-07-19 12:05:07 +0000
+++ qml/WordRibbon.qml 2013-08-23 04:29:04 +0000
@@ -20,6 +20,7 @@
20Rectangle {20Rectangle {
2121
22 id: wordRibbonCanvas22 id: wordRibbonCanvas
23 objectName: "wordRibbenCanvas"
23 state: "NORMAL"24 state: "NORMAL"
2425
25 Rectangle {26 Rectangle {
@@ -29,6 +30,7 @@
2930
30 ListView {31 ListView {
31 id: listView32 id: listView
33 objectName: "wordListView"
32 anchors.fill: parent;34 anchors.fill: parent;
3335
34 model: maliit_wordribbon36 model: maliit_wordribbon
@@ -44,6 +46,7 @@
44 id: wordCandidateItem46 id: wordCandidateItem
45 width: wordItem.width + units.gu(2);47 width: wordItem.width + units.gu(2);
46 height: wordRibbonCanvas.height48 height: wordRibbonCanvas.height
49 property alias word_text: wordItem // For testing in Autopilot
4750
48 Item {51 Item {
49 anchors.fill: parent52 anchors.fill: parent
5053
=== modified file 'src/lib/logic/layouthelper.h'
--- src/lib/logic/layouthelper.h 2013-07-19 12:05:07 +0000
+++ src/lib/logic/layouthelper.h 2013-08-23 04:29:04 +0000
@@ -35,6 +35,7 @@
35#include "models/key.h"35#include "models/key.h"
36#include "models/keyarea.h"36#include "models/keyarea.h"
37#include "models/wordribbon.h"37#include "models/wordribbon.h"
38#include "models/layout.h"
3839
39#include <QtCore>40#include <QtCore>
4041
@@ -143,6 +144,7 @@
143 Q_SLOT void onKeysOverriden(const Logic::KeyOverrides &overriden_keys,144 Q_SLOT void onKeysOverriden(const Logic::KeyOverrides &overriden_keys,
144 bool update);145 bool update);
145146
147 Q_SIGNAL void stateChanged(Model::Layout::State state);
146private:148private:
147 const QScopedPointer<LayoutHelperPrivate> d_ptr;149 const QScopedPointer<LayoutHelperPrivate> d_ptr;
148};150};
149151
=== modified file 'src/lib/logic/layoutupdater.cpp'
--- src/lib/logic/layoutupdater.cpp 2013-08-09 15:22:29 +0000
+++ src/lib/logic/layoutupdater.cpp 2013-08-23 04:29:04 +0000
@@ -733,6 +733,13 @@
733 converter.setLayoutOrientation(orientation);733 converter.setLayoutOrientation(orientation);
734 d->layout->setCenterPanel(d->inShiftedState() ? converter.shiftedKeyArea()734 d->layout->setCenterPanel(d->inShiftedState() ? converter.shiftedKeyArea()
735 : converter.keyArea());735 : converter.keyArea());
736
737 if (d->inShiftedState())
738 Q_EMIT d->layout->stateChanged(Model::Layout::ShiftedState);
739 else if (d->inDeadkeyState())
740 Q_EMIT d->layout->stateChanged(Model::Layout::DeadkeyState);
741 else
742 Q_EMIT d->layout->stateChanged(Model::Layout::DefaultState);
736}743}
737744
738void LayoutUpdater::switchToPrimarySymView()745void LayoutUpdater::switchToPrimarySymView()
@@ -750,6 +757,8 @@
750757
751 // Reset shift state machine, also see switchToMainView.758 // Reset shift state machine, also see switchToMainView.
752 d->shift_machine.restart();759 d->shift_machine.restart();
760
761 Q_EMIT d->layout->stateChanged(Model::Layout::PrimarySymbolState);
753}762}
754763
755void LayoutUpdater::switchToSecondarySymView()764void LayoutUpdater::switchToSecondarySymView()
@@ -764,6 +773,8 @@
764 KeyAreaConverter converter(d->style->attributes(), &d->loader);773 KeyAreaConverter converter(d->style->attributes(), &d->loader);
765 converter.setLayoutOrientation(orientation);774 converter.setLayoutOrientation(orientation);
766 d->layout->setCenterPanel(converter.symbolsKeyArea(1));775 d->layout->setCenterPanel(converter.symbolsKeyArea(1));
776
777 Q_EMIT d->layout->stateChanged(Model::Layout::SecondarySymbolState);
767}778}
768779
769void LayoutUpdater::switchToAccentedView()780void LayoutUpdater::switchToAccentedView()
770781
=== modified file 'src/lib/models/layout.cpp'
--- src/lib/models/layout.cpp 2013-08-09 15:22:29 +0000
+++ src/lib/models/layout.cpp 2013-08-23 04:29:04 +0000
@@ -63,6 +63,7 @@
63 KeyArea key_area;63 KeyArea key_area;
64 QString image_directory;64 QString image_directory;
65 QHash<int, QByteArray> roles;65 QHash<int, QByteArray> roles;
66 Layout::State state;
66 QString activeViewId;67 QString activeViewId;
6768
68 explicit LayoutPrivate();69 explicit LayoutPrivate();
@@ -74,6 +75,7 @@
74 , key_area()75 , key_area()
75 , image_directory()76 , image_directory()
76 , roles()77 , roles()
78 , state(Layout::DefaultState)
77{79{
78 // Model roles are used as variables in QML, hence the under_score naming80 // Model roles are used as variables in QML, hence the under_score naming
79 // convention:81 // convention:
@@ -88,6 +90,7 @@
88 roles[Layout::RoleKeyFontStretch] = "key_font_stretch";90 roles[Layout::RoleKeyFontStretch] = "key_font_stretch";
89 roles[Layout::RoleKeyIcon] = "key_icon";91 roles[Layout::RoleKeyIcon] = "key_icon";
90 roles[Layout::RoleKeyActionInsert] = "key_action_insert";92 roles[Layout::RoleKeyActionInsert] = "key_action_insert";
93 roles[Layout::RoleKeyAction] = "key_action_type";
91}94}
9295
9396
@@ -230,6 +233,18 @@
230 qGuiApp->primaryScreen()->orientation()) );233 qGuiApp->primaryScreen()->orientation()) );
231}234}
232235
236Layout::State Layout::state() const
237{
238 Q_D(const Layout);
239 return d->state;
240}
241
242void Layout::setState(Model::Layout::State state)
243{
244 Q_D(Layout);
245 d->state = state;
246 Q_EMIT stateChanged(state);
247}
233248
234QString Layout::activeView() const249QString Layout::activeView() const
235{250{
@@ -321,6 +336,9 @@
321336
322 case RoleKeyActionInsert:337 case RoleKeyActionInsert:
323 return QVariant(key.action() == Key::ActionInsert);338 return QVariant(key.action() == Key::ActionInsert);
339
340 case RoleKeyAction:
341 return QVariant(key.action());
324 }342 }
325343
326 qWarning() << __PRETTY_FUNCTION__344 qWarning() << __PRETTY_FUNCTION__
327345
=== modified file 'src/lib/models/layout.h'
--- src/lib/models/layout.h 2013-08-09 15:22:29 +0000
+++ src/lib/models/layout.h 2013-08-23 04:29:04 +0000
@@ -78,11 +78,22 @@
78 Q_PROPERTY(int invisible_toucharea_height READ invisibleTouchAreaHeight78 Q_PROPERTY(int invisible_toucharea_height READ invisibleTouchAreaHeight
79 NOTIFY invisibleTouchAreaHeightChanged)79 NOTIFY invisibleTouchAreaHeightChanged)
8080
81 Q_PROPERTY(State keyboard_state READ state WRITE setState
82 NOTIFY stateChanged)
81 Q_PROPERTY(QString activeView READ activeView WRITE setActiveView83 Q_PROPERTY(QString activeView READ activeView WRITE setActiveView
82 NOTIFY activeViewChanged)84 NOTIFY activeViewChanged)
8385
86 Q_ENUMS(State)
8487
85public:88public:
89 enum State {
90 DefaultState,
91 ShiftedState,
92 PrimarySymbolState,
93 SecondarySymbolState,
94 DeadkeyState
95 };
96
86 enum Roles {97 enum Roles {
87 RoleKeyRectangle = Qt::UserRole + 1,98 RoleKeyRectangle = Qt::UserRole + 1,
88 RoleKeyReactiveArea,99 RoleKeyReactiveArea,
@@ -94,7 +105,8 @@
94 RoleKeyFontSize,105 RoleKeyFontSize,
95 RoleKeyFontStretch,106 RoleKeyFontStretch,
96 RoleKeyIcon,107 RoleKeyIcon,
97 RoleKeyActionInsert108 RoleKeyActionInsert,
109 RoleKeyAction // Extra introspection detail for testing.
98 };110 };
99111
100 explicit Layout(QObject *parent = 0);112 explicit Layout(QObject *parent = 0);
@@ -137,6 +149,10 @@
137 Q_SLOT int invisibleTouchAreaHeight() const;149 Q_SLOT int invisibleTouchAreaHeight() const;
138 Q_SIGNAL void invisibleTouchAreaHeightChanged(int &changed);150 Q_SIGNAL void invisibleTouchAreaHeightChanged(int &changed);
139151
152 Q_SLOT State state() const;
153 Q_SLOT void setState(Model::Layout::State state);
154 Q_SIGNAL void stateChanged(Model::Layout::State state);
155
140 Q_SLOT QString activeView() const;156 Q_SLOT QString activeView() const;
141 Q_SLOT void setActiveView(const QString& activeViewId);157 Q_SLOT void setActiveView(const QString& activeViewId);
142 Q_SIGNAL void activeViewChanged(const QString &activeViewId);158 Q_SIGNAL void activeViewChanged(const QString &activeViewId);
143159
=== modified file 'src/plugin/inputmethod.cpp'
--- src/plugin/inputmethod.cpp 2013-08-22 17:05:44 +0000
+++ src/plugin/inputmethod.cpp 2013-08-23 04:29:04 +0000
@@ -244,6 +244,9 @@
244 QObject::connect(&layout.updater, SIGNAL(languageChanged(QString)),244 QObject::connect(&layout.updater, SIGNAL(languageChanged(QString)),
245 &editor, SLOT(onLanguageChanged(const QString&)));245 &editor, SLOT(onLanguageChanged(const QString&)));
246246
247 QObject::connect(&layout.helper, SIGNAL(stateChanged(Model::Layout::State)),
248 &layout.model, SLOT(setState(Model::Layout::State)));
249
247#ifdef EXTENDED_SURFACE_TEMP_DISABLED250#ifdef EXTENDED_SURFACE_TEMP_DISABLED
248 QObject::connect(&layout.event_handler, SIGNAL(extendedKeysShown(Key)),251 QObject::connect(&layout.event_handler, SIGNAL(extendedKeysShown(Key)),
249 &extended_layout.event_handler, SLOT(onExtendedKeysShown(Key)));252 &extended_layout.event_handler, SLOT(onExtendedKeysShown(Key)));
250253
=== added directory 'tests/autopilot'
=== added file 'tests/autopilot/setup.py'
--- tests/autopilot/setup.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/setup.py 2013-08-23 04:29:04 +0000
@@ -0,0 +1,32 @@
1#!/usr/bin/python
2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3#
4# Ubuntu Keyboard Autopilot Test Suite
5# Copyright (C) 2013 Canonical
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20
21
22from distutils.core import setup
23from setuptools import find_packages
24
25setup(
26 name='ubuntu_keyboard',
27 version='1.0',
28 description='Ubuntu Keyboard autopilot tests and emulators.',
29 url='https://launchpad.net/ubuntu-keyboard',
30 license='GPLv3',
31 packages=find_packages(),
32)
033
=== added directory 'tests/autopilot/ubuntu_keyboard'
=== added file 'tests/autopilot/ubuntu_keyboard/__init__.py'
--- tests/autopilot/ubuntu_keyboard/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/__init__.py 2013-08-23 04:29:04 +0000
@@ -0,0 +1,18 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
019
=== added directory 'tests/autopilot/ubuntu_keyboard/emulators'
=== added file 'tests/autopilot/ubuntu_keyboard/emulators/__init__.py'
--- tests/autopilot/ubuntu_keyboard/emulators/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/emulators/__init__.py 2013-08-23 04:29:04 +0000
@@ -0,0 +1,18 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
019
=== added file 'tests/autopilot/ubuntu_keyboard/emulators/keyboard.py'
--- tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/emulators/keyboard.py 2013-08-23 04:29:04 +0000
@@ -0,0 +1,346 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
20from collections import defaultdict, namedtuple
21from time import sleep
22import logging
23
24from autopilot.input import Pointer, Touch
25from autopilot.introspection import (
26 get_proxy_object_for_existing_process,
27 ProcessSearchError
28)
29
30
31logger = logging.getLogger(__name__)
32
33
34# Definitions of enums used within the cpp source code.
35class KeyboardState:
36 DEFAULT = 0
37 SHIFTED = 1
38 SYMBOL_1 = 2
39 SYMBOL_2 = 3
40
41
42class KeyAction:
43 INSERT = 0
44 SHIFT = 1
45 BACKSPACE = 2
46 SPACE = 3
47 SYM = 6
48 RETURN = 7
49 SWITCH = 11
50
51
52class UnsupportedKey(RuntimeError):
53 pass
54
55
56class Keyboard(object):
57
58 KeyPos = namedtuple("KeyPos", ['x', 'y', 'h', 'w'])
59
60 # Note (veebers 19-aug-13): this hardcoded right now, but will be reading
61 # data from the keyboard itself in the very near future. Moved '/' to
62 # primary symbol, default layout can have a .com instead.
63 default_keys = "qwertyuiopasdfghjklzxcvbnm."
64 shifted_keys = "QWERTYUIOPASDFGHJKLZXCVBNM."
65 primary_symbol = "1234567890*#+-=()!?@~/\\';:,."
66 secondary_symbol = u"$%<>[]`^|_{}\"&,.\u20ac\xa3\xa5\u20b9\xa7\xa1\xbf" \
67 u"\xab\xbb\u201c\u201d\u201e"
68
69 # The ability to name the non-text keys.
70 _action_id_to_text = {
71 KeyAction.SHIFT: 'SHIFT',
72 KeyAction.BACKSPACE: '\b',
73 KeyAction.SPACE: ' ',
74 KeyAction.RETURN: '\n'
75 }
76
77 def __init__(self, pointer=None):
78 try:
79 maliit = get_proxy_object_for_existing_process(
80 connection_name='org.maliit.server'
81 )
82 except ProcessSearchError as e:
83 e.args += (
84 "Unable to find maliit-server dbus object. Has it been "
85 "started with introspection enabled?",
86 )
87 raise
88
89 try:
90 self.keyboard = maliit.select_single(
91 "Keyboard",
92 objectName="ubuntuKeyboard"
93 )
94 if self.keyboard is None:
95 raise RuntimeError(
96 "Unable to find the Ubuntu Keyboard object within the "
97 "maliit server"
98 )
99 except ValueError as e:
100 e.args += (
101 "There was more than one Keyboard object found, aborting.",
102 )
103 raise
104
105 try:
106 self.keypad = self.keyboard.select_single(
107 "QQuickItem",
108 objectName="keyboardKeypad"
109 )
110
111 if self.keypad is None:
112 raise RuntimeError(
113 "Unable to find the keypad object within the "
114 "maliit server"
115 )
116 except ValueError as e:
117 e.args += (
118 "There was more than one keyboard keypad object found, "
119 "aborting.",
120 )
121 raise
122
123 # Contains instructions on how to move the keyboard into a specific
124 # state/layout so that we can successfully press the required key.
125 self._state_lookup_table = self._generate_state_lookup_table()
126 # Cache the position of the keys
127 self._key_pos_table = defaultdict(dict)
128
129 if pointer is None:
130 self.pointer = Pointer(Touch.create())
131 else:
132 self.pointer = pointer
133
134 def dismiss(self):
135 """Swipe the keyboard down to hide it.
136
137 :raises: *AssertionError* if the state.wait_for fails meaning that the
138 keyboard failed to hide.
139
140 """
141 if self.is_available():
142 x, y, h, w = self.keyboard.globalRect
143 x_pos = int(w / 2)
144 # start_y: just inside the keyboard, must be a better way than +1px
145 start_y = y + 1
146 end_y = y + int(h / 2)
147 self.pointer.drag(x_pos, start_y, x_pos, end_y)
148
149 self.keyboard.state.wait_for("HIDDEN")
150
151 def is_available(self):
152 """Returns true if the keyboard is shown and ready to use."""
153 return (
154 self.keyboard.state == "SHOWN"
155 and not self.keyboard.hideAnimationFinished
156 )
157
158 @property
159 def current_state(self):
160 return self.keyboard.layoutState
161
162 # Much like is_available, but attempts to wait for the keyboard to be
163 # ready.
164 def wait_for_keyboard_ready(self, timeout=10):
165 """Waits for *timeout* for the keyboard to be ready and returns
166 true. Returns False if the keyboard fails to be considered ready within
167 the alloted time.
168
169 """
170 try:
171 self.keyboard.state.wait_for("SHOWN", timeout=timeout)
172 self.keyboard.hideAnimationFinished.wait_for(
173 False,
174 timeout=timeout
175 )
176 return True
177 except RuntimeError:
178 return False
179
180 def get_key_position(self, key):
181 """Returns the global rect of the given key.
182
183 It may need to do a lookup to update the table of positions.
184
185 """
186 current_state = self.keyboard.layoutState
187 if self._key_pos_table.get(current_state) is None:
188 self._update_pos_table_for_current_state()
189
190 return self._key_pos_table[current_state][key]
191
192 def _update_pos_table_for_current_state(self):
193 all_keys = self.keypad.select_many('QQuickText')
194 current_state = self.keyboard.layoutState
195 labeled_keys = (KeyAction.INSERT, KeyAction.SWITCH, KeyAction.SYM)
196 for key in all_keys:
197 with key.no_automatic_refreshing():
198 key_pos = Keyboard.KeyPos(*key.globalRect)
199 if key.action_type in labeled_keys:
200 self._key_pos_table[current_state][key.text] = key_pos
201 else:
202 key_text = Keyboard._action_id_to_text[key.action_type]
203 self._key_pos_table[current_state][key_text] = key_pos
204
205 def press_key(self, key):
206 """Tap on the key with the internal pointer
207
208 :params key: String containing the text of the key to tap.
209
210 :raises: *RuntimeError* if the keyboard is not available and thus not
211 ready to be used.
212 :raises: *UnsupportedKey* if the supplied key cannot be found on any of
213 the the current keyboards layouts.
214 """
215 if not self.is_available():
216 raise RuntimeError("Keyboard is not on screen")
217
218 if not self._is_special_key(key):
219 required_state_for_key = self._get_keys_required_state(key)
220 self._switch_keyboard_to_state(required_state_for_key)
221
222 key_rect = self.get_key_position(key)
223 self.pointer.click_object(key_rect)
224
225 def type(self, string, delay=0.1):
226 """Type the string *string* with a delay of *delay* between each key
227 press
228
229 .. note:: The delay provides a minimum delay, it may take longer
230 between each press as the keyboard shifts between states etc.
231
232 Only 'normal' or single characters can be typed this way.
233
234 :raises: *UnsupportedKey* if one of the the supplied keys cannot be
235 found on any of the the current keyboards layouts.
236
237 """
238 for char in string:
239 self.press_key(char)
240 sleep(delay)
241
242 def _get_keys_required_state(self, char):
243 """Given a character determine which state the keyboard needs to be in
244 so that it is visible and can be clicked.
245
246 """
247
248 if char in Keyboard.default_keys:
249 return KeyboardState.DEFAULT
250 elif char in Keyboard.shifted_keys:
251 return KeyboardState.SHIFTED
252 elif char in Keyboard.primary_symbol:
253 return KeyboardState.SYMBOL_1
254 elif char in Keyboard.secondary_symbol:
255 return KeyboardState.SYMBOL_2
256 else:
257 raise UnsupportedKey(
258 "Don't know which state key '%s' requires" % char
259 )
260
261 def _switch_keyboard_to_state(self, target_state):
262 """Given a target_state, presses the required keys to bring the
263 keyboard into the correct state.
264
265 :raises: *RuntimeError* if unable to change the keyboard into the
266 expected state.
267
268 """
269 current_state = self.keyboard.layoutState
270
271 if target_state == current_state:
272 return
273
274 instructions = self._state_lookup_table[target_state].get(
275 current_state,
276 None
277 )
278 if instructions is None:
279 raise RuntimeError(
280 "Don't know how to get to state %d from current state (%d)"
281 % (target_state, current_state)
282 )
283
284 for step in instructions:
285 key, expected_state = step
286 self.press_key(key)
287 self.keyboard.layoutState.wait_for(expected_state)
288
289 def _is_special_key(self, key):
290 return key in ["\n", "\b", " ", "SHIFT", "?123", "ABC", "1/2", "2/2"]
291
292 # Give the state that you want and the current state, get instructions on
293 # how to move to that state.
294 # lookup_table[REQUESTED_STATE][CURRENT_STATE] -> Instructions(Key to
295 # press, Expected state after key press.)
296 def _generate_state_lookup_table(self):
297 return {
298 KeyboardState.DEFAULT: {
299 KeyboardState.SHIFTED: [
300 ("SHIFT", KeyboardState.DEFAULT)
301 ],
302 KeyboardState.SYMBOL_1: [
303 ("ABC", KeyboardState.DEFAULT)
304 ],
305 KeyboardState.SYMBOL_2: [
306 ("ABC", KeyboardState.DEFAULT)
307 ],
308 },
309 KeyboardState.SHIFTED: {
310 KeyboardState.DEFAULT: [
311 ("SHIFT", KeyboardState.SHIFTED)
312 ],
313 KeyboardState.SYMBOL_1: [
314 ("ABC", KeyboardState.DEFAULT),
315 ("SHIFT", KeyboardState.SHIFTED)
316 ],
317 KeyboardState.SYMBOL_2: [
318 ("ABC", KeyboardState.DEFAULT),
319 ("SHIFT", KeyboardState.SHIFTED)
320 ],
321 },
322 KeyboardState.SYMBOL_1: {
323 KeyboardState.DEFAULT: [
324 ("?123", KeyboardState.SYMBOL_1)
325 ],
326 KeyboardState.SHIFTED: [
327 ("?123", KeyboardState.SYMBOL_1)
328 ],
329 KeyboardState.SYMBOL_2: [
330 ("2/2", KeyboardState.SYMBOL_1)
331 ],
332 },
333 KeyboardState.SYMBOL_2: {
334 KeyboardState.DEFAULT: [
335 ("?123", KeyboardState.SYMBOL_1),
336 ("1/2", KeyboardState.SYMBOL_2)
337 ],
338 KeyboardState.SHIFTED: [
339 ("?123", KeyboardState.SYMBOL_1),
340 ("1/2", KeyboardState.SYMBOL_2)
341 ],
342 KeyboardState.SYMBOL_1: [
343 ("1/2", KeyboardState.SYMBOL_2)
344 ],
345 },
346 }
0347
=== added directory 'tests/autopilot/ubuntu_keyboard/tests'
=== added file 'tests/autopilot/ubuntu_keyboard/tests/__init__.py'
--- tests/autopilot/ubuntu_keyboard/tests/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/tests/__init__.py 2013-08-23 04:29:04 +0000
@@ -0,0 +1,18 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
019
=== added file 'tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py'
--- tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntu_keyboard/tests/test_keyboard.py 2013-08-23 04:29:04 +0000
@@ -0,0 +1,342 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Ubuntu Keyboard Test Suite
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
20import os
21
22from testtools.matchers import Equals
23from tempfile import mktemp
24from textwrap import dedent
25
26from autopilot.testcase import AutopilotTestCase
27from autopilot.input import Pointer, Touch
28from autopilot.matchers import Eventually
29
30from ubuntu_keyboard.emulators.keyboard import Keyboard, KeyboardState
31
32
33class UbuntuKeyboardTests(AutopilotTestCase):
34 def setUp(self):
35 super(UbuntuKeyboardTests, self).setUp()
36 self.pointer = Pointer(Touch.create())
37
38 def launch_test_input_area(self, label="", input_hints=None):
39 self.app = self._launch_simple_input(label, input_hints)
40 text_area = self.app.select_single("QQuickTextInput")
41
42 return text_area
43
44 def ensure_focus_on_input(self, input_area):
45 self.pointer.click_object(input_area)
46 keyboard = Keyboard()
47 self.addCleanup(keyboard.dismiss)
48 self.assertThat(keyboard.is_available, Eventually(Equals(True)))
49
50 def _start_qml_script(self, script_contents):
51 """Launch a qml script."""
52 qml_path = mktemp(suffix='.qml')
53 open(qml_path, 'w').write(script_contents)
54 self.addCleanup(os.remove, qml_path)
55
56 return self.launch_test_application(
57 "qmlscene",
58 qml_path,
59 app_type='qt',
60 )
61
62 def _launch_simple_input(self, label="", input_hints=None):
63 if input_hints is None:
64 extra_script = "Qt.ImhNoPredictiveText"
65 else:
66 extra_script = "|".join(input_hints)
67
68 simple_script = dedent("""
69 import QtQuick 2.0
70 import Ubuntu.Components 0.1
71
72 Rectangle {
73 id: window
74 objectName: "windowRectangle"
75 color: "lightgrey"
76
77 Text {
78 id: inputLabel
79 text: "%(label)s"
80 font.pixelSize: units.gu(3)
81 anchors {
82 left: input.left
83 top: parent.top
84 topMargin: 25
85 bottomMargin: 25
86 }
87 }
88
89 TextField {
90 id: input;
91 objectName: "input"
92 anchors {
93 top: inputLabel.bottom
94 horizontalCenter: parent.horizontalCenter
95 topMargin: 10
96 }
97 inputMethodHints: %(input_method)s
98 }
99 }
100
101 """ % {'label': label, 'input_method': extra_script})
102
103 return self._start_qml_script(simple_script)
104
105
106class UbuntuKeyboardTestsAccess(UbuntuKeyboardTests):
107
108 def test_keyboard_is_available(self):
109 keyboard = Keyboard()
110 self.addCleanup(keyboard.dismiss)
111 app = self._launch_simple_input()
112 text_rectangle = app.select_single("QQuickTextInput")
113
114 self.pointer.click_object(text_rectangle)
115
116 self.assertThat(keyboard.is_available, Eventually(Equals(True)))
117
118
119class UbuntuKeyboardTypingTests(UbuntuKeyboardTests):
120
121 scenarios = [
122 (
123 'lower_alpha',
124 dict(
125 label="Lowercase",
126 input='abcdefghijklmnopqrstuvwxyz'
127 )
128 ),
129 (
130 'upper_alpha',
131 dict(
132 label="Uppercase",
133 input='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
134 )
135 ),
136 (
137 'numeric',
138 dict(
139 label="Numeric",
140 input='0123456789'
141 )
142 ),
143 (
144 'punctuation',
145 dict(
146 label="Puncuation",
147 input='`~!@#$%^&*()_-+={}[]|\\:;"\'<>,.?/'
148 )
149 )
150 ]
151
152 def test_can_type_string(self):
153 text_area = self.launch_test_input_area(label=self.label)
154 self.ensure_focus_on_input(text_area)
155 keyboard = Keyboard()
156 self.addCleanup(keyboard.dismiss)
157
158 keyboard.type(self.input)
159
160 self.assertThat(text_area.text, Eventually(Equals(self.input)))
161
162
163class UbuntuKeyboardStateChanges(UbuntuKeyboardTests):
164
165 # Note: this is a failing test due to bug lp:1214695
166 # Note: based on UX design doc
167 def test_keyboard_layout_starts_shifted(self):
168 """When first launched the keyboard state must be
169 shifted/capitalised.
170
171 """
172 text_area = self.launch_test_input_area()
173 self.ensure_focus_on_input(text_area)
174 keyboard = Keyboard()
175 self.addCleanup(keyboard.dismiss)
176
177 self.assertThat(
178 keyboard.keyboard.layoutState,
179 Eventually(Equals(KeyboardState.SHIFTED))
180 )
181
182 def test_shift_latch(self):
183 """Double tap of the shift key must lock it 'On' until the shift key
184 tapped again.
185
186 Normally hitting shift then a letter reverts from the shifted state
187 back to the default. If double clicked it should stay in the shifted
188 until the shift key is clicked again.
189
190 """
191 text_area = self.launch_test_input_area()
192 self.ensure_focus_on_input(text_area)
193 keyboard = Keyboard()
194 self.addCleanup(keyboard.dismiss)
195
196 keyboard.type('abc')
197 keyboard.press_key('SHIFT')
198 keyboard.press_key('SHIFT')
199 keyboard.type('S')
200
201 self.assertThat(
202 keyboard.keyboard.layoutState,
203 Eventually(Equals(KeyboardState.SHIFTED))
204 )
205 self.assertThat(text_area.text, Eventually(Equals('abcS')))
206
207 # Note: based on UX design doc
208 def test_shift_state_returns_to_default_after_letter_typed(self):
209 """Pushing shift and then typing an uppercase letter must automatically
210 shift the keyboard back into the default state.
211
212 """
213 text_area = self.launch_test_input_area()
214 self.ensure_focus_on_input(text_area)
215 keyboard = Keyboard()
216 self.addCleanup(keyboard.dismiss)
217
218 # Normally, type and (press_key) take care of shifting into the correct
219 # state, we do it manually here as that's what we're testing.
220 keyboard.type('abc')
221 keyboard.press_key('SHIFT')
222 keyboard.type('A')
223
224 # Once the capital letter has been typed, we must be able to access the
225 # lowercase letters, otherwise it's not in the correct state.
226 self.assertThat(
227 keyboard.keyboard.layoutState,
228 Eventually(Equals(KeyboardState.DEFAULT))
229 )
230
231 self.assertThat(text_area.text, Eventually(Equals('abcA')))
232
233 # Note: this is a failing test due to bug lp:1214695
234 # Note: Based on UX design doc.
235 def test_shift_state_entered_after_fullstop(self):
236 """After typing a fullstop the keyboard state must automatically
237 enter the shifted state.
238
239 """
240 text_area = self.launch_test_input_area()
241 self.ensure_focus_on_input(text_area)
242 keyboard = Keyboard()
243 self.addCleanup(keyboard.dismiss)
244
245 keyboard.type("abc.")
246
247 self.assertThat(
248 text_area.text,
249 Eventually(Equals("abc."))
250 )
251
252 self.assertThat(
253 keyboard.keyboard.layoutState,
254 Eventually(Equals(KeyboardState.SHIFTED))
255 )
256
257 def test_switching_between_states(self):
258 """The user must be able to type many different characters including
259 spaces and backspaces.
260
261 """
262 text_area = self.launch_test_input_area()
263 self.ensure_focus_on_input(text_area)
264 keyboard = Keyboard()
265 self.addCleanup(keyboard.dismiss)
266
267 keyboard.type(
268 'abc gone\b\b & \bABC (123)'
269 )
270
271 expected = "abc go & ABC (123)"
272 self.assertThat(
273 text_area.text,
274 Eventually(Equals(expected))
275 )
276
277
278class UbuntuKeyboardInputTypeStateChange(UbuntuKeyboardTests):
279 """Note: these tests are currently failing due to bug lp:1214694 (the
280 activeView detail isn't exposed correctly nor is it updated as expected
281 (i.e. when the view changes.))
282
283 """
284
285 scenarios = [
286 (
287 "Url",
288 dict(
289 label="Url",
290 hints=['Qt.ImhUrlCharactersOnly'],
291 expected_activeview="url"
292 )
293 ),
294 (
295 "Password",
296 dict(
297 label="Password",
298 hints=['Qt.ImhHiddenText', 'Qt.ImhSensitiveData'],
299 expected_activeview="password"
300 )
301 ),
302 (
303 "Email",
304 dict(
305 label="Email",
306 hints=['Qt.ImhEmailCharactersOnly'],
307 expected_activeview="email"
308 )
309 ),
310 (
311 "Number",
312 dict(
313 label="Number",
314 hints=['Qt.ImhFormattedNumbersOnly'],
315 expected_activeview="number"
316 )
317 ),
318 (
319 "Telephone",
320 dict(
321 label="Telephone",
322 hints=['Qt.ImhDigitsOnly'],
323 expected_activeview="phonenumber"
324 )
325 ),
326 ]
327
328 # Note: based on UX design doc
329 def test_keyboard_layout(self):
330 """The Keyboard must respond to the input type and change to be the
331 correct state.
332
333 """
334 text_area = self.launch_test_input_area(self.label, self.hints)
335 self.ensure_focus_on_input(text_area)
336 keyboard = Keyboard()
337 self.addCleanup(keyboard.dismiss)
338
339 self.assertThat(
340 keyboard.keyboard.activeView,
341 Eventually(Equals(self.expected_activeview))
342 )

Subscribers

People subscribed via source and target branches