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