Merge lp:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers into lp:ubuntu-ui-toolkit
- reorg_autopilot_helpers
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers |
Merge into: | lp:ubuntu-ui-toolkit |
Prerequisite: | lp:~elopio/ubuntu-ui-toolkit/flake8 |
Diff against target: |
7625 lines (+3882/-2405) 54 files modified
components.api (+10/-0) debian/control (+4/-0) modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp (+61/-24) modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h (+2/-0) modules/Ubuntu/Components/plugin/alarmmanager_p.h (+10/-0) modules/Ubuntu/Components/plugin/plugin.cpp (+3/-5) modules/Ubuntu/Components/plugin/ucalarm.cpp (+33/-27) modules/Ubuntu/Components/plugin/ucalarm_p.h (+1/-1) modules/Ubuntu/Components/plugin/ucmouse.h (+52/-11) modules/Ubuntu/Components/plugin/ucmousefilters.cpp (+220/-84) modules/Ubuntu/Test/UbuntuTestCase.qml (+69/-1) modules/Ubuntu/Test/deployment.pri (+6/-1) modules/Ubuntu/Test/plugin/uctestcase.cpp (+12/-0) modules/Ubuntu/Test/plugin/uctestcase.h (+1/-1) tests/autopilot/ubuntuuitoolkit/__init__.py (+0/-17) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py (+66/-781) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py (+65/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py (+69/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py (+150/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py (+57/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py (+114/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py (+162/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py (+82/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py (+65/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py (+68/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py (+41/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py (+88/-0) tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py (+106/-0) tests/autopilot/ubuntuuitoolkit/emulators.py (+85/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py (+136/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py (+68/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py (+41/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py (+170/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py (+132/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py (+172/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py (+193/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py (+149/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py (+93/-0) tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py (+121/-0) tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py (+0/-1081) tests/resources/alarm/Alarms.qml (+6/-2) tests/unit/runtest.sh (+1/-2) tests/unit/tst_alarms/tst_alarms.cpp (+204/-21) tests/unit/tst_mainview/tst_mainview.cpp (+3/-6) tests/unit/tst_page/tst_page.cpp (+3/-39) tests/unit_x11/tst_mousefilters/ForwardComposedEvents.qml (+55/-0) tests/unit_x11/tst_mousefilters/ForwardEventChained.qml (+51/-0) tests/unit_x11/tst_mousefilters/HoverEvent.qml (+38/-0) tests/unit_x11/tst_mousefilters/tst_mousefilters.pro (+4/-1) tests/unit_x11/tst_mousefilters/tst_mousefilterstest.cpp (+396/-201) tests/unit_x11/tst_orientation/tst_orientation.cpp (+9/-47) tests/unit_x11/tst_statesaver/tst_statesaver.cpp (+29/-37) tests/unit_x11/tst_test/tst_ubuntutestcase.qml (+103/-15) tests/unit_x11/tst_theme_engine/tst_theme_enginetest.cpp (+3/-0) |
To merge this branch: | bzr merge lp:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Cris Dywan | Approve | ||
Review via email:
|
This proposal supersedes a proposal from 2014-04-11.
This proposal has been superseded by a proposal from 2014-04-17.
Commit message
Deprecated the ubuntuuitoolkit
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1013
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1014
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1017
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Cris Dywan (kalikiana) wrote : | # |
Makes sense. I presume we need to wait on having this altered in the Jenkins configuration since it'll always fail otherwise.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1018
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1019
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 1020. By Leo Arias
-
Split the tests.
- 1021. By Leo Arias
-
Use __all__.
- 1022. By Leo Arias
-
Fixed flake8.
- 1023. By Leo Arias
-
Rename the rest of emulators.
- 1024. By Leo Arias
-
Merged with trunk.
- 1025. By Leo Arias
-
Merged with staging.
- 1026. By Leo Arias
-
Added the missing option selector files.
- 1027. By Leo Arias
-
Flake8 is now not prerequisite.
- 1028. By Leo Arias
-
Flake8 is now not prerequisite.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1024
http://
Executed test runs:
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 1029. By Leo Arias
-
Use a reload compatible with py3
. - 1030. By Leo Arias
-
Expose the rest of the modules.
- 1031. By Leo Arias
-
Fixed the new circular dep.
- 1032. By Leo Arias
-
Fixed the new circular dep.
- 1033. By Leo Arias
-
Fixed the new circular dep.
- 1034. By Leo Arias
-
Merged with 30-optIn-
tabsDrawer. - 1035. By Leo Arias
-
Merged with 40-back-in-header.
- 1036. By Leo Arias
-
Reverted the change on the control.
- 1037. By Leo Arias
-
Reverted the change in debina/rules.
- 1038. By Leo Arias
-
Merged with staging.
- 1039. By Leo Arias
-
Merged with staging.
- 1040. By Leo Arias
-
Changed the namespace for listitems and popups.
- 1041. By Leo Arias
-
Merged with staging.
- 1042. By Leo Arias
-
Fixed the popups import.
Unmerged revisions
Preview Diff
1 | === modified file 'components.api' |
2 | --- components.api 2014-04-01 12:57:27 +0000 |
3 | +++ components.api 2014-04-17 01:29:24 +0000 |
4 | @@ -593,6 +593,8 @@ |
5 | function findChild(obj,objectName) |
6 | function findInvisibleChild(obj,objectName) |
7 | function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay) |
8 | + function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) |
9 | + function mouseLongPress(item, x, y, button, modifiers, delay) |
10 | function tryCompareFunction(func, expectedResult, timeout) |
11 | plugins.qmltypes |
12 | name: "InverseMouseAreaType" |
13 | @@ -687,27 +689,35 @@ |
14 | Signal { |
15 | name: "pressed" |
16 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
17 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
18 | Signal { |
19 | name: "released" |
20 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
21 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
22 | Signal { |
23 | name: "clicked" |
24 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
25 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
26 | Signal { |
27 | name: "pressAndHold" |
28 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
29 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
30 | Signal { |
31 | name: "doubleClicked" |
32 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
33 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
34 | Signal { |
35 | name: "positionChanged" |
36 | Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true } |
37 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
38 | Signal { |
39 | name: "entered" |
40 | Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true } |
41 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
42 | Signal { |
43 | name: "exited" |
44 | Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true } |
45 | + Parameter { name: "host"; type: "QQuickItem"; isPointer: true } |
46 | name: "UCQQuickImageExtension" |
47 | prototype: "QQuickImageBase" |
48 | exports: ["QQuickImageBase 0.1"] |
49 | |
50 | === modified file 'debian/control' |
51 | --- debian/control 2014-04-17 01:29:24 +0000 |
52 | +++ debian/control 2014-04-17 01:29:24 +0000 |
53 | @@ -135,7 +135,11 @@ |
54 | python-autopilot (>= 1.4), |
55 | python-fixtures, |
56 | python-mock, |
57 | + python-testscenarios, |
58 | + python-testtools, |
59 | python3-fixtures, |
60 | + python3-testscenarios, |
61 | + python3-testtools, |
62 | python3-autopilot (>= 1.4), |
63 | ubuntu-ui-toolkit-examples (>= ${source:Version}), |
64 | Description: Test package for Ubuntu UI Toolkit |
65 | |
66 | === modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp' |
67 | --- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-02-21 20:02:29 +0000 |
68 | +++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-04-17 01:29:24 +0000 |
69 | @@ -21,6 +21,7 @@ |
70 | #include "alarmmanager_p.h" |
71 | #include "alarmrequest_p.h" |
72 | #include "alarmsadapter_p.h" |
73 | +#include <qorganizertodooccurrence.h> |
74 | |
75 | #include <QtCore/QFile> |
76 | #include <QtCore/QDir> |
77 | @@ -55,11 +56,15 @@ |
78 | , manager(0) |
79 | , fetchRequest(0) |
80 | { |
81 | - QOrganizerManager local; |
82 | - bool usingDefaultManager = local.availableManagers().contains(ALARM_MANAGER); |
83 | - manager = (usingDefaultManager) ? new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK); |
84 | + QString envManager(qgetenv("ALARM_BACKEND")); |
85 | + if (!envManager.isEmpty() && QOrganizerManager::availableManagers().contains(envManager)) { |
86 | + manager = new QOrganizerManager(envManager); |
87 | + } else { |
88 | + manager = QOrganizerManager::availableManagers().contains(ALARM_MANAGER) ? |
89 | + new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK); |
90 | + } |
91 | manager->setParent(q_ptr); |
92 | - if (!usingDefaultManager) { |
93 | + if (manager->managerName() != ALARM_MANAGER) { |
94 | qWarning() << "WARNING: default alarm manager not installed, using" << manager->managerName() << "manager."; |
95 | qWarning() << "This manager may not provide all the needed features."; |
96 | } |
97 | @@ -114,6 +119,7 @@ |
98 | |
99 | int type, days; |
100 | in >> alarm.message >> alarm.date >> alarm.sound >> type >> days >> alarm.enabled; |
101 | + alarm.originalDate = alarm.date = AlarmData::transcodeDate(alarm.date, Qt::LocalTime); |
102 | alarm.type = static_cast<UCAlarm::AlarmType>(type); |
103 | alarm.days = static_cast<UCAlarm::DaysOfWeek>(days); |
104 | |
105 | @@ -142,7 +148,7 @@ |
106 | |
107 | Q_FOREACH(const AlarmData &alarm, alarmList) { |
108 | out << alarm.message |
109 | - << alarm.date |
110 | + << AlarmData::transcodeDate(alarm.originalDate, Qt::UTC) |
111 | << alarm.sound |
112 | << alarm.type |
113 | << alarm.days |
114 | @@ -156,8 +162,7 @@ |
115 | { |
116 | event.setCollectionId(collection.id()); |
117 | event.setAllDay(false); |
118 | - event.setStartDateTime(alarm.date); |
119 | - event.setDueDateTime(alarm.date); |
120 | + event.setStartDateTime(AlarmData::transcodeDate(alarm.date, Qt::UTC)); |
121 | event.setDisplayLabel(alarm.message); |
122 | |
123 | if (alarm.enabled) { |
124 | @@ -224,8 +229,9 @@ |
125 | |
126 | alarm.cookie = QVariant::fromValue<QOrganizerItemId>(event.id()); |
127 | alarm.message = event.displayLabel(); |
128 | - alarm.date = AlarmData::normalizeDate(event.dueDateTime()); |
129 | + alarm.date = AlarmData::transcodeDate(event.startDateTime().toUTC(), Qt::LocalTime); |
130 | alarm.sound = QUrl(event.description()); |
131 | + alarm.originalDate = alarm.date; |
132 | |
133 | // check if the alarm is enabled or not |
134 | QOrganizerItemVisualReminder visual = event.detail(QOrganizerItemDetail::TypeVisualReminder); |
135 | @@ -303,8 +309,8 @@ |
136 | Q_FOREACH(const QOrganizerItem &item, alarms) { |
137 | // repeating alarms may be fetched as occurences, therefore check their parent event |
138 | if (item.type() == QOrganizerItemType::TypeTodoOccurrence) { |
139 | - QOrganizerTodoOccurrence occurence = static_cast<QOrganizerTodoOccurrence>(item); |
140 | - QOrganizerItemId eventId = occurence.parentId(); |
141 | + QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(item); |
142 | + QOrganizerItemId eventId = occurrence.parentId(); |
143 | if (parentId.contains(eventId)) { |
144 | continue; |
145 | } |
146 | @@ -317,6 +323,7 @@ |
147 | } |
148 | AlarmData alarm; |
149 | if (alarmDataFromOrganizerEvent(event, alarm) == UCAlarm::NoError) { |
150 | + adjustAlarmOccurrence(event, alarm); |
151 | alarmList << alarm; |
152 | } |
153 | } |
154 | @@ -324,9 +331,52 @@ |
155 | saveAlarms(); |
156 | Q_EMIT q_ptr->alarmsChanged(); |
157 | completed = true; |
158 | + fetchRequest->deleteLater(); |
159 | fetchRequest = 0; |
160 | } |
161 | |
162 | +void AlarmsAdapter::adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm) |
163 | +{ |
164 | + if (alarm.type == UCAlarm::OneTime) { |
165 | + return; |
166 | + } |
167 | + // with EDS we need to query the occurrences separately as the fetch reports only the main events |
168 | + // with fallback manager this does not reduce the performance and does work the same way. |
169 | + QDateTime currentDate = AlarmData::normalizeDate(QDateTime::currentDateTime()); |
170 | + if (alarm.date > currentDate) { |
171 | + // no need to adjust date, the event occurs in the future |
172 | + return; |
173 | + } |
174 | + QDateTime startDate; |
175 | + QDateTime endDate; |
176 | + if (alarm.type == UCAlarm::Repeating) { |
177 | + // 8 days is enough from the starting date (or current date depending on the start date) |
178 | + startDate = (alarm.date > currentDate) ? alarm.date : currentDate; |
179 | + endDate = startDate.addDays(8); |
180 | + } |
181 | + |
182 | + // transcode both dates |
183 | + startDate = AlarmData::transcodeDate(startDate, Qt::UTC); |
184 | + endDate = AlarmData::transcodeDate(endDate, Qt::UTC); |
185 | + |
186 | + QList<QOrganizerItem> occurrences = manager->itemOccurrences(event, startDate, endDate, 10); |
187 | + // get the first occurrence and use the date from it |
188 | + if ((occurrences.length() > 0) && (occurrences[0].type() == QOrganizerItemType::TypeTodoOccurrence)) { |
189 | + // loop till we get a proper future due date |
190 | + for (int i = 0; i < occurrences.count(); i++) { |
191 | + QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(occurrences[i]); |
192 | + // check if the date is after the current datetime |
193 | + // the first occurrence is the one closest to the currentDate, therefore we can safely |
194 | + // set that startDate to the alarm |
195 | + alarm.date = AlarmData::transcodeDate(occurrence.startDateTime().toUTC(), Qt::LocalTime); |
196 | + if (alarm.date > currentDate) { |
197 | + // we have the proper date set, leave |
198 | + break; |
199 | + } |
200 | + } |
201 | + } |
202 | +} |
203 | + |
204 | /*----------------------------------------------------------------------------- |
205 | * AlarmRequestAdapter implementation |
206 | */ |
207 | @@ -410,23 +460,10 @@ |
208 | QOrganizerItemFetchRequest *operation = new QOrganizerItemFetchRequest(q_ptr); |
209 | operation->setManager(owner->manager); |
210 | |
211 | - // FIXME: Since returning all events without a limit of date is not a good solution we need to find |
212 | - // a better solution for that. |
213 | - // The current solution filters only the next 7 days (one week). |
214 | - // This will be enough for now, since the current alarms occur weekly, but for the future |
215 | - // we want to allow create alarms with monthly or yearly recurrence |
216 | - QDate currentDate = QDate::currentDate(); |
217 | - QDateTime startDate(currentDate, |
218 | - QTime(0,0,0)); |
219 | - QDateTime endDate(currentDate.addDays(7), |
220 | - QTime(23,59,59)); |
221 | - operation->setStartDate(startDate); |
222 | - operation->setEndDate(endDate); |
223 | - |
224 | // set sort order |
225 | QOrganizerItemSortOrder sortOrder; |
226 | sortOrder.setDirection(Qt::AscendingOrder); |
227 | - sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldDueDateTime); |
228 | + sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldStartDateTime); |
229 | operation->setSorting(QList<QOrganizerItemSortOrder>() << sortOrder); |
230 | |
231 | // set filter |
232 | |
233 | === modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h' |
234 | --- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2013-09-18 10:13:04 +0000 |
235 | +++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2014-04-17 01:29:24 +0000 |
236 | @@ -24,6 +24,7 @@ |
237 | |
238 | #include <qorganizer.h> |
239 | #include <qorganizermanager.h> |
240 | +#include <qorganizertodo.h> |
241 | |
242 | QTORGANIZER_USE_NAMESPACE |
243 | |
244 | @@ -78,6 +79,7 @@ |
245 | QOrganizerCollection collection; |
246 | |
247 | void completeFetchAlarms(const QList<QOrganizerItem> &alarmList); |
248 | + void adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm); |
249 | |
250 | void loadAlarms(); |
251 | void saveAlarms(); |
252 | |
253 | === modified file 'modules/Ubuntu/Components/plugin/alarmmanager_p.h' |
254 | --- modules/Ubuntu/Components/plugin/alarmmanager_p.h 2013-09-12 05:27:40 +0000 |
255 | +++ modules/Ubuntu/Components/plugin/alarmmanager_p.h 2014-04-17 01:29:24 +0000 |
256 | @@ -48,6 +48,7 @@ |
257 | AlarmData(const AlarmData &other) |
258 | : changes(0) |
259 | , cookie(other.cookie) |
260 | + , originalDate(other.originalDate) |
261 | , date(other.date) |
262 | , message(other.message) |
263 | , type(other.type) |
264 | @@ -100,10 +101,19 @@ |
265 | return QDateTime(dt.date(), time, dt.timeSpec()); |
266 | } |
267 | |
268 | + // the function normalizes and transcodes the date into UTC/LocalTime equivalent |
269 | + static QDateTime transcodeDate(const QDateTime &dt, Qt::TimeSpec targetSpec) { |
270 | + if (dt.timeSpec() == targetSpec) { |
271 | + return normalizeDate(dt); |
272 | + } |
273 | + return QDateTime(dt.date(), normalizeDate(dt).time(), targetSpec); |
274 | + } |
275 | + |
276 | unsigned int changes; |
277 | QVariant cookie; |
278 | |
279 | // data members |
280 | + QDateTime originalDate; |
281 | QDateTime date; |
282 | QString message; |
283 | QUrl sound; |
284 | |
285 | === modified file 'modules/Ubuntu/Components/plugin/plugin.cpp' |
286 | --- modules/Ubuntu/Components/plugin/plugin.cpp 2014-03-05 12:29:58 +0000 |
287 | +++ modules/Ubuntu/Components/plugin/plugin.cpp 2014-04-17 01:29:24 +0000 |
288 | @@ -53,9 +53,6 @@ |
289 | #include <unistd.h> |
290 | #include <stdexcept> |
291 | |
292 | -// Needed for unit tests |
293 | -Q_DECLARE_METATYPE(QList<QQmlError>) |
294 | - |
295 | /* |
296 | * Type registration functions. |
297 | */ |
298 | @@ -177,10 +174,11 @@ |
299 | qmlRegisterSingletonType<UCUriHandler>(uri, 0, 1, "UriHandler", registerUriHandler); |
300 | qmlRegisterType<UCMouse>(uri, 0, 1, "Mouse"); |
301 | qmlRegisterType<UCInverseMouse>(uri, 0, 1, "InverseMouse"); |
302 | - // Needed for unit tests |
303 | - qRegisterMetaType<QList <QQmlError> >(); |
304 | // register QML singletons |
305 | qmlRegisterSingletonType<QObject>(uri, 0, 1, "PickerPanel", registerPickerPanel); |
306 | + |
307 | + // register custom event |
308 | + ForwardedEvent::registerForwardedEvent(); |
309 | } |
310 | |
311 | void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri) |
312 | |
313 | === modified file 'modules/Ubuntu/Components/plugin/ucalarm.cpp' |
314 | --- modules/Ubuntu/Components/plugin/ucalarm.cpp 2013-09-18 10:25:18 +0000 |
315 | +++ modules/Ubuntu/Components/plugin/ucalarm.cpp 2014-04-17 01:29:24 +0000 |
316 | @@ -102,15 +102,21 @@ |
317 | |
318 | int UCAlarmPrivate::nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay) |
319 | { |
320 | - if (fromDay <= 0) { |
321 | + if (fromDay <= 0 || fromDay >= Qt::Sunday) { |
322 | + // start from the beginning of the week |
323 | fromDay = Qt::Monday; |
324 | + } else { |
325 | + // start checking from the next day onwards |
326 | + fromDay++; |
327 | } |
328 | for (int d = fromDay; d <= Qt::Sunday; d++) { |
329 | if ((1 << (d - 1)) & days) { |
330 | return d; |
331 | } |
332 | } |
333 | - return 0; |
334 | + |
335 | + // none found for the rest of the week, return the fist day set |
336 | + return firstDayOfWeek(days); |
337 | } |
338 | |
339 | // checks whether the given num has more than one bit set |
340 | @@ -147,23 +153,14 @@ |
341 | return UCAlarm::NoError; |
342 | } |
343 | |
344 | -UCAlarm::Error UCAlarmPrivate::checkDow() |
345 | +// adjust dayOfWeek |
346 | +UCAlarm::Error UCAlarmPrivate::adjustDow() |
347 | { |
348 | if (!rawData.days) { |
349 | return UCAlarm::NoDaysOfWeek; |
350 | } else if (rawData.days == UCAlarm::AutoDetect) { |
351 | rawData.days = dayOfWeek(rawData.date); |
352 | rawData.changes |= AlarmData::Days; |
353 | - } else if (rawData.days != UCAlarm::Daily) { |
354 | - int alarmDay = firstDayOfWeek(rawData.days); |
355 | - int dayOfWeek = rawData.date.date().dayOfWeek(); |
356 | - if (alarmDay < dayOfWeek) { |
357 | - rawData.date = rawData.date.addDays(7 - dayOfWeek + alarmDay); |
358 | - rawData.changes |= AlarmData::Date; |
359 | - } else if (alarmDay > dayOfWeek) { |
360 | - rawData.date = rawData.date.addDays(alarmDay - dayOfWeek); |
361 | - rawData.changes |= AlarmData::Date; |
362 | - } |
363 | } |
364 | return UCAlarm::NoError; |
365 | } |
366 | @@ -175,8 +172,7 @@ |
367 | return UCAlarm::OneTimeOnMoreDays; |
368 | } |
369 | |
370 | - // adjust start date and/or dayOfWeek according to their values |
371 | - UCAlarm::Error result = checkDow(); |
372 | + UCAlarm::Error result = adjustDow(); |
373 | if (result != UCAlarm::NoError) { |
374 | return result; |
375 | } |
376 | @@ -193,23 +189,25 @@ |
377 | // start date is adjusted depending on the days value; |
378 | // start date can be set to be the current time, as scheduling will move |
379 | // it to the first occurence. |
380 | - UCAlarm::Error result = checkDow(); |
381 | + UCAlarm::Error result = adjustDow(); |
382 | if (result != UCAlarm::NoError) { |
383 | return result; |
384 | } |
385 | |
386 | - // move start time to the first occurence if needed |
387 | + // move start time of the first occurence if needed |
388 | int dayOfWeek = rawData.date.date().dayOfWeek(); |
389 | if (!isDaySet(dayOfWeek, rawData.days) || (rawData.date <= QDateTime::currentDateTime())) { |
390 | // check the next occurence of the alarm |
391 | - int nextOccurence = nextDayOfWeek(rawData.days, dayOfWeek); |
392 | - if (nextOccurence <= 0) { |
393 | - // the starting date should be moved to the next week |
394 | - nextOccurence = firstDayOfWeek(rawData.days); |
395 | - rawData.date.addDays(7 - dayOfWeek + nextOccurence); |
396 | + int nextOccurrence = nextDayOfWeek(rawData.days, dayOfWeek); |
397 | + if (nextOccurrence == dayOfWeek) { |
398 | + // move the date to the same day next week |
399 | + rawData.date = rawData.date.addDays(7); |
400 | + } else if (nextOccurrence < dayOfWeek) { |
401 | + // the starting date should be moved to the next week's occurrence |
402 | + rawData.date = rawData.date.addDays(7 - dayOfWeek + nextOccurrence); |
403 | } else { |
404 | // the starting date is still this week |
405 | - rawData.date.addDays(nextOccurence - dayOfWeek); |
406 | + rawData.date = rawData.date.addDays(nextOccurrence - dayOfWeek); |
407 | } |
408 | rawData.changes |= AlarmData::Date; |
409 | } |
410 | @@ -304,7 +302,7 @@ |
411 | : QObject(parent) |
412 | , d_ptr(new UCAlarmPrivate(this)) |
413 | { |
414 | - d_ptr->rawData.date = dt; |
415 | + d_ptr->rawData.date = AlarmData::normalizeDate(dt); |
416 | if (!message.isEmpty()) { |
417 | d_ptr->rawData.message = message; |
418 | } |
419 | @@ -315,7 +313,7 @@ |
420 | : QObject(parent) |
421 | , d_ptr(new UCAlarmPrivate(this)) |
422 | { |
423 | - d_ptr->rawData.date = dt; |
424 | + d_ptr->rawData.date = AlarmData::normalizeDate(dt); |
425 | d_ptr->rawData.type = Repeating; |
426 | d_ptr->rawData.days = days; |
427 | if (!message.isEmpty()) { |
428 | @@ -366,12 +364,16 @@ |
429 | void UCAlarm::setDate(const QDateTime &date) |
430 | { |
431 | Q_D(UCAlarm); |
432 | - if (d->rawData.date == date) { |
433 | + if (d->rawData.date == AlarmData::normalizeDate(date)) { |
434 | return; |
435 | } |
436 | - d->rawData.date = date; |
437 | + d->rawData.date = AlarmData::normalizeDate(date); |
438 | d->rawData.changes |= AlarmData::Date; |
439 | Q_EMIT dateChanged(); |
440 | + if (d->rawData.type == UCAlarm::OneTime) { |
441 | + // adjust dayOfWeek as well |
442 | + setDaysOfWeek(UCAlarm::AutoDetect); |
443 | + } |
444 | } |
445 | |
446 | /*! |
447 | @@ -520,6 +522,8 @@ |
448 | Q_EMIT soundChanged(); |
449 | } |
450 | |
451 | + |
452 | + |
453 | /*! |
454 | * \qmlproperty Error Alarm::error |
455 | * The property holds the error code occurred during the last performed operation. |
456 | @@ -670,6 +674,8 @@ |
457 | if (result != UCAlarm::NoError) { |
458 | d->_q_syncStatus(Saving, Fail, result); |
459 | } else { |
460 | + // the alarm has been modified, therefore update the original date as well |
461 | + d->rawData.originalDate = d->rawData.date; |
462 | if (d->createRequest()) { |
463 | d->request->save(d->rawData); |
464 | } |
465 | |
466 | === modified file 'modules/Ubuntu/Components/plugin/ucalarm_p.h' |
467 | --- modules/Ubuntu/Components/plugin/ucalarm_p.h 2013-09-18 10:25:18 +0000 |
468 | +++ modules/Ubuntu/Components/plugin/ucalarm_p.h 2014-04-17 01:29:24 +0000 |
469 | @@ -50,7 +50,7 @@ |
470 | static int nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay); |
471 | static bool multipleDaysSet(UCAlarm::DaysOfWeek days); |
472 | UCAlarm::Error checkAlarm(); |
473 | - UCAlarm::Error checkDow(); |
474 | + UCAlarm::Error adjustDow(); |
475 | UCAlarm::Error checkOneTime(); |
476 | UCAlarm::Error checkRepeatingWeekly(); |
477 | |
478 | |
479 | === modified file 'modules/Ubuntu/Components/plugin/ucmouse.h' |
480 | --- modules/Ubuntu/Components/plugin/ucmouse.h 2014-02-13 17:16:06 +0000 |
481 | +++ modules/Ubuntu/Components/plugin/ucmouse.h 2014-04-17 01:29:24 +0000 |
482 | @@ -20,10 +20,47 @@ |
483 | |
484 | #include <QtCore/QObject> |
485 | #include <QtQml> |
486 | +#include <QtQuick/QQuickItem> |
487 | #include <private/qquickevents_p_p.h> |
488 | #include <QtCore/qbasictimer.h> |
489 | |
490 | -class QQuickItem; |
491 | +class ForwardedEvent : public QEvent { |
492 | +public: |
493 | + enum EventType { |
494 | + MousePress, |
495 | + MouseRelease, |
496 | + MouseMove, |
497 | + MouseDblClick, |
498 | + HoverEnter, |
499 | + HoverExit, |
500 | + MouseClick, |
501 | + MouseLongPress, |
502 | + }; |
503 | + ForwardedEvent(EventType type, QQuickItem *sender, QEvent *originalEvent, QQuickMouseEvent *quickEvent) |
504 | + : QEvent((QEvent::Type)m_eventBase) |
505 | + , m_subType(type) |
506 | + , m_sender(sender) |
507 | + , m_originalEvent(originalEvent) |
508 | + , m_quickEvent(quickEvent) |
509 | + { |
510 | + setAccepted(false); |
511 | + } |
512 | + |
513 | + static void registerForwardedEvent(); |
514 | + |
515 | + EventType subType() const { return m_subType; } |
516 | + QQuickItem *sender() const { return m_sender; } |
517 | + QQuickMouseEvent *quickEvent() const { return m_quickEvent; } |
518 | + QEvent *originalEvent() const { return m_originalEvent; } |
519 | + static QEvent::Type baseType() { return m_eventBase; } |
520 | +private: |
521 | + EventType m_subType; |
522 | + QPointer<QQuickItem> m_sender; |
523 | + QEvent *m_originalEvent; |
524 | + QPointer<QQuickMouseEvent> m_quickEvent; |
525 | + static QEvent::Type m_eventBase; |
526 | +}; |
527 | + |
528 | class UCMouse : public QObject |
529 | { |
530 | Q_OBJECT |
531 | @@ -62,23 +99,24 @@ |
532 | void clickAndHoldThresholdChanged(); |
533 | void priorityChanged(); |
534 | |
535 | - void pressed(QQuickMouseEvent *mouse); |
536 | - void released(QQuickMouseEvent *mouse); |
537 | - void clicked(QQuickMouseEvent *mouse); |
538 | - void pressAndHold(QQuickMouseEvent *mouse); |
539 | - void doubleClicked(QQuickMouseEvent *mouse); |
540 | - void positionChanged(QQuickMouseEvent *mouse); |
541 | - void entered(QQuickMouseEvent *event); |
542 | - void exited(QQuickMouseEvent *event); |
543 | + void pressed(QQuickMouseEvent *mouse, QQuickItem *host); |
544 | + void released(QQuickMouseEvent *mouse, QQuickItem *host); |
545 | + void clicked(QQuickMouseEvent *mouse, QQuickItem *host); |
546 | + void pressAndHold(QQuickMouseEvent *mouse, QQuickItem *host); |
547 | + void doubleClicked(QQuickMouseEvent *mouse, QQuickItem *host); |
548 | + void positionChanged(QQuickMouseEvent *mouse, QQuickItem *host); |
549 | + void entered(QQuickMouseEvent *event, QQuickItem *host); |
550 | + void exited(QQuickMouseEvent *event, QQuickItem *host); |
551 | |
552 | protected: |
553 | virtual bool eventFilter(QObject *, QEvent *); |
554 | virtual void timerEvent(QTimerEvent *event); |
555 | virtual bool mouseEvents(QObject *target, QMouseEvent *event); |
556 | virtual bool hoverEvents(QObject *target, QHoverEvent *event); |
557 | + virtual bool forwardedEvents(ForwardedEvent *event); |
558 | virtual bool hasAttachedFilter(QQuickItem *item); |
559 | |
560 | - void setHovered(bool hovered); |
561 | + void setHovered(bool hovered, QEvent *hoverEvent); |
562 | bool mousePressed(QMouseEvent *event); |
563 | bool mouseReleased(QMouseEvent *event); |
564 | bool mouseDblClick(QMouseEvent *event); |
565 | @@ -91,7 +129,7 @@ |
566 | bool isDoubleClickConnected(); |
567 | bool isMouseEvent(QEvent::Type type); |
568 | bool isHoverEvent(QEvent::Type type); |
569 | - void forwardEvent(QEvent *event); |
570 | + bool forwardEvent(ForwardedEvent::EventType type, QEvent *event, QQuickMouseEvent *quickEvent); |
571 | |
572 | protected: |
573 | QQuickItem *m_owner; |
574 | @@ -107,6 +145,7 @@ |
575 | Priority m_priority; |
576 | int m_moveThreshold; |
577 | |
578 | + bool m_signalWhenContains:1; |
579 | bool m_enabled: 1; |
580 | bool m_moved:1; |
581 | bool m_longPress:1; |
582 | @@ -115,4 +154,6 @@ |
583 | }; |
584 | QML_DECLARE_TYPEINFO(UCMouse, QML_HAS_ATTACHED_PROPERTIES) |
585 | |
586 | +extern const int DefaultPressAndHoldDelay; |
587 | + |
588 | #endif // UCMOUSE_H |
589 | |
590 | === modified file 'modules/Ubuntu/Components/plugin/ucmousefilters.cpp' |
591 | --- modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-02-13 17:16:06 +0000 |
592 | +++ modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-04-17 01:29:24 +0000 |
593 | @@ -18,7 +18,6 @@ |
594 | #include "ucmouse.h" |
595 | #include "ucinversemouse.h" |
596 | #include <QtQml/QQmlInfo> |
597 | -#include <QtQuick/QQuickItem> |
598 | #include <QtGui/QGuiApplication> |
599 | #include <private/qqmlglobal_p.h> |
600 | #include <QtQuick/private/qquickmousearea_p.h> |
601 | @@ -31,6 +30,15 @@ |
602 | // keep in sync with QQuickMouseArea PressAndHoldDelay |
603 | const int DefaultPressAndHoldDelay = 800; |
604 | |
605 | +QEvent::Type ForwardedEvent::m_eventBase = QEvent::None; |
606 | +void ForwardedEvent::registerForwardedEvent() |
607 | +{ |
608 | + if (m_eventBase > 0) { |
609 | + return; |
610 | + } |
611 | + m_eventBase = (QEvent::Type)QEvent::registerEventType(); |
612 | +} |
613 | + |
614 | /* |
615 | * Attached filter instantiator template |
616 | */ |
617 | @@ -158,7 +166,8 @@ |
618 | their parent. In this way mouse events will land in these items too, and mouse |
619 | filter attached to those can also handle the event. This is useful when creating |
620 | custom types where the mouse handling item is nested into a non-mouse handling |
621 | - one, and we want to provide additional filtering possibility to the user. |
622 | + one, and we want to provide additional filtering possibility to the user. These |
623 | + type of items are called proxy handlers. |
624 | |
625 | \qml |
626 | Item { |
627 | @@ -180,7 +189,54 @@ |
628 | In this example the mouse press is first handled by the mouse filter attached |
629 | to TextInput, then it is forwarded to the top item and finally to the TextInput. |
630 | Accepting the mouse event will stop propagation to the top item as well as to |
631 | - the TextInput. |
632 | + the TextInput. The topmost item itself does not handle mouse events, therefore |
633 | + it will be a sinple proxy handler item. However, proxies can themself handle |
634 | + mouse events. Therefore each mouse event signal has the \a host parameter specifying |
635 | + the sender of the mouse event reported. |
636 | + |
637 | + \note The forwarded events are handled in the proxy handlers only if the mouse |
638 | + position points inside their area. If the forwarded mouse position falls outside |
639 | + the target area, the event will not be reported, however will be forwarded further |
640 | + to the items in the list. In the following example the mouse press in red rectangle |
641 | + will be printed as well as the proxied mouse press from the main item. |
642 | + \qml |
643 | + import QtQuick 2.0 |
644 | + import Ubuntu.Components 0.1 |
645 | + |
646 | + Item { |
647 | + id: main |
648 | + width: units.gu(40) |
649 | + height: units.gu(71) |
650 | + |
651 | + Mouse.onPressed: console.log("got the mouse press forwarded by " + host.objectName) |
652 | + |
653 | + Column { |
654 | + anchors.fill: parent |
655 | + spacing: units.gu(1) |
656 | + |
657 | + Rectangle { |
658 | + id: blueRect |
659 | + objectName: "BlueRect" |
660 | + width: parent.width |
661 | + height: units.gu(20) |
662 | + color: "blue" |
663 | + Mouse.forwardTo: [main] |
664 | + Mouse.onPressed: console.log("This should not be printed") |
665 | + } |
666 | + Rectangle { |
667 | + objectName: "RedRect" |
668 | + width: parent.width |
669 | + height: units.gu(20) |
670 | + color: "red" |
671 | + MouseArea { |
672 | + anchors.fill: parent |
673 | + Mouse.forwardTo: [blueRect] |
674 | + Mouse.onPressed: console.log("Pressed in " + host.objectName) |
675 | + } |
676 | + } |
677 | + } |
678 | + } |
679 | + \endqml |
680 | |
681 | An interesting feature that can be achieved using Mouse filter is the event |
682 | "transparency" towards the MouseArea lying behind the items which handle mouse |
683 | @@ -226,6 +282,7 @@ |
684 | } |
685 | \endqml |
686 | |
687 | + |
688 | Mouse filter provides ability to control the order of the event dispatching. |
689 | The filter can receive the events prior the owner or after the owner. This |
690 | can be controlled through the \l priority property. In the following example |
691 | @@ -281,6 +338,9 @@ |
692 | UCMouse::UCMouse(QObject *parent) |
693 | : QObject(parent) |
694 | , m_owner(qobject_cast<QQuickItem*>(parent)) |
695 | + , m_lastButton(Qt::NoButton) |
696 | + , m_lastButtons(Qt::NoButton) |
697 | + , m_lastModifiers(Qt::NoModifier) |
698 | , m_pressedButtons(Qt::NoButton) |
699 | , m_priority(BeforeItem) |
700 | , m_moveThreshold(0.0) |
701 | @@ -328,6 +388,9 @@ |
702 | } else { |
703 | return hoverEvents(target, static_cast<QHoverEvent*>(event)); |
704 | } |
705 | + } else if (type == ForwardedEvent::baseType()) { |
706 | + // this is a forwarded event, handle it as such |
707 | + return forwardedEvents(static_cast<ForwardedEvent*>(event)); |
708 | } |
709 | |
710 | return QObject::eventFilter(target, event); |
711 | @@ -341,7 +404,8 @@ |
712 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
713 | false, m_longPress); |
714 | mev.setAccepted(false); |
715 | - Q_EMIT pressAndHold(&mev); |
716 | + Q_EMIT pressAndHold(&mev, m_owner); |
717 | + mev.setAccepted(forwardEvent(ForwardedEvent::MouseLongPress, 0, &mev)); |
718 | // if the event wasn't handled, allow click |
719 | if (!mev.isAccepted()) { |
720 | m_longPress = false; |
721 | @@ -376,7 +440,6 @@ |
722 | default: |
723 | break; |
724 | } |
725 | - forwardEvent(event); |
726 | return result || event->isAccepted(); |
727 | } |
728 | |
729 | @@ -400,27 +463,67 @@ |
730 | default: |
731 | break; |
732 | } |
733 | - forwardEvent(event); |
734 | return result || event->isAccepted(); |
735 | } |
736 | |
737 | +// emit signals and forward the events further |
738 | +bool UCMouse::forwardedEvents(ForwardedEvent *event) |
739 | +{ |
740 | + // the quick event is always specified! |
741 | + switch (event->subType()) { |
742 | + case ForwardedEvent::MousePress: { |
743 | + Q_EMIT pressed(event->quickEvent(), event->sender()); |
744 | + } break; |
745 | + case ForwardedEvent::MouseRelease: { |
746 | + Q_EMIT released(event->quickEvent(), event->sender()); |
747 | + } break; |
748 | + case ForwardedEvent::MouseMove: { |
749 | + Q_EMIT positionChanged(event->quickEvent(), event->sender()); |
750 | + } break; |
751 | + case ForwardedEvent::MouseDblClick: { |
752 | + Q_EMIT doubleClicked(event->quickEvent(), event->sender()); |
753 | + } break; |
754 | + case ForwardedEvent::HoverEnter: { |
755 | + Q_EMIT entered(event->quickEvent(), event->sender()); |
756 | + } break; |
757 | + case ForwardedEvent::HoverExit: { |
758 | + Q_EMIT exited(event->quickEvent(), event->sender()); |
759 | + } break; |
760 | + // composed events |
761 | + case ForwardedEvent::MouseClick: { |
762 | + Q_EMIT clicked(event->quickEvent(), event->sender()); |
763 | + } break; |
764 | + case ForwardedEvent::MouseLongPress: { |
765 | + Q_EMIT pressAndHold(event->quickEvent(), event->sender()); |
766 | + } break; |
767 | + default: break; |
768 | + } |
769 | + |
770 | + // forward event, but use the current owner as sender |
771 | + event->setAccepted(forwardEvent(event->subType(), event->originalEvent(), event->quickEvent())); |
772 | + return event->isAccepted(); |
773 | +} |
774 | + |
775 | bool UCMouse::hasAttachedFilter(QQuickItem *item) |
776 | { |
777 | return (qmlAttachedPropertiesObject<UCMouse>(item, false) != 0); |
778 | } |
779 | |
780 | -void UCMouse::setHovered(bool hovered) |
781 | +void UCMouse::setHovered(bool hovered, QEvent *hoverEvent) |
782 | { |
783 | if (m_hovered != hovered) { |
784 | m_hovered = hovered; |
785 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
786 | false, false); |
787 | mev.setAccepted(false); |
788 | + ForwardedEvent::EventType type = (m_hovered) ? ForwardedEvent::HoverEnter : ForwardedEvent::HoverExit; |
789 | if (m_hovered) { |
790 | - Q_EMIT entered(&mev); |
791 | + Q_EMIT entered(&mev, m_owner); |
792 | } else { |
793 | - Q_EMIT exited(&mev); |
794 | + m_pressAndHoldTimer.stop(); |
795 | + Q_EMIT exited(&mev, m_owner); |
796 | } |
797 | + forwardEvent(type, hoverEvent, &mev); |
798 | } |
799 | } |
800 | |
801 | @@ -432,13 +535,13 @@ |
802 | m_pressedButtons |= m_lastButton; |
803 | m_longPress = m_doubleClicked = false; |
804 | |
805 | - setHovered(true); |
806 | + setHovered(true, 0); |
807 | |
808 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
809 | false, m_longPress); |
810 | mev.setAccepted(false); |
811 | - Q_EMIT pressed(&mev); |
812 | - event->setAccepted(mev.isAccepted()); |
813 | + Q_EMIT pressed(&mev, m_owner); |
814 | + event->setAccepted(forwardEvent(ForwardedEvent::MousePress, event, &mev)); |
815 | |
816 | // start long press timer |
817 | m_pressAndHoldTimer.start(DefaultPressAndHoldDelay, this); |
818 | @@ -460,14 +563,14 @@ |
819 | m_pressAndHoldTimer.stop(); |
820 | } |
821 | |
822 | - setHovered(true); |
823 | + setHovered(true, 0); |
824 | m_moved = true; |
825 | m_doubleClicked = false; |
826 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
827 | false, m_longPress); |
828 | mev.setAccepted(false); |
829 | - Q_EMIT positionChanged(&mev); |
830 | - event->setAccepted(mev.isAccepted()); |
831 | + Q_EMIT positionChanged(&mev, m_owner); |
832 | + event->setAccepted(forwardEvent(ForwardedEvent::MouseMove, event, &mev)); |
833 | return mev.isAccepted(); |
834 | } |
835 | |
836 | @@ -487,18 +590,20 @@ |
837 | QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
838 | isClicked, m_longPress); |
839 | mev.setAccepted(false); |
840 | - Q_EMIT released(&mev); |
841 | - event->setAccepted(mev.isAccepted()); |
842 | + Q_EMIT released(&mev, m_owner); |
843 | + event->setAccepted(forwardEvent(ForwardedEvent::MouseRelease, event, &mev)); |
844 | |
845 | // remove button from press |
846 | m_pressedButtons &= ~m_lastButton; |
847 | if (isClicked) { |
848 | // emit clicked |
849 | - Q_EMIT clicked(&mev); |
850 | + mev.setAccepted(false); |
851 | + Q_EMIT clicked(&mev, m_owner); |
852 | + forwardEvent(ForwardedEvent::MouseClick, 0, &mev); |
853 | } |
854 | |
855 | if (!m_pressedButtons && !m_owner->acceptHoverEvents()) { |
856 | - setHovered(false); |
857 | + setHovered(false, 0); |
858 | } |
859 | return mev.isAccepted(); |
860 | } |
861 | @@ -511,18 +616,17 @@ |
862 | { |
863 | if (m_pressedButtons) { |
864 | saveEvent(event); |
865 | + |
866 | + QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
867 | + true, m_longPress); |
868 | + mev.setAccepted(false); |
869 | // if double click connected, suppress release() and click() signals |
870 | if (isDoubleClickConnected()) { |
871 | - QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
872 | - true, m_longPress); |
873 | - mev.setAccepted(false); |
874 | - Q_EMIT doubleClicked(&mev); |
875 | - event->setAccepted(mev.isAccepted()); |
876 | + Q_EMIT doubleClicked(&mev, m_owner); |
877 | m_doubleClicked = true; |
878 | - } else { |
879 | - // set accepted to false still |
880 | - event->setAccepted(false); |
881 | } |
882 | + // forward event even if it wasn't handled for the owner |
883 | + event->setAccepted(forwardEvent(ForwardedEvent::MouseDblClick, event, &mev)); |
884 | return event->isAccepted(); |
885 | } |
886 | |
887 | @@ -534,7 +638,9 @@ |
888 | { |
889 | m_lastPos = event->posF(); |
890 | m_lastModifiers = event->modifiers(); |
891 | - setHovered(true); |
892 | + m_lastButton = Qt::NoButton; |
893 | + m_lastButtons = Qt::NoButton; |
894 | + setHovered(true, event); |
895 | return false; |
896 | } |
897 | |
898 | @@ -542,11 +648,11 @@ |
899 | { |
900 | m_lastPos = event->posF(); |
901 | m_lastModifiers = event->modifiers(); |
902 | - QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers, |
903 | + QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), Qt::NoButton, Qt::NoButton, m_lastModifiers, |
904 | false, m_longPress); |
905 | mev.setAccepted(false); |
906 | - Q_EMIT positionChanged(&mev); |
907 | - forwardEvent(event); |
908 | + Q_EMIT positionChanged(&mev, m_owner); |
909 | + event->setAccepted(forwardEvent(ForwardedEvent::MouseMove, event, &mev)); |
910 | return false; |
911 | } |
912 | |
913 | @@ -554,7 +660,9 @@ |
914 | { |
915 | m_lastPos = event->posF(); |
916 | m_lastModifiers = event->modifiers(); |
917 | - setHovered(false); |
918 | + m_lastButton = Qt::NoButton; |
919 | + m_lastButtons = Qt::NoButton; |
920 | + setHovered(false, event); |
921 | return false; |
922 | } |
923 | |
924 | @@ -562,8 +670,10 @@ |
925 | { |
926 | m_lastPos = event->localPos(); |
927 | m_lastScenePos = event->windowPos(); |
928 | - m_lastButton = event->button(); |
929 | - m_lastButtons = event->buttons(); |
930 | + if (event->type() != QEvent::MouseMove) { |
931 | + m_lastButton = event->button(); |
932 | + m_lastButtons = event->buttons(); |
933 | + } |
934 | m_lastModifiers = event->modifiers(); |
935 | if ((event->type() == QEvent::MouseButtonPress) && (m_moveThreshold > 0.0)) { |
936 | m_toleranceArea.setX(m_lastPos.x() - m_moveThreshold); |
937 | @@ -574,7 +684,7 @@ |
938 | } |
939 | bool UCMouse::isDoubleClickConnected() |
940 | { |
941 | - IS_SIGNAL_CONNECTED(this, UCMouse, doubleClicked, (QQuickMouseEvent*)); |
942 | + IS_SIGNAL_CONNECTED(this, UCMouse, doubleClicked, (QQuickMouseEvent*,QQuickItem*)); |
943 | } |
944 | |
945 | bool UCMouse::isMouseEvent(QEvent::Type type) |
946 | @@ -588,57 +698,73 @@ |
947 | return (type == QEvent::HoverEnter) || (type == QEvent::HoverMove) || (type == QEvent::HoverLeave); |
948 | } |
949 | |
950 | -// forwards the events to the listed items; only mouse and hover events qualify |
951 | -void UCMouse::forwardEvent(QEvent *event) |
952 | + |
953 | +/* |
954 | + * Forwards the events to the listed items. The event coordinates are mapped to the destination's coordinates |
955 | + * and sent to the destination in case the destination has no filter attached. Otherwise the quick event |
956 | + * coordinates will be mapped and sent as ForwardedEvents. |
957 | + */ |
958 | +bool UCMouse::forwardEvent(ForwardedEvent::EventType type, QEvent *event, QQuickMouseEvent *quickEvent) |
959 | { |
960 | - /* |
961 | - * alter acceptedButtons and hoverEnabled for the time the event is delivered |
962 | - * exclude MouseArea and InverseMouseArea!! |
963 | - */ |
964 | + // first set the accepted flag to the original event |
965 | + if (event && quickEvent) { |
966 | + event->setAccepted(quickEvent->isAccepted()); |
967 | + } |
968 | + bool accepted = event ? event->isAccepted() : (quickEvent ? quickEvent->isAccepted() : false); |
969 | + |
970 | Q_FOREACH(QQuickItem *item, m_forwardList) { |
971 | |
972 | - if (event->isAccepted()) { |
973 | + if (accepted) { |
974 | // the event got accepted, therefore should not be forwarded |
975 | - return; |
976 | + return accepted; |
977 | } |
978 | // skip InverseMouseArea otherwise those will get the event twice |
979 | if (qobject_cast<InverseMouseAreaType*>(item)) { |
980 | continue; |
981 | } |
982 | - /* |
983 | - * temporarily enable mouse buttons and hover for items which have Mouse or InverseMouse |
984 | - * filter attached, but exclude targets which is MouseArea or InverseMouseArea |
985 | - */ |
986 | - Qt::MouseButtons acceptedButtons = item->acceptedMouseButtons(); |
987 | - bool hoverEnabled = item->acceptHoverEvents(); |
988 | - if (!qobject_cast<QQuickMouseArea*>(item) && hasAttachedFilter(item)) { |
989 | - // set accepted buttons and hover temporarily |
990 | - item->setAcceptedMouseButtons(m_owner->acceptedMouseButtons()); |
991 | - item->setAcceptHoverEvents(m_owner->acceptHoverEvents()); |
992 | - } |
993 | - |
994 | - // forward events |
995 | - bool accepted = false; |
996 | - if (isMouseEvent(event->type())) { |
997 | - QMouseEvent *mouse = static_cast<QMouseEvent*>(event); |
998 | - QPointF itemPos = item->mapFromScene(m_owner->mapToScene(mouse->pos())); |
999 | - QMouseEvent me = QMouseEvent(event->type(), itemPos, mouse->button(), mouse->buttons(), mouse->modifiers()); |
1000 | - QGuiApplication::sendEvent(item, &me); |
1001 | - accepted = me.isAccepted(); |
1002 | - } else { |
1003 | - QHoverEvent *hover = static_cast<QHoverEvent*>(event); |
1004 | - QPointF itemPos = item->mapFromScene(m_owner->mapToScene(hover->pos())); |
1005 | - QPointF itemOldPos = item->mapFromScene(m_owner->mapToScene(hover->oldPos())); |
1006 | - QHoverEvent he = QHoverEvent(event->type(), itemPos, itemOldPos, hover->modifiers()); |
1007 | - QGuiApplication::sendEvent(item, &he); |
1008 | - accepted = he.isAccepted(); |
1009 | - } |
1010 | - event->setAccepted(accepted); |
1011 | - |
1012 | - // restore acceptedButtons and hover |
1013 | - item->setAcceptedMouseButtons(acceptedButtons); |
1014 | - item->setAcceptHoverEvents(hoverEnabled); |
1015 | + |
1016 | + // map the normal event coordinates to item |
1017 | + QEvent *mappedEvent = 0; |
1018 | + if (event) { |
1019 | + if (isMouseEvent(event->type())) { |
1020 | + QMouseEvent *mouse = static_cast<QMouseEvent*>(event); |
1021 | + QPointF itemPos = item->mapFromScene(m_owner->mapToScene(mouse->pos())); |
1022 | + mappedEvent = new QMouseEvent(event->type(), itemPos, mouse->button(), mouse->buttons(), mouse->modifiers()); |
1023 | + } else if (isHoverEvent(event->type())){ |
1024 | + QHoverEvent *hover = static_cast<QHoverEvent*>(event); |
1025 | + QPointF itemPos = item->mapFromScene(m_owner->mapToScene(hover->pos())); |
1026 | + QPointF itemOldPos = item->mapFromScene(m_owner->mapToScene(hover->oldPos())); |
1027 | + mappedEvent = new QHoverEvent(event->type(), itemPos, itemOldPos, hover->modifiers()); |
1028 | + } |
1029 | + } |
1030 | + |
1031 | + // if the item has no filter attached, deliver the mapped event to it as it is |
1032 | + UCMouse *filter = qobject_cast<UCMouse*>(qmlAttachedPropertiesObject<UCMouse>(item, false)); |
1033 | + if (!filter && mappedEvent) { |
1034 | + QGuiApplication::sendEvent(item, mappedEvent); |
1035 | + accepted = mappedEvent->isAccepted(); |
1036 | + } else if (quickEvent) { |
1037 | + // map the quick event coordinates as well |
1038 | + QPoint itemPos(item->mapFromScene(m_owner->mapToScene(QPointF(quickEvent->x(), quickEvent->y()))).toPoint()); |
1039 | + QQuickMouseEvent mev(itemPos.x(), itemPos.y(), (Qt::MouseButton)quickEvent->button(), (Qt::MouseButtons)quickEvent->buttons(), |
1040 | + (Qt::KeyboardModifiers)quickEvent->modifiers(), quickEvent->isClick(), quickEvent->wasHeld()); |
1041 | + mev.setAccepted(false); |
1042 | + ForwardedEvent forwardedEvent(type, m_owner, mappedEvent, &mev); |
1043 | + QGuiApplication::sendEvent(item, &forwardedEvent); |
1044 | + accepted = mev.isAccepted(); |
1045 | + } |
1046 | + |
1047 | + // cleanup and transfer accepted flag |
1048 | + delete mappedEvent; |
1049 | + if (event) { |
1050 | + event->setAccepted(accepted); |
1051 | + } |
1052 | + if (quickEvent) { |
1053 | + quickEvent->setAccepted(accepted); |
1054 | + } |
1055 | } |
1056 | + |
1057 | + return accepted; |
1058 | } |
1059 | |
1060 | |
1061 | @@ -697,6 +823,9 @@ |
1062 | composed events will come until the mouse is moved inside the owner's area. |
1063 | |
1064 | The default value is 0. |
1065 | + |
1066 | + \note The value has no effect for the forwarded events. The threshold is only |
1067 | + valid when the host handles mouse events. |
1068 | */ |
1069 | int UCMouse::clickAndHoldThreshold() const |
1070 | { |
1071 | @@ -767,29 +896,34 @@ |
1072 | } |
1073 | |
1074 | /*! |
1075 | - \qmlsignal Mouse::onPressed(MouseEvent event) |
1076 | - The signal reports the mouse press. |
1077 | + \qmlsignal Mouse::onPressed(MouseEvent event, Item host) |
1078 | + The signal reports the mouse press. The \a host specifies the item that triggered |
1079 | + the event. |
1080 | */ |
1081 | |
1082 | /*! |
1083 | \qmlsignal Mouse::onReleased(MouseEvent event) |
1084 | - The signal reports the mouse release. |
1085 | + The signal reports the mouse release. The \a host specifies the item that triggered |
1086 | + the event. |
1087 | */ |
1088 | |
1089 | /*! |
1090 | \qmlsignal Mouse::onClicked(MouseEvent event) |
1091 | The signal reports the mouse click. The signal is not emitted if the onPressAndHold |
1092 | - got triggered or if onDoubleClicked is handled (a slot is connected to it). |
1093 | + got triggered or if onDoubleClicked is handled (a slot is connected to it). The \a |
1094 | + host specifies the item that triggered the event. |
1095 | */ |
1096 | |
1097 | /*! |
1098 | \qmlsignal Mouse::onPressAndHold(MouseEvent event) |
1099 | - The signal reports the mouse press and hold. |
1100 | + The signal reports the mouse press and hold. The \a host specifies the item that triggered |
1101 | + the event. |
1102 | */ |
1103 | |
1104 | /*! |
1105 | \qmlsignal Mouse::onDoubleClicked(MouseEvent event) |
1106 | - The signal reports mouse double click. |
1107 | + The signal reports mouse double click. The \a host specifies the item that triggered |
1108 | + the event. |
1109 | */ |
1110 | |
1111 | /*! |
1112 | @@ -797,20 +931,22 @@ |
1113 | The signal reports the mouse pointer position change. If the hover events are |
1114 | enabled for the owner, the signal will come continuously. Otherwise the position |
1115 | chanes are reported when one of the accepted mouse buttons are being kept pressed. |
1116 | + The \a host specifies the item that triggered the event. |
1117 | */ |
1118 | |
1119 | /*! |
1120 | \qmlsignal Mouse::onEntered(MouseEvent event) |
1121 | The signal reports that the mouse has entered into the area. The signal is |
1122 | emitted when the hover events are enabled and the mouse enters the area or |
1123 | - when one of the accepted mouse button is pressed. |
1124 | + when one of the accepted mouse button is pressed. The \a host specifies the |
1125 | + item that triggered the event. |
1126 | */ |
1127 | |
1128 | /*! |
1129 | \qmlsignal Mouse::onExited(MouseEvent event) |
1130 | The signal reports that the mouse has left the area. The signal is emitted when |
1131 | the hover events are enabled for the owner or if not, when one of the accepted |
1132 | - button is released. |
1133 | + button is released. The \a host specifies the item that triggered the event. |
1134 | */ |
1135 | |
1136 | /****************************************************************************** |
1137 | |
1138 | === modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml' |
1139 | --- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-02-25 12:36:27 +0000 |
1140 | +++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-17 01:29:24 +0000 |
1141 | @@ -87,7 +87,75 @@ |
1142 | } |
1143 | } |
1144 | |
1145 | - /*! |
1146 | + /*! |
1147 | + \qmlmethod UbuntuTestCase::flick(item, x, y, dx, dy, pressTimeout = -1, steps = -1, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1) |
1148 | + |
1149 | + The function produces a flick event when executed on Flickables. When used |
1150 | + on other components it provides the same functionality as \l mouseDrag() |
1151 | + function. The optional \a pressTimeout parameter can be used to introduce |
1152 | + a small delay between the mouse press and the first mouse move. Setting a |
1153 | + negative or zero value will disable the timeout. |
1154 | + |
1155 | + The default flick velocity is built up using 5 move points. This can be altered |
1156 | + by setting a positive value to \a steps parameter. The bigger the number the |
1157 | + longer the flick will be. When a negative or zero value is given, the default |
1158 | + of 5 move points will be used. |
1159 | + |
1160 | + \note The function can be used to select a text in a TextField or TextArea by |
1161 | + specifying at least 400 millisecods to \a pressTimeout. |
1162 | + */ |
1163 | + function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) { |
1164 | + if (item === undefined || item.x === undefined || item.y === undefined) |
1165 | + return |
1166 | + if (button === undefined) |
1167 | + button = Qt.LeftButton |
1168 | + if (modifiers === undefined) |
1169 | + modifiers = Qt.NoModifier |
1170 | + if (steps === undefined || steps <= 0) |
1171 | + steps = 4; |
1172 | + // make sure we have at least two move steps so the flick will be sensed |
1173 | + steps += 1; |
1174 | + if (delay === undefined) |
1175 | + delay = -1; |
1176 | + |
1177 | + var ddx = dx / steps; |
1178 | + var ddy = dy / steps; |
1179 | + |
1180 | + mousePress(item, x, y, button, modifiers, delay); |
1181 | + if (pressTimeout !== undefined && pressTimeout > 0) { |
1182 | + wait(pressTimeout); |
1183 | + } |
1184 | + for (var i = 1; i <= steps; i++) { |
1185 | + // mouse moves are all processed immediately, without delay in between events |
1186 | + mouseMove(item, x + i * ddx, y + i * ddy, -1, button); |
1187 | + } |
1188 | + mouseRelease(item, x + dx, y + dy, button, modifiers, delay); |
1189 | + // empty event buffer |
1190 | + wait(200); |
1191 | + } |
1192 | + |
1193 | + /*! |
1194 | + \qmlmethod UbuntuTestCase::mouseLongPress(item, x, y, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1) |
1195 | + |
1196 | + Simulates a long press on a mouse \a button with an optional \a modifier |
1197 | + on an \a item. The position is defined by \a x and \a y. If \a delay is |
1198 | + specified, the test will wait the specified amount of milliseconds before |
1199 | + the press. |
1200 | + |
1201 | + The position given by \a x and \a y is transformed from the co-ordinate |
1202 | + system of \a item into window co-ordinates and then delivered. |
1203 | + If \a item is obscured by another item, or a child of \a item occupies |
1204 | + that position, then the event will be delivered to the other item instead. |
1205 | + |
1206 | + \sa mouseRelease(), mouseClick(), mouseDoubleClick(), mouseMove(), mouseDrag(), mouseWheel() |
1207 | + */ |
1208 | + function mouseLongPress(item, x, y, button, modifiers, delay) { |
1209 | + mousePress(item, x, y, button, modifiers, delay); |
1210 | + // the delay is taken from QQuickMouseArea |
1211 | + wait(800); |
1212 | + } |
1213 | + |
1214 | + /*! |
1215 | Keeps executing a given parameter-less function until it returns the given |
1216 | expected result or the timemout is reached (in which case a test failure |
1217 | is generated) |
1218 | |
1219 | === modified file 'modules/Ubuntu/Test/deployment.pri' |
1220 | --- modules/Ubuntu/Test/deployment.pri 2014-01-17 12:30:05 +0000 |
1221 | +++ modules/Ubuntu/Test/deployment.pri 2014-04-17 01:29:24 +0000 |
1222 | @@ -7,9 +7,14 @@ |
1223 | # make found deployables visible in Qt Creator |
1224 | OTHER_FILES += $$QMLDIR_FILE |
1225 | |
1226 | +QML_FILES = $$system(ls *.qml) |
1227 | +JS_FILES = $$system(ls *.js) |
1228 | + |
1229 | # define deployment for found deployables |
1230 | qmldir_file.path = $$installPath |
1231 | qmldir_file.files = $$QMLDIR_FILE |
1232 | +qml_files.path = $$installPath |
1233 | +qml_files.files = $$QML_FILES |
1234 | js_files.path = $$installPath |
1235 | js_files.files = $$JS_FILES |
1236 | |
1237 | @@ -20,4 +25,4 @@ |
1238 | # https://bugreports.qt-project.org/browse/QTBUG-36243 |
1239 | plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes |
1240 | |
1241 | -INSTALLS += qmldir_file plugins_qmltypes |
1242 | +INSTALLS += qmldir_file plugins_qmltypes qml_files js_files |
1243 | |
1244 | === modified file 'modules/Ubuntu/Test/plugin/uctestcase.cpp' |
1245 | --- modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-03-19 12:48:33 +0000 |
1246 | +++ modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-04-17 01:29:24 +0000 |
1247 | @@ -26,6 +26,8 @@ |
1248 | #include <QtTest/QtTest> |
1249 | #include <QtQuick/QQuickItem> |
1250 | |
1251 | +Q_DECLARE_METATYPE(QList<QQmlError>) |
1252 | + |
1253 | /*! |
1254 | * \ingroup ubuntu |
1255 | * \brief UbuntuTestCase is the C++ pendant to the QML UbuntuTestCase. |
1256 | @@ -37,6 +39,7 @@ |
1257 | QString modulePath(QDir(modules).absolutePath()); |
1258 | engine()->addImportPath(modulePath); |
1259 | |
1260 | + qRegisterMetaType<QList <QQmlError> >(); |
1261 | m_spy = new QSignalSpy(engine(), SIGNAL(warnings(QList<QQmlError>))); |
1262 | m_spy->setParent(this); |
1263 | |
1264 | @@ -48,3 +51,12 @@ |
1265 | QTest::qWaitForWindowExposed(this); |
1266 | } |
1267 | |
1268 | +/*! |
1269 | + * The number of all warnings from the point of loading the first line of QML code. |
1270 | + */ |
1271 | +int |
1272 | +UbuntuTestCase::warnings() const |
1273 | +{ |
1274 | + return m_spy->count(); |
1275 | +} |
1276 | + |
1277 | |
1278 | === modified file 'modules/Ubuntu/Test/plugin/uctestcase.h' |
1279 | --- modules/Ubuntu/Test/plugin/uctestcase.h 2014-03-19 12:48:33 +0000 |
1280 | +++ modules/Ubuntu/Test/plugin/uctestcase.h 2014-04-17 01:29:24 +0000 |
1281 | @@ -29,6 +29,7 @@ |
1282 | |
1283 | public: |
1284 | UbuntuTestCase(const QString& file, QWindow* parent = 0); |
1285 | + int warnings() const; |
1286 | // getter |
1287 | template<class T> |
1288 | inline T findItem(const QString& objectName) const { |
1289 | @@ -39,7 +40,6 @@ |
1290 | qFatal("Item '%s' found with unexpected type", qPrintable(objectName)); |
1291 | qFatal("No item '%s' found", qPrintable(objectName)); |
1292 | } |
1293 | - |
1294 | private: |
1295 | QSignalSpy* m_spy; |
1296 | }; |
1297 | |
1298 | === removed file 'tests/autopilot/ubuntuuitoolkit/__init__.py' |
1299 | --- tests/autopilot/ubuntuuitoolkit/__init__.py 2013-07-10 16:07:33 +0000 |
1300 | +++ tests/autopilot/ubuntuuitoolkit/__init__.py 1970-01-01 00:00:00 +0000 |
1301 | @@ -1,17 +0,0 @@ |
1302 | -# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
1303 | -# |
1304 | -# Copyright (C) 2012, 2013 Canonical Ltd. |
1305 | -# |
1306 | -# This program is free software; you can redistribute it and/or modify |
1307 | -# it under the terms of the GNU Lesser General Public License as published by |
1308 | -# the Free Software Foundation; version 3. |
1309 | -# |
1310 | -# This program is distributed in the hope that it will be useful, |
1311 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1312 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1313 | -# GNU Lesser General Public License for more details. |
1314 | -# |
1315 | -# You should have received a copy of the GNU Lesser General Public License |
1316 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1317 | - |
1318 | -"""Ubuntu UI Toolkit autopilot tests and emulators - top level package.""" |
1319 | |
1320 | === added directory 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects' |
1321 | === renamed file 'tests/autopilot/ubuntuuitoolkit/emulators.py' => 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py' |
1322 | --- tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-08 14:46:25 +0000 |
1323 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py 2014-04-17 01:29:24 +0000 |
1324 | @@ -14,784 +14,69 @@ |
1325 | # You should have received a copy of the GNU Lesser General Public License |
1326 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1327 | |
1328 | -import logging |
1329 | -from distutils import version |
1330 | - |
1331 | -import autopilot |
1332 | -from autopilot import ( |
1333 | - input, |
1334 | - logging as autopilot_logging, |
1335 | - platform |
1336 | -) |
1337 | -from autopilot.introspection import dbus |
1338 | - |
1339 | - |
1340 | -_NO_TABS_ERROR = 'The MainView has no Tabs.' |
1341 | - |
1342 | -logger = logging.getLogger(__name__) |
1343 | - |
1344 | - |
1345 | -class ToolkitEmulatorException(Exception): |
1346 | - """Exception raised when there is an error with the emulator.""" |
1347 | - |
1348 | - |
1349 | -def get_pointing_device(): |
1350 | - """Return the pointing device depending on the platform. |
1351 | - |
1352 | - If the platform is `Desktop`, the pointing device will be a `Mouse`. |
1353 | - If not, the pointing device will be `Touch`. |
1354 | - |
1355 | - """ |
1356 | - if platform.model() == 'Desktop': |
1357 | - input_device_class = input.Mouse |
1358 | - else: |
1359 | - input_device_class = input.Touch |
1360 | - return input.Pointer(device=input_device_class.create()) |
1361 | - |
1362 | - |
1363 | -def get_keyboard(): |
1364 | - """Return the keyboard device.""" |
1365 | - # TODO return the OSK if we are on the phone. --elopio - 2014-01-13 |
1366 | - return input.Keyboard.create() |
1367 | - |
1368 | - |
1369 | -def check_autopilot_version(): |
1370 | - """Check that the Autopilot installed version matches the one required. |
1371 | - |
1372 | - :raise ToolkitEmulatorException: If the installed Autopilot version does't |
1373 | - match the required by the emulators. |
1374 | - |
1375 | - """ |
1376 | - installed_version = version.LooseVersion(autopilot.version) |
1377 | - if installed_version < version.LooseVersion('1.4'): |
1378 | - raise ToolkitEmulatorException( |
1379 | - 'The emulators need Autopilot 1.4 or higher.') |
1380 | - |
1381 | - |
1382 | -# Containers helpers. |
1383 | - |
1384 | -def _get_visible_container_top(containers): |
1385 | - containers_top = [container.globalRect.y for container in containers] |
1386 | - return max(containers_top) |
1387 | - |
1388 | - |
1389 | -def _get_visible_container_bottom(containers): |
1390 | - containers_bottom = [ |
1391 | - container.globalRect.y + container.globalRect.height |
1392 | - for container in containers if container.globalRect.height > 0] |
1393 | - return min(containers_bottom) |
1394 | - |
1395 | - |
1396 | -class UbuntuUIToolkitEmulatorBase(dbus.CustomEmulatorBase): |
1397 | - """A base class for all the Ubuntu UI Toolkit emulators.""" |
1398 | - |
1399 | - def __init__(self, *args): |
1400 | - check_autopilot_version() |
1401 | - super(UbuntuUIToolkitEmulatorBase, self).__init__(*args) |
1402 | - self.pointing_device = get_pointing_device() |
1403 | - |
1404 | - |
1405 | -class MainView(UbuntuUIToolkitEmulatorBase): |
1406 | - """MainView Autopilot emulator.""" |
1407 | - |
1408 | - def get_header(self): |
1409 | - """Return the Header emulator of the MainView.""" |
1410 | - try: |
1411 | - return self.select_single('Header', objectName='MainView_Header') |
1412 | - except dbus.StateNotFoundError: |
1413 | - raise ToolkitEmulatorException('The main view has no header.') |
1414 | - |
1415 | - def get_toolbar(self): |
1416 | - """Return the Toolbar emulator of the MainView.""" |
1417 | - return self.select_single(Toolbar) |
1418 | - |
1419 | - @autopilot_logging.log_action(logger.info) |
1420 | - def open_toolbar(self): |
1421 | - """Open the toolbar if it's not already opened. |
1422 | - |
1423 | - :return: The toolbar. |
1424 | - |
1425 | - """ |
1426 | - return self.get_toolbar().open() |
1427 | - |
1428 | - @autopilot_logging.log_action(logger.info) |
1429 | - def close_toolbar(self): |
1430 | - """Close the toolbar if it's opened.""" |
1431 | - self.get_toolbar().close() |
1432 | - |
1433 | - def get_tabs(self): |
1434 | - """Return the Tabs emulator of the MainView. |
1435 | - |
1436 | - :raise ToolkitEmulatorException: If the main view has no tabs. |
1437 | - |
1438 | - """ |
1439 | - try: |
1440 | - return self.select_single(Tabs) |
1441 | - except dbus.StateNotFoundError: |
1442 | - raise ToolkitEmulatorException(_NO_TABS_ERROR) |
1443 | - |
1444 | - @autopilot_logging.log_action(logger.info) |
1445 | - def switch_to_next_tab(self): |
1446 | - """Open the next tab. |
1447 | - |
1448 | - :return: The newly opened tab. |
1449 | - |
1450 | - """ |
1451 | - logger.debug('Switch to next tab.') |
1452 | - self.get_header().switch_to_next_tab() |
1453 | - current_tab = self.get_tabs().get_current_tab() |
1454 | - current_tab.visible.wait_for(True) |
1455 | - return current_tab |
1456 | - |
1457 | - @autopilot_logging.log_action(logger.info) |
1458 | - def switch_to_tab_by_index(self, index): |
1459 | - """Open a tab. |
1460 | - |
1461 | - :parameter index: The index of the tab to open. |
1462 | - :return: The newly opened tab. |
1463 | - :raise ToolkitEmulatorException: If the tab index is out of range. |
1464 | - |
1465 | - """ |
1466 | - logger.debug('Switch to tab with index {0}.'.format(index)) |
1467 | - tabs = self.get_tabs() |
1468 | - number_of_tabs = tabs.get_number_of_tabs() |
1469 | - if index >= number_of_tabs: |
1470 | - raise ToolkitEmulatorException('Tab index out of range.') |
1471 | - current_tab = tabs.get_current_tab() |
1472 | - number_of_switches = 0 |
1473 | - while not tabs.selectedTabIndex == index: |
1474 | - logger.debug( |
1475 | - 'Current tab index: {0}.'.format(tabs.selectedTabIndex)) |
1476 | - if number_of_switches >= number_of_tabs - 1: |
1477 | - # This prevents a loop. But if this error is ever raised, it's |
1478 | - # likely there's a bug on the emulator or on the QML Tab. |
1479 | - raise ToolkitEmulatorException( |
1480 | - 'The tab with index {0} was not selected.'.format(index)) |
1481 | - current_tab = self.switch_to_next_tab() |
1482 | - number_of_switches += 1 |
1483 | - return current_tab |
1484 | - |
1485 | - @autopilot_logging.log_action(logger.info) |
1486 | - def switch_to_previous_tab(self): |
1487 | - """Open the previous tab. |
1488 | - |
1489 | - :return: The newly opened tab. |
1490 | - |
1491 | - """ |
1492 | - tabs = self.get_tabs() |
1493 | - if tabs.selectedTabIndex == 0: |
1494 | - previous_tab_index = tabs.get_number_of_tabs() - 1 |
1495 | - else: |
1496 | - previous_tab_index = tabs.selectedTabIndex - 1 |
1497 | - return self.switch_to_tab_by_index(previous_tab_index) |
1498 | - |
1499 | - @autopilot_logging.log_action(logger.info) |
1500 | - def switch_to_tab(self, object_name): |
1501 | - """Open a tab. |
1502 | - |
1503 | - :parameter object_name: The QML objectName property of the tab. |
1504 | - :return: The newly opened tab. |
1505 | - :raise ToolkitEmulatorException: If there is no tab with that object |
1506 | - name. |
1507 | - |
1508 | - """ |
1509 | - tabs = self.get_tabs() |
1510 | - for index, tab in enumerate(tabs.select_many('Tab')): |
1511 | - if tab.objectName == object_name: |
1512 | - return self.switch_to_tab_by_index(tab.index) |
1513 | - raise ToolkitEmulatorException( |
1514 | - 'Tab with objectName "{0}" not found.'.format(object_name)) |
1515 | - |
1516 | - def get_action_selection_popover(self, object_name): |
1517 | - """Return an ActionSelectionPopover emulator. |
1518 | - |
1519 | - :parameter object_name: The QML objectName property of the popover. |
1520 | - |
1521 | - """ |
1522 | - return self.select_single( |
1523 | - ActionSelectionPopover, objectName=object_name) |
1524 | - |
1525 | - @autopilot_logging.log_action(logger.info) |
1526 | - def go_back(self): |
1527 | - """Go to the previous page.""" |
1528 | - toolbar = self.open_toolbar() |
1529 | - toolbar.click_back_button() |
1530 | - |
1531 | - |
1532 | -class Header(UbuntuUIToolkitEmulatorBase): |
1533 | - """Header Autopilot emulator.""" |
1534 | - |
1535 | - def __init__(self, *args): |
1536 | - super(Header, self).__init__(*args) |
1537 | - self.pointing_device = get_pointing_device() |
1538 | - |
1539 | - def _get_animating(self): |
1540 | - tab_bar_style = self.select_single('TabBarStyle') |
1541 | - return tab_bar_style.animating |
1542 | - |
1543 | - @autopilot_logging.log_action(logger.info) |
1544 | - def switch_to_next_tab(self): |
1545 | - """Open the next tab. |
1546 | - |
1547 | - :raise ToolkitEmulatorException: If the main view has no tabs. |
1548 | - |
1549 | - """ |
1550 | - try: |
1551 | - tab_bar = self.select_single(TabBar) |
1552 | - except dbus.StateNotFoundError: |
1553 | - raise ToolkitEmulatorException(_NO_TABS_ERROR) |
1554 | - tab_bar.switch_to_next_tab() |
1555 | - self._get_animating().wait_for(False) |
1556 | - |
1557 | - |
1558 | -class Toolbar(UbuntuUIToolkitEmulatorBase): |
1559 | - """Toolbar Autopilot emulator.""" |
1560 | - |
1561 | - @autopilot_logging.log_action(logger.info) |
1562 | - def open(self): |
1563 | - """Open the toolbar if it's not already opened. |
1564 | - |
1565 | - :return: The toolbar. |
1566 | - |
1567 | - """ |
1568 | - self.animating.wait_for(False) |
1569 | - if not self.opened: |
1570 | - self._drag_to_open() |
1571 | - self.opened.wait_for(True) |
1572 | - self.animating.wait_for(False) |
1573 | - |
1574 | - return self |
1575 | - |
1576 | - def _drag_to_open(self): |
1577 | - x, y, _, _ = self.globalRect |
1578 | - line_x = x + self.width * 0.50 |
1579 | - start_y = y + self.height - 1 |
1580 | - stop_y = y |
1581 | - |
1582 | - self.pointing_device.drag(line_x, start_y, line_x, stop_y) |
1583 | - |
1584 | - @autopilot_logging.log_action(logger.info) |
1585 | - def close(self): |
1586 | - """Close the toolbar if it's opened.""" |
1587 | - self.animating.wait_for(False) |
1588 | - if self.opened: |
1589 | - self._drag_to_close() |
1590 | - self.opened.wait_for(False) |
1591 | - self.animating.wait_for(False) |
1592 | - |
1593 | - def _drag_to_close(self): |
1594 | - x, y, _, _ = self.globalRect |
1595 | - line_x = x + self.width * 0.50 |
1596 | - start_y = y |
1597 | - stop_y = y + self.height - 1 |
1598 | - |
1599 | - self.pointing_device.drag(line_x, start_y, line_x, stop_y) |
1600 | - |
1601 | - @autopilot_logging.log_action(logger.info) |
1602 | - def click_button(self, object_name): |
1603 | - """Click a button of the toolbar. |
1604 | - |
1605 | - The toolbar should be opened before clicking the button, or an |
1606 | - exception will be raised. If the toolbar is closed for some reason |
1607 | - (e.g., timer finishes) after moving the mouse cursor and before |
1608 | - clicking the button, it is re-opened automatically by this function. |
1609 | - |
1610 | - :parameter object_name: The QML objectName property of the button. |
1611 | - :raise ToolkitEmulatorException: If there is no button with that object |
1612 | - name. |
1613 | - |
1614 | - """ |
1615 | - # ensure the toolbar is open |
1616 | - if not self.opened: |
1617 | - raise ToolkitEmulatorException( |
1618 | - 'Toolbar must be opened before calling click_button().') |
1619 | - try: |
1620 | - button = self._get_button(object_name) |
1621 | - except dbus.StateNotFoundError: |
1622 | - raise ToolkitEmulatorException( |
1623 | - 'Button with objectName "{0}" not found.'.format(object_name)) |
1624 | - self.pointing_device.move_to_object(button) |
1625 | - # ensure the toolbar is still open (may have closed due to timeout) |
1626 | - self.open() |
1627 | - # click the button |
1628 | - self.pointing_device.click_object(button) |
1629 | - |
1630 | - def _get_button(self, object_name): |
1631 | - return self.select_single('ActionItem', objectName=object_name) |
1632 | - |
1633 | - @autopilot_logging.log_action(logger.info) |
1634 | - def click_back_button(self): |
1635 | - """Click the back button of the toolbar.""" |
1636 | - self.click_button('back_toolbar_button') |
1637 | - |
1638 | - |
1639 | -class Tabs(UbuntuUIToolkitEmulatorBase): |
1640 | - """Tabs Autopilot emulator.""" |
1641 | - |
1642 | - def get_current_tab(self): |
1643 | - """Return the currently selected tab.""" |
1644 | - return self._get_tab(self.selectedTabIndex) |
1645 | - |
1646 | - def _get_tab(self, index): |
1647 | - tabs = self._get_tabs() |
1648 | - for tab in tabs: |
1649 | - if tab.index == index: |
1650 | - return tab |
1651 | - else: |
1652 | - raise ToolkitEmulatorException( |
1653 | - 'There is no tab with index {0}.'.format(index)) |
1654 | - |
1655 | - def _get_tabs(self): |
1656 | - return self.select_many('Tab') |
1657 | - |
1658 | - def get_number_of_tabs(self): |
1659 | - """Return the number of tabs.""" |
1660 | - return len(self._get_tabs()) |
1661 | - |
1662 | - |
1663 | -class TabBar(UbuntuUIToolkitEmulatorBase): |
1664 | - """TabBar Autopilot emulator.""" |
1665 | - |
1666 | - @autopilot_logging.log_action(logger.info) |
1667 | - def switch_to_next_tab(self): |
1668 | - """Open the next tab.""" |
1669 | - self._activate_tab_bar() |
1670 | - logger.debug('Click the next tab bar button.') |
1671 | - self.pointing_device.click_object(self._get_next_tab_button()) |
1672 | - |
1673 | - def _activate_tab_bar(self): |
1674 | - # First move to the tab bar to avoid timing issues when we find it in |
1675 | - # selection mode but it's deselected while we move to it. |
1676 | - self.pointing_device.move_to_object(self) |
1677 | - if self.selectionMode: |
1678 | - logger.debug('Already in selection mode.') |
1679 | - else: |
1680 | - # Click the tab bar to switch to selection mode. |
1681 | - logger.debug('Click the tab bar to enable selection mode.') |
1682 | - self.pointing_device.click_object(self) |
1683 | - |
1684 | - def _get_next_tab_button(self): |
1685 | - current_index = self._get_selected_button_index() |
1686 | - next_index = (current_index + 1) % self._get_number_of_tab_buttons() |
1687 | - return self._get_tab_button(next_index) |
1688 | - |
1689 | - def _get_selected_button_index(self): |
1690 | - return self.select_single('QQuickPathView').selectedButtonIndex |
1691 | - |
1692 | - def _get_number_of_tab_buttons(self): |
1693 | - return len(self._get_tab_buttons()) |
1694 | - |
1695 | - def _get_tab_buttons(self): |
1696 | - return self.select_many('AbstractButton') |
1697 | - |
1698 | - def _get_tab_button(self, index): |
1699 | - buttons = self._get_tab_buttons() |
1700 | - for button in buttons: |
1701 | - if button.buttonIndex == index: |
1702 | - return button |
1703 | - raise ToolkitEmulatorException( |
1704 | - 'There is no tab button with index {0}.'.format(index)) |
1705 | - |
1706 | - |
1707 | -class ActionSelectionPopover(UbuntuUIToolkitEmulatorBase): |
1708 | - """ActionSelectionPopover Autopilot emulator.""" |
1709 | - |
1710 | - def click_button_by_text(self, text): |
1711 | - """Click a button on the popover. |
1712 | - |
1713 | - XXX We are receiving the text because there's no way to set the |
1714 | - objectName on the action. This is reported at |
1715 | - https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1205144 |
1716 | - --elopio - 2013-07-25 |
1717 | - |
1718 | - :parameter text: The text of the button. |
1719 | - :raise ToolkitEmulatorException: If the popover is not open. |
1720 | - |
1721 | - """ |
1722 | - if not self.visible: |
1723 | - raise ToolkitEmulatorException('The popover is not open.') |
1724 | - button = self._get_button(text) |
1725 | - if button is None: |
1726 | - raise ToolkitEmulatorException( |
1727 | - 'Button with text "{0}" not found.'.format(text)) |
1728 | - self.pointing_device.click_object(button) |
1729 | - if self.autoClose: |
1730 | - try: |
1731 | - self.visible.wait_for(False) |
1732 | - except dbus.StateNotFoundError: |
1733 | - # The popover was removed from the tree. |
1734 | - pass |
1735 | - |
1736 | - def _get_button(self, text): |
1737 | - buttons = self.select_many('Empty') |
1738 | - for button in buttons: |
1739 | - if button.text == text: |
1740 | - return button |
1741 | - |
1742 | - |
1743 | -class CheckBox(UbuntuUIToolkitEmulatorBase): |
1744 | - """CheckBox Autopilot emulator.""" |
1745 | - |
1746 | - @autopilot_logging.log_action(logger.info) |
1747 | - def check(self, timeout=10): |
1748 | - """Check a CheckBox, if its not already checked. |
1749 | - |
1750 | - :parameter timeout: number of seconds to wait for the CheckBox to be |
1751 | - checked. Default is 10. |
1752 | - |
1753 | - """ |
1754 | - if not self.checked: |
1755 | - self.change_state(timeout) |
1756 | - |
1757 | - @autopilot_logging.log_action(logger.info) |
1758 | - def uncheck(self, timeout=10): |
1759 | - """Uncheck a CheckBox, if its not already unchecked. |
1760 | - |
1761 | - :parameter timeout: number of seconds to wait for the CheckBox to be |
1762 | - unchecked. Default is 10. |
1763 | - |
1764 | - """ |
1765 | - if self.checked: |
1766 | - self.change_state(timeout) |
1767 | - |
1768 | - @autopilot_logging.log_action(logger.info) |
1769 | - def change_state(self, timeout=10): |
1770 | - """Change the state of a CheckBox. |
1771 | - |
1772 | - If it is checked, it will be unchecked. If it is unchecked, it will be |
1773 | - checked. |
1774 | - |
1775 | - :parameter time_out: number of seconds to wait for the CheckBox state |
1776 | - to change. Default is 10. |
1777 | - |
1778 | - """ |
1779 | - original_state = self.checked |
1780 | - self.pointing_device.click_object(self) |
1781 | - self.checked.wait_for(not original_state, timeout) |
1782 | - |
1783 | - |
1784 | -class TextField(UbuntuUIToolkitEmulatorBase): |
1785 | - """TextField Autopilot emulator.""" |
1786 | - |
1787 | - def __init__(self, *args): |
1788 | - super(TextField, self).__init__(*args) |
1789 | - self.keyboard = get_keyboard() |
1790 | - |
1791 | - def write(self, text, clear=True): |
1792 | - """Write into the text field. |
1793 | - |
1794 | - :parameter text: The text to write. |
1795 | - :parameter clear: If True, the text field will be cleared before |
1796 | - writing the text. If False, the text will be appended at the end |
1797 | - of the text field. Default is True. |
1798 | - |
1799 | - """ |
1800 | - with self.keyboard.focused_type(self): |
1801 | - self.focus.wait_for(True) |
1802 | - if clear: |
1803 | - self.clear() |
1804 | - else: |
1805 | - if not self.is_empty(): |
1806 | - self.keyboard.press_and_release('End') |
1807 | - self.keyboard.type(text) |
1808 | - |
1809 | - def clear(self): |
1810 | - """Clear the text field.""" |
1811 | - if not self.is_empty(): |
1812 | - if self.hasClearButton: |
1813 | - self._click_clear_button() |
1814 | - else: |
1815 | - self._clear_with_keys() |
1816 | - self.text.wait_for('') |
1817 | - |
1818 | - def is_empty(self): |
1819 | - """Return True if the text field is empty. False otherwise.""" |
1820 | - return self.text == '' |
1821 | - |
1822 | - def _click_clear_button(self): |
1823 | - clear_button = self.select_single( |
1824 | - 'AbstractButton', objectName='clear_button') |
1825 | - if not clear_button.visible: |
1826 | - self.pointing_device.click_object(self) |
1827 | - self.pointing_device.click_object(clear_button) |
1828 | - |
1829 | - def _clear_with_keys(self): |
1830 | - if platform.model() == 'Desktop': |
1831 | - self._select_all() |
1832 | - else: |
1833 | - # Touch tap currently doesn't have a press_duration parameter, so |
1834 | - # we can't show the popover. Reported as bug http://pad.lv/1268782 |
1835 | - # --elopio - 2014-01-13 |
1836 | - self.keyboard.press_and_release('End') |
1837 | - while not self.is_empty(): |
1838 | - # We delete with backspace because the on-screen keyboard has that |
1839 | - # key. |
1840 | - self.keyboard.press_and_release('BackSpace') |
1841 | - |
1842 | - def _select_all(self): |
1843 | - self.pointing_device.click_object(self, press_duration=1) |
1844 | - root = self.get_root_instance() |
1845 | - main_view = root.select_single(MainView) |
1846 | - popover = main_view.get_action_selection_popover('text_input_popover') |
1847 | - popover.click_button_by_text('Select All') |
1848 | - |
1849 | - |
1850 | -class Flickable(UbuntuUIToolkitEmulatorBase): |
1851 | - |
1852 | - @autopilot_logging.log_action(logger.info) |
1853 | - def swipe_child_into_view(self, child): |
1854 | - """Make the child visible. |
1855 | - |
1856 | - Currently it works only when the object needs to be swiped vertically. |
1857 | - TODO implement horizontal swiping. --elopio - 2014-03-21 |
1858 | - |
1859 | - """ |
1860 | - containers = self._get_containers() |
1861 | - if not self._is_child_visible(child, containers): |
1862 | - self._swipe_non_visible_child_into_view(child, containers) |
1863 | - else: |
1864 | - logger.debug('The element is already visible.') |
1865 | - |
1866 | - def _get_containers(self): |
1867 | - """Return a list with the containers to take into account when swiping. |
1868 | - |
1869 | - The list includes this flickable and the top-most container. |
1870 | - TODO add additional flickables that are between this and the top |
1871 | - container. --elopio - 2014-03-22 |
1872 | - |
1873 | - """ |
1874 | - containers = [self._get_top_container(), self] |
1875 | - return containers |
1876 | - |
1877 | - def _get_top_container(self): |
1878 | - """Return the top-most container with a globalRect.""" |
1879 | - root = self.get_root_instance() |
1880 | - containers = [root] |
1881 | - while len(containers) == 1: |
1882 | - try: |
1883 | - containers[0].globalRect |
1884 | - return containers[0] |
1885 | - except AttributeError: |
1886 | - containers = containers[0].get_children() |
1887 | - |
1888 | - raise ToolkitEmulatorException("Couldn't find the top-most container.") |
1889 | - |
1890 | - def _is_child_visible(self, child, containers): |
1891 | - """Check if the center of the child is visible. |
1892 | - |
1893 | - :return: True if the center of the child is visible, False otherwise. |
1894 | - |
1895 | - """ |
1896 | - object_center = child.globalRect.y + child.globalRect.height // 2 |
1897 | - visible_top = _get_visible_container_top(containers) |
1898 | - visible_bottom = _get_visible_container_bottom(containers) |
1899 | - return (object_center >= visible_top and |
1900 | - object_center <= visible_bottom) |
1901 | - |
1902 | - @autopilot_logging.log_action(logger.info) |
1903 | - def _swipe_non_visible_child_into_view(self, child, containers): |
1904 | - while not self._is_child_visible(child, containers): |
1905 | - # Check the direction of the swipe based on the position of the |
1906 | - # child relative to the immediate flickable container. |
1907 | - if child.globalRect.y < self.globalRect.y: |
1908 | - self._swipe_to_show_more_above(containers) |
1909 | - else: |
1910 | - self._swipe_to_show_more_below(containers) |
1911 | - |
1912 | - @autopilot_logging.log_action(logger.info) |
1913 | - def _swipe_to_show_more_above(self, containers): |
1914 | - if self.atYBeginning: |
1915 | - raise ToolkitEmulatorException( |
1916 | - "Can't swipe more, we are already at the top of the " |
1917 | - "container.") |
1918 | - else: |
1919 | - self._swipe_to_show_more('above', containers) |
1920 | - |
1921 | - @autopilot_logging.log_action(logger.info) |
1922 | - def _swipe_to_show_more_below(self, containers): |
1923 | - if self.atYEnd: |
1924 | - raise ToolkitEmulatorException( |
1925 | - "Can't swipe more, we are already at the bottom of the " |
1926 | - "container.") |
1927 | - else: |
1928 | - self._swipe_to_show_more('below', containers) |
1929 | - |
1930 | - def _swipe_to_show_more(self, direction, containers): |
1931 | - start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2) |
1932 | - # Start and stop just a little under the top and a little over the |
1933 | - # bottom. |
1934 | - top = _get_visible_container_top(containers) + 5 |
1935 | - bottom = _get_visible_container_bottom(containers) - 5 |
1936 | - if direction == 'below': |
1937 | - start_y = bottom |
1938 | - stop_y = top |
1939 | - elif direction == 'above': |
1940 | - start_y = top |
1941 | - stop_y = bottom |
1942 | - else: |
1943 | - raise ToolkitEmulatorException( |
1944 | - 'Invalid direction {}.'.format(direction)) |
1945 | - self._slow_drag(start_x, stop_x, start_y, stop_y) |
1946 | - self.dragging.wait_for(False) |
1947 | - self.moving.wait_for(False) |
1948 | - |
1949 | - def _slow_drag(self, start_x, stop_x, start_y, stop_y): |
1950 | - # If we drag too fast, we end up scrolling more than what we |
1951 | - # should, sometimes missing the element we are looking for. |
1952 | - self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5) |
1953 | - |
1954 | - @autopilot_logging.log_action(logger.info) |
1955 | - def _scroll_to_top(self): |
1956 | - if not self.atYBeginning: |
1957 | - containers = self._get_containers() |
1958 | - while not self.atYBeginning: |
1959 | - self._swipe_to_show_more_above(containers) |
1960 | - |
1961 | - |
1962 | -class QQuickListView(Flickable): |
1963 | - |
1964 | - @autopilot_logging.log_action(logger.info) |
1965 | - def click_element(self, object_name): |
1966 | - """Click an element from the list. |
1967 | - |
1968 | - It swipes the element into view if it's center is not visible. |
1969 | - |
1970 | - :parameter objectName: The objectName property of the element to click. |
1971 | - |
1972 | - """ |
1973 | - try: |
1974 | - element = self.select_single(objectName=object_name) |
1975 | - except dbus.StateNotFoundError: |
1976 | - # The element might be on a part of the list that hasn't been |
1977 | - # created yet. We have to search for it scrolling the entire list. |
1978 | - element = self._find_element(object_name) |
1979 | - self.swipe_child_into_view(element) |
1980 | - self.pointing_device.click_object(element) |
1981 | - |
1982 | - @autopilot_logging.log_action(logger.info) |
1983 | - def _find_element(self, object_name): |
1984 | - self._scroll_to_top() |
1985 | - while not self.atYEnd: |
1986 | - containers = self._get_containers() |
1987 | - self._swipe_to_show_more_below(containers) |
1988 | - try: |
1989 | - return self.select_single(objectName=object_name) |
1990 | - except dbus.StateNotFoundError: |
1991 | - pass |
1992 | - raise ToolkitEmulatorException( |
1993 | - 'List element with objectName "{}" not found.'.format(object_name)) |
1994 | - |
1995 | - def _is_element_clickable(self, object_name): |
1996 | - child = self.select_single(objectName=object_name) |
1997 | - containers = self._get_containers() |
1998 | - return self._is_child_visible(child, containers) |
1999 | - |
2000 | - |
2001 | -class Empty(UbuntuUIToolkitEmulatorBase): |
2002 | - """Base class to emulate swipe to delete.""" |
2003 | - |
2004 | - def exists(self): |
2005 | - try: |
2006 | - return self.implicitHeight > 0 |
2007 | - except dbus.StateNotFoundError: |
2008 | - return False |
2009 | - |
2010 | - def _get_confirm_button(self): |
2011 | - return self.select_single( |
2012 | - 'QQuickItem', objectName='confirmRemovalDialog') |
2013 | - |
2014 | - @autopilot_logging.log_action(logger.info) |
2015 | - def swipe_to_delete(self, direction='right'): |
2016 | - """Swipe the item in a specific direction.""" |
2017 | - if self.removable: |
2018 | - self._drag_pointing_device_to_delete(direction) |
2019 | - if self.confirmRemoval: |
2020 | - self.waitingConfirmationForRemoval.wait_for(True) |
2021 | - else: |
2022 | - self._wait_until_deleted() |
2023 | - else: |
2024 | - raise ToolkitEmulatorException( |
2025 | - 'The item "{0}" is not removable'.format(self.objectName)) |
2026 | - |
2027 | - def _drag_pointing_device_to_delete(self, direction): |
2028 | - x, y, w, h = self.globalRect |
2029 | - tx = x + (w // 8) |
2030 | - ty = y + (h // 2) |
2031 | - |
2032 | - if direction == 'right': |
2033 | - self.pointing_device.drag(tx, ty, w, ty) |
2034 | - elif direction == 'left': |
2035 | - self.pointing_device.drag(w - (w*0.1), ty, x, ty) |
2036 | - else: |
2037 | - raise ToolkitEmulatorException( |
2038 | - 'Invalid direction "{0}" used on swipe to delete function' |
2039 | - .format(direction)) |
2040 | - |
2041 | - def _wait_until_deleted(self): |
2042 | - try: |
2043 | - # The item was hidden. |
2044 | - self.implicitHeight.wait_for(0) |
2045 | - except dbus.StateNotFoundError: |
2046 | - # The item was destroyed. |
2047 | - pass |
2048 | - |
2049 | - @autopilot_logging.log_action(logger.info) |
2050 | - def confirm_removal(self): |
2051 | - """Comfirm item removal if this was already swiped.""" |
2052 | - if self.waitingConfirmationForRemoval: |
2053 | - deleteButton = self._get_confirm_button() |
2054 | - self.pointing_device.click_object(deleteButton) |
2055 | - self._wait_until_deleted() |
2056 | - else: |
2057 | - raise ToolkitEmulatorException( |
2058 | - 'The item "{0}" is not waiting for removal confirmation'. |
2059 | - format(self.objectName)) |
2060 | - |
2061 | - |
2062 | -class Base(Empty): |
2063 | - pass |
2064 | - |
2065 | - |
2066 | -class Standard(Empty): |
2067 | - pass |
2068 | - |
2069 | - |
2070 | -class ItemSelector(Empty): |
2071 | - pass |
2072 | - |
2073 | - |
2074 | -class SingleControl(Empty): |
2075 | - pass |
2076 | - |
2077 | - |
2078 | -class MultiValue(Base): |
2079 | - pass |
2080 | - |
2081 | - |
2082 | -class SingleValue(Base): |
2083 | - pass |
2084 | - |
2085 | - |
2086 | -class Subtitled(Base): |
2087 | - pass |
2088 | - |
2089 | - |
2090 | -class ComposerSheet(UbuntuUIToolkitEmulatorBase): |
2091 | - """ComposerSheet Autopilot emulator.""" |
2092 | - |
2093 | - def __init__(self, *args): |
2094 | - super(ComposerSheet, self).__init__(*args) |
2095 | - |
2096 | - @autopilot_logging.log_action(logger.info) |
2097 | - def confirm(self): |
2098 | - """Confirm the composer sheet.""" |
2099 | - button = self.select_single('Button', objectName='confirmButton') |
2100 | - self.pointing_device.click_object(button) |
2101 | - self.wait_until_destroyed() |
2102 | - |
2103 | - @autopilot_logging.log_action(logger.info) |
2104 | - def cancel(self): |
2105 | - """Cancel the composer sheet.""" |
2106 | - button = self.select_single('Button', objectName='cancelButton') |
2107 | - self.pointing_device.click_object(button) |
2108 | - self.wait_until_destroyed() |
2109 | +"""Ubuntu UI Toolkit Autopilot custom proxy objects.""" |
2110 | + |
2111 | + |
2112 | +__all__ = [ |
2113 | + 'ActionSelectionPopover', |
2114 | + 'Base', |
2115 | + 'check_autopilot_version', |
2116 | + 'CheckBox', |
2117 | + 'ComposerSheet', |
2118 | + 'Empty', |
2119 | + 'Flickable', |
2120 | + 'get_keyboard', |
2121 | + 'get_pointing_device', |
2122 | + 'Header', |
2123 | + 'ItemSelector', |
2124 | + 'MainView', |
2125 | + 'MultiValue', |
2126 | + 'OptionSelector', |
2127 | + 'QQuickListView', |
2128 | + 'SingleControl', |
2129 | + 'SingleValue', |
2130 | + 'Standard', |
2131 | + 'Subtitled', |
2132 | + 'TabBar', |
2133 | + 'Tabs', |
2134 | + 'TextField', |
2135 | + 'Toolbar', |
2136 | + 'ToolkitException', |
2137 | + 'UbuntuUIToolkitCustomProxyObjectBase', |
2138 | +] |
2139 | + |
2140 | +from ubuntuuitoolkit._custom_proxy_objects._checkbox import CheckBox |
2141 | +from ubuntuuitoolkit._custom_proxy_objects._common import ( |
2142 | + check_autopilot_version, |
2143 | + get_keyboard, |
2144 | + get_pointing_device, |
2145 | + ToolkitException, |
2146 | + UbuntuUIToolkitCustomProxyObjectBase, |
2147 | +) |
2148 | +from ubuntuuitoolkit._custom_proxy_objects._flickable import Flickable |
2149 | +from ubuntuuitoolkit._custom_proxy_objects._header import Header |
2150 | +from ubuntuuitoolkit._custom_proxy_objects._listitems import ( |
2151 | + Base, |
2152 | + Empty, |
2153 | + ItemSelector, |
2154 | + MultiValue, |
2155 | + SingleControl, |
2156 | + SingleValue, |
2157 | + Standard, |
2158 | + Subtitled, |
2159 | +) |
2160 | +from ubuntuuitoolkit._custom_proxy_objects._mainview import MainView |
2161 | +from ubuntuuitoolkit._custom_proxy_objects._optionselector import ( |
2162 | + OptionSelector |
2163 | +) |
2164 | +from ubuntuuitoolkit._custom_proxy_objects._popups import ( |
2165 | + ActionSelectionPopover, |
2166 | + ComposerSheet, |
2167 | +) |
2168 | +from ubuntuuitoolkit._custom_proxy_objects._qquicklistview import ( |
2169 | + QQuickListView |
2170 | +) |
2171 | +from ubuntuuitoolkit._custom_proxy_objects._tabbar import TabBar |
2172 | +from ubuntuuitoolkit._custom_proxy_objects._tabs import Tabs |
2173 | +from ubuntuuitoolkit._custom_proxy_objects._textfield import TextField |
2174 | +from ubuntuuitoolkit._custom_proxy_objects._toolbar import Toolbar |
2175 | |
2176 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py' |
2177 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py 1970-01-01 00:00:00 +0000 |
2178 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py 2014-04-17 01:29:24 +0000 |
2179 | @@ -0,0 +1,65 @@ |
2180 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2181 | +# |
2182 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2183 | +# |
2184 | +# This program is free software; you can redistribute it and/or modify |
2185 | +# it under the terms of the GNU Lesser General Public License as published by |
2186 | +# the Free Software Foundation; version 3. |
2187 | +# |
2188 | +# This program is distributed in the hope that it will be useful, |
2189 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2190 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2191 | +# GNU Lesser General Public License for more details. |
2192 | +# |
2193 | +# You should have received a copy of the GNU Lesser General Public License |
2194 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2195 | + |
2196 | +import logging |
2197 | + |
2198 | +from autopilot import logging as autopilot_logging |
2199 | + |
2200 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
2201 | + |
2202 | + |
2203 | +logger = logging.getLogger(__name__) |
2204 | + |
2205 | + |
2206 | +class CheckBox(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2207 | + """CheckBox Autopilot emulator.""" |
2208 | + |
2209 | + @autopilot_logging.log_action(logger.info) |
2210 | + def check(self, timeout=10): |
2211 | + """Check a CheckBox, if its not already checked. |
2212 | + |
2213 | + :parameter timeout: number of seconds to wait for the CheckBox to be |
2214 | + checked. Default is 10. |
2215 | + |
2216 | + """ |
2217 | + if not self.checked: |
2218 | + self.change_state(timeout) |
2219 | + |
2220 | + @autopilot_logging.log_action(logger.info) |
2221 | + def uncheck(self, timeout=10): |
2222 | + """Uncheck a CheckBox, if its not already unchecked. |
2223 | + |
2224 | + :parameter timeout: number of seconds to wait for the CheckBox to be |
2225 | + unchecked. Default is 10. |
2226 | + |
2227 | + """ |
2228 | + if self.checked: |
2229 | + self.change_state(timeout) |
2230 | + |
2231 | + @autopilot_logging.log_action(logger.info) |
2232 | + def change_state(self, timeout=10): |
2233 | + """Change the state of a CheckBox. |
2234 | + |
2235 | + If it is checked, it will be unchecked. If it is unchecked, it will be |
2236 | + checked. |
2237 | + |
2238 | + :parameter time_out: number of seconds to wait for the CheckBox state |
2239 | + to change. Default is 10. |
2240 | + |
2241 | + """ |
2242 | + original_state = self.checked |
2243 | + self.pointing_device.click_object(self) |
2244 | + self.checked.wait_for(not original_state, timeout) |
2245 | |
2246 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py' |
2247 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py 1970-01-01 00:00:00 +0000 |
2248 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py 2014-04-17 01:29:24 +0000 |
2249 | @@ -0,0 +1,69 @@ |
2250 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2251 | +# |
2252 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2253 | +# |
2254 | +# This program is free software; you can redistribute it and/or modify |
2255 | +# it under the terms of the GNU Lesser General Public License as published by |
2256 | +# the Free Software Foundation; version 3. |
2257 | +# |
2258 | +# This program is distributed in the hope that it will be useful, |
2259 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2260 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2261 | +# GNU Lesser General Public License for more details. |
2262 | +# |
2263 | +# You should have received a copy of the GNU Lesser General Public License |
2264 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2265 | + |
2266 | +"""Common helpers for Ubuntu UI Toolkit Autopilot custom proxy objects.""" |
2267 | + |
2268 | +from distutils import version |
2269 | + |
2270 | +import autopilot |
2271 | +from autopilot import platform, input |
2272 | +from autopilot.introspection import dbus |
2273 | + |
2274 | + |
2275 | +class ToolkitException(Exception): |
2276 | + """Exception raised when there is an error with the emulator.""" |
2277 | + |
2278 | + |
2279 | +def get_pointing_device(): |
2280 | + """Return the pointing device depending on the platform. |
2281 | + |
2282 | + If the platform is `Desktop`, the pointing device will be a `Mouse`. |
2283 | + If not, the pointing device will be `Touch`. |
2284 | + |
2285 | + """ |
2286 | + if platform.model() == 'Desktop': |
2287 | + input_device_class = input.Mouse |
2288 | + else: |
2289 | + input_device_class = input.Touch |
2290 | + return input.Pointer(device=input_device_class.create()) |
2291 | + |
2292 | + |
2293 | +def get_keyboard(): |
2294 | + """Return the keyboard device.""" |
2295 | + # TODO return the OSK if we are on the phone. --elopio - 2014-01-13 |
2296 | + return input.Keyboard.create() |
2297 | + |
2298 | + |
2299 | +def check_autopilot_version(): |
2300 | + """Check that the Autopilot installed version matches the one required. |
2301 | + |
2302 | + :raise ToolkitException: If the installed Autopilot version does't |
2303 | + match the required by the emulators. |
2304 | + |
2305 | + """ |
2306 | + installed_version = version.LooseVersion(autopilot.version) |
2307 | + if installed_version < version.LooseVersion('1.4'): |
2308 | + raise ToolkitException( |
2309 | + 'The emulators need Autopilot 1.4 or higher.') |
2310 | + |
2311 | + |
2312 | +class UbuntuUIToolkitCustomProxyObjectBase(dbus.CustomEmulatorBase): |
2313 | + """A base class for all the Ubuntu UI Toolkit emulators.""" |
2314 | + |
2315 | + def __init__(self, *args): |
2316 | + check_autopilot_version() |
2317 | + super(UbuntuUIToolkitCustomProxyObjectBase, self).__init__(*args) |
2318 | + self.pointing_device = get_pointing_device() |
2319 | |
2320 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py' |
2321 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 1970-01-01 00:00:00 +0000 |
2322 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 2014-04-17 01:29:24 +0000 |
2323 | @@ -0,0 +1,150 @@ |
2324 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2325 | +# |
2326 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2327 | +# |
2328 | +# This program is free software; you can redistribute it and/or modify |
2329 | +# it under the terms of the GNU Lesser General Public License as published by |
2330 | +# the Free Software Foundation; version 3. |
2331 | +# |
2332 | +# This program is distributed in the hope that it will be useful, |
2333 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2334 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2335 | +# GNU Lesser General Public License for more details. |
2336 | +# |
2337 | +# You should have received a copy of the GNU Lesser General Public License |
2338 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2339 | + |
2340 | +import logging |
2341 | + |
2342 | +from autopilot import logging as autopilot_logging |
2343 | + |
2344 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
2345 | + |
2346 | + |
2347 | +logger = logging.getLogger(__name__) |
2348 | + |
2349 | + |
2350 | +# Containers helpers. |
2351 | + |
2352 | +def _get_visible_container_top(containers): |
2353 | + containers_top = [container.globalRect.y for container in containers] |
2354 | + return max(containers_top) |
2355 | + |
2356 | + |
2357 | +def _get_visible_container_bottom(containers): |
2358 | + containers_bottom = [ |
2359 | + container.globalRect.y + container.globalRect.height |
2360 | + for container in containers if container.globalRect.height > 0] |
2361 | + return min(containers_bottom) |
2362 | + |
2363 | + |
2364 | +class Flickable(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2365 | + |
2366 | + @autopilot_logging.log_action(logger.info) |
2367 | + def swipe_child_into_view(self, child): |
2368 | + """Make the child visible. |
2369 | + |
2370 | + Currently it works only when the object needs to be swiped vertically. |
2371 | + TODO implement horizontal swiping. --elopio - 2014-03-21 |
2372 | + |
2373 | + """ |
2374 | + containers = self._get_containers() |
2375 | + if not self._is_child_visible(child, containers): |
2376 | + self._swipe_non_visible_child_into_view(child, containers) |
2377 | + else: |
2378 | + logger.debug('The element is already visible.') |
2379 | + |
2380 | + def _get_containers(self): |
2381 | + """Return a list with the containers to take into account when swiping. |
2382 | + |
2383 | + The list includes this flickable and the top-most container. |
2384 | + TODO add additional flickables that are between this and the top |
2385 | + container. --elopio - 2014-03-22 |
2386 | + |
2387 | + """ |
2388 | + containers = [self._get_top_container(), self] |
2389 | + return containers |
2390 | + |
2391 | + def _get_top_container(self): |
2392 | + """Return the top-most container with a globalRect.""" |
2393 | + root = self.get_root_instance() |
2394 | + containers = [root] |
2395 | + while len(containers) == 1: |
2396 | + try: |
2397 | + containers[0].globalRect |
2398 | + return containers[0] |
2399 | + except AttributeError: |
2400 | + containers = containers[0].get_children() |
2401 | + |
2402 | + raise _common.ToolkitException("Couldn't find the top-most container.") |
2403 | + |
2404 | + def _is_child_visible(self, child, containers): |
2405 | + """Check if the center of the child is visible. |
2406 | + |
2407 | + :return: True if the center of the child is visible, False otherwise. |
2408 | + |
2409 | + """ |
2410 | + object_center = child.globalRect.y + child.globalRect.height // 2 |
2411 | + visible_top = _get_visible_container_top(containers) |
2412 | + visible_bottom = _get_visible_container_bottom(containers) |
2413 | + return (object_center >= visible_top and |
2414 | + object_center <= visible_bottom) |
2415 | + |
2416 | + @autopilot_logging.log_action(logger.info) |
2417 | + def _swipe_non_visible_child_into_view(self, child, containers): |
2418 | + while not self._is_child_visible(child, containers): |
2419 | + # Check the direction of the swipe based on the position of the |
2420 | + # child relative to the immediate flickable container. |
2421 | + if child.globalRect.y < self.globalRect.y: |
2422 | + self._swipe_to_show_more_above(containers) |
2423 | + else: |
2424 | + self._swipe_to_show_more_below(containers) |
2425 | + |
2426 | + @autopilot_logging.log_action(logger.info) |
2427 | + def _swipe_to_show_more_above(self, containers): |
2428 | + if self.atYBeginning: |
2429 | + raise _common.ToolkitException( |
2430 | + "Can't swipe more, we are already at the top of the " |
2431 | + "container.") |
2432 | + else: |
2433 | + self._swipe_to_show_more('above', containers) |
2434 | + |
2435 | + @autopilot_logging.log_action(logger.info) |
2436 | + def _swipe_to_show_more_below(self, containers): |
2437 | + if self.atYEnd: |
2438 | + raise _common.ToolkitException( |
2439 | + "Can't swipe more, we are already at the bottom of the " |
2440 | + "container.") |
2441 | + else: |
2442 | + self._swipe_to_show_more('below', containers) |
2443 | + |
2444 | + def _swipe_to_show_more(self, direction, containers): |
2445 | + start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2) |
2446 | + # Start and stop just a little under the top and a little over the |
2447 | + # bottom. |
2448 | + top = _get_visible_container_top(containers) + 5 |
2449 | + bottom = _get_visible_container_bottom(containers) - 5 |
2450 | + if direction == 'below': |
2451 | + start_y = bottom |
2452 | + stop_y = top |
2453 | + elif direction == 'above': |
2454 | + start_y = top |
2455 | + stop_y = bottom |
2456 | + else: |
2457 | + raise _common.ToolkitException( |
2458 | + 'Invalid direction {}.'.format(direction)) |
2459 | + self._slow_drag(start_x, stop_x, start_y, stop_y) |
2460 | + self.dragging.wait_for(False) |
2461 | + self.moving.wait_for(False) |
2462 | + |
2463 | + def _slow_drag(self, start_x, stop_x, start_y, stop_y): |
2464 | + # If we drag too fast, we end up scrolling more than what we |
2465 | + # should, sometimes missing the element we are looking for. |
2466 | + self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5) |
2467 | + |
2468 | + @autopilot_logging.log_action(logger.info) |
2469 | + def _scroll_to_top(self): |
2470 | + if not self.atYBeginning: |
2471 | + containers = self._get_containers() |
2472 | + while not self.atYBeginning: |
2473 | + self._swipe_to_show_more_above(containers) |
2474 | |
2475 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py' |
2476 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 1970-01-01 00:00:00 +0000 |
2477 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 2014-04-17 01:29:24 +0000 |
2478 | @@ -0,0 +1,57 @@ |
2479 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2480 | +# |
2481 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2482 | +# |
2483 | +# This program is free software; you can redistribute it and/or modify |
2484 | +# it under the terms of the GNU Lesser General Public License as published by |
2485 | +# the Free Software Foundation; version 3. |
2486 | +# |
2487 | +# This program is distributed in the hope that it will be useful, |
2488 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2489 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2490 | +# GNU Lesser General Public License for more details. |
2491 | +# |
2492 | +# You should have received a copy of the GNU Lesser General Public License |
2493 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2494 | + |
2495 | +import logging |
2496 | + |
2497 | +from autopilot import logging as autopilot_logging |
2498 | +from autopilot.introspection import dbus |
2499 | + |
2500 | +from ubuntuuitoolkit._custom_proxy_objects import ( |
2501 | + _common, |
2502 | + _tabbar |
2503 | +) |
2504 | + |
2505 | + |
2506 | +_NO_TABS_ERROR = 'The MainView has no Tabs.' |
2507 | + |
2508 | + |
2509 | +logger = logging.getLogger(__name__) |
2510 | + |
2511 | + |
2512 | +class Header(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2513 | + """Header Autopilot emulator.""" |
2514 | + |
2515 | + def __init__(self, *args): |
2516 | + super(Header, self).__init__(*args) |
2517 | + self.pointing_device = _common.get_pointing_device() |
2518 | + |
2519 | + def _get_animating(self): |
2520 | + tab_bar_style = self.select_single('TabBarStyle') |
2521 | + return tab_bar_style.animating |
2522 | + |
2523 | + @autopilot_logging.log_action(logger.info) |
2524 | + def switch_to_next_tab(self): |
2525 | + """Open the next tab. |
2526 | + |
2527 | + :raise ToolkitException: If the main view has no tabs. |
2528 | + |
2529 | + """ |
2530 | + try: |
2531 | + tab_bar = self.select_single(_tabbar.TabBar) |
2532 | + except dbus.StateNotFoundError: |
2533 | + raise _common.ToolkitException(_NO_TABS_ERROR) |
2534 | + tab_bar.switch_to_next_tab() |
2535 | + self._get_animating().wait_for(False) |
2536 | |
2537 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py' |
2538 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py 1970-01-01 00:00:00 +0000 |
2539 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py 2014-04-17 01:29:24 +0000 |
2540 | @@ -0,0 +1,114 @@ |
2541 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2542 | +# |
2543 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2544 | +# |
2545 | +# This program is free software; you can redistribute it and/or modify |
2546 | +# it under the terms of the GNU Lesser General Public License as published by |
2547 | +# the Free Software Foundation; version 3. |
2548 | +# |
2549 | +# This program is distributed in the hope that it will be useful, |
2550 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2551 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2552 | +# GNU Lesser General Public License for more details. |
2553 | +# |
2554 | +# You should have received a copy of the GNU Lesser General Public License |
2555 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2556 | + |
2557 | +import logging |
2558 | + |
2559 | +from autopilot import logging as autopilot_logging |
2560 | +from autopilot.introspection import dbus |
2561 | + |
2562 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
2563 | + |
2564 | + |
2565 | +logger = logging.getLogger(__name__) |
2566 | + |
2567 | + |
2568 | +class Empty(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2569 | + """Base class to emulate swipe to delete.""" |
2570 | + |
2571 | + def exists(self): |
2572 | + try: |
2573 | + return self.implicitHeight > 0 |
2574 | + except dbus.StateNotFoundError: |
2575 | + return False |
2576 | + |
2577 | + def _get_confirm_button(self): |
2578 | + return self.select_single( |
2579 | + 'QQuickItem', objectName='confirmRemovalDialog') |
2580 | + |
2581 | + @autopilot_logging.log_action(logger.info) |
2582 | + def swipe_to_delete(self, direction='right'): |
2583 | + """Swipe the item in a specific direction.""" |
2584 | + if self.removable: |
2585 | + self._drag_pointing_device_to_delete(direction) |
2586 | + if self.confirmRemoval: |
2587 | + self.waitingConfirmationForRemoval.wait_for(True) |
2588 | + else: |
2589 | + self._wait_until_deleted() |
2590 | + else: |
2591 | + raise _common.ToolkitException( |
2592 | + 'The item "{0}" is not removable'.format(self.objectName)) |
2593 | + |
2594 | + def _drag_pointing_device_to_delete(self, direction): |
2595 | + x, y, w, h = self.globalRect |
2596 | + tx = x + (w // 8) |
2597 | + ty = y + (h // 2) |
2598 | + |
2599 | + if direction == 'right': |
2600 | + self.pointing_device.drag(tx, ty, w, ty) |
2601 | + elif direction == 'left': |
2602 | + self.pointing_device.drag(w - (w*0.1), ty, x, ty) |
2603 | + else: |
2604 | + raise _common.ToolkitException( |
2605 | + 'Invalid direction "{0}" used on swipe to delete function' |
2606 | + .format(direction)) |
2607 | + |
2608 | + def _wait_until_deleted(self): |
2609 | + try: |
2610 | + # The item was hidden. |
2611 | + self.implicitHeight.wait_for(0) |
2612 | + except dbus.StateNotFoundError: |
2613 | + # The item was destroyed. |
2614 | + pass |
2615 | + |
2616 | + @autopilot_logging.log_action(logger.info) |
2617 | + def confirm_removal(self): |
2618 | + """Comfirm item removal if this was already swiped.""" |
2619 | + if self.waitingConfirmationForRemoval: |
2620 | + deleteButton = self._get_confirm_button() |
2621 | + self.pointing_device.click_object(deleteButton) |
2622 | + self._wait_until_deleted() |
2623 | + else: |
2624 | + raise _common.ToolkitException( |
2625 | + 'The item "{0}" is not waiting for removal confirmation'. |
2626 | + format(self.objectName)) |
2627 | + |
2628 | + |
2629 | +class Base(Empty): |
2630 | + pass |
2631 | + |
2632 | + |
2633 | +class Standard(Empty): |
2634 | + pass |
2635 | + |
2636 | + |
2637 | +class ItemSelector(Empty): |
2638 | + pass |
2639 | + |
2640 | + |
2641 | +class SingleControl(Empty): |
2642 | + pass |
2643 | + |
2644 | + |
2645 | +class MultiValue(Base): |
2646 | + pass |
2647 | + |
2648 | + |
2649 | +class SingleValue(Base): |
2650 | + pass |
2651 | + |
2652 | + |
2653 | +class Subtitled(Base): |
2654 | + pass |
2655 | |
2656 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py' |
2657 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py 1970-01-01 00:00:00 +0000 |
2658 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py 2014-04-17 01:29:24 +0000 |
2659 | @@ -0,0 +1,162 @@ |
2660 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2661 | +# |
2662 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2663 | +# |
2664 | +# This program is free software; you can redistribute it and/or modify |
2665 | +# it under the terms of the GNU Lesser General Public License as published by |
2666 | +# the Free Software Foundation; version 3. |
2667 | +# |
2668 | +# This program is distributed in the hope that it will be useful, |
2669 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2670 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2671 | +# GNU Lesser General Public License for more details. |
2672 | +# |
2673 | +# You should have received a copy of the GNU Lesser General Public License |
2674 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2675 | + |
2676 | +"""Ubuntu UI Toolkit Autopilot custom proxy objects.""" |
2677 | + |
2678 | +import logging |
2679 | + |
2680 | +from autopilot import logging as autopilot_logging |
2681 | +from autopilot.introspection import dbus |
2682 | + |
2683 | +from ubuntuuitoolkit._custom_proxy_objects import ( |
2684 | + _common, |
2685 | + _popups, |
2686 | + _tabs, |
2687 | + _toolbar, |
2688 | +) |
2689 | + |
2690 | + |
2691 | +_NO_TABS_ERROR = 'The MainView has no Tabs.' |
2692 | + |
2693 | + |
2694 | +logger = logging.getLogger(__name__) |
2695 | + |
2696 | + |
2697 | +class MainView(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2698 | + """MainView Autopilot emulator.""" |
2699 | + |
2700 | + def get_header(self): |
2701 | + """Return the Header emulator of the MainView.""" |
2702 | + try: |
2703 | + return self.select_single('Header', objectName='MainView_Header') |
2704 | + except dbus.StateNotFoundError: |
2705 | + raise _common.ToolkitException('The main view has no header.') |
2706 | + |
2707 | + def get_toolbar(self): |
2708 | + """Return the Toolbar emulator of the MainView.""" |
2709 | + return self.select_single(_toolbar.Toolbar) |
2710 | + |
2711 | + @autopilot_logging.log_action(logger.info) |
2712 | + def open_toolbar(self): |
2713 | + """Open the toolbar if it's not already opened. |
2714 | + |
2715 | + :return: The toolbar. |
2716 | + |
2717 | + """ |
2718 | + return self.get_toolbar().open() |
2719 | + |
2720 | + @autopilot_logging.log_action(logger.info) |
2721 | + def close_toolbar(self): |
2722 | + """Close the toolbar if it's opened.""" |
2723 | + self.get_toolbar().close() |
2724 | + |
2725 | + def get_tabs(self): |
2726 | + """Return the Tabs emulator of the MainView. |
2727 | + |
2728 | + :raise ToolkitException: If the main view has no tabs. |
2729 | + |
2730 | + """ |
2731 | + try: |
2732 | + return self.select_single(_tabs.Tabs) |
2733 | + except dbus.StateNotFoundError: |
2734 | + raise _common.ToolkitException(_NO_TABS_ERROR) |
2735 | + |
2736 | + @autopilot_logging.log_action(logger.info) |
2737 | + def switch_to_next_tab(self): |
2738 | + """Open the next tab. |
2739 | + |
2740 | + :return: The newly opened tab. |
2741 | + |
2742 | + """ |
2743 | + logger.debug('Switch to next tab.') |
2744 | + self.get_header().switch_to_next_tab() |
2745 | + current_tab = self.get_tabs().get_current_tab() |
2746 | + current_tab.visible.wait_for(True) |
2747 | + return current_tab |
2748 | + |
2749 | + @autopilot_logging.log_action(logger.info) |
2750 | + def switch_to_tab_by_index(self, index): |
2751 | + """Open a tab. |
2752 | + |
2753 | + :parameter index: The index of the tab to open. |
2754 | + :return: The newly opened tab. |
2755 | + :raise ToolkitException: If the tab index is out of range. |
2756 | + |
2757 | + """ |
2758 | + logger.debug('Switch to tab with index {0}.'.format(index)) |
2759 | + tabs = self.get_tabs() |
2760 | + number_of_tabs = tabs.get_number_of_tabs() |
2761 | + if index >= number_of_tabs: |
2762 | + raise _common.ToolkitException('Tab index out of range.') |
2763 | + current_tab = tabs.get_current_tab() |
2764 | + number_of_switches = 0 |
2765 | + while not tabs.selectedTabIndex == index: |
2766 | + logger.debug( |
2767 | + 'Current tab index: {0}.'.format(tabs.selectedTabIndex)) |
2768 | + if number_of_switches >= number_of_tabs - 1: |
2769 | + # This prevents a loop. But if this error is ever raised, it's |
2770 | + # likely there's a bug on the emulator or on the QML Tab. |
2771 | + raise _common.ToolkitException( |
2772 | + 'The tab with index {0} was not selected.'.format(index)) |
2773 | + current_tab = self.switch_to_next_tab() |
2774 | + number_of_switches += 1 |
2775 | + return current_tab |
2776 | + |
2777 | + @autopilot_logging.log_action(logger.info) |
2778 | + def switch_to_previous_tab(self): |
2779 | + """Open the previous tab. |
2780 | + |
2781 | + :return: The newly opened tab. |
2782 | + |
2783 | + """ |
2784 | + tabs = self.get_tabs() |
2785 | + if tabs.selectedTabIndex == 0: |
2786 | + previous_tab_index = tabs.get_number_of_tabs() - 1 |
2787 | + else: |
2788 | + previous_tab_index = tabs.selectedTabIndex - 1 |
2789 | + return self.switch_to_tab_by_index(previous_tab_index) |
2790 | + |
2791 | + @autopilot_logging.log_action(logger.info) |
2792 | + def switch_to_tab(self, object_name): |
2793 | + """Open a tab. |
2794 | + |
2795 | + :parameter object_name: The QML objectName property of the tab. |
2796 | + :return: The newly opened tab. |
2797 | + :raise ToolkitException: If there is no tab with that object |
2798 | + name. |
2799 | + |
2800 | + """ |
2801 | + tabs = self.get_tabs() |
2802 | + for index, tab in enumerate(tabs.select_many('Tab')): |
2803 | + if tab.objectName == object_name: |
2804 | + return self.switch_to_tab_by_index(tab.index) |
2805 | + raise _common.ToolkitException( |
2806 | + 'Tab with objectName "{0}" not found.'.format(object_name)) |
2807 | + |
2808 | + def get_action_selection_popover(self, object_name): |
2809 | + """Return an ActionSelectionPopover emulator. |
2810 | + |
2811 | + :parameter object_name: The QML objectName property of the popover. |
2812 | + |
2813 | + """ |
2814 | + return self.select_single( |
2815 | + _popups.ActionSelectionPopover, objectName=object_name) |
2816 | + |
2817 | + @autopilot_logging.log_action(logger.info) |
2818 | + def go_back(self): |
2819 | + """Go to the previous page.""" |
2820 | + toolbar = self.open_toolbar() |
2821 | + toolbar.click_back_button() |
2822 | |
2823 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py' |
2824 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py 1970-01-01 00:00:00 +0000 |
2825 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py 2014-04-17 01:29:24 +0000 |
2826 | @@ -0,0 +1,82 @@ |
2827 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2828 | +# |
2829 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2830 | +# |
2831 | +# This program is free software; you can redistribute it and/or modify |
2832 | +# it under the terms of the GNU Lesser General Public License as published by |
2833 | +# the Free Software Foundation; version 3. |
2834 | +# |
2835 | +# This program is distributed in the hope that it will be useful, |
2836 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2837 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2838 | +# GNU Lesser General Public License for more details. |
2839 | +# |
2840 | +# You should have received a copy of the GNU Lesser General Public License |
2841 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2842 | + |
2843 | +import logging |
2844 | + |
2845 | +from autopilot import logging as autopilot_logging |
2846 | +from autopilot.introspection import dbus |
2847 | + |
2848 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
2849 | + |
2850 | + |
2851 | +logger = logging.getLogger(__name__) |
2852 | + |
2853 | + |
2854 | +class ActionSelectionPopover(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2855 | + """ActionSelectionPopover Autopilot emulator.""" |
2856 | + |
2857 | + def click_button_by_text(self, text): |
2858 | + """Click a button on the popover. |
2859 | + |
2860 | + XXX We are receiving the text because there's no way to set the |
2861 | + objectName on the action. This is reported at |
2862 | + https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1205144 |
2863 | + --elopio - 2013-07-25 |
2864 | + |
2865 | + :parameter text: The text of the button. |
2866 | + :raise ToolkitException: If the popover is not open. |
2867 | + |
2868 | + """ |
2869 | + if not self.visible: |
2870 | + raise _common.ToolkitException('The popover is not open.') |
2871 | + button = self._get_button(text) |
2872 | + if button is None: |
2873 | + raise _common.ToolkitException( |
2874 | + 'Button with text "{0}" not found.'.format(text)) |
2875 | + self.pointing_device.click_object(button) |
2876 | + if self.autoClose: |
2877 | + try: |
2878 | + self.visible.wait_for(False) |
2879 | + except dbus.StateNotFoundError: |
2880 | + # The popover was removed from the tree. |
2881 | + pass |
2882 | + |
2883 | + def _get_button(self, text): |
2884 | + buttons = self.select_many('Empty') |
2885 | + for button in buttons: |
2886 | + if button.text == text: |
2887 | + return button |
2888 | + |
2889 | + |
2890 | +class ComposerSheet(_common.UbuntuUIToolkitCustomProxyObjectBase): |
2891 | + """ComposerSheet Autopilot emulator.""" |
2892 | + |
2893 | + def __init__(self, *args): |
2894 | + super(ComposerSheet, self).__init__(*args) |
2895 | + |
2896 | + @autopilot_logging.log_action(logger.info) |
2897 | + def confirm(self): |
2898 | + """Confirm the composer sheet.""" |
2899 | + button = self.select_single('Button', objectName='confirmButton') |
2900 | + self.pointing_device.click_object(button) |
2901 | + self.wait_until_destroyed() |
2902 | + |
2903 | + @autopilot_logging.log_action(logger.info) |
2904 | + def cancel(self): |
2905 | + """Cancel the composer sheet.""" |
2906 | + button = self.select_single('Button', objectName='cancelButton') |
2907 | + self.pointing_device.click_object(button) |
2908 | + self.wait_until_destroyed() |
2909 | |
2910 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py' |
2911 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 1970-01-01 00:00:00 +0000 |
2912 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2014-04-17 01:29:24 +0000 |
2913 | @@ -0,0 +1,65 @@ |
2914 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2915 | +# |
2916 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2917 | +# |
2918 | +# This program is free software; you can redistribute it and/or modify |
2919 | +# it under the terms of the GNU Lesser General Public License as published by |
2920 | +# the Free Software Foundation; version 3. |
2921 | +# |
2922 | +# This program is distributed in the hope that it will be useful, |
2923 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2924 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2925 | +# GNU Lesser General Public License for more details. |
2926 | +# |
2927 | +# You should have received a copy of the GNU Lesser General Public License |
2928 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2929 | + |
2930 | +import logging |
2931 | + |
2932 | +from autopilot import logging as autopilot_logging |
2933 | +from autopilot.introspection import dbus |
2934 | + |
2935 | +from ubuntuuitoolkit._custom_proxy_objects import _flickable |
2936 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
2937 | + |
2938 | + |
2939 | +logger = logging.getLogger(__name__) |
2940 | + |
2941 | + |
2942 | +class QQuickListView(_flickable.Flickable): |
2943 | + |
2944 | + @autopilot_logging.log_action(logger.info) |
2945 | + def click_element(self, object_name): |
2946 | + """Click an element from the list. |
2947 | + |
2948 | + It swipes the element into view if it's center is not visible. |
2949 | + |
2950 | + :parameter objectName: The objectName property of the element to click. |
2951 | + |
2952 | + """ |
2953 | + try: |
2954 | + element = self.select_single(objectName=object_name) |
2955 | + except dbus.StateNotFoundError: |
2956 | + # The element might be on a part of the list that hasn't been |
2957 | + # created yet. We have to search for it scrolling the entire list. |
2958 | + element = self._find_element(object_name) |
2959 | + self.swipe_child_into_view(element) |
2960 | + self.pointing_device.click_object(element) |
2961 | + |
2962 | + @autopilot_logging.log_action(logger.info) |
2963 | + def _find_element(self, object_name): |
2964 | + self._scroll_to_top() |
2965 | + while not self.atYEnd: |
2966 | + containers = self._get_containers() |
2967 | + self._swipe_to_show_more_below(containers) |
2968 | + try: |
2969 | + return self.select_single(objectName=object_name) |
2970 | + except dbus.StateNotFoundError: |
2971 | + pass |
2972 | + raise _common.ToolkitException( |
2973 | + 'List element with objectName "{}" not found.'.format(object_name)) |
2974 | + |
2975 | + def _is_element_clickable(self, object_name): |
2976 | + child = self.select_single(objectName=object_name) |
2977 | + containers = self._get_containers() |
2978 | + return self._is_child_visible(child, containers) |
2979 | |
2980 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py' |
2981 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py 1970-01-01 00:00:00 +0000 |
2982 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py 2014-04-17 01:29:24 +0000 |
2983 | @@ -0,0 +1,68 @@ |
2984 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
2985 | +# |
2986 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
2987 | +# |
2988 | +# This program is free software; you can redistribute it and/or modify |
2989 | +# it under the terms of the GNU Lesser General Public License as published by |
2990 | +# the Free Software Foundation; version 3. |
2991 | +# |
2992 | +# This program is distributed in the hope that it will be useful, |
2993 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2994 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2995 | +# GNU Lesser General Public License for more details. |
2996 | +# |
2997 | +# You should have received a copy of the GNU Lesser General Public License |
2998 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
2999 | + |
3000 | +import logging |
3001 | + |
3002 | +from autopilot import logging as autopilot_logging |
3003 | + |
3004 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
3005 | + |
3006 | + |
3007 | +logger = logging.getLogger(__name__) |
3008 | + |
3009 | + |
3010 | +class TabBar(_common.UbuntuUIToolkitCustomProxyObjectBase): |
3011 | + """TabBar Autopilot emulator.""" |
3012 | + |
3013 | + @autopilot_logging.log_action(logger.info) |
3014 | + def switch_to_next_tab(self): |
3015 | + """Open the next tab.""" |
3016 | + self._activate_tab_bar() |
3017 | + logger.debug('Click the next tab bar button.') |
3018 | + self.pointing_device.click_object(self._get_next_tab_button()) |
3019 | + |
3020 | + def _activate_tab_bar(self): |
3021 | + # First move to the tab bar to avoid timing issues when we find it in |
3022 | + # selection mode but it's deselected while we move to it. |
3023 | + self.pointing_device.move_to_object(self) |
3024 | + if self.selectionMode: |
3025 | + logger.debug('Already in selection mode.') |
3026 | + else: |
3027 | + # Click the tab bar to switch to selection mode. |
3028 | + logger.debug('Click the tab bar to enable selection mode.') |
3029 | + self.pointing_device.click_object(self) |
3030 | + |
3031 | + def _get_next_tab_button(self): |
3032 | + current_index = self._get_selected_button_index() |
3033 | + next_index = (current_index + 1) % self._get_number_of_tab_buttons() |
3034 | + return self._get_tab_button(next_index) |
3035 | + |
3036 | + def _get_selected_button_index(self): |
3037 | + return self.select_single('QQuickPathView').selectedButtonIndex |
3038 | + |
3039 | + def _get_number_of_tab_buttons(self): |
3040 | + return len(self._get_tab_buttons()) |
3041 | + |
3042 | + def _get_tab_buttons(self): |
3043 | + return self.select_many('AbstractButton') |
3044 | + |
3045 | + def _get_tab_button(self, index): |
3046 | + buttons = self._get_tab_buttons() |
3047 | + for button in buttons: |
3048 | + if button.buttonIndex == index: |
3049 | + return button |
3050 | + raise _common.ToolkitException( |
3051 | + 'There is no tab button with index {0}.'.format(index)) |
3052 | |
3053 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py' |
3054 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py 1970-01-01 00:00:00 +0000 |
3055 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py 2014-04-17 01:29:24 +0000 |
3056 | @@ -0,0 +1,41 @@ |
3057 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3058 | +# |
3059 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
3060 | +# |
3061 | +# This program is free software; you can redistribute it and/or modify |
3062 | +# it under the terms of the GNU Lesser General Public License as published by |
3063 | +# the Free Software Foundation; version 3. |
3064 | +# |
3065 | +# This program is distributed in the hope that it will be useful, |
3066 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3067 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3068 | +# GNU Lesser General Public License for more details. |
3069 | +# |
3070 | +# You should have received a copy of the GNU Lesser General Public License |
3071 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3072 | + |
3073 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
3074 | + |
3075 | + |
3076 | +class Tabs(_common.UbuntuUIToolkitCustomProxyObjectBase): |
3077 | + """Tabs Autopilot emulator.""" |
3078 | + |
3079 | + def get_current_tab(self): |
3080 | + """Return the currently selected tab.""" |
3081 | + return self._get_tab(self.selectedTabIndex) |
3082 | + |
3083 | + def _get_tab(self, index): |
3084 | + tabs = self._get_tabs() |
3085 | + for tab in tabs: |
3086 | + if tab.index == index: |
3087 | + return tab |
3088 | + else: |
3089 | + raise _common.ToolkitException( |
3090 | + 'There is no tab with index {0}.'.format(index)) |
3091 | + |
3092 | + def _get_tabs(self): |
3093 | + return self.select_many('Tab') |
3094 | + |
3095 | + def get_number_of_tabs(self): |
3096 | + """Return the number of tabs.""" |
3097 | + return len(self._get_tabs()) |
3098 | |
3099 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py' |
3100 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py 1970-01-01 00:00:00 +0000 |
3101 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py 2014-04-17 01:29:24 +0000 |
3102 | @@ -0,0 +1,88 @@ |
3103 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3104 | +# |
3105 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
3106 | +# |
3107 | +# This program is free software; you can redistribute it and/or modify |
3108 | +# it under the terms of the GNU Lesser General Public License as published by |
3109 | +# the Free Software Foundation; version 3. |
3110 | +# |
3111 | +# This program is distributed in the hope that it will be useful, |
3112 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3113 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3114 | +# GNU Lesser General Public License for more details. |
3115 | +# |
3116 | +# You should have received a copy of the GNU Lesser General Public License |
3117 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3118 | + |
3119 | +from autopilot import platform |
3120 | + |
3121 | +from ubuntuuitoolkit._custom_proxy_objects import ( |
3122 | + _common, |
3123 | + _mainview |
3124 | +) |
3125 | + |
3126 | + |
3127 | +class TextField(_common.UbuntuUIToolkitCustomProxyObjectBase): |
3128 | + """TextField Autopilot emulator.""" |
3129 | + |
3130 | + def __init__(self, *args): |
3131 | + super(TextField, self).__init__(*args) |
3132 | + self.keyboard = _common.get_keyboard() |
3133 | + |
3134 | + def write(self, text, clear=True): |
3135 | + """Write into the text field. |
3136 | + |
3137 | + :parameter text: The text to write. |
3138 | + :parameter clear: If True, the text field will be cleared before |
3139 | + writing the text. If False, the text will be appended at the end |
3140 | + of the text field. Default is True. |
3141 | + |
3142 | + """ |
3143 | + with self.keyboard.focused_type(self): |
3144 | + self.focus.wait_for(True) |
3145 | + if clear: |
3146 | + self.clear() |
3147 | + else: |
3148 | + if not self.is_empty(): |
3149 | + self.keyboard.press_and_release('End') |
3150 | + self.keyboard.type(text) |
3151 | + |
3152 | + def clear(self): |
3153 | + """Clear the text field.""" |
3154 | + if not self.is_empty(): |
3155 | + if self.hasClearButton: |
3156 | + self._click_clear_button() |
3157 | + else: |
3158 | + self._clear_with_keys() |
3159 | + self.text.wait_for('') |
3160 | + |
3161 | + def is_empty(self): |
3162 | + """Return True if the text field is empty. False otherwise.""" |
3163 | + return self.text == '' |
3164 | + |
3165 | + def _click_clear_button(self): |
3166 | + clear_button = self.select_single( |
3167 | + 'AbstractButton', objectName='clear_button') |
3168 | + if not clear_button.visible: |
3169 | + self.pointing_device.click_object(self) |
3170 | + self.pointing_device.click_object(clear_button) |
3171 | + |
3172 | + def _clear_with_keys(self): |
3173 | + if platform.model() == 'Desktop': |
3174 | + self._select_all() |
3175 | + else: |
3176 | + # Touch tap currently doesn't have a press_duration parameter, so |
3177 | + # we can't show the popover. Reported as bug http://pad.lv/1268782 |
3178 | + # --elopio - 2014-01-13 |
3179 | + self.keyboard.press_and_release('End') |
3180 | + while not self.is_empty(): |
3181 | + # We delete with backspace because the on-screen keyboard has that |
3182 | + # key. |
3183 | + self.keyboard.press_and_release('BackSpace') |
3184 | + |
3185 | + def _select_all(self): |
3186 | + self.pointing_device.click_object(self, press_duration=1) |
3187 | + root = self.get_root_instance() |
3188 | + main_view = root.select_single(_mainview.MainView) |
3189 | + popover = main_view.get_action_selection_popover('text_input_popover') |
3190 | + popover.click_button_by_text('Select All') |
3191 | |
3192 | === added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py' |
3193 | --- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py 1970-01-01 00:00:00 +0000 |
3194 | +++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py 2014-04-17 01:29:24 +0000 |
3195 | @@ -0,0 +1,106 @@ |
3196 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3197 | +# |
3198 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
3199 | +# |
3200 | +# This program is free software; you can redistribute it and/or modify |
3201 | +# it under the terms of the GNU Lesser General Public License as published by |
3202 | +# the Free Software Foundation; version 3. |
3203 | +# |
3204 | +# This program is distributed in the hope that it will be useful, |
3205 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3206 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3207 | +# GNU Lesser General Public License for more details. |
3208 | +# |
3209 | +# You should have received a copy of the GNU Lesser General Public License |
3210 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3211 | + |
3212 | +import logging |
3213 | + |
3214 | +from autopilot import logging as autopilot_logging |
3215 | +from autopilot.introspection import dbus |
3216 | + |
3217 | +from ubuntuuitoolkit._custom_proxy_objects import _common |
3218 | + |
3219 | + |
3220 | +logger = logging.getLogger(__name__) |
3221 | + |
3222 | + |
3223 | +class Toolbar(_common.UbuntuUIToolkitCustomProxyObjectBase): |
3224 | + """Toolbar Autopilot emulator.""" |
3225 | + |
3226 | + @autopilot_logging.log_action(logger.info) |
3227 | + def open(self): |
3228 | + """Open the toolbar if it's not already opened. |
3229 | + |
3230 | + :return: The toolbar. |
3231 | + |
3232 | + """ |
3233 | + self.animating.wait_for(False) |
3234 | + if not self.opened: |
3235 | + self._drag_to_open() |
3236 | + self.opened.wait_for(True) |
3237 | + self.animating.wait_for(False) |
3238 | + |
3239 | + return self |
3240 | + |
3241 | + def _drag_to_open(self): |
3242 | + x, y, _, _ = self.globalRect |
3243 | + line_x = x + self.width * 0.50 |
3244 | + start_y = y + self.height - 1 |
3245 | + stop_y = y |
3246 | + |
3247 | + self.pointing_device.drag(line_x, start_y, line_x, stop_y) |
3248 | + |
3249 | + @autopilot_logging.log_action(logger.info) |
3250 | + def close(self): |
3251 | + """Close the toolbar if it's opened.""" |
3252 | + self.animating.wait_for(False) |
3253 | + if self.opened: |
3254 | + self._drag_to_close() |
3255 | + self.opened.wait_for(False) |
3256 | + self.animating.wait_for(False) |
3257 | + |
3258 | + def _drag_to_close(self): |
3259 | + x, y, _, _ = self.globalRect |
3260 | + line_x = x + self.width * 0.50 |
3261 | + start_y = y |
3262 | + stop_y = y + self.height - 1 |
3263 | + |
3264 | + self.pointing_device.drag(line_x, start_y, line_x, stop_y) |
3265 | + |
3266 | + @autopilot_logging.log_action(logger.info) |
3267 | + def click_button(self, object_name): |
3268 | + """Click a button of the toolbar. |
3269 | + |
3270 | + The toolbar should be opened before clicking the button, or an |
3271 | + exception will be raised. If the toolbar is closed for some reason |
3272 | + (e.g., timer finishes) after moving the mouse cursor and before |
3273 | + clicking the button, it is re-opened automatically by this function. |
3274 | + |
3275 | + :parameter object_name: The QML objectName property of the button. |
3276 | + :raise ToolkitException: If there is no button with that object |
3277 | + name. |
3278 | + |
3279 | + """ |
3280 | + # ensure the toolbar is open |
3281 | + if not self.opened: |
3282 | + raise _common.ToolkitException( |
3283 | + 'Toolbar must be opened before calling click_button().') |
3284 | + try: |
3285 | + button = self._get_button(object_name) |
3286 | + except dbus.StateNotFoundError: |
3287 | + raise _common.ToolkitException( |
3288 | + 'Button with objectName "{0}" not found.'.format(object_name)) |
3289 | + self.pointing_device.move_to_object(button) |
3290 | + # ensure the toolbar is still open (may have closed due to timeout) |
3291 | + self.open() |
3292 | + # click the button |
3293 | + self.pointing_device.click_object(button) |
3294 | + |
3295 | + def _get_button(self, object_name): |
3296 | + return self.select_single('ActionItem', objectName=object_name) |
3297 | + |
3298 | + @autopilot_logging.log_action(logger.info) |
3299 | + def click_back_button(self): |
3300 | + """Click the back button of the toolbar.""" |
3301 | + self.click_button('back_toolbar_button') |
3302 | |
3303 | === added file 'tests/autopilot/ubuntuuitoolkit/emulators.py' |
3304 | --- tests/autopilot/ubuntuuitoolkit/emulators.py 1970-01-01 00:00:00 +0000 |
3305 | +++ tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-17 01:29:24 +0000 |
3306 | @@ -0,0 +1,85 @@ |
3307 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3308 | +# |
3309 | +# Copyright (C) 2012, 2013, 2014 Canonical Ltd. |
3310 | +# |
3311 | +# This program is free software; you can redistribute it and/or modify |
3312 | +# it under the terms of the GNU Lesser General Public License as published by |
3313 | +# the Free Software Foundation; version 3. |
3314 | +# |
3315 | +# This program is distributed in the hope that it will be useful, |
3316 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3317 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3318 | +# GNU Lesser General Public License for more details. |
3319 | +# |
3320 | +# You should have received a copy of the GNU Lesser General Public License |
3321 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3322 | + |
3323 | +"""Emulators was the old name for the custom proxy objects.""" |
3324 | + |
3325 | +import logging |
3326 | + |
3327 | + |
3328 | +logger = logging.getLogger(__name__) |
3329 | + |
3330 | + |
3331 | +logger.warning( |
3332 | + 'The ubuntuuitoolkit.emulators module is deprecated. Import the autopilot ' |
3333 | + 'helpers from the top-level ubuntuuitoolkit module.') |
3334 | + |
3335 | + |
3336 | +__all__ = [ |
3337 | + 'check_autopilot_version', |
3338 | + 'get_keyboard', |
3339 | + 'get_pointing_device', |
3340 | + 'ActionSelectionPopover', |
3341 | + 'Base', |
3342 | + 'CheckBox', |
3343 | + 'ComposerSheet', |
3344 | + 'Empty', |
3345 | + 'Flickable', |
3346 | + 'Header', |
3347 | + 'ItemSelector', |
3348 | + 'MainView', |
3349 | + 'MultiValue', |
3350 | + 'OptionSelector', |
3351 | + 'QQuickListView', |
3352 | + 'SingleControl', |
3353 | + 'SingleValue', |
3354 | + 'Standard', |
3355 | + 'Subtitled', |
3356 | + 'TabBar', |
3357 | + 'Tabs', |
3358 | + 'TextField', |
3359 | + 'Toolbar', |
3360 | + 'ToolkitEmulatorException', |
3361 | + 'UbuntuUIToolkitEmulatorBase', |
3362 | +] |
3363 | + |
3364 | + |
3365 | +from ubuntuuitoolkit import ( |
3366 | + check_autopilot_version, |
3367 | + get_keyboard, |
3368 | + get_pointing_device, |
3369 | + ActionSelectionPopover, |
3370 | + Base, |
3371 | + CheckBox, |
3372 | + ComposerSheet, |
3373 | + Empty, |
3374 | + Flickable, |
3375 | + Header, |
3376 | + ItemSelector, |
3377 | + MainView, |
3378 | + MultiValue, |
3379 | + OptionSelector, |
3380 | + QQuickListView, |
3381 | + SingleControl, |
3382 | + SingleValue, |
3383 | + Standard, |
3384 | + Subtitled, |
3385 | + TabBar, |
3386 | + Tabs, |
3387 | + TextField, |
3388 | + Toolbar, |
3389 | + ToolkitException as ToolkitEmulatorException, |
3390 | + UbuntuUIToolkitCustomProxyObjectBase as UbuntuUIToolkitEmulatorBase, |
3391 | +) |
3392 | |
3393 | === added directory 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects' |
3394 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/__init__.py' |
3395 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py' |
3396 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py 1970-01-01 00:00:00 +0000 |
3397 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py 2014-04-17 01:29:24 +0000 |
3398 | @@ -0,0 +1,136 @@ |
3399 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3400 | +# |
3401 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
3402 | +# |
3403 | +# This program is free software; you can redistribute it and/or modify |
3404 | +# it under the terms of the GNU Lesser General Public License as published by |
3405 | +# the Free Software Foundation; version 3. |
3406 | +# |
3407 | +# This program is distributed in the hope that it will be useful, |
3408 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3409 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3410 | +# GNU Lesser General Public License for more details. |
3411 | +# |
3412 | +# You should have received a copy of the GNU Lesser General Public License |
3413 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3414 | + |
3415 | +import time |
3416 | + |
3417 | +from autopilot import input |
3418 | +from testtools.matchers import GreaterThan, LessThan |
3419 | +try: |
3420 | + from unittest import mock |
3421 | +except ImportError: |
3422 | + import mock |
3423 | + |
3424 | +import ubuntuuitoolkit |
3425 | +from ubuntuuitoolkit import tests |
3426 | + |
3427 | + |
3428 | +TEST_QML_WITH_CHECKBOX = (""" |
3429 | +import QtQuick 2.0 |
3430 | +import Ubuntu.Components 0.1 |
3431 | + |
3432 | +MainView { |
3433 | + width: units.gu(48) |
3434 | + height: units.gu(60) |
3435 | + |
3436 | + Item { |
3437 | + CheckBox { |
3438 | + checked: false |
3439 | + objectName: "test_checkbox" |
3440 | + } |
3441 | + } |
3442 | +} |
3443 | +""") |
3444 | + |
3445 | + |
3446 | +TEST_QML_WITH_SWITCH = (""" |
3447 | +import QtQuick 2.0 |
3448 | +import Ubuntu.Components 0.1 |
3449 | + |
3450 | +MainView { |
3451 | + width: units.gu(48) |
3452 | + height: units.gu(60) |
3453 | + |
3454 | + Item { |
3455 | + Switch { |
3456 | + checked: false |
3457 | + objectName: "test_switch" |
3458 | + } |
3459 | + } |
3460 | +} |
3461 | +""") |
3462 | + |
3463 | + |
3464 | +class ToggleTestCase(tests.QMLStringAppTestCase): |
3465 | + |
3466 | + scenarios = [ |
3467 | + ('checkbox', dict( |
3468 | + test_qml=TEST_QML_WITH_CHECKBOX, objectName='test_checkbox')), |
3469 | + ('switch', dict( |
3470 | + test_qml=TEST_QML_WITH_SWITCH, objectName='test_switch')) |
3471 | + ] |
3472 | + |
3473 | + def setUp(self): |
3474 | + super(ToggleTestCase, self).setUp() |
3475 | + self.toggle = self.main_view.select_single( |
3476 | + ubuntuuitoolkit.CheckBox, objectName=self.objectName) |
3477 | + self.assertFalse(self.toggle.checked) |
3478 | + |
3479 | + def test_toggle_custom_proxy_object(self): |
3480 | + self.assertIsInstance(self.toggle, ubuntuuitoolkit.CheckBox) |
3481 | + |
3482 | + def test_check_toggle(self): |
3483 | + self.toggle.check() |
3484 | + self.assertTrue(self.toggle.checked) |
3485 | + |
3486 | + def test_check_toggle_already_checked(self): |
3487 | + self.toggle.check() |
3488 | + with mock.patch.object(input.Pointer, 'click_object') as mock_click: |
3489 | + self.toggle.check() |
3490 | + self.assertFalse(mock_click.called) |
3491 | + |
3492 | + def test_uncheck_toggle(self): |
3493 | + self.toggle.check() |
3494 | + self.toggle.uncheck() |
3495 | + self.assertFalse(self.toggle.checked) |
3496 | + |
3497 | + def test_uncheck_toggle_already_unchecked(self): |
3498 | + with mock.patch.object(input.Pointer, 'click_object') as mock_click: |
3499 | + self.toggle.uncheck() |
3500 | + self.assertFalse(mock_click.called) |
3501 | + |
3502 | + def test_change_state_from_checked(self): |
3503 | + self.toggle.check() |
3504 | + self.toggle.change_state() |
3505 | + self.assertFalse(self.toggle.checked) |
3506 | + |
3507 | + def test_change_state_from_unchecked(self): |
3508 | + self.toggle.change_state() |
3509 | + self.assertTrue(self.toggle.checked) |
3510 | + |
3511 | + def test_check_with_timeout(self): |
3512 | + with mock.patch.object( |
3513 | + ubuntuuitoolkit.CheckBox, 'change_state') as mock_change: |
3514 | + self.toggle.check(timeout=1) |
3515 | + |
3516 | + mock_change.assert_called_once_with(1) |
3517 | + |
3518 | + def test_uncheck_with_timeout(self): |
3519 | + self.toggle.check() |
3520 | + with mock.patch.object( |
3521 | + ubuntuuitoolkit.CheckBox, 'change_state') as mock_change: |
3522 | + self.toggle.uncheck(timeout=1) |
3523 | + |
3524 | + mock_change.assert_called_once_with(1) |
3525 | + |
3526 | + def test_change_state_with_timeout(self): |
3527 | + with mock.patch.object(self.toggle, 'pointing_device'): |
3528 | + # mock the pointing device so the checkbox is not clicked. |
3529 | + timestamp_before_call = time.time() |
3530 | + self.assertRaises(AssertionError, self.toggle.change_state, 1) |
3531 | + |
3532 | + waiting_time = time.time() - timestamp_before_call |
3533 | + self.assertThat(waiting_time, GreaterThan(1)) |
3534 | + self.assertThat(waiting_time, LessThan(2)) |
3535 | |
3536 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py' |
3537 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py 1970-01-01 00:00:00 +0000 |
3538 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py 2014-04-17 01:29:24 +0000 |
3539 | @@ -0,0 +1,68 @@ |
3540 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3541 | +# |
3542 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
3543 | +# |
3544 | +# This program is free software; you can redistribute it and/or modify |
3545 | +# it under the terms of the GNU Lesser General Public License as published by |
3546 | +# the Free Software Foundation; version 3. |
3547 | +# |
3548 | +# This program is distributed in the hope that it will be useful, |
3549 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3550 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3551 | +# GNU Lesser General Public License for more details. |
3552 | +# |
3553 | +# You should have received a copy of the GNU Lesser General Public License |
3554 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3555 | + |
3556 | +import unittest |
3557 | + |
3558 | +import autopilot |
3559 | +from autopilot import platform, input |
3560 | +try: |
3561 | + from unittest import mock |
3562 | +except ImportError: |
3563 | + import mock |
3564 | + |
3565 | +import ubuntuuitoolkit |
3566 | +from ubuntuuitoolkit import tests |
3567 | + |
3568 | + |
3569 | +class CheckAutopilotVersionTestCase(unittest.TestCase): |
3570 | + |
3571 | + def test_lower_version_should_raise_exception(self): |
3572 | + with mock.patch.object(autopilot, 'version', '1.3'): |
3573 | + self.assertRaises( |
3574 | + ubuntuuitoolkit.ToolkitException, |
3575 | + ubuntuuitoolkit.check_autopilot_version) |
3576 | + |
3577 | + def test_required_version_should_succeed(self): |
3578 | + with mock.patch.object(autopilot, 'version', '1.4'): |
3579 | + ubuntuuitoolkit.check_autopilot_version() |
3580 | + |
3581 | + def test_higher_version_should_succeed(self): |
3582 | + with mock.patch.object(autopilot, 'version', '1.5'): |
3583 | + ubuntuuitoolkit.check_autopilot_version() |
3584 | + |
3585 | + |
3586 | +class UbuntuUIToolkitCustomProxyObjectBaseTestCase(tests.QMLStringAppTestCase): |
3587 | + |
3588 | + def test_pointing_device(self): |
3589 | + self.assertIsInstance(self.app.pointing_device, input.Pointer) |
3590 | + |
3591 | + @unittest.skipIf(platform.model() != 'Desktop', 'Desktop only') |
3592 | + def test_pointing_device_in_desktop(self): |
3593 | + self.assertIsInstance(self.app.pointing_device._device, input.Mouse) |
3594 | + |
3595 | + @unittest.skipIf(platform.model() == 'Desktop', 'Phablet only') |
3596 | + def test_pointing_device_in_phablet(self): |
3597 | + self.assertIsInstance(self.app.pointing_device._device, input.Touch) |
3598 | + |
3599 | + def test_custom_proxy_objects_should_check_version_on_init(self): |
3600 | + check_name = ( |
3601 | + 'ubuntuuitoolkit._custom_proxy_objects._common.' |
3602 | + 'check_autopilot_version') |
3603 | + with mock.patch(check_name, autospec=True) as mock_check: |
3604 | + # Instantiate any custom proxy object. |
3605 | + self.main_view |
3606 | + |
3607 | + mock_check.assert_called_once_with() |
3608 | |
3609 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py' |
3610 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py 1970-01-01 00:00:00 +0000 |
3611 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py 2014-04-17 01:29:24 +0000 |
3612 | @@ -0,0 +1,41 @@ |
3613 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3614 | +# |
3615 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
3616 | +# |
3617 | +# This program is free software; you can redistribute it and/or modify |
3618 | +# it under the terms of the GNU Lesser General Public License as published by |
3619 | +# the Free Software Foundation; version 3. |
3620 | +# |
3621 | +# This program is distributed in the hope that it will be useful, |
3622 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3623 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3624 | +# GNU Lesser General Public License for more details. |
3625 | +# |
3626 | +# You should have received a copy of the GNU Lesser General Public License |
3627 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3628 | + |
3629 | +import ubuntuuitoolkit |
3630 | +from ubuntuuitoolkit import tests |
3631 | + |
3632 | + |
3633 | +class PageTestCase(tests.QMLStringAppTestCase): |
3634 | + |
3635 | + test_qml = (""" |
3636 | +import QtQuick 2.0 |
3637 | +import Ubuntu.Components 0.1 |
3638 | + |
3639 | +MainView { |
3640 | + width: units.gu(48) |
3641 | + height: units.gu(60) |
3642 | + |
3643 | + Page { |
3644 | + title: "Test title" |
3645 | + } |
3646 | +} |
3647 | +""") |
3648 | + |
3649 | + def test_header_custom_proxy_object(self): |
3650 | + header = self.main_view.get_header() |
3651 | + self.assertIsInstance(header, ubuntuuitoolkit.Header) |
3652 | + self.assertTrue(header.visible) |
3653 | + self.assertEqual(header.title, "Test title") |
3654 | |
3655 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py' |
3656 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py 1970-01-01 00:00:00 +0000 |
3657 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py 2014-04-17 01:29:24 +0000 |
3658 | @@ -0,0 +1,170 @@ |
3659 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3660 | +# |
3661 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
3662 | +# |
3663 | +# This program is free software; you can redistribute it and/or modify |
3664 | +# it under the terms of the GNU Lesser General Public License as published by |
3665 | +# the Free Software Foundation; version 3. |
3666 | +# |
3667 | +# This program is distributed in the hope that it will be useful, |
3668 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3669 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3670 | +# GNU Lesser General Public License for more details. |
3671 | +# |
3672 | +# You should have received a copy of the GNU Lesser General Public License |
3673 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3674 | + |
3675 | +import ubuntuuitoolkit |
3676 | +from ubuntuuitoolkit import tests |
3677 | + |
3678 | + |
3679 | +class SwipeToDeleteTestCase(tests.QMLStringAppTestCase): |
3680 | + |
3681 | + test_qml = (""" |
3682 | +import QtQuick 2.0 |
3683 | +import Ubuntu.Components 0.1 |
3684 | +import Ubuntu.Components.ListItems 0.1 |
3685 | + |
3686 | + |
3687 | +MainView { |
3688 | + width: units.gu(48) |
3689 | + height: units.gu(60) |
3690 | + |
3691 | + Page { |
3692 | + |
3693 | + ListModel { |
3694 | + id: testModel |
3695 | + |
3696 | + ListElement { |
3697 | + name: "listitem_destroyed_on_remove_with_confirm" |
3698 | + label: "Item destroyed on remove with confirmation" |
3699 | + confirm: true |
3700 | + } |
3701 | + ListElement { |
3702 | + name: "listitem_destroyed_on_remove_without_confirm" |
3703 | + label: "Item destroyed on remove without confirmation" |
3704 | + confirm: false |
3705 | + } |
3706 | + } |
3707 | + |
3708 | + Column { |
3709 | + anchors { fill: parent } |
3710 | + |
3711 | + Standard { |
3712 | + objectName: "listitem_standard" |
3713 | + confirmRemoval: true |
3714 | + removable: true |
3715 | + text: 'Slide to remove' |
3716 | + } |
3717 | + |
3718 | + Empty { |
3719 | + objectName: "listitem_empty" |
3720 | + } |
3721 | + |
3722 | + Standard { |
3723 | + objectName: "listitem_without_confirm" |
3724 | + confirmRemoval: false |
3725 | + removable: true |
3726 | + text: "Item without delete confirmation" |
3727 | + } |
3728 | + |
3729 | + ListView { |
3730 | + anchors { left: parent.left; right: parent.right } |
3731 | + height: childrenRect.height |
3732 | + model: testModel |
3733 | + |
3734 | + delegate: Standard { |
3735 | + removable: true |
3736 | + confirmRemoval: confirm |
3737 | + onItemRemoved: testModel.remove(index) |
3738 | + text: label |
3739 | + objectName: name |
3740 | + } |
3741 | + } |
3742 | + } |
3743 | + } |
3744 | +} |
3745 | +""") |
3746 | + |
3747 | + def setUp(self): |
3748 | + super(SwipeToDeleteTestCase, self).setUp() |
3749 | + self._item = self.main_view.select_single( |
3750 | + ubuntuuitoolkit.Standard, objectName='listitem_standard') |
3751 | + self.assertTrue(self._item.exists()) |
3752 | + |
3753 | + def test_supported_class(self): |
3754 | + self.assertTrue(issubclass( |
3755 | + ubuntuuitoolkit.Base, ubuntuuitoolkit.Empty)) |
3756 | + self.assertTrue(issubclass( |
3757 | + ubuntuuitoolkit.ItemSelector, ubuntuuitoolkit.Empty)) |
3758 | + self.assertTrue(issubclass( |
3759 | + ubuntuuitoolkit.Standard, ubuntuuitoolkit.Empty)) |
3760 | + self.assertTrue(issubclass( |
3761 | + ubuntuuitoolkit.SingleControl, ubuntuuitoolkit.Empty)) |
3762 | + self.assertTrue(issubclass( |
3763 | + ubuntuuitoolkit.MultiValue, ubuntuuitoolkit.Base)) |
3764 | + self.assertTrue(issubclass( |
3765 | + ubuntuuitoolkit.SingleValue, ubuntuuitoolkit.Base)) |
3766 | + self.assertTrue(issubclass( |
3767 | + ubuntuuitoolkit.Subtitled, ubuntuuitoolkit.Base)) |
3768 | + |
3769 | + def test_standard_custom_proxy_object(self): |
3770 | + self.assertIsInstance(self._item, ubuntuuitoolkit.Standard) |
3771 | + |
3772 | + def test_swipe_item(self): |
3773 | + self._item.swipe_to_delete() |
3774 | + self.assertTrue(self._item.waitingConfirmationForRemoval) |
3775 | + |
3776 | + def test_swipe_item_to_right(self): |
3777 | + self._item.swipe_to_delete('right') |
3778 | + self.assertTrue(self._item.waitingConfirmationForRemoval) |
3779 | + |
3780 | + def test_swipe_item_to_left(self): |
3781 | + self._item.swipe_to_delete('left') |
3782 | + self.assertTrue(self._item.waitingConfirmationForRemoval) |
3783 | + |
3784 | + def test_swipe_item_to_wrong_direction(self): |
3785 | + self.assertRaises( |
3786 | + ubuntuuitoolkit.ToolkitException, |
3787 | + self._item.swipe_to_delete, 'up') |
3788 | + |
3789 | + def test_delete_item_moving_right(self): |
3790 | + self._item.swipe_to_delete('right') |
3791 | + self._item.confirm_removal() |
3792 | + self.assertFalse(self._item.exists()) |
3793 | + |
3794 | + def test_delete_item_moving_left(self): |
3795 | + self._item.swipe_to_delete('left') |
3796 | + self._item.confirm_removal() |
3797 | + self.assertFalse(self._item.exists()) |
3798 | + |
3799 | + def test_delete_non_removable_item(self): |
3800 | + self._item = self.main_view.select_single( |
3801 | + ubuntuuitoolkit.Empty, objectName='listitem_empty') |
3802 | + self.assertRaises( |
3803 | + ubuntuuitoolkit.ToolkitException, self._item.swipe_to_delete) |
3804 | + |
3805 | + def test_confirm_removal_when_item_was_not_swiped(self): |
3806 | + self.assertRaises( |
3807 | + ubuntuuitoolkit.ToolkitException, self._item.confirm_removal) |
3808 | + |
3809 | + def test_delete_item_without_confirm(self): |
3810 | + item = self.main_view.select_single( |
3811 | + ubuntuuitoolkit.Standard, objectName='listitem_without_confirm') |
3812 | + item.swipe_to_delete() |
3813 | + self.assertFalse(item.exists()) |
3814 | + |
3815 | + def test_delete_item_with_confirmation_that_will_be_destroyed(self): |
3816 | + item = self.main_view.select_single( |
3817 | + ubuntuuitoolkit.Standard, |
3818 | + objectName='listitem_destroyed_on_remove_with_confirm') |
3819 | + item.swipe_to_delete() |
3820 | + item.confirm_removal() |
3821 | + self.assertFalse(item.exists()) |
3822 | + |
3823 | + def test_delete_item_without_confirmation_that_will_be_destroyed(self): |
3824 | + item = self.main_view.select_single( |
3825 | + ubuntuuitoolkit.Standard, |
3826 | + objectName='listitem_destroyed_on_remove_without_confirm') |
3827 | + item.swipe_to_delete() |
3828 | + self.assertFalse(item.exists()) |
3829 | |
3830 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py' |
3831 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py 1970-01-01 00:00:00 +0000 |
3832 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py 2014-04-17 01:29:24 +0000 |
3833 | @@ -0,0 +1,132 @@ |
3834 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3835 | +# |
3836 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
3837 | +# |
3838 | +# This program is free software; you can redistribute it and/or modify |
3839 | +# it under the terms of the GNU Lesser General Public License as published by |
3840 | +# the Free Software Foundation; version 3. |
3841 | +# |
3842 | +# This program is distributed in the hope that it will be useful, |
3843 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3844 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3845 | +# GNU Lesser General Public License for more details. |
3846 | +# |
3847 | +# You should have received a copy of the GNU Lesser General Public License |
3848 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3849 | + |
3850 | +try: |
3851 | + from unittest import mock |
3852 | +except ImportError: |
3853 | + import mock |
3854 | + |
3855 | +import ubuntuuitoolkit |
3856 | +from ubuntuuitoolkit import tests |
3857 | + |
3858 | + |
3859 | +class MainViewTestCase(tests.QMLStringAppTestCase): |
3860 | + |
3861 | + test_qml = (""" |
3862 | +import QtQuick 2.0 |
3863 | +import Ubuntu.Components 0.1 |
3864 | + |
3865 | +MainView { |
3866 | + width: units.gu(48) |
3867 | + height: units.gu(60) |
3868 | +} |
3869 | +""") |
3870 | + |
3871 | + def test_main_view_custom_proxy_object(self): |
3872 | + self.assertIsInstance(self.main_view, ubuntuuitoolkit.MainView) |
3873 | + |
3874 | + def test_get_header_without_header(self): |
3875 | + header = self.main_view.get_header() |
3876 | + self.assertFalse(header.visible) |
3877 | + |
3878 | + def test_toolbar_custom_proxy_object(self): |
3879 | + toolbar = self.main_view.get_toolbar() |
3880 | + self.assertIsInstance(toolbar, ubuntuuitoolkit.Toolbar) |
3881 | + |
3882 | + def test_open_toolbar(self): |
3883 | + with mock.patch.object(ubuntuuitoolkit.Toolbar, 'open') as mock_open: |
3884 | + self.main_view.open_toolbar() |
3885 | + |
3886 | + mock_open.assert_called_once_with() |
3887 | + |
3888 | + def test_close_toolbar(self): |
3889 | + with mock.patch.object(ubuntuuitoolkit.Toolbar, 'close') as mock_close: |
3890 | + self.main_view.close_toolbar() |
3891 | + |
3892 | + mock_close.assert_called_once_with() |
3893 | + |
3894 | + def test_open_toolbar_returns_the_toolbar(self): |
3895 | + toolbar = self.main_view.open_toolbar() |
3896 | + self.assertIsInstance(toolbar, ubuntuuitoolkit.Toolbar) |
3897 | + |
3898 | + def test_get_tabs_without_tabs(self): |
3899 | + error = self.assertRaises( |
3900 | + ubuntuuitoolkit.ToolkitException, self.main_view.get_tabs) |
3901 | + self.assertEqual( |
3902 | + str(error), 'The MainView has no Tabs.') |
3903 | + |
3904 | + def test_switch_to_next_tab_without_tabs(self): |
3905 | + header = self.main_view.get_header() |
3906 | + error = self.assertRaises( |
3907 | + ubuntuuitoolkit.ToolkitException, |
3908 | + header.switch_to_next_tab) |
3909 | + self.assertEqual( |
3910 | + str(error), 'The MainView has no Tabs.') |
3911 | + |
3912 | + |
3913 | +class GoBackTestCase(tests.QMLStringAppTestCase): |
3914 | + |
3915 | + test_qml = (""" |
3916 | +import QtQuick 2.0 |
3917 | +import Ubuntu.Components 0.1 |
3918 | + |
3919 | +MainView { |
3920 | + width: units.gu(48) |
3921 | + height: units.gu(60) |
3922 | + |
3923 | + PageStack { |
3924 | + id: pageStack |
3925 | + Component.onCompleted: push(page0) |
3926 | + |
3927 | + Page { |
3928 | + id: page0 |
3929 | + title: "Page 0" |
3930 | + visible: false |
3931 | + |
3932 | + Button { |
3933 | + objectName: "go_to_page1" |
3934 | + text: "Go to page 1" |
3935 | + onClicked: pageStack.push(page1) |
3936 | + } |
3937 | + } |
3938 | + |
3939 | + Page { |
3940 | + id: page1 |
3941 | + title: "Page 1" |
3942 | + visible: false |
3943 | + } |
3944 | + } |
3945 | +} |
3946 | +""") |
3947 | + |
3948 | + def setUp(self): |
3949 | + super(GoBackTestCase, self).setUp() |
3950 | + self.header = self.main_view.get_header() |
3951 | + self.assertEqual(self.header.title, 'Page 0') |
3952 | + |
3953 | + def test_open_page(self): |
3954 | + self._go_to_page1() |
3955 | + self.assertEqual(self.header.title, 'Page 1') |
3956 | + |
3957 | + def _go_to_page1(self): |
3958 | + button = self.main_view.select_single( |
3959 | + 'Button', objectName='go_to_page1') |
3960 | + self.pointing_device.click_object(button) |
3961 | + |
3962 | + def test_go_back(self): |
3963 | + self._go_to_page1() |
3964 | + self.main_view.go_back() |
3965 | + self.assertEqual(self.header.title, 'Page 0') |
3966 | |
3967 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py' |
3968 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py 1970-01-01 00:00:00 +0000 |
3969 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py 2014-04-17 01:29:24 +0000 |
3970 | @@ -0,0 +1,172 @@ |
3971 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
3972 | +# |
3973 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
3974 | +# |
3975 | +# This program is free software; you can redistribute it and/or modify |
3976 | +# it under the terms of the GNU Lesser General Public License as published by |
3977 | +# the Free Software Foundation; version 3. |
3978 | +# |
3979 | +# This program is distributed in the hope that it will be useful, |
3980 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
3981 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3982 | +# GNU Lesser General Public License for more details. |
3983 | +# |
3984 | +# You should have received a copy of the GNU Lesser General Public License |
3985 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
3986 | + |
3987 | +from autopilot.introspection import dbus |
3988 | + |
3989 | +import ubuntuuitoolkit |
3990 | +from ubuntuuitoolkit import tests |
3991 | + |
3992 | + |
3993 | +class ActionSelectionPopoverTestCase(tests.QMLStringAppTestCase): |
3994 | + |
3995 | + test_qml = (""" |
3996 | +import QtQuick 2.0 |
3997 | +import Ubuntu.Components 0.1 |
3998 | +import Ubuntu.Components.Popups 0.1 |
3999 | + |
4000 | +MainView { |
4001 | + width: units.gu(48) |
4002 | + height: units.gu(60) |
4003 | + |
4004 | + Button { |
4005 | + objectName: "open_popover" |
4006 | + text: "Open Popover" |
4007 | + onClicked: testActionsPopover.show(); |
4008 | + } |
4009 | + |
4010 | + Label { |
4011 | + id: "label" |
4012 | + objectName: "clicked_label" |
4013 | + anchors.centerIn: parent |
4014 | + text: "Button not clicked." |
4015 | + } |
4016 | + |
4017 | + ActionSelectionPopover { |
4018 | + objectName: "test_actions_popover" |
4019 | + id: testActionsPopover |
4020 | + actions: ActionList { |
4021 | + Action { |
4022 | + text: "Action one" |
4023 | + onTriggered: label.text = "Button clicked." |
4024 | + } |
4025 | + } |
4026 | + } |
4027 | +} |
4028 | +""") |
4029 | + |
4030 | + def test_action_selection_popover_custom_proxy_object(self): |
4031 | + popover = self.main_view.get_action_selection_popover( |
4032 | + 'test_actions_popover') |
4033 | + self.assertIsInstance(popover, ubuntuuitoolkit.ActionSelectionPopover) |
4034 | + |
4035 | + def test_click_action_select_popover_button(self): |
4036 | + label = self.app.select_single('Label', objectName='clicked_label') |
4037 | + self.assertNotEqual(label.text, 'Button clicked.') |
4038 | + self._open_popover() |
4039 | + popover = self.main_view.get_action_selection_popover( |
4040 | + 'test_actions_popover') |
4041 | + popover.click_button_by_text('Action one') |
4042 | + self.assertEqual(label.text, 'Button clicked.') |
4043 | + |
4044 | + def _open_popover(self): |
4045 | + open_button = self.main_view.select_single( |
4046 | + 'Button', objectName='open_popover') |
4047 | + self.pointing_device.click_object(open_button) |
4048 | + |
4049 | + def test_click_unexisting_button(self): |
4050 | + self._open_popover() |
4051 | + popover = self.main_view.get_action_selection_popover( |
4052 | + 'test_actions_popover') |
4053 | + error = self.assertRaises( |
4054 | + ubuntuuitoolkit.ToolkitException, popover.click_button_by_text, |
4055 | + 'unexisting') |
4056 | + self.assertEqual( |
4057 | + str(error), 'Button with text "unexisting" not found.') |
4058 | + |
4059 | + def test_click_button_with_closed_popover(self): |
4060 | + popover = self.main_view.get_action_selection_popover( |
4061 | + 'test_actions_popover') |
4062 | + error = self.assertRaises( |
4063 | + ubuntuuitoolkit.ToolkitException, popover.click_button_by_text, |
4064 | + 'Action one') |
4065 | + self.assertEqual( |
4066 | + str(error), 'The popover is not open.') |
4067 | + |
4068 | + |
4069 | +class ComposerSheetTestCase(tests.QMLStringAppTestCase): |
4070 | + |
4071 | + test_qml = (""" |
4072 | +import QtQuick 2.0 |
4073 | +import Ubuntu.Components 0.1 |
4074 | +import Ubuntu.Components.Popups 0.1 |
4075 | + |
4076 | +MainView { |
4077 | + width: units.gu(48) |
4078 | + height: units.gu(60) |
4079 | + |
4080 | + Button { |
4081 | + objectName: "openComposerSheetButton" |
4082 | + text: "Open Composer Sheet" |
4083 | + onClicked: PopupUtils.open(testComposerSheet); |
4084 | + } |
4085 | + |
4086 | + Label { |
4087 | + id: "label" |
4088 | + objectName: "actionLabel" |
4089 | + anchors.centerIn: parent |
4090 | + text: "No action taken." |
4091 | + } |
4092 | + |
4093 | + Component { |
4094 | + id: testComposerSheet |
4095 | + ComposerSheet { |
4096 | + id: sheet |
4097 | + objectName: "testComposerSheet" |
4098 | + onCancelClicked: { |
4099 | + label.text = "Cancel selected." |
4100 | + } |
4101 | + onConfirmClicked: { |
4102 | + label.text = "Confirm selected." |
4103 | + } |
4104 | + } |
4105 | + } |
4106 | +} |
4107 | +""") |
4108 | + |
4109 | + def setUp(self): |
4110 | + super(ComposerSheetTestCase, self).setUp() |
4111 | + self.label = self.main_view.select_single( |
4112 | + 'Label', objectName='actionLabel') |
4113 | + self.assertEqual(self.label.text, 'No action taken.') |
4114 | + self._open_composer_sheet() |
4115 | + self.composer_sheet = self._select_composer_sheet() |
4116 | + |
4117 | + def _open_composer_sheet(self): |
4118 | + button = self.main_view.select_single( |
4119 | + 'Button', objectName='openComposerSheetButton') |
4120 | + self.pointing_device.click_object(button) |
4121 | + |
4122 | + def _select_composer_sheet(self): |
4123 | + return self.main_view.select_single( |
4124 | + ubuntuuitoolkit.ComposerSheet, objectName='testComposerSheet') |
4125 | + |
4126 | + def test_select_composer_sheet_custom_proxy_object(self): |
4127 | + self.assertIsInstance( |
4128 | + self.composer_sheet, ubuntuuitoolkit.ComposerSheet) |
4129 | + |
4130 | + def test_confirm_composer_sheet(self): |
4131 | + self.composer_sheet.confirm() |
4132 | + self.assertEqual(self.label.text, 'Confirm selected.') |
4133 | + self._assert_composer_sheet_is_closed() |
4134 | + |
4135 | + def _assert_composer_sheet_is_closed(self): |
4136 | + self.assertRaises( |
4137 | + dbus.StateNotFoundError, self._select_composer_sheet) |
4138 | + |
4139 | + def test_cancel_composer_sheet(self): |
4140 | + self.composer_sheet.cancel() |
4141 | + self.assertEqual(self.label.text, 'Cancel selected.') |
4142 | + self._assert_composer_sheet_is_closed() |
4143 | |
4144 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py' |
4145 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py 1970-01-01 00:00:00 +0000 |
4146 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py 2014-04-17 01:29:24 +0000 |
4147 | @@ -0,0 +1,193 @@ |
4148 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4149 | +# |
4150 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
4151 | +# |
4152 | +# This program is free software; you can redistribute it and/or modify |
4153 | +# it under the terms of the GNU Lesser General Public License as published by |
4154 | +# the Free Software Foundation; version 3. |
4155 | +# |
4156 | +# This program is distributed in the hope that it will be useful, |
4157 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4158 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4159 | +# GNU Lesser General Public License for more details. |
4160 | +# |
4161 | +# You should have received a copy of the GNU Lesser General Public License |
4162 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4163 | + |
4164 | +from autopilot.introspection import dbus |
4165 | + |
4166 | +import ubuntuuitoolkit |
4167 | +from ubuntuuitoolkit import tests |
4168 | + |
4169 | + |
4170 | +class QQuickListViewTestCase(tests.QMLStringAppTestCase): |
4171 | + |
4172 | + test_qml = (""" |
4173 | +import QtQuick 2.0 |
4174 | +import Ubuntu.Components 0.1 |
4175 | +import Ubuntu.Components.ListItems 0.1 as ListItem |
4176 | + |
4177 | +MainView { |
4178 | + width: units.gu(48) |
4179 | + height: units.gu(20) |
4180 | + |
4181 | + Page { |
4182 | + |
4183 | + Column { |
4184 | + id: column |
4185 | + width: units.gu(48) |
4186 | + height: units.gu(20) |
4187 | + |
4188 | + Label { |
4189 | + id: clickedLabel |
4190 | + objectName: "clickedLabel" |
4191 | + text: "No element clicked." |
4192 | + } |
4193 | + |
4194 | + ListView { |
4195 | + id: testListView |
4196 | + objectName: "testListView" |
4197 | + anchors.left: parent.left |
4198 | + anchors.right: parent.right |
4199 | + height: column.height - clickedLabel.paintedHeight |
4200 | + clip: true |
4201 | + model: 20 |
4202 | + |
4203 | + delegate: ListItem.Standard { |
4204 | + objectName: "testListElement%1".arg(index) |
4205 | + text: "test list element %1".arg(index) |
4206 | + onClicked: clickedLabel.text = objectName |
4207 | + height: units.gu(5) |
4208 | + } |
4209 | + } |
4210 | + } |
4211 | + } |
4212 | +} |
4213 | +""") |
4214 | + |
4215 | + def setUp(self): |
4216 | + super(QQuickListViewTestCase, self).setUp() |
4217 | + self.list_view = self.main_view.select_single( |
4218 | + ubuntuuitoolkit.QQuickListView, objectName='testListView') |
4219 | + self.label = self.main_view.select_single( |
4220 | + 'Label', objectName='clickedLabel') |
4221 | + self.assertEqual(self.label.text, 'No element clicked.') |
4222 | + |
4223 | + def test_qquicklistview_custom_proxy_object(self): |
4224 | + self.assertIsInstance(self.list_view, ubuntuuitoolkit.QQuickListView) |
4225 | + |
4226 | + def test_click_element(self): |
4227 | + self.list_view.click_element('testListElement0') |
4228 | + self.assertEqual(self.label.text, 'testListElement0') |
4229 | + |
4230 | + def test_click_element_outside_view_below(self): |
4231 | + # Click the first element out of view to make sure we are not scrolling |
4232 | + # to the bottom at once. |
4233 | + self.assertFalse( |
4234 | + self.list_view._is_element_clickable('testListElement5')) |
4235 | + |
4236 | + self.list_view.click_element('testListElement5') |
4237 | + self.assertEqual(self.label.text, 'testListElement5') |
4238 | + |
4239 | + def test_click_element_outside_view_above(self): |
4240 | + self.list_view.click_element('testListElement9') |
4241 | + |
4242 | + # Click the first element out of view to make sure we are not scrolling |
4243 | + # to the top at once. |
4244 | + self.assertFalse( |
4245 | + self.list_view._is_element_clickable('testListElement4')) |
4246 | + |
4247 | + self.list_view.click_element('testListElement4') |
4248 | + self.assertEqual(self.label.text, 'testListElement4') |
4249 | + |
4250 | + def test_click_element_not_created_at_start(self): |
4251 | + objectName = 'testListElement19' |
4252 | + self.assertRaises( |
4253 | + dbus.StateNotFoundError, |
4254 | + self.list_view.select_single, |
4255 | + objectName=objectName) |
4256 | + self.list_view.click_element(objectName) |
4257 | + |
4258 | + def test_click_unexisting_element(self): |
4259 | + error = self.assertRaises( |
4260 | + ubuntuuitoolkit.ToolkitException, |
4261 | + self.list_view.click_element, |
4262 | + 'unexisting') |
4263 | + self.assertEqual( |
4264 | + str(error), 'List element with objectName "unexisting" not found.') |
4265 | + |
4266 | + |
4267 | +class QQuickListViewOutOfViewTestCase(tests.QMLStringAppTestCase): |
4268 | + """Test that we can make elements visible when the list is out of view.""" |
4269 | + |
4270 | + test_qml = (""" |
4271 | +import QtQuick 2.0 |
4272 | +import Ubuntu.Components 0.1 |
4273 | +import Ubuntu.Components.ListItems 0.1 as ListItem |
4274 | + |
4275 | +MainView { |
4276 | + width: units.gu(48) |
4277 | + height: units.gu(20) |
4278 | + |
4279 | + Page { |
4280 | + |
4281 | + Flickable { |
4282 | + |
4283 | + Column { |
4284 | + id: column |
4285 | + width: units.gu(48) |
4286 | + // The column height is greater than the main view height, so |
4287 | + // the bottom of the list is out of view. |
4288 | + height: units.gu(40) |
4289 | + |
4290 | + Label { |
4291 | + id: clickedLabel |
4292 | + objectName: "clickedLabel" |
4293 | + text: "No element clicked." |
4294 | + } |
4295 | + |
4296 | + ListView { |
4297 | + id: testListView |
4298 | + objectName: "testListView" |
4299 | + anchors.left: parent.left |
4300 | + anchors.right: parent.right |
4301 | + height: column.height - clickedLabel.paintedHeight |
4302 | + clip: true |
4303 | + model: 20 |
4304 | + |
4305 | + delegate: ListItem.Standard { |
4306 | + objectName: "testListElement%1".arg(index) |
4307 | + text: "test list element %1".arg(index) |
4308 | + onClicked: clickedLabel.text = objectName |
4309 | + height: units.gu(5) |
4310 | + } |
4311 | + } |
4312 | + } |
4313 | + } |
4314 | + } |
4315 | +} |
4316 | +""") |
4317 | + |
4318 | + def setUp(self): |
4319 | + super(QQuickListViewOutOfViewTestCase, self).setUp() |
4320 | + self.list_view = self.main_view.select_single( |
4321 | + ubuntuuitoolkit.QQuickListView, objectName='testListView') |
4322 | + self.label = self.main_view.select_single( |
4323 | + 'Label', objectName='clickedLabel') |
4324 | + self.assertEqual(self.label.text, 'No element clicked.') |
4325 | + |
4326 | + def test_click_element_outside_view_below(self): |
4327 | + """Test that we can click an element that's out of view below. |
4328 | + |
4329 | + The list is also out of view, so we must scroll from the bottom of the |
4330 | + main view. |
4331 | + |
4332 | + """ |
4333 | + # Test for http://pad.lv/1275060. |
4334 | + # Click the first element out of view to make sure we are not scrolling |
4335 | + # to the bottom at once. |
4336 | + self.assertFalse( |
4337 | + self.list_view._is_element_clickable('testListElement9')) |
4338 | + |
4339 | + self.list_view.click_element('testListElement9') |
4340 | + self.assertEqual(self.label.text, 'testListElement9') |
4341 | |
4342 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py' |
4343 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py 1970-01-01 00:00:00 +0000 |
4344 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py 2014-04-17 01:29:24 +0000 |
4345 | @@ -0,0 +1,149 @@ |
4346 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4347 | +# |
4348 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
4349 | +# |
4350 | +# This program is free software; you can redistribute it and/or modify |
4351 | +# it under the terms of the GNU Lesser General Public License as published by |
4352 | +# the Free Software Foundation; version 3. |
4353 | +# |
4354 | +# This program is distributed in the hope that it will be useful, |
4355 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4356 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4357 | +# GNU Lesser General Public License for more details. |
4358 | +# |
4359 | +# You should have received a copy of the GNU Lesser General Public License |
4360 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4361 | + |
4362 | +try: |
4363 | + from unittest import mock |
4364 | +except ImportError: |
4365 | + import mock |
4366 | + |
4367 | +import ubuntuuitoolkit |
4368 | +from ubuntuuitoolkit import tests |
4369 | + |
4370 | + |
4371 | +class TabsTestCase(tests.QMLStringAppTestCase): |
4372 | + |
4373 | + test_qml = (""" |
4374 | +import QtQuick 2.0 |
4375 | +import Ubuntu.Components 0.1 |
4376 | +import Ubuntu.Components.ListItems 0.1 as ListItem |
4377 | + |
4378 | +MainView { |
4379 | + width: units.gu(70) |
4380 | + height: units.gu(60) |
4381 | + |
4382 | + Tabs { |
4383 | + id: tabs |
4384 | + Tab { |
4385 | + objectName: "tab1" |
4386 | + title: "Tab1" |
4387 | + Page { |
4388 | + tools: ToolbarItems { |
4389 | + ToolbarButton { |
4390 | + text: "Test1" |
4391 | + } |
4392 | + } |
4393 | + } |
4394 | + } |
4395 | + Tab { |
4396 | + objectName: "tab2" |
4397 | + title: "Tab2" |
4398 | + Page { |
4399 | + tools: ToolbarItems { |
4400 | + ToolbarButton { |
4401 | + text: "Test2" |
4402 | + } |
4403 | + } |
4404 | + } |
4405 | + } |
4406 | + Tab { |
4407 | + objectName: "tab3" |
4408 | + title: "Tab3" |
4409 | + Page { |
4410 | + tools: ToolbarItems { |
4411 | + ToolbarButton { |
4412 | + text: "Test3" |
4413 | + } |
4414 | + } |
4415 | + } |
4416 | + } |
4417 | + } |
4418 | +} |
4419 | +""") |
4420 | + |
4421 | + def test_tabs_custom_proxy_object(self): |
4422 | + self.assertIsInstance(self.main_view.get_tabs(), ubuntuuitoolkit.Tabs) |
4423 | + |
4424 | + def test_get_current_tab(self): |
4425 | + tabs = self.main_view.get_tabs() |
4426 | + self.assertEqual(tabs.get_current_tab().title, 'Tab1') |
4427 | + |
4428 | + def test_switch_to_next_tab_from_header(self): |
4429 | + header = self.main_view.get_header() |
4430 | + header.switch_to_next_tab() |
4431 | + current_tab = self.main_view.get_tabs().get_current_tab() |
4432 | + self.assertEqual(current_tab.title, 'Tab2') |
4433 | + |
4434 | + def test_switch_to_next_tab_from_main_view(self): |
4435 | + current_tab = self.main_view.switch_to_next_tab() |
4436 | + self.assertEqual(current_tab.title, 'Tab2') |
4437 | + |
4438 | + def test_switch_to_next_tab_from_last(self): |
4439 | + last_tab_index = self.main_view.get_tabs().get_number_of_tabs() - 1 |
4440 | + self.main_view.switch_to_tab_by_index(last_tab_index) |
4441 | + current_tab = self.main_view.switch_to_next_tab() |
4442 | + self.assertEqual(current_tab.title, 'Tab1') |
4443 | + |
4444 | + def test_switch_to_tab_by_index(self): |
4445 | + current_tab = self.main_view.switch_to_tab_by_index(2) |
4446 | + self.assertEqual(current_tab.title, 'Tab3') |
4447 | + current_tab = self.main_view.switch_to_tab_by_index(1) |
4448 | + self.assertEqual(current_tab.title, 'Tab2') |
4449 | + current_tab = self.main_view.switch_to_tab_by_index(0) |
4450 | + self.assertEqual(current_tab.title, 'Tab1') |
4451 | + |
4452 | + def test_switch_to_opened_tab_is_not_opened_again(self): |
4453 | + with mock.patch.object( |
4454 | + ubuntuuitoolkit.Header, 'switch_to_next_tab') as mock_switch: |
4455 | + current_tab = self.main_view.switch_to_tab_by_index(0) |
4456 | + |
4457 | + self.assertFalse(mock_switch.called) |
4458 | + self.assertEqual(current_tab.title, 'Tab1') |
4459 | + |
4460 | + def test_get_number_of_tabs(self): |
4461 | + tabs = self.main_view.get_tabs() |
4462 | + self.assertEqual(tabs.get_number_of_tabs(), 3) |
4463 | + |
4464 | + def test_swith_to_tab_by_index_out_of_range(self): |
4465 | + last_tab_index = self.main_view.get_tabs().get_number_of_tabs() - 1 |
4466 | + error = self.assertRaises( |
4467 | + ubuntuuitoolkit.ToolkitException, |
4468 | + self.main_view.switch_to_tab_by_index, |
4469 | + last_tab_index + 1) |
4470 | + self.assertEqual(str(error), 'Tab index out of range.') |
4471 | + |
4472 | + def test_switch_to_previous_tab_from_first(self): |
4473 | + current_tab = self.main_view.switch_to_previous_tab() |
4474 | + self.assertEqual(current_tab.title, 'Tab3') |
4475 | + |
4476 | + def test_switch_to_previous_tab_not_from_first(self): |
4477 | + self.main_view.switch_to_tab_by_index(1) |
4478 | + current_tab = self.main_view.switch_to_previous_tab() |
4479 | + self.assertEqual(current_tab.title, 'Tab1') |
4480 | + |
4481 | + def test_switch_to_tab_by_object_name(self): |
4482 | + current_tab = self.main_view.switch_to_tab('tab3') |
4483 | + self.assertEqual(current_tab.title, 'Tab3') |
4484 | + current_tab = self.main_view.switch_to_tab('tab2') |
4485 | + self.assertEqual(current_tab.title, 'Tab2') |
4486 | + current_tab = self.main_view.switch_to_tab('tab1') |
4487 | + self.assertEqual(current_tab.title, 'Tab1') |
4488 | + |
4489 | + def test_switch_to_unexisting_tab(self): |
4490 | + error = self.assertRaises( |
4491 | + ubuntuuitoolkit.ToolkitException, self.main_view.switch_to_tab, |
4492 | + 'unexisting') |
4493 | + self.assertEqual( |
4494 | + str(error), 'Tab with objectName "unexisting" not found.') |
4495 | |
4496 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py' |
4497 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py 1970-01-01 00:00:00 +0000 |
4498 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py 2014-04-17 01:29:24 +0000 |
4499 | @@ -0,0 +1,93 @@ |
4500 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4501 | +# |
4502 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
4503 | +# |
4504 | +# This program is free software; you can redistribute it and/or modify |
4505 | +# it under the terms of the GNU Lesser General Public License as published by |
4506 | +# the Free Software Foundation; version 3. |
4507 | +# |
4508 | +# This program is distributed in the hope that it will be useful, |
4509 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4510 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4511 | +# GNU Lesser General Public License for more details. |
4512 | +# |
4513 | +# You should have received a copy of the GNU Lesser General Public License |
4514 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4515 | + |
4516 | +import ubuntuuitoolkit |
4517 | +from ubuntuuitoolkit import tests |
4518 | + |
4519 | + |
4520 | +class TextFieldTestCase(tests.QMLStringAppTestCase): |
4521 | + |
4522 | + test_qml = (""" |
4523 | +import QtQuick 2.0 |
4524 | +import Ubuntu.Components 0.1 |
4525 | + |
4526 | +MainView { |
4527 | + width: units.gu(48) |
4528 | + height: units.gu(60) |
4529 | + |
4530 | + Item { |
4531 | + TextField { |
4532 | + id: simpleTextField |
4533 | + objectName: "simple_text_field" |
4534 | + } |
4535 | + TextField { |
4536 | + id: textFieldWithoutClearButton |
4537 | + objectName: "text_field_without_clear_button" |
4538 | + hasClearButton: false |
4539 | + anchors.top: simpleTextField.bottom |
4540 | + } |
4541 | + } |
4542 | +} |
4543 | +""") |
4544 | + |
4545 | + def setUp(self): |
4546 | + super(TextFieldTestCase, self).setUp() |
4547 | + self.simple_text_field = self.main_view.select_single( |
4548 | + ubuntuuitoolkit.TextField, objectName='simple_text_field') |
4549 | + |
4550 | + def test_text_field_custom_proxy_object(self): |
4551 | + self.assertIsInstance( |
4552 | + self.simple_text_field, ubuntuuitoolkit.TextField) |
4553 | + |
4554 | + def test_write(self): |
4555 | + self.simple_text_field.write('test') |
4556 | + self.assertEqual(self.simple_text_field.text, 'test') |
4557 | + |
4558 | + def test_clear_with_clear_button(self): |
4559 | + self.simple_text_field.write('test') |
4560 | + self.simple_text_field.clear() |
4561 | + self.assertEqual(self.simple_text_field.text, '') |
4562 | + |
4563 | + def test_clear_without_clear_button(self): |
4564 | + text_field = self.main_view.select_single( |
4565 | + ubuntuuitoolkit.TextField, |
4566 | + objectName='text_field_without_clear_button') |
4567 | + text_field.write('test') |
4568 | + text_field.clear() |
4569 | + self.assertEqual(text_field.text, '') |
4570 | + |
4571 | + def test_clear_and_write(self): |
4572 | + self.simple_text_field.write('test1') |
4573 | + self.simple_text_field.write('test2') |
4574 | + self.assertEqual(self.simple_text_field.text, 'test2') |
4575 | + |
4576 | + def test_write_without_clear(self): |
4577 | + self.simple_text_field.write('test1') |
4578 | + self.simple_text_field.write('test2', clear=False) |
4579 | + self.assertEqual(self.simple_text_field.text, 'test1test2') |
4580 | + |
4581 | + def test_write_without_clear_writes_at_the_end(self): |
4582 | + self.simple_text_field.write( |
4583 | + 'long text that will fill more than half of the text field.') |
4584 | + self.simple_text_field.write('append', clear=False) |
4585 | + self.assertEqual( |
4586 | + self.simple_text_field.text, |
4587 | + 'long text that will fill more than half of the text field.append') |
4588 | + |
4589 | + def test_is_empty(self): |
4590 | + self.assertTrue(self.simple_text_field.is_empty()) |
4591 | + self.simple_text_field.write('test') |
4592 | + self.assertFalse(self.simple_text_field.is_empty()) |
4593 | |
4594 | === added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py' |
4595 | --- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py 1970-01-01 00:00:00 +0000 |
4596 | +++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py 2014-04-17 01:29:24 +0000 |
4597 | @@ -0,0 +1,121 @@ |
4598 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4599 | +# |
4600 | +# Copyright (C) 2013, 2014 Canonical Ltd. |
4601 | +# |
4602 | +# This program is free software; you can redistribute it and/or modify |
4603 | +# it under the terms of the GNU Lesser General Public License as published by |
4604 | +# the Free Software Foundation; version 3. |
4605 | +# |
4606 | +# This program is distributed in the hope that it will be useful, |
4607 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4608 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4609 | +# GNU Lesser General Public License for more details. |
4610 | +# |
4611 | +# You should have received a copy of the GNU Lesser General Public License |
4612 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4613 | + |
4614 | +try: |
4615 | + from unittest import mock |
4616 | +except ImportError: |
4617 | + import mock |
4618 | + |
4619 | +import ubuntuuitoolkit |
4620 | +from ubuntuuitoolkit import tests |
4621 | + |
4622 | + |
4623 | +class ToolbarTestCase(tests.QMLStringAppTestCase): |
4624 | + |
4625 | + test_qml = (""" |
4626 | +import QtQuick 2.0 |
4627 | +import Ubuntu.Components 0.1 |
4628 | + |
4629 | +MainView { |
4630 | + width: units.gu(50) |
4631 | + height: units.gu(50) |
4632 | + |
4633 | + // Make sure that for these tests the toolbar starts closed. |
4634 | + Component.onCompleted: { |
4635 | + __propagated.toolbar.close(); |
4636 | + } |
4637 | + |
4638 | + Page { |
4639 | + |
4640 | + Label { |
4641 | + id: "label" |
4642 | + objectName: "clicked_label" |
4643 | + anchors.centerIn: parent |
4644 | + text: "Button not clicked." |
4645 | + } |
4646 | + |
4647 | + tools: ToolbarItems { |
4648 | + ToolbarButton { |
4649 | + objectName: "buttonName" |
4650 | + action: Action { |
4651 | + text: "buttonText" |
4652 | + onTriggered: label.text = "Button clicked." |
4653 | + } |
4654 | + } |
4655 | + } |
4656 | + } |
4657 | +} |
4658 | +""") |
4659 | + |
4660 | + def setUp(self): |
4661 | + super(ToolbarTestCase, self).setUp() |
4662 | + self.toolbar = self.main_view.get_toolbar() |
4663 | + # toolbar may be opened or closed now, depending on whether |
4664 | + # the application has been deactivated and resumed already |
4665 | + |
4666 | + def test_open_toolbar(self): |
4667 | + self.toolbar.open() |
4668 | + self.assertTrue(self.toolbar.opened) |
4669 | + self.assertFalse(self.toolbar.animating) |
4670 | + |
4671 | + def test_opened_toolbar_is_not_opened_again(self): |
4672 | + self.toolbar.open() |
4673 | + with mock.patch.object( |
4674 | + self.main_view.pointing_device, 'drag') as mock_drag: |
4675 | + self.toolbar.open() |
4676 | + |
4677 | + self.assertFalse(mock_drag.called) |
4678 | + self.assertTrue(self.toolbar.opened) |
4679 | + |
4680 | + def test_close_toolbar(self): |
4681 | + self.toolbar.open() |
4682 | + self.toolbar.close() |
4683 | + self.assertFalse(self.toolbar.opened) |
4684 | + self.assertFalse(self.toolbar.animating) |
4685 | + |
4686 | + def test_closed_toolbar_is_not_closed_again(self): |
4687 | + self.toolbar.close() |
4688 | + with mock.patch.object( |
4689 | + self.main_view.pointing_device, 'drag') as mock_drag: |
4690 | + self.toolbar.close() |
4691 | + |
4692 | + self.assertFalse(mock_drag.called) |
4693 | + self.assertFalse(self.toolbar.opened) |
4694 | + |
4695 | + def test_click_toolbar_button(self): |
4696 | + self.toolbar.close() |
4697 | + label = self.app.select_single('Label', objectName='clicked_label') |
4698 | + self.assertNotEqual(label.text, 'Button clicked.') |
4699 | + self.toolbar.open() |
4700 | + self.toolbar.click_button('buttonName') |
4701 | + self.assertEqual(label.text, 'Button clicked.') |
4702 | + |
4703 | + def test_click_unexisting_button(self): |
4704 | + self.main_view.open_toolbar() |
4705 | + error = self.assertRaises( |
4706 | + ubuntuuitoolkit.ToolkitException, self.toolbar.click_button, |
4707 | + 'unexisting') |
4708 | + self.assertEqual( |
4709 | + str(error), 'Button with objectName "unexisting" not found.') |
4710 | + |
4711 | + def test_click_button_on_closed_toolbar(self): |
4712 | + self.toolbar.close() |
4713 | + error = self.assertRaises( |
4714 | + ubuntuuitoolkit.ToolkitException, self.toolbar.click_button, |
4715 | + 'buttonName') |
4716 | + self.assertEqual( |
4717 | + str(error), |
4718 | + 'Toolbar must be opened before calling click_button().') |
4719 | |
4720 | === removed file 'tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py' |
4721 | --- tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 2014-03-22 06:53:24 +0000 |
4722 | +++ tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 1970-01-01 00:00:00 +0000 |
4723 | @@ -1,1081 +0,0 @@ |
4724 | -# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
4725 | -# |
4726 | -# Copyright (C) 2013 Canonical Ltd. |
4727 | -# |
4728 | -# This program is free software; you can redistribute it and/or modify |
4729 | -# it under the terms of the GNU Lesser General Public License as published by |
4730 | -# the Free Software Foundation; version 3. |
4731 | -# |
4732 | -# This program is distributed in the hope that it will be useful, |
4733 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
4734 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
4735 | -# GNU Lesser General Public License for more details. |
4736 | -# |
4737 | -# You should have received a copy of the GNU Lesser General Public License |
4738 | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
4739 | - |
4740 | -import time |
4741 | -import unittest |
4742 | - |
4743 | -import autopilot |
4744 | -from autopilot import input, platform |
4745 | -from autopilot.introspection import dbus |
4746 | -from testtools.matchers import GreaterThan, LessThan |
4747 | -try: |
4748 | - from unittest import mock |
4749 | -except ImportError: |
4750 | - import mock |
4751 | - |
4752 | -from ubuntuuitoolkit import emulators, tests |
4753 | - |
4754 | - |
4755 | -class CheckAutopilotVersionTestCase(unittest.TestCase): |
4756 | - |
4757 | - def test_lower_version_should_raise_exception(self): |
4758 | - with mock.patch.object(autopilot, 'version', '1.3'): |
4759 | - self.assertRaises( |
4760 | - emulators.ToolkitEmulatorException, |
4761 | - emulators.check_autopilot_version) |
4762 | - |
4763 | - def test_required_version_should_succeed(self): |
4764 | - with mock.patch.object(autopilot, 'version', '1.4'): |
4765 | - emulators.check_autopilot_version() |
4766 | - |
4767 | - def test_higher_version_should_succeed(self): |
4768 | - with mock.patch.object(autopilot, 'version', '1.5'): |
4769 | - emulators.check_autopilot_version() |
4770 | - |
4771 | - |
4772 | -class UbuntuUIToolkitEmulatorBaseTestCase(tests.QMLStringAppTestCase): |
4773 | - |
4774 | - def test_pointing_device(self): |
4775 | - self.assertIsInstance(self.app.pointing_device, input.Pointer) |
4776 | - |
4777 | - @unittest.skipIf(platform.model() != 'Desktop', 'Desktop only') |
4778 | - def test_pointing_device_in_desktop(self): |
4779 | - self.assertIsInstance(self.app.pointing_device._device, input.Mouse) |
4780 | - |
4781 | - @unittest.skipIf(platform.model() == 'Desktop', 'Phablet only') |
4782 | - def test_pointing_device_in_phablet(self): |
4783 | - self.assertIsInstance(self.app.pointing_device._device, input.Touch) |
4784 | - |
4785 | - def test_emulators_should_check_version_on_init(self): |
4786 | - check_name = 'ubuntuuitoolkit.emulators.check_autopilot_version' |
4787 | - with mock.patch(check_name, autospec=True) as mock_check: |
4788 | - # Instantiate any emulator. |
4789 | - self.main_view |
4790 | - |
4791 | - mock_check.assert_called_once_with() |
4792 | - |
4793 | - |
4794 | -class MainViewTestCase(tests.QMLStringAppTestCase): |
4795 | - |
4796 | - test_qml = (""" |
4797 | -import QtQuick 2.0 |
4798 | -import Ubuntu.Components 0.1 |
4799 | - |
4800 | -MainView { |
4801 | - width: units.gu(48) |
4802 | - height: units.gu(60) |
4803 | -} |
4804 | -""") |
4805 | - |
4806 | - def test_main_view_custom_emulator(self): |
4807 | - self.assertIsInstance(self.main_view, emulators.MainView) |
4808 | - |
4809 | - def test_get_header_without_header(self): |
4810 | - header = self.main_view.get_header() |
4811 | - self.assertFalse(header.visible) |
4812 | - |
4813 | - def test_toolbar_custom_emulator(self): |
4814 | - toolbar = self.main_view.get_toolbar() |
4815 | - self.assertIsInstance(toolbar, emulators.Toolbar) |
4816 | - |
4817 | - def test_open_toolbar(self): |
4818 | - with mock.patch.object(emulators.Toolbar, 'open') as mock_open: |
4819 | - self.main_view.open_toolbar() |
4820 | - |
4821 | - mock_open.assert_called_once_with() |
4822 | - |
4823 | - def test_close_toolbar(self): |
4824 | - with mock.patch.object(emulators.Toolbar, 'close') as mock_close: |
4825 | - self.main_view.close_toolbar() |
4826 | - |
4827 | - mock_close.assert_called_once_with() |
4828 | - |
4829 | - def test_open_toolbar_returns_the_toolbar(self): |
4830 | - toolbar = self.main_view.open_toolbar() |
4831 | - self.assertIsInstance(toolbar, emulators.Toolbar) |
4832 | - |
4833 | - def test_get_tabs_without_tabs(self): |
4834 | - error = self.assertRaises( |
4835 | - emulators.ToolkitEmulatorException, self.main_view.get_tabs) |
4836 | - self.assertEqual( |
4837 | - str(error), 'The MainView has no Tabs.') |
4838 | - |
4839 | - def test_switch_to_next_tab_without_tabs(self): |
4840 | - header = self.main_view.get_header() |
4841 | - error = self.assertRaises( |
4842 | - emulators.ToolkitEmulatorException, header.switch_to_next_tab) |
4843 | - self.assertEqual( |
4844 | - str(error), 'The MainView has no Tabs.') |
4845 | - |
4846 | - |
4847 | -class PageTestCase(tests.QMLStringAppTestCase): |
4848 | - |
4849 | - test_qml = (""" |
4850 | -import QtQuick 2.0 |
4851 | -import Ubuntu.Components 0.1 |
4852 | - |
4853 | -MainView { |
4854 | - width: units.gu(48) |
4855 | - height: units.gu(60) |
4856 | - |
4857 | - Page { |
4858 | - title: "Test title" |
4859 | - } |
4860 | -} |
4861 | -""") |
4862 | - |
4863 | - def test_header_custom_emulator(self): |
4864 | - header = self.main_view.get_header() |
4865 | - self.assertIsInstance(header, emulators.Header) |
4866 | - self.assertTrue(header.visible) |
4867 | - self.assertEqual(header.title, "Test title") |
4868 | - |
4869 | - |
4870 | -class ToolbarTestCase(tests.QMLStringAppTestCase): |
4871 | - |
4872 | - test_qml = (""" |
4873 | -import QtQuick 2.0 |
4874 | -import Ubuntu.Components 0.1 |
4875 | - |
4876 | -MainView { |
4877 | - width: units.gu(50) |
4878 | - height: units.gu(50) |
4879 | - |
4880 | - // Make sure that for these tests the toolbar starts closed. |
4881 | - Component.onCompleted: { |
4882 | - __propagated.toolbar.close(); |
4883 | - } |
4884 | - |
4885 | - Page { |
4886 | - |
4887 | - Label { |
4888 | - id: "label" |
4889 | - objectName: "clicked_label" |
4890 | - anchors.centerIn: parent |
4891 | - text: "Button not clicked." |
4892 | - } |
4893 | - |
4894 | - tools: ToolbarItems { |
4895 | - ToolbarButton { |
4896 | - objectName: "buttonName" |
4897 | - action: Action { |
4898 | - text: "buttonText" |
4899 | - onTriggered: label.text = "Button clicked." |
4900 | - } |
4901 | - } |
4902 | - } |
4903 | - } |
4904 | -} |
4905 | -""") |
4906 | - |
4907 | - def setUp(self): |
4908 | - super(ToolbarTestCase, self).setUp() |
4909 | - self.toolbar = self.main_view.get_toolbar() |
4910 | - # toolbar may be opened or closed now, depending on whether |
4911 | - # the application has been deactivated and resumed already |
4912 | - |
4913 | - def test_open_toolbar(self): |
4914 | - self.toolbar.open() |
4915 | - self.assertTrue(self.toolbar.opened) |
4916 | - self.assertFalse(self.toolbar.animating) |
4917 | - |
4918 | - def test_opened_toolbar_is_not_opened_again(self): |
4919 | - self.toolbar.open() |
4920 | - with mock.patch.object( |
4921 | - self.main_view.pointing_device, 'drag') as mock_drag: |
4922 | - self.toolbar.open() |
4923 | - |
4924 | - self.assertFalse(mock_drag.called) |
4925 | - self.assertTrue(self.toolbar.opened) |
4926 | - |
4927 | - def test_close_toolbar(self): |
4928 | - self.toolbar.open() |
4929 | - self.toolbar.close() |
4930 | - self.assertFalse(self.toolbar.opened) |
4931 | - self.assertFalse(self.toolbar.animating) |
4932 | - |
4933 | - def test_closed_toolbar_is_not_closed_again(self): |
4934 | - self.toolbar.close() |
4935 | - with mock.patch.object( |
4936 | - self.main_view.pointing_device, 'drag') as mock_drag: |
4937 | - self.toolbar.close() |
4938 | - |
4939 | - self.assertFalse(mock_drag.called) |
4940 | - self.assertFalse(self.toolbar.opened) |
4941 | - |
4942 | - def test_click_toolbar_button(self): |
4943 | - self.toolbar.close() |
4944 | - label = self.app.select_single('Label', objectName='clicked_label') |
4945 | - self.assertNotEqual(label.text, 'Button clicked.') |
4946 | - self.toolbar.open() |
4947 | - self.toolbar.click_button('buttonName') |
4948 | - self.assertEqual(label.text, 'Button clicked.') |
4949 | - |
4950 | - def test_click_unexisting_button(self): |
4951 | - self.main_view.open_toolbar() |
4952 | - error = self.assertRaises( |
4953 | - emulators.ToolkitEmulatorException, self.toolbar.click_button, |
4954 | - 'unexisting') |
4955 | - self.assertEqual( |
4956 | - str(error), 'Button with objectName "unexisting" not found.') |
4957 | - |
4958 | - def test_click_button_on_closed_toolbar(self): |
4959 | - self.toolbar.close() |
4960 | - error = self.assertRaises( |
4961 | - emulators.ToolkitEmulatorException, self.toolbar.click_button, |
4962 | - 'buttonName') |
4963 | - self.assertEqual( |
4964 | - str(error), |
4965 | - 'Toolbar must be opened before calling click_button().') |
4966 | - |
4967 | - |
4968 | -class TabsTestCase(tests.QMLStringAppTestCase): |
4969 | - |
4970 | - test_qml = (""" |
4971 | -import QtQuick 2.0 |
4972 | -import Ubuntu.Components 0.1 |
4973 | -import Ubuntu.Components.ListItems 0.1 as ListItem |
4974 | - |
4975 | -MainView { |
4976 | - width: units.gu(70) |
4977 | - height: units.gu(60) |
4978 | - |
4979 | - Tabs { |
4980 | - id: tabs |
4981 | - Tab { |
4982 | - objectName: "tab1" |
4983 | - title: "Tab1" |
4984 | - Page { |
4985 | - tools: ToolbarItems { |
4986 | - ToolbarButton { |
4987 | - text: "Test1" |
4988 | - } |
4989 | - } |
4990 | - } |
4991 | - } |
4992 | - Tab { |
4993 | - objectName: "tab2" |
4994 | - title: "Tab2" |
4995 | - Page { |
4996 | - tools: ToolbarItems { |
4997 | - ToolbarButton { |
4998 | - text: "Test2" |
4999 | - } |
5000 | - } |
FAILED: Continuous integration, rev:1010 jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- ci/2022/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- trusty- touch/200/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- trusty/ 4777/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- amd64-ci/ 970/console jenkins. qa.ubuntu. com/job/ ubuntu- ui-toolkit- trusty- armhf-ci/ 970/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- armhf/4358/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- trusty- amd64/4909/ console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- ui-toolkit- ci/2022/ rebuild
http://