Merge lp:~elopio/ubuntu-ui-toolkit/reorg_autopilot_helpers into lp:ubuntu-ui-toolkit

Proposed by Leo Arias
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
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Cris Dywan Approve
Review via email: mp+215340@code.launchpad.net

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.emulators module from the autopilot helpers.

To post a comment you must log in.
1011. By Leo Arias

Renamed the custom proxy object tests.

1012. By Leo Arias

Renamed UbuntuUIToolkitException to ToolkitException as suggested by thomi.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1013. By Leo Arias

Added a test that checks that the deprecated symbols point to the new namespace.

1014. By Leo Arias

Added a test for the deprecation warning.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1015. By Leo Arias

Added a _custom_proxy_objects module.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1016. By Leo Arias

For now, make it a file.

1017. By Leo Arias

Added the missing init.

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

review: Approve
1018. By Leo Arias

Split the big file.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1019. By Leo Arias

Use __all__.

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

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'components.api'
--- components.api 2014-04-01 12:57:27 +0000
+++ components.api 2014-04-17 01:29:24 +0000
@@ -593,6 +593,8 @@
593 function findChild(obj,objectName)593 function findChild(obj,objectName)
594 function findInvisibleChild(obj,objectName)594 function findInvisibleChild(obj,objectName)
595 function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay)595 function mouseMoveSlowly(item,x,y,dx,dy,steps,stepdelay)
596 function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay)
597 function mouseLongPress(item, x, y, button, modifiers, delay)
596 function tryCompareFunction(func, expectedResult, timeout)598 function tryCompareFunction(func, expectedResult, timeout)
597plugins.qmltypes599plugins.qmltypes
598 name: "InverseMouseAreaType"600 name: "InverseMouseAreaType"
@@ -687,27 +689,35 @@
687 Signal {689 Signal {
688 name: "pressed"690 name: "pressed"
689 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }691 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }
692 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
690 Signal {693 Signal {
691 name: "released"694 name: "released"
692 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }695 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }
696 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
693 Signal {697 Signal {
694 name: "clicked"698 name: "clicked"
695 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }699 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }
700 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
696 Signal {701 Signal {
697 name: "pressAndHold"702 name: "pressAndHold"
698 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }703 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }
704 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
699 Signal {705 Signal {
700 name: "doubleClicked"706 name: "doubleClicked"
701 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }707 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }
708 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
702 Signal {709 Signal {
703 name: "positionChanged"710 name: "positionChanged"
704 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }711 Parameter { name: "mouse"; type: "QQuickMouseEvent"; isPointer: true }
712 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
705 Signal {713 Signal {
706 name: "entered"714 name: "entered"
707 Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true }715 Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true }
716 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
708 Signal {717 Signal {
709 name: "exited"718 name: "exited"
710 Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true }719 Parameter { name: "event"; type: "QQuickMouseEvent"; isPointer: true }
720 Parameter { name: "host"; type: "QQuickItem"; isPointer: true }
711 name: "UCQQuickImageExtension"721 name: "UCQQuickImageExtension"
712 prototype: "QQuickImageBase"722 prototype: "QQuickImageBase"
713 exports: ["QQuickImageBase 0.1"]723 exports: ["QQuickImageBase 0.1"]
714724
=== modified file 'debian/control'
--- debian/control 2014-04-17 01:29:24 +0000
+++ debian/control 2014-04-17 01:29:24 +0000
@@ -135,7 +135,11 @@
135 python-autopilot (>= 1.4),135 python-autopilot (>= 1.4),
136 python-fixtures,136 python-fixtures,
137 python-mock,137 python-mock,
138 python-testscenarios,
139 python-testtools,
138 python3-fixtures,140 python3-fixtures,
141 python3-testscenarios,
142 python3-testtools,
139 python3-autopilot (>= 1.4),143 python3-autopilot (>= 1.4),
140 ubuntu-ui-toolkit-examples (>= ${source:Version}),144 ubuntu-ui-toolkit-examples (>= ${source:Version}),
141Description: Test package for Ubuntu UI Toolkit145Description: Test package for Ubuntu UI Toolkit
142146
=== modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp'
--- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-02-21 20:02:29 +0000
+++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_organizer.cpp 2014-04-17 01:29:24 +0000
@@ -21,6 +21,7 @@
21#include "alarmmanager_p.h"21#include "alarmmanager_p.h"
22#include "alarmrequest_p.h"22#include "alarmrequest_p.h"
23#include "alarmsadapter_p.h"23#include "alarmsadapter_p.h"
24#include <qorganizertodooccurrence.h>
2425
25#include <QtCore/QFile>26#include <QtCore/QFile>
26#include <QtCore/QDir>27#include <QtCore/QDir>
@@ -55,11 +56,15 @@
55 , manager(0)56 , manager(0)
56 , fetchRequest(0)57 , fetchRequest(0)
57{58{
58 QOrganizerManager local;59 QString envManager(qgetenv("ALARM_BACKEND"));
59 bool usingDefaultManager = local.availableManagers().contains(ALARM_MANAGER);60 if (!envManager.isEmpty() && QOrganizerManager::availableManagers().contains(envManager)) {
60 manager = (usingDefaultManager) ? new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK);61 manager = new QOrganizerManager(envManager);
62 } else {
63 manager = QOrganizerManager::availableManagers().contains(ALARM_MANAGER) ?
64 new QOrganizerManager(ALARM_MANAGER) : new QOrganizerManager(ALARM_MANAGER_FALLBACK);
65 }
61 manager->setParent(q_ptr);66 manager->setParent(q_ptr);
62 if (!usingDefaultManager) {67 if (manager->managerName() != ALARM_MANAGER) {
63 qWarning() << "WARNING: default alarm manager not installed, using" << manager->managerName() << "manager.";68 qWarning() << "WARNING: default alarm manager not installed, using" << manager->managerName() << "manager.";
64 qWarning() << "This manager may not provide all the needed features.";69 qWarning() << "This manager may not provide all the needed features.";
65 }70 }
@@ -114,6 +119,7 @@
114119
115 int type, days;120 int type, days;
116 in >> alarm.message >> alarm.date >> alarm.sound >> type >> days >> alarm.enabled;121 in >> alarm.message >> alarm.date >> alarm.sound >> type >> days >> alarm.enabled;
122 alarm.originalDate = alarm.date = AlarmData::transcodeDate(alarm.date, Qt::LocalTime);
117 alarm.type = static_cast<UCAlarm::AlarmType>(type);123 alarm.type = static_cast<UCAlarm::AlarmType>(type);
118 alarm.days = static_cast<UCAlarm::DaysOfWeek>(days);124 alarm.days = static_cast<UCAlarm::DaysOfWeek>(days);
119125
@@ -142,7 +148,7 @@
142148
143 Q_FOREACH(const AlarmData &alarm, alarmList) {149 Q_FOREACH(const AlarmData &alarm, alarmList) {
144 out << alarm.message150 out << alarm.message
145 << alarm.date151 << AlarmData::transcodeDate(alarm.originalDate, Qt::UTC)
146 << alarm.sound152 << alarm.sound
147 << alarm.type153 << alarm.type
148 << alarm.days154 << alarm.days
@@ -156,8 +162,7 @@
156{162{
157 event.setCollectionId(collection.id());163 event.setCollectionId(collection.id());
158 event.setAllDay(false);164 event.setAllDay(false);
159 event.setStartDateTime(alarm.date);165 event.setStartDateTime(AlarmData::transcodeDate(alarm.date, Qt::UTC));
160 event.setDueDateTime(alarm.date);
161 event.setDisplayLabel(alarm.message);166 event.setDisplayLabel(alarm.message);
162167
163 if (alarm.enabled) {168 if (alarm.enabled) {
@@ -224,8 +229,9 @@
224229
225 alarm.cookie = QVariant::fromValue<QOrganizerItemId>(event.id());230 alarm.cookie = QVariant::fromValue<QOrganizerItemId>(event.id());
226 alarm.message = event.displayLabel();231 alarm.message = event.displayLabel();
227 alarm.date = AlarmData::normalizeDate(event.dueDateTime());232 alarm.date = AlarmData::transcodeDate(event.startDateTime().toUTC(), Qt::LocalTime);
228 alarm.sound = QUrl(event.description());233 alarm.sound = QUrl(event.description());
234 alarm.originalDate = alarm.date;
229235
230 // check if the alarm is enabled or not236 // check if the alarm is enabled or not
231 QOrganizerItemVisualReminder visual = event.detail(QOrganizerItemDetail::TypeVisualReminder);237 QOrganizerItemVisualReminder visual = event.detail(QOrganizerItemDetail::TypeVisualReminder);
@@ -303,8 +309,8 @@
303 Q_FOREACH(const QOrganizerItem &item, alarms) {309 Q_FOREACH(const QOrganizerItem &item, alarms) {
304 // repeating alarms may be fetched as occurences, therefore check their parent event310 // repeating alarms may be fetched as occurences, therefore check their parent event
305 if (item.type() == QOrganizerItemType::TypeTodoOccurrence) {311 if (item.type() == QOrganizerItemType::TypeTodoOccurrence) {
306 QOrganizerTodoOccurrence occurence = static_cast<QOrganizerTodoOccurrence>(item);312 QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(item);
307 QOrganizerItemId eventId = occurence.parentId();313 QOrganizerItemId eventId = occurrence.parentId();
308 if (parentId.contains(eventId)) {314 if (parentId.contains(eventId)) {
309 continue;315 continue;
310 }316 }
@@ -317,6 +323,7 @@
317 }323 }
318 AlarmData alarm;324 AlarmData alarm;
319 if (alarmDataFromOrganizerEvent(event, alarm) == UCAlarm::NoError) {325 if (alarmDataFromOrganizerEvent(event, alarm) == UCAlarm::NoError) {
326 adjustAlarmOccurrence(event, alarm);
320 alarmList << alarm;327 alarmList << alarm;
321 }328 }
322 }329 }
@@ -324,9 +331,52 @@
324 saveAlarms();331 saveAlarms();
325 Q_EMIT q_ptr->alarmsChanged();332 Q_EMIT q_ptr->alarmsChanged();
326 completed = true;333 completed = true;
334 fetchRequest->deleteLater();
327 fetchRequest = 0;335 fetchRequest = 0;
328}336}
329337
338void AlarmsAdapter::adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm)
339{
340 if (alarm.type == UCAlarm::OneTime) {
341 return;
342 }
343 // with EDS we need to query the occurrences separately as the fetch reports only the main events
344 // with fallback manager this does not reduce the performance and does work the same way.
345 QDateTime currentDate = AlarmData::normalizeDate(QDateTime::currentDateTime());
346 if (alarm.date > currentDate) {
347 // no need to adjust date, the event occurs in the future
348 return;
349 }
350 QDateTime startDate;
351 QDateTime endDate;
352 if (alarm.type == UCAlarm::Repeating) {
353 // 8 days is enough from the starting date (or current date depending on the start date)
354 startDate = (alarm.date > currentDate) ? alarm.date : currentDate;
355 endDate = startDate.addDays(8);
356 }
357
358 // transcode both dates
359 startDate = AlarmData::transcodeDate(startDate, Qt::UTC);
360 endDate = AlarmData::transcodeDate(endDate, Qt::UTC);
361
362 QList<QOrganizerItem> occurrences = manager->itemOccurrences(event, startDate, endDate, 10);
363 // get the first occurrence and use the date from it
364 if ((occurrences.length() > 0) && (occurrences[0].type() == QOrganizerItemType::TypeTodoOccurrence)) {
365 // loop till we get a proper future due date
366 for (int i = 0; i < occurrences.count(); i++) {
367 QOrganizerTodoOccurrence occurrence = static_cast<QOrganizerTodoOccurrence>(occurrences[i]);
368 // check if the date is after the current datetime
369 // the first occurrence is the one closest to the currentDate, therefore we can safely
370 // set that startDate to the alarm
371 alarm.date = AlarmData::transcodeDate(occurrence.startDateTime().toUTC(), Qt::LocalTime);
372 if (alarm.date > currentDate) {
373 // we have the proper date set, leave
374 break;
375 }
376 }
377 }
378}
379
330/*-----------------------------------------------------------------------------380/*-----------------------------------------------------------------------------
331 * AlarmRequestAdapter implementation381 * AlarmRequestAdapter implementation
332 */382 */
@@ -410,23 +460,10 @@
410 QOrganizerItemFetchRequest *operation = new QOrganizerItemFetchRequest(q_ptr);460 QOrganizerItemFetchRequest *operation = new QOrganizerItemFetchRequest(q_ptr);
411 operation->setManager(owner->manager);461 operation->setManager(owner->manager);
412462
413 // FIXME: Since returning all events without a limit of date is not a good solution we need to find
414 // a better solution for that.
415 // The current solution filters only the next 7 days (one week).
416 // This will be enough for now, since the current alarms occur weekly, but for the future
417 // we want to allow create alarms with monthly or yearly recurrence
418 QDate currentDate = QDate::currentDate();
419 QDateTime startDate(currentDate,
420 QTime(0,0,0));
421 QDateTime endDate(currentDate.addDays(7),
422 QTime(23,59,59));
423 operation->setStartDate(startDate);
424 operation->setEndDate(endDate);
425
426 // set sort order463 // set sort order
427 QOrganizerItemSortOrder sortOrder;464 QOrganizerItemSortOrder sortOrder;
428 sortOrder.setDirection(Qt::AscendingOrder);465 sortOrder.setDirection(Qt::AscendingOrder);
429 sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldDueDateTime);466 sortOrder.setDetail(QOrganizerItemDetail::TypeTodoTime, QOrganizerTodoTime::FieldStartDateTime);
430 operation->setSorting(QList<QOrganizerItemSortOrder>() << sortOrder);467 operation->setSorting(QList<QOrganizerItemSortOrder>() << sortOrder);
431468
432 // set filter469 // set filter
433470
=== modified file 'modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h'
--- modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2013-09-18 10:13:04 +0000
+++ modules/Ubuntu/Components/plugin/adapters/alarmsadapter_p.h 2014-04-17 01:29:24 +0000
@@ -24,6 +24,7 @@
2424
25#include <qorganizer.h>25#include <qorganizer.h>
26#include <qorganizermanager.h>26#include <qorganizermanager.h>
27#include <qorganizertodo.h>
2728
28QTORGANIZER_USE_NAMESPACE29QTORGANIZER_USE_NAMESPACE
2930
@@ -78,6 +79,7 @@
78 QOrganizerCollection collection;79 QOrganizerCollection collection;
7980
80 void completeFetchAlarms(const QList<QOrganizerItem> &alarmList);81 void completeFetchAlarms(const QList<QOrganizerItem> &alarmList);
82 void adjustAlarmOccurrence(const QOrganizerTodo &event, AlarmData &alarm);
8183
82 void loadAlarms();84 void loadAlarms();
83 void saveAlarms();85 void saveAlarms();
8486
=== modified file 'modules/Ubuntu/Components/plugin/alarmmanager_p.h'
--- modules/Ubuntu/Components/plugin/alarmmanager_p.h 2013-09-12 05:27:40 +0000
+++ modules/Ubuntu/Components/plugin/alarmmanager_p.h 2014-04-17 01:29:24 +0000
@@ -48,6 +48,7 @@
48 AlarmData(const AlarmData &other)48 AlarmData(const AlarmData &other)
49 : changes(0)49 : changes(0)
50 , cookie(other.cookie)50 , cookie(other.cookie)
51 , originalDate(other.originalDate)
51 , date(other.date)52 , date(other.date)
52 , message(other.message)53 , message(other.message)
53 , type(other.type)54 , type(other.type)
@@ -100,10 +101,19 @@
100 return QDateTime(dt.date(), time, dt.timeSpec());101 return QDateTime(dt.date(), time, dt.timeSpec());
101 }102 }
102103
104 // the function normalizes and transcodes the date into UTC/LocalTime equivalent
105 static QDateTime transcodeDate(const QDateTime &dt, Qt::TimeSpec targetSpec) {
106 if (dt.timeSpec() == targetSpec) {
107 return normalizeDate(dt);
108 }
109 return QDateTime(dt.date(), normalizeDate(dt).time(), targetSpec);
110 }
111
103 unsigned int changes;112 unsigned int changes;
104 QVariant cookie;113 QVariant cookie;
105114
106 // data members115 // data members
116 QDateTime originalDate;
107 QDateTime date;117 QDateTime date;
108 QString message;118 QString message;
109 QUrl sound;119 QUrl sound;
110120
=== modified file 'modules/Ubuntu/Components/plugin/plugin.cpp'
--- modules/Ubuntu/Components/plugin/plugin.cpp 2014-03-05 12:29:58 +0000
+++ modules/Ubuntu/Components/plugin/plugin.cpp 2014-04-17 01:29:24 +0000
@@ -53,9 +53,6 @@
53#include <unistd.h>53#include <unistd.h>
54#include <stdexcept>54#include <stdexcept>
5555
56// Needed for unit tests
57Q_DECLARE_METATYPE(QList<QQmlError>)
58
59/*56/*
60 * Type registration functions.57 * Type registration functions.
61 */58 */
@@ -177,10 +174,11 @@
177 qmlRegisterSingletonType<UCUriHandler>(uri, 0, 1, "UriHandler", registerUriHandler);174 qmlRegisterSingletonType<UCUriHandler>(uri, 0, 1, "UriHandler", registerUriHandler);
178 qmlRegisterType<UCMouse>(uri, 0, 1, "Mouse");175 qmlRegisterType<UCMouse>(uri, 0, 1, "Mouse");
179 qmlRegisterType<UCInverseMouse>(uri, 0, 1, "InverseMouse");176 qmlRegisterType<UCInverseMouse>(uri, 0, 1, "InverseMouse");
180 // Needed for unit tests
181 qRegisterMetaType<QList <QQmlError> >();
182 // register QML singletons177 // register QML singletons
183 qmlRegisterSingletonType<QObject>(uri, 0, 1, "PickerPanel", registerPickerPanel);178 qmlRegisterSingletonType<QObject>(uri, 0, 1, "PickerPanel", registerPickerPanel);
179
180 // register custom event
181 ForwardedEvent::registerForwardedEvent();
184}182}
185183
186void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)184void UbuntuComponentsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
187185
=== modified file 'modules/Ubuntu/Components/plugin/ucalarm.cpp'
--- modules/Ubuntu/Components/plugin/ucalarm.cpp 2013-09-18 10:25:18 +0000
+++ modules/Ubuntu/Components/plugin/ucalarm.cpp 2014-04-17 01:29:24 +0000
@@ -102,15 +102,21 @@
102102
103int UCAlarmPrivate::nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay)103int UCAlarmPrivate::nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay)
104{104{
105 if (fromDay <= 0) {105 if (fromDay <= 0 || fromDay >= Qt::Sunday) {
106 // start from the beginning of the week
106 fromDay = Qt::Monday;107 fromDay = Qt::Monday;
108 } else {
109 // start checking from the next day onwards
110 fromDay++;
107 }111 }
108 for (int d = fromDay; d <= Qt::Sunday; d++) {112 for (int d = fromDay; d <= Qt::Sunday; d++) {
109 if ((1 << (d - 1)) & days) {113 if ((1 << (d - 1)) & days) {
110 return d;114 return d;
111 }115 }
112 }116 }
113 return 0;117
118 // none found for the rest of the week, return the fist day set
119 return firstDayOfWeek(days);
114}120}
115121
116// checks whether the given num has more than one bit set122// checks whether the given num has more than one bit set
@@ -147,23 +153,14 @@
147 return UCAlarm::NoError;153 return UCAlarm::NoError;
148}154}
149155
150UCAlarm::Error UCAlarmPrivate::checkDow()156// adjust dayOfWeek
157UCAlarm::Error UCAlarmPrivate::adjustDow()
151{158{
152 if (!rawData.days) {159 if (!rawData.days) {
153 return UCAlarm::NoDaysOfWeek;160 return UCAlarm::NoDaysOfWeek;
154 } else if (rawData.days == UCAlarm::AutoDetect) {161 } else if (rawData.days == UCAlarm::AutoDetect) {
155 rawData.days = dayOfWeek(rawData.date);162 rawData.days = dayOfWeek(rawData.date);
156 rawData.changes |= AlarmData::Days;163 rawData.changes |= AlarmData::Days;
157 } else if (rawData.days != UCAlarm::Daily) {
158 int alarmDay = firstDayOfWeek(rawData.days);
159 int dayOfWeek = rawData.date.date().dayOfWeek();
160 if (alarmDay < dayOfWeek) {
161 rawData.date = rawData.date.addDays(7 - dayOfWeek + alarmDay);
162 rawData.changes |= AlarmData::Date;
163 } else if (alarmDay > dayOfWeek) {
164 rawData.date = rawData.date.addDays(alarmDay - dayOfWeek);
165 rawData.changes |= AlarmData::Date;
166 }
167 }164 }
168 return UCAlarm::NoError;165 return UCAlarm::NoError;
169}166}
@@ -175,8 +172,7 @@
175 return UCAlarm::OneTimeOnMoreDays;172 return UCAlarm::OneTimeOnMoreDays;
176 }173 }
177174
178 // adjust start date and/or dayOfWeek according to their values175 UCAlarm::Error result = adjustDow();
179 UCAlarm::Error result = checkDow();
180 if (result != UCAlarm::NoError) {176 if (result != UCAlarm::NoError) {
181 return result;177 return result;
182 }178 }
@@ -193,23 +189,25 @@
193 // start date is adjusted depending on the days value;189 // start date is adjusted depending on the days value;
194 // start date can be set to be the current time, as scheduling will move190 // start date can be set to be the current time, as scheduling will move
195 // it to the first occurence.191 // it to the first occurence.
196 UCAlarm::Error result = checkDow();192 UCAlarm::Error result = adjustDow();
197 if (result != UCAlarm::NoError) {193 if (result != UCAlarm::NoError) {
198 return result;194 return result;
199 }195 }
200196
201 // move start time to the first occurence if needed197 // move start time of the first occurence if needed
202 int dayOfWeek = rawData.date.date().dayOfWeek();198 int dayOfWeek = rawData.date.date().dayOfWeek();
203 if (!isDaySet(dayOfWeek, rawData.days) || (rawData.date <= QDateTime::currentDateTime())) {199 if (!isDaySet(dayOfWeek, rawData.days) || (rawData.date <= QDateTime::currentDateTime())) {
204 // check the next occurence of the alarm200 // check the next occurence of the alarm
205 int nextOccurence = nextDayOfWeek(rawData.days, dayOfWeek);201 int nextOccurrence = nextDayOfWeek(rawData.days, dayOfWeek);
206 if (nextOccurence <= 0) {202 if (nextOccurrence == dayOfWeek) {
207 // the starting date should be moved to the next week203 // move the date to the same day next week
208 nextOccurence = firstDayOfWeek(rawData.days);204 rawData.date = rawData.date.addDays(7);
209 rawData.date.addDays(7 - dayOfWeek + nextOccurence);205 } else if (nextOccurrence < dayOfWeek) {
206 // the starting date should be moved to the next week's occurrence
207 rawData.date = rawData.date.addDays(7 - dayOfWeek + nextOccurrence);
210 } else {208 } else {
211 // the starting date is still this week209 // the starting date is still this week
212 rawData.date.addDays(nextOccurence - dayOfWeek);210 rawData.date = rawData.date.addDays(nextOccurrence - dayOfWeek);
213 }211 }
214 rawData.changes |= AlarmData::Date;212 rawData.changes |= AlarmData::Date;
215 }213 }
@@ -304,7 +302,7 @@
304 : QObject(parent)302 : QObject(parent)
305 , d_ptr(new UCAlarmPrivate(this))303 , d_ptr(new UCAlarmPrivate(this))
306{304{
307 d_ptr->rawData.date = dt;305 d_ptr->rawData.date = AlarmData::normalizeDate(dt);
308 if (!message.isEmpty()) {306 if (!message.isEmpty()) {
309 d_ptr->rawData.message = message;307 d_ptr->rawData.message = message;
310 }308 }
@@ -315,7 +313,7 @@
315 : QObject(parent)313 : QObject(parent)
316 , d_ptr(new UCAlarmPrivate(this))314 , d_ptr(new UCAlarmPrivate(this))
317{315{
318 d_ptr->rawData.date = dt;316 d_ptr->rawData.date = AlarmData::normalizeDate(dt);
319 d_ptr->rawData.type = Repeating;317 d_ptr->rawData.type = Repeating;
320 d_ptr->rawData.days = days;318 d_ptr->rawData.days = days;
321 if (!message.isEmpty()) {319 if (!message.isEmpty()) {
@@ -366,12 +364,16 @@
366void UCAlarm::setDate(const QDateTime &date)364void UCAlarm::setDate(const QDateTime &date)
367{365{
368 Q_D(UCAlarm);366 Q_D(UCAlarm);
369 if (d->rawData.date == date) {367 if (d->rawData.date == AlarmData::normalizeDate(date)) {
370 return;368 return;
371 }369 }
372 d->rawData.date = date;370 d->rawData.date = AlarmData::normalizeDate(date);
373 d->rawData.changes |= AlarmData::Date;371 d->rawData.changes |= AlarmData::Date;
374 Q_EMIT dateChanged();372 Q_EMIT dateChanged();
373 if (d->rawData.type == UCAlarm::OneTime) {
374 // adjust dayOfWeek as well
375 setDaysOfWeek(UCAlarm::AutoDetect);
376 }
375}377}
376378
377/*!379/*!
@@ -520,6 +522,8 @@
520 Q_EMIT soundChanged();522 Q_EMIT soundChanged();
521}523}
522524
525
526
523/*!527/*!
524 * \qmlproperty Error Alarm::error528 * \qmlproperty Error Alarm::error
525 * The property holds the error code occurred during the last performed operation.529 * The property holds the error code occurred during the last performed operation.
@@ -670,6 +674,8 @@
670 if (result != UCAlarm::NoError) {674 if (result != UCAlarm::NoError) {
671 d->_q_syncStatus(Saving, Fail, result);675 d->_q_syncStatus(Saving, Fail, result);
672 } else {676 } else {
677 // the alarm has been modified, therefore update the original date as well
678 d->rawData.originalDate = d->rawData.date;
673 if (d->createRequest()) {679 if (d->createRequest()) {
674 d->request->save(d->rawData);680 d->request->save(d->rawData);
675 }681 }
676682
=== modified file 'modules/Ubuntu/Components/plugin/ucalarm_p.h'
--- modules/Ubuntu/Components/plugin/ucalarm_p.h 2013-09-18 10:25:18 +0000
+++ modules/Ubuntu/Components/plugin/ucalarm_p.h 2014-04-17 01:29:24 +0000
@@ -50,7 +50,7 @@
50 static int nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay);50 static int nextDayOfWeek(UCAlarm::DaysOfWeek days, int fromDay);
51 static bool multipleDaysSet(UCAlarm::DaysOfWeek days);51 static bool multipleDaysSet(UCAlarm::DaysOfWeek days);
52 UCAlarm::Error checkAlarm();52 UCAlarm::Error checkAlarm();
53 UCAlarm::Error checkDow();53 UCAlarm::Error adjustDow();
54 UCAlarm::Error checkOneTime();54 UCAlarm::Error checkOneTime();
55 UCAlarm::Error checkRepeatingWeekly();55 UCAlarm::Error checkRepeatingWeekly();
5656
5757
=== modified file 'modules/Ubuntu/Components/plugin/ucmouse.h'
--- modules/Ubuntu/Components/plugin/ucmouse.h 2014-02-13 17:16:06 +0000
+++ modules/Ubuntu/Components/plugin/ucmouse.h 2014-04-17 01:29:24 +0000
@@ -20,10 +20,47 @@
2020
21#include <QtCore/QObject>21#include <QtCore/QObject>
22#include <QtQml>22#include <QtQml>
23#include <QtQuick/QQuickItem>
23#include <private/qquickevents_p_p.h>24#include <private/qquickevents_p_p.h>
24#include <QtCore/qbasictimer.h>25#include <QtCore/qbasictimer.h>
2526
26class QQuickItem;27class ForwardedEvent : public QEvent {
28public:
29 enum EventType {
30 MousePress,
31 MouseRelease,
32 MouseMove,
33 MouseDblClick,
34 HoverEnter,
35 HoverExit,
36 MouseClick,
37 MouseLongPress,
38 };
39 ForwardedEvent(EventType type, QQuickItem *sender, QEvent *originalEvent, QQuickMouseEvent *quickEvent)
40 : QEvent((QEvent::Type)m_eventBase)
41 , m_subType(type)
42 , m_sender(sender)
43 , m_originalEvent(originalEvent)
44 , m_quickEvent(quickEvent)
45 {
46 setAccepted(false);
47 }
48
49 static void registerForwardedEvent();
50
51 EventType subType() const { return m_subType; }
52 QQuickItem *sender() const { return m_sender; }
53 QQuickMouseEvent *quickEvent() const { return m_quickEvent; }
54 QEvent *originalEvent() const { return m_originalEvent; }
55 static QEvent::Type baseType() { return m_eventBase; }
56private:
57 EventType m_subType;
58 QPointer<QQuickItem> m_sender;
59 QEvent *m_originalEvent;
60 QPointer<QQuickMouseEvent> m_quickEvent;
61 static QEvent::Type m_eventBase;
62};
63
27class UCMouse : public QObject64class UCMouse : public QObject
28{65{
29 Q_OBJECT66 Q_OBJECT
@@ -62,23 +99,24 @@
62 void clickAndHoldThresholdChanged();99 void clickAndHoldThresholdChanged();
63 void priorityChanged();100 void priorityChanged();
64101
65 void pressed(QQuickMouseEvent *mouse);102 void pressed(QQuickMouseEvent *mouse, QQuickItem *host);
66 void released(QQuickMouseEvent *mouse);103 void released(QQuickMouseEvent *mouse, QQuickItem *host);
67 void clicked(QQuickMouseEvent *mouse);104 void clicked(QQuickMouseEvent *mouse, QQuickItem *host);
68 void pressAndHold(QQuickMouseEvent *mouse);105 void pressAndHold(QQuickMouseEvent *mouse, QQuickItem *host);
69 void doubleClicked(QQuickMouseEvent *mouse);106 void doubleClicked(QQuickMouseEvent *mouse, QQuickItem *host);
70 void positionChanged(QQuickMouseEvent *mouse);107 void positionChanged(QQuickMouseEvent *mouse, QQuickItem *host);
71 void entered(QQuickMouseEvent *event);108 void entered(QQuickMouseEvent *event, QQuickItem *host);
72 void exited(QQuickMouseEvent *event);109 void exited(QQuickMouseEvent *event, QQuickItem *host);
73110
74protected:111protected:
75 virtual bool eventFilter(QObject *, QEvent *);112 virtual bool eventFilter(QObject *, QEvent *);
76 virtual void timerEvent(QTimerEvent *event);113 virtual void timerEvent(QTimerEvent *event);
77 virtual bool mouseEvents(QObject *target, QMouseEvent *event);114 virtual bool mouseEvents(QObject *target, QMouseEvent *event);
78 virtual bool hoverEvents(QObject *target, QHoverEvent *event);115 virtual bool hoverEvents(QObject *target, QHoverEvent *event);
116 virtual bool forwardedEvents(ForwardedEvent *event);
79 virtual bool hasAttachedFilter(QQuickItem *item);117 virtual bool hasAttachedFilter(QQuickItem *item);
80118
81 void setHovered(bool hovered);119 void setHovered(bool hovered, QEvent *hoverEvent);
82 bool mousePressed(QMouseEvent *event);120 bool mousePressed(QMouseEvent *event);
83 bool mouseReleased(QMouseEvent *event);121 bool mouseReleased(QMouseEvent *event);
84 bool mouseDblClick(QMouseEvent *event);122 bool mouseDblClick(QMouseEvent *event);
@@ -91,7 +129,7 @@
91 bool isDoubleClickConnected();129 bool isDoubleClickConnected();
92 bool isMouseEvent(QEvent::Type type);130 bool isMouseEvent(QEvent::Type type);
93 bool isHoverEvent(QEvent::Type type);131 bool isHoverEvent(QEvent::Type type);
94 void forwardEvent(QEvent *event);132 bool forwardEvent(ForwardedEvent::EventType type, QEvent *event, QQuickMouseEvent *quickEvent);
95133
96protected:134protected:
97 QQuickItem *m_owner;135 QQuickItem *m_owner;
@@ -107,6 +145,7 @@
107 Priority m_priority;145 Priority m_priority;
108 int m_moveThreshold;146 int m_moveThreshold;
109147
148 bool m_signalWhenContains:1;
110 bool m_enabled: 1;149 bool m_enabled: 1;
111 bool m_moved:1;150 bool m_moved:1;
112 bool m_longPress:1;151 bool m_longPress:1;
@@ -115,4 +154,6 @@
115};154};
116QML_DECLARE_TYPEINFO(UCMouse, QML_HAS_ATTACHED_PROPERTIES)155QML_DECLARE_TYPEINFO(UCMouse, QML_HAS_ATTACHED_PROPERTIES)
117156
157extern const int DefaultPressAndHoldDelay;
158
118#endif // UCMOUSE_H159#endif // UCMOUSE_H
119160
=== modified file 'modules/Ubuntu/Components/plugin/ucmousefilters.cpp'
--- modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-02-13 17:16:06 +0000
+++ modules/Ubuntu/Components/plugin/ucmousefilters.cpp 2014-04-17 01:29:24 +0000
@@ -18,7 +18,6 @@
18#include "ucmouse.h"18#include "ucmouse.h"
19#include "ucinversemouse.h"19#include "ucinversemouse.h"
20#include <QtQml/QQmlInfo>20#include <QtQml/QQmlInfo>
21#include <QtQuick/QQuickItem>
22#include <QtGui/QGuiApplication>21#include <QtGui/QGuiApplication>
23#include <private/qqmlglobal_p.h>22#include <private/qqmlglobal_p.h>
24#include <QtQuick/private/qquickmousearea_p.h>23#include <QtQuick/private/qquickmousearea_p.h>
@@ -31,6 +30,15 @@
31// keep in sync with QQuickMouseArea PressAndHoldDelay30// keep in sync with QQuickMouseArea PressAndHoldDelay
32const int DefaultPressAndHoldDelay = 800;31const int DefaultPressAndHoldDelay = 800;
3332
33QEvent::Type ForwardedEvent::m_eventBase = QEvent::None;
34void ForwardedEvent::registerForwardedEvent()
35{
36 if (m_eventBase > 0) {
37 return;
38 }
39 m_eventBase = (QEvent::Type)QEvent::registerEventType();
40}
41
34/*42/*
35 * Attached filter instantiator template43 * Attached filter instantiator template
36 */44 */
@@ -158,7 +166,8 @@
158 their parent. In this way mouse events will land in these items too, and mouse166 their parent. In this way mouse events will land in these items too, and mouse
159 filter attached to those can also handle the event. This is useful when creating167 filter attached to those can also handle the event. This is useful when creating
160 custom types where the mouse handling item is nested into a non-mouse handling168 custom types where the mouse handling item is nested into a non-mouse handling
161 one, and we want to provide additional filtering possibility to the user.169 one, and we want to provide additional filtering possibility to the user. These
170 type of items are called proxy handlers.
162171
163 \qml172 \qml
164 Item {173 Item {
@@ -180,7 +189,54 @@
180 In this example the mouse press is first handled by the mouse filter attached189 In this example the mouse press is first handled by the mouse filter attached
181 to TextInput, then it is forwarded to the top item and finally to the TextInput.190 to TextInput, then it is forwarded to the top item and finally to the TextInput.
182 Accepting the mouse event will stop propagation to the top item as well as to191 Accepting the mouse event will stop propagation to the top item as well as to
183 the TextInput.192 the TextInput. The topmost item itself does not handle mouse events, therefore
193 it will be a sinple proxy handler item. However, proxies can themself handle
194 mouse events. Therefore each mouse event signal has the \a host parameter specifying
195 the sender of the mouse event reported.
196
197 \note The forwarded events are handled in the proxy handlers only if the mouse
198 position points inside their area. If the forwarded mouse position falls outside
199 the target area, the event will not be reported, however will be forwarded further
200 to the items in the list. In the following example the mouse press in red rectangle
201 will be printed as well as the proxied mouse press from the main item.
202 \qml
203 import QtQuick 2.0
204 import Ubuntu.Components 0.1
205
206 Item {
207 id: main
208 width: units.gu(40)
209 height: units.gu(71)
210
211 Mouse.onPressed: console.log("got the mouse press forwarded by " + host.objectName)
212
213 Column {
214 anchors.fill: parent
215 spacing: units.gu(1)
216
217 Rectangle {
218 id: blueRect
219 objectName: "BlueRect"
220 width: parent.width
221 height: units.gu(20)
222 color: "blue"
223 Mouse.forwardTo: [main]
224 Mouse.onPressed: console.log("This should not be printed")
225 }
226 Rectangle {
227 objectName: "RedRect"
228 width: parent.width
229 height: units.gu(20)
230 color: "red"
231 MouseArea {
232 anchors.fill: parent
233 Mouse.forwardTo: [blueRect]
234 Mouse.onPressed: console.log("Pressed in " + host.objectName)
235 }
236 }
237 }
238 }
239 \endqml
184240
185 An interesting feature that can be achieved using Mouse filter is the event241 An interesting feature that can be achieved using Mouse filter is the event
186 "transparency" towards the MouseArea lying behind the items which handle mouse242 "transparency" towards the MouseArea lying behind the items which handle mouse
@@ -226,6 +282,7 @@
226 }282 }
227 \endqml283 \endqml
228284
285
229 Mouse filter provides ability to control the order of the event dispatching.286 Mouse filter provides ability to control the order of the event dispatching.
230 The filter can receive the events prior the owner or after the owner. This287 The filter can receive the events prior the owner or after the owner. This
231 can be controlled through the \l priority property. In the following example288 can be controlled through the \l priority property. In the following example
@@ -281,6 +338,9 @@
281UCMouse::UCMouse(QObject *parent)338UCMouse::UCMouse(QObject *parent)
282 : QObject(parent)339 : QObject(parent)
283 , m_owner(qobject_cast<QQuickItem*>(parent))340 , m_owner(qobject_cast<QQuickItem*>(parent))
341 , m_lastButton(Qt::NoButton)
342 , m_lastButtons(Qt::NoButton)
343 , m_lastModifiers(Qt::NoModifier)
284 , m_pressedButtons(Qt::NoButton)344 , m_pressedButtons(Qt::NoButton)
285 , m_priority(BeforeItem)345 , m_priority(BeforeItem)
286 , m_moveThreshold(0.0)346 , m_moveThreshold(0.0)
@@ -328,6 +388,9 @@
328 } else {388 } else {
329 return hoverEvents(target, static_cast<QHoverEvent*>(event));389 return hoverEvents(target, static_cast<QHoverEvent*>(event));
330 }390 }
391 } else if (type == ForwardedEvent::baseType()) {
392 // this is a forwarded event, handle it as such
393 return forwardedEvents(static_cast<ForwardedEvent*>(event));
331 }394 }
332395
333 return QObject::eventFilter(target, event);396 return QObject::eventFilter(target, event);
@@ -341,7 +404,8 @@
341 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,404 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,
342 false, m_longPress);405 false, m_longPress);
343 mev.setAccepted(false);406 mev.setAccepted(false);
344 Q_EMIT pressAndHold(&mev);407 Q_EMIT pressAndHold(&mev, m_owner);
408 mev.setAccepted(forwardEvent(ForwardedEvent::MouseLongPress, 0, &mev));
345 // if the event wasn't handled, allow click409 // if the event wasn't handled, allow click
346 if (!mev.isAccepted()) {410 if (!mev.isAccepted()) {
347 m_longPress = false;411 m_longPress = false;
@@ -376,7 +440,6 @@
376 default:440 default:
377 break;441 break;
378 }442 }
379 forwardEvent(event);
380 return result || event->isAccepted();443 return result || event->isAccepted();
381}444}
382445
@@ -400,27 +463,67 @@
400 default:463 default:
401 break;464 break;
402 }465 }
403 forwardEvent(event);
404 return result || event->isAccepted();466 return result || event->isAccepted();
405}467}
406468
469// emit signals and forward the events further
470bool UCMouse::forwardedEvents(ForwardedEvent *event)
471{
472 // the quick event is always specified!
473 switch (event->subType()) {
474 case ForwardedEvent::MousePress: {
475 Q_EMIT pressed(event->quickEvent(), event->sender());
476 } break;
477 case ForwardedEvent::MouseRelease: {
478 Q_EMIT released(event->quickEvent(), event->sender());
479 } break;
480 case ForwardedEvent::MouseMove: {
481 Q_EMIT positionChanged(event->quickEvent(), event->sender());
482 } break;
483 case ForwardedEvent::MouseDblClick: {
484 Q_EMIT doubleClicked(event->quickEvent(), event->sender());
485 } break;
486 case ForwardedEvent::HoverEnter: {
487 Q_EMIT entered(event->quickEvent(), event->sender());
488 } break;
489 case ForwardedEvent::HoverExit: {
490 Q_EMIT exited(event->quickEvent(), event->sender());
491 } break;
492 // composed events
493 case ForwardedEvent::MouseClick: {
494 Q_EMIT clicked(event->quickEvent(), event->sender());
495 } break;
496 case ForwardedEvent::MouseLongPress: {
497 Q_EMIT pressAndHold(event->quickEvent(), event->sender());
498 } break;
499 default: break;
500 }
501
502 // forward event, but use the current owner as sender
503 event->setAccepted(forwardEvent(event->subType(), event->originalEvent(), event->quickEvent()));
504 return event->isAccepted();
505}
506
407bool UCMouse::hasAttachedFilter(QQuickItem *item)507bool UCMouse::hasAttachedFilter(QQuickItem *item)
408{508{
409 return (qmlAttachedPropertiesObject<UCMouse>(item, false) != 0);509 return (qmlAttachedPropertiesObject<UCMouse>(item, false) != 0);
410}510}
411511
412void UCMouse::setHovered(bool hovered)512void UCMouse::setHovered(bool hovered, QEvent *hoverEvent)
413{513{
414 if (m_hovered != hovered) {514 if (m_hovered != hovered) {
415 m_hovered = hovered;515 m_hovered = hovered;
416 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,516 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,
417 false, false);517 false, false);
418 mev.setAccepted(false);518 mev.setAccepted(false);
519 ForwardedEvent::EventType type = (m_hovered) ? ForwardedEvent::HoverEnter : ForwardedEvent::HoverExit;
419 if (m_hovered) {520 if (m_hovered) {
420 Q_EMIT entered(&mev);521 Q_EMIT entered(&mev, m_owner);
421 } else {522 } else {
422 Q_EMIT exited(&mev);523 m_pressAndHoldTimer.stop();
524 Q_EMIT exited(&mev, m_owner);
423 }525 }
526 forwardEvent(type, hoverEvent, &mev);
424 }527 }
425}528}
426529
@@ -432,13 +535,13 @@
432 m_pressedButtons |= m_lastButton;535 m_pressedButtons |= m_lastButton;
433 m_longPress = m_doubleClicked = false;536 m_longPress = m_doubleClicked = false;
434537
435 setHovered(true);538 setHovered(true, 0);
436539
437 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,540 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,
438 false, m_longPress);541 false, m_longPress);
439 mev.setAccepted(false);542 mev.setAccepted(false);
440 Q_EMIT pressed(&mev);543 Q_EMIT pressed(&mev, m_owner);
441 event->setAccepted(mev.isAccepted());544 event->setAccepted(forwardEvent(ForwardedEvent::MousePress, event, &mev));
442545
443 // start long press timer546 // start long press timer
444 m_pressAndHoldTimer.start(DefaultPressAndHoldDelay, this);547 m_pressAndHoldTimer.start(DefaultPressAndHoldDelay, this);
@@ -460,14 +563,14 @@
460 m_pressAndHoldTimer.stop();563 m_pressAndHoldTimer.stop();
461 }564 }
462565
463 setHovered(true);566 setHovered(true, 0);
464 m_moved = true;567 m_moved = true;
465 m_doubleClicked = false;568 m_doubleClicked = false;
466 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,569 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,
467 false, m_longPress);570 false, m_longPress);
468 mev.setAccepted(false);571 mev.setAccepted(false);
469 Q_EMIT positionChanged(&mev);572 Q_EMIT positionChanged(&mev, m_owner);
470 event->setAccepted(mev.isAccepted());573 event->setAccepted(forwardEvent(ForwardedEvent::MouseMove, event, &mev));
471 return mev.isAccepted();574 return mev.isAccepted();
472 }575 }
473576
@@ -487,18 +590,20 @@
487 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,590 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,
488 isClicked, m_longPress);591 isClicked, m_longPress);
489 mev.setAccepted(false);592 mev.setAccepted(false);
490 Q_EMIT released(&mev);593 Q_EMIT released(&mev, m_owner);
491 event->setAccepted(mev.isAccepted());594 event->setAccepted(forwardEvent(ForwardedEvent::MouseRelease, event, &mev));
492595
493 // remove button from press596 // remove button from press
494 m_pressedButtons &= ~m_lastButton;597 m_pressedButtons &= ~m_lastButton;
495 if (isClicked) {598 if (isClicked) {
496 // emit clicked599 // emit clicked
497 Q_EMIT clicked(&mev);600 mev.setAccepted(false);
601 Q_EMIT clicked(&mev, m_owner);
602 forwardEvent(ForwardedEvent::MouseClick, 0, &mev);
498 }603 }
499604
500 if (!m_pressedButtons && !m_owner->acceptHoverEvents()) {605 if (!m_pressedButtons && !m_owner->acceptHoverEvents()) {
501 setHovered(false);606 setHovered(false, 0);
502 }607 }
503 return mev.isAccepted();608 return mev.isAccepted();
504 }609 }
@@ -511,18 +616,17 @@
511{616{
512 if (m_pressedButtons) {617 if (m_pressedButtons) {
513 saveEvent(event);618 saveEvent(event);
619
620 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,
621 true, m_longPress);
622 mev.setAccepted(false);
514 // if double click connected, suppress release() and click() signals623 // if double click connected, suppress release() and click() signals
515 if (isDoubleClickConnected()) {624 if (isDoubleClickConnected()) {
516 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,625 Q_EMIT doubleClicked(&mev, m_owner);
517 true, m_longPress);
518 mev.setAccepted(false);
519 Q_EMIT doubleClicked(&mev);
520 event->setAccepted(mev.isAccepted());
521 m_doubleClicked = true;626 m_doubleClicked = true;
522 } else {
523 // set accepted to false still
524 event->setAccepted(false);
525 }627 }
628 // forward event even if it wasn't handled for the owner
629 event->setAccepted(forwardEvent(ForwardedEvent::MouseDblClick, event, &mev));
526 return event->isAccepted();630 return event->isAccepted();
527 }631 }
528632
@@ -534,7 +638,9 @@
534{638{
535 m_lastPos = event->posF();639 m_lastPos = event->posF();
536 m_lastModifiers = event->modifiers();640 m_lastModifiers = event->modifiers();
537 setHovered(true);641 m_lastButton = Qt::NoButton;
642 m_lastButtons = Qt::NoButton;
643 setHovered(true, event);
538 return false;644 return false;
539}645}
540646
@@ -542,11 +648,11 @@
542{648{
543 m_lastPos = event->posF();649 m_lastPos = event->posF();
544 m_lastModifiers = event->modifiers();650 m_lastModifiers = event->modifiers();
545 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), m_lastButton, m_lastButtons, m_lastModifiers,651 QQuickMouseEvent mev(m_lastPos.x(), m_lastPos.y(), Qt::NoButton, Qt::NoButton, m_lastModifiers,
546 false, m_longPress);652 false, m_longPress);
547 mev.setAccepted(false);653 mev.setAccepted(false);
548 Q_EMIT positionChanged(&mev);654 Q_EMIT positionChanged(&mev, m_owner);
549 forwardEvent(event);655 event->setAccepted(forwardEvent(ForwardedEvent::MouseMove, event, &mev));
550 return false;656 return false;
551}657}
552658
@@ -554,7 +660,9 @@
554{660{
555 m_lastPos = event->posF();661 m_lastPos = event->posF();
556 m_lastModifiers = event->modifiers();662 m_lastModifiers = event->modifiers();
557 setHovered(false);663 m_lastButton = Qt::NoButton;
664 m_lastButtons = Qt::NoButton;
665 setHovered(false, event);
558 return false;666 return false;
559}667}
560668
@@ -562,8 +670,10 @@
562{670{
563 m_lastPos = event->localPos();671 m_lastPos = event->localPos();
564 m_lastScenePos = event->windowPos();672 m_lastScenePos = event->windowPos();
565 m_lastButton = event->button();673 if (event->type() != QEvent::MouseMove) {
566 m_lastButtons = event->buttons();674 m_lastButton = event->button();
675 m_lastButtons = event->buttons();
676 }
567 m_lastModifiers = event->modifiers();677 m_lastModifiers = event->modifiers();
568 if ((event->type() == QEvent::MouseButtonPress) && (m_moveThreshold > 0.0)) {678 if ((event->type() == QEvent::MouseButtonPress) && (m_moveThreshold > 0.0)) {
569 m_toleranceArea.setX(m_lastPos.x() - m_moveThreshold);679 m_toleranceArea.setX(m_lastPos.x() - m_moveThreshold);
@@ -574,7 +684,7 @@
574}684}
575bool UCMouse::isDoubleClickConnected()685bool UCMouse::isDoubleClickConnected()
576{686{
577 IS_SIGNAL_CONNECTED(this, UCMouse, doubleClicked, (QQuickMouseEvent*));687 IS_SIGNAL_CONNECTED(this, UCMouse, doubleClicked, (QQuickMouseEvent*,QQuickItem*));
578}688}
579689
580bool UCMouse::isMouseEvent(QEvent::Type type)690bool UCMouse::isMouseEvent(QEvent::Type type)
@@ -588,57 +698,73 @@
588 return (type == QEvent::HoverEnter) || (type == QEvent::HoverMove) || (type == QEvent::HoverLeave);698 return (type == QEvent::HoverEnter) || (type == QEvent::HoverMove) || (type == QEvent::HoverLeave);
589}699}
590700
591// forwards the events to the listed items; only mouse and hover events qualify701
592void UCMouse::forwardEvent(QEvent *event)702/*
703 * Forwards the events to the listed items. The event coordinates are mapped to the destination's coordinates
704 * and sent to the destination in case the destination has no filter attached. Otherwise the quick event
705 * coordinates will be mapped and sent as ForwardedEvents.
706 */
707bool UCMouse::forwardEvent(ForwardedEvent::EventType type, QEvent *event, QQuickMouseEvent *quickEvent)
593{708{
594 /*709 // first set the accepted flag to the original event
595 * alter acceptedButtons and hoverEnabled for the time the event is delivered710 if (event && quickEvent) {
596 * exclude MouseArea and InverseMouseArea!!711 event->setAccepted(quickEvent->isAccepted());
597 */712 }
713 bool accepted = event ? event->isAccepted() : (quickEvent ? quickEvent->isAccepted() : false);
714
598 Q_FOREACH(QQuickItem *item, m_forwardList) {715 Q_FOREACH(QQuickItem *item, m_forwardList) {
599716
600 if (event->isAccepted()) {717 if (accepted) {
601 // the event got accepted, therefore should not be forwarded718 // the event got accepted, therefore should not be forwarded
602 return;719 return accepted;
603 }720 }
604 // skip InverseMouseArea otherwise those will get the event twice721 // skip InverseMouseArea otherwise those will get the event twice
605 if (qobject_cast<InverseMouseAreaType*>(item)) {722 if (qobject_cast<InverseMouseAreaType*>(item)) {
606 continue;723 continue;
607 }724 }
608 /*725
609 * temporarily enable mouse buttons and hover for items which have Mouse or InverseMouse726 // map the normal event coordinates to item
610 * filter attached, but exclude targets which is MouseArea or InverseMouseArea727 QEvent *mappedEvent = 0;
611 */728 if (event) {
612 Qt::MouseButtons acceptedButtons = item->acceptedMouseButtons();729 if (isMouseEvent(event->type())) {
613 bool hoverEnabled = item->acceptHoverEvents();730 QMouseEvent *mouse = static_cast<QMouseEvent*>(event);
614 if (!qobject_cast<QQuickMouseArea*>(item) && hasAttachedFilter(item)) {731 QPointF itemPos = item->mapFromScene(m_owner->mapToScene(mouse->pos()));
615 // set accepted buttons and hover temporarily732 mappedEvent = new QMouseEvent(event->type(), itemPos, mouse->button(), mouse->buttons(), mouse->modifiers());
616 item->setAcceptedMouseButtons(m_owner->acceptedMouseButtons());733 } else if (isHoverEvent(event->type())){
617 item->setAcceptHoverEvents(m_owner->acceptHoverEvents());734 QHoverEvent *hover = static_cast<QHoverEvent*>(event);
618 }735 QPointF itemPos = item->mapFromScene(m_owner->mapToScene(hover->pos()));
619736 QPointF itemOldPos = item->mapFromScene(m_owner->mapToScene(hover->oldPos()));
620 // forward events737 mappedEvent = new QHoverEvent(event->type(), itemPos, itemOldPos, hover->modifiers());
621 bool accepted = false;738 }
622 if (isMouseEvent(event->type())) {739 }
623 QMouseEvent *mouse = static_cast<QMouseEvent*>(event);740
624 QPointF itemPos = item->mapFromScene(m_owner->mapToScene(mouse->pos()));741 // if the item has no filter attached, deliver the mapped event to it as it is
625 QMouseEvent me = QMouseEvent(event->type(), itemPos, mouse->button(), mouse->buttons(), mouse->modifiers());742 UCMouse *filter = qobject_cast<UCMouse*>(qmlAttachedPropertiesObject<UCMouse>(item, false));
626 QGuiApplication::sendEvent(item, &me);743 if (!filter && mappedEvent) {
627 accepted = me.isAccepted();744 QGuiApplication::sendEvent(item, mappedEvent);
628 } else {745 accepted = mappedEvent->isAccepted();
629 QHoverEvent *hover = static_cast<QHoverEvent*>(event);746 } else if (quickEvent) {
630 QPointF itemPos = item->mapFromScene(m_owner->mapToScene(hover->pos()));747 // map the quick event coordinates as well
631 QPointF itemOldPos = item->mapFromScene(m_owner->mapToScene(hover->oldPos()));748 QPoint itemPos(item->mapFromScene(m_owner->mapToScene(QPointF(quickEvent->x(), quickEvent->y()))).toPoint());
632 QHoverEvent he = QHoverEvent(event->type(), itemPos, itemOldPos, hover->modifiers());749 QQuickMouseEvent mev(itemPos.x(), itemPos.y(), (Qt::MouseButton)quickEvent->button(), (Qt::MouseButtons)quickEvent->buttons(),
633 QGuiApplication::sendEvent(item, &he);750 (Qt::KeyboardModifiers)quickEvent->modifiers(), quickEvent->isClick(), quickEvent->wasHeld());
634 accepted = he.isAccepted();751 mev.setAccepted(false);
635 }752 ForwardedEvent forwardedEvent(type, m_owner, mappedEvent, &mev);
636 event->setAccepted(accepted);753 QGuiApplication::sendEvent(item, &forwardedEvent);
637754 accepted = mev.isAccepted();
638 // restore acceptedButtons and hover755 }
639 item->setAcceptedMouseButtons(acceptedButtons);756
640 item->setAcceptHoverEvents(hoverEnabled);757 // cleanup and transfer accepted flag
758 delete mappedEvent;
759 if (event) {
760 event->setAccepted(accepted);
761 }
762 if (quickEvent) {
763 quickEvent->setAccepted(accepted);
764 }
641 }765 }
766
767 return accepted;
642}768}
643769
644770
@@ -697,6 +823,9 @@
697 composed events will come until the mouse is moved inside the owner's area.823 composed events will come until the mouse is moved inside the owner's area.
698824
699 The default value is 0.825 The default value is 0.
826
827 \note The value has no effect for the forwarded events. The threshold is only
828 valid when the host handles mouse events.
700 */829 */
701int UCMouse::clickAndHoldThreshold() const830int UCMouse::clickAndHoldThreshold() const
702{831{
@@ -767,29 +896,34 @@
767}896}
768897
769/*!898/*!
770 \qmlsignal Mouse::onPressed(MouseEvent event)899 \qmlsignal Mouse::onPressed(MouseEvent event, Item host)
771 The signal reports the mouse press.900 The signal reports the mouse press. The \a host specifies the item that triggered
901 the event.
772 */902 */
773903
774/*!904/*!
775 \qmlsignal Mouse::onReleased(MouseEvent event)905 \qmlsignal Mouse::onReleased(MouseEvent event)
776 The signal reports the mouse release.906 The signal reports the mouse release. The \a host specifies the item that triggered
907 the event.
777 */908 */
778909
779/*!910/*!
780 \qmlsignal Mouse::onClicked(MouseEvent event)911 \qmlsignal Mouse::onClicked(MouseEvent event)
781 The signal reports the mouse click. The signal is not emitted if the onPressAndHold912 The signal reports the mouse click. The signal is not emitted if the onPressAndHold
782 got triggered or if onDoubleClicked is handled (a slot is connected to it).913 got triggered or if onDoubleClicked is handled (a slot is connected to it). The \a
914 host specifies the item that triggered the event.
783 */915 */
784916
785/*!917/*!
786 \qmlsignal Mouse::onPressAndHold(MouseEvent event)918 \qmlsignal Mouse::onPressAndHold(MouseEvent event)
787 The signal reports the mouse press and hold.919 The signal reports the mouse press and hold. The \a host specifies the item that triggered
920 the event.
788 */921 */
789922
790/*!923/*!
791 \qmlsignal Mouse::onDoubleClicked(MouseEvent event)924 \qmlsignal Mouse::onDoubleClicked(MouseEvent event)
792 The signal reports mouse double click.925 The signal reports mouse double click. The \a host specifies the item that triggered
926 the event.
793 */927 */
794928
795/*!929/*!
@@ -797,20 +931,22 @@
797 The signal reports the mouse pointer position change. If the hover events are931 The signal reports the mouse pointer position change. If the hover events are
798 enabled for the owner, the signal will come continuously. Otherwise the position932 enabled for the owner, the signal will come continuously. Otherwise the position
799 chanes are reported when one of the accepted mouse buttons are being kept pressed.933 chanes are reported when one of the accepted mouse buttons are being kept pressed.
934 The \a host specifies the item that triggered the event.
800 */935 */
801936
802/*!937/*!
803 \qmlsignal Mouse::onEntered(MouseEvent event)938 \qmlsignal Mouse::onEntered(MouseEvent event)
804 The signal reports that the mouse has entered into the area. The signal is939 The signal reports that the mouse has entered into the area. The signal is
805 emitted when the hover events are enabled and the mouse enters the area or940 emitted when the hover events are enabled and the mouse enters the area or
806 when one of the accepted mouse button is pressed.941 when one of the accepted mouse button is pressed. The \a host specifies the
942 item that triggered the event.
807 */943 */
808944
809/*!945/*!
810 \qmlsignal Mouse::onExited(MouseEvent event)946 \qmlsignal Mouse::onExited(MouseEvent event)
811 The signal reports that the mouse has left the area. The signal is emitted when947 The signal reports that the mouse has left the area. The signal is emitted when
812 the hover events are enabled for the owner or if not, when one of the accepted948 the hover events are enabled for the owner or if not, when one of the accepted
813 button is released.949 button is released. The \a host specifies the item that triggered the event.
814 */950 */
815951
816/******************************************************************************952/******************************************************************************
817953
=== modified file 'modules/Ubuntu/Test/UbuntuTestCase.qml'
--- modules/Ubuntu/Test/UbuntuTestCase.qml 2014-02-25 12:36:27 +0000
+++ modules/Ubuntu/Test/UbuntuTestCase.qml 2014-04-17 01:29:24 +0000
@@ -87,7 +87,75 @@
87 }87 }
88 }88 }
8989
90 /*!90 /*!
91 \qmlmethod UbuntuTestCase::flick(item, x, y, dx, dy, pressTimeout = -1, steps = -1, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1)
92
93 The function produces a flick event when executed on Flickables. When used
94 on other components it provides the same functionality as \l mouseDrag()
95 function. The optional \a pressTimeout parameter can be used to introduce
96 a small delay between the mouse press and the first mouse move. Setting a
97 negative or zero value will disable the timeout.
98
99 The default flick velocity is built up using 5 move points. This can be altered
100 by setting a positive value to \a steps parameter. The bigger the number the
101 longer the flick will be. When a negative or zero value is given, the default
102 of 5 move points will be used.
103
104 \note The function can be used to select a text in a TextField or TextArea by
105 specifying at least 400 millisecods to \a pressTimeout.
106 */
107 function flick(item, x, y, dx, dy, pressTimeout, steps, button, modifiers, delay) {
108 if (item === undefined || item.x === undefined || item.y === undefined)
109 return
110 if (button === undefined)
111 button = Qt.LeftButton
112 if (modifiers === undefined)
113 modifiers = Qt.NoModifier
114 if (steps === undefined || steps <= 0)
115 steps = 4;
116 // make sure we have at least two move steps so the flick will be sensed
117 steps += 1;
118 if (delay === undefined)
119 delay = -1;
120
121 var ddx = dx / steps;
122 var ddy = dy / steps;
123
124 mousePress(item, x, y, button, modifiers, delay);
125 if (pressTimeout !== undefined && pressTimeout > 0) {
126 wait(pressTimeout);
127 }
128 for (var i = 1; i <= steps; i++) {
129 // mouse moves are all processed immediately, without delay in between events
130 mouseMove(item, x + i * ddx, y + i * ddy, -1, button);
131 }
132 mouseRelease(item, x + dx, y + dy, button, modifiers, delay);
133 // empty event buffer
134 wait(200);
135 }
136
137 /*!
138 \qmlmethod UbuntuTestCase::mouseLongPress(item, x, y, button = Qt.LeftButton, modifiers = Qt.NoModifiers, delay = -1)
139
140 Simulates a long press on a mouse \a button with an optional \a modifier
141 on an \a item. The position is defined by \a x and \a y. If \a delay is
142 specified, the test will wait the specified amount of milliseconds before
143 the press.
144
145 The position given by \a x and \a y is transformed from the co-ordinate
146 system of \a item into window co-ordinates and then delivered.
147 If \a item is obscured by another item, or a child of \a item occupies
148 that position, then the event will be delivered to the other item instead.
149
150 \sa mouseRelease(), mouseClick(), mouseDoubleClick(), mouseMove(), mouseDrag(), mouseWheel()
151 */
152 function mouseLongPress(item, x, y, button, modifiers, delay) {
153 mousePress(item, x, y, button, modifiers, delay);
154 // the delay is taken from QQuickMouseArea
155 wait(800);
156 }
157
158 /*!
91 Keeps executing a given parameter-less function until it returns the given159 Keeps executing a given parameter-less function until it returns the given
92 expected result or the timemout is reached (in which case a test failure160 expected result or the timemout is reached (in which case a test failure
93 is generated)161 is generated)
94162
=== modified file 'modules/Ubuntu/Test/deployment.pri'
--- modules/Ubuntu/Test/deployment.pri 2014-01-17 12:30:05 +0000
+++ modules/Ubuntu/Test/deployment.pri 2014-04-17 01:29:24 +0000
@@ -7,9 +7,14 @@
7# make found deployables visible in Qt Creator7# make found deployables visible in Qt Creator
8OTHER_FILES += $$QMLDIR_FILE8OTHER_FILES += $$QMLDIR_FILE
99
10QML_FILES = $$system(ls *.qml)
11JS_FILES = $$system(ls *.js)
12
10# define deployment for found deployables13# define deployment for found deployables
11qmldir_file.path = $$installPath14qmldir_file.path = $$installPath
12qmldir_file.files = $$QMLDIR_FILE15qmldir_file.files = $$QMLDIR_FILE
16qml_files.path = $$installPath
17qml_files.files = $$QML_FILES
13js_files.path = $$installPath18js_files.path = $$installPath
14js_files.files = $$JS_FILES19js_files.files = $$JS_FILES
1520
@@ -20,4 +25,4 @@
20# https://bugreports.qt-project.org/browse/QTBUG-3624325# https://bugreports.qt-project.org/browse/QTBUG-36243
21plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes26plugins_qmltypes.extra = $$[QT_INSTALL_BINS]/qmlplugindump -notrelocatable Ubuntu.Test 0.1 ../../ 2>/dev/null > $(INSTALL_ROOT)/$$installPath/plugins.qmltypes
2227
23INSTALLS += qmldir_file plugins_qmltypes28INSTALLS += qmldir_file plugins_qmltypes qml_files js_files
2429
=== modified file 'modules/Ubuntu/Test/plugin/uctestcase.cpp'
--- modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-03-19 12:48:33 +0000
+++ modules/Ubuntu/Test/plugin/uctestcase.cpp 2014-04-17 01:29:24 +0000
@@ -26,6 +26,8 @@
26#include <QtTest/QtTest>26#include <QtTest/QtTest>
27#include <QtQuick/QQuickItem>27#include <QtQuick/QQuickItem>
2828
29Q_DECLARE_METATYPE(QList<QQmlError>)
30
29/*!31/*!
30 * \ingroup ubuntu32 * \ingroup ubuntu
31 * \brief UbuntuTestCase is the C++ pendant to the QML UbuntuTestCase.33 * \brief UbuntuTestCase is the C++ pendant to the QML UbuntuTestCase.
@@ -37,6 +39,7 @@
37 QString modulePath(QDir(modules).absolutePath());39 QString modulePath(QDir(modules).absolutePath());
38 engine()->addImportPath(modulePath);40 engine()->addImportPath(modulePath);
3941
42 qRegisterMetaType<QList <QQmlError> >();
40 m_spy = new QSignalSpy(engine(), SIGNAL(warnings(QList<QQmlError>)));43 m_spy = new QSignalSpy(engine(), SIGNAL(warnings(QList<QQmlError>)));
41 m_spy->setParent(this);44 m_spy->setParent(this);
4245
@@ -48,3 +51,12 @@
48 QTest::qWaitForWindowExposed(this);51 QTest::qWaitForWindowExposed(this);
49}52}
5053
54/*!
55 * The number of all warnings from the point of loading the first line of QML code.
56 */
57int
58UbuntuTestCase::warnings() const
59{
60 return m_spy->count();
61}
62
5163
=== modified file 'modules/Ubuntu/Test/plugin/uctestcase.h'
--- modules/Ubuntu/Test/plugin/uctestcase.h 2014-03-19 12:48:33 +0000
+++ modules/Ubuntu/Test/plugin/uctestcase.h 2014-04-17 01:29:24 +0000
@@ -29,6 +29,7 @@
2929
30public:30public:
31 UbuntuTestCase(const QString& file, QWindow* parent = 0);31 UbuntuTestCase(const QString& file, QWindow* parent = 0);
32 int warnings() const;
32 // getter33 // getter
33 template<class T>34 template<class T>
34 inline T findItem(const QString& objectName) const {35 inline T findItem(const QString& objectName) const {
@@ -39,7 +40,6 @@
39 qFatal("Item '%s' found with unexpected type", qPrintable(objectName));40 qFatal("Item '%s' found with unexpected type", qPrintable(objectName));
40 qFatal("No item '%s' found", qPrintable(objectName));41 qFatal("No item '%s' found", qPrintable(objectName));
41 }42 }
42
43private:43private:
44 QSignalSpy* m_spy;44 QSignalSpy* m_spy;
45};45};
4646
=== removed file 'tests/autopilot/ubuntuuitoolkit/__init__.py'
--- tests/autopilot/ubuntuuitoolkit/__init__.py 2013-07-10 16:07:33 +0000
+++ tests/autopilot/ubuntuuitoolkit/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,17 +0,0 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Ubuntu UI Toolkit autopilot tests and emulators - top level package."""
180
=== added directory 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects'
=== renamed file 'tests/autopilot/ubuntuuitoolkit/emulators.py' => 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py'
--- tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-08 14:46:25 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/__init__.py 2014-04-17 01:29:24 +0000
@@ -14,784 +14,69 @@
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.15# along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
17import logging17"""Ubuntu UI Toolkit Autopilot custom proxy objects."""
18from distutils import version18
1919
20import autopilot20__all__ = [
21from autopilot import (21 'ActionSelectionPopover',
22 input,22 'Base',
23 logging as autopilot_logging,23 'check_autopilot_version',
24 platform24 'CheckBox',
25)25 'ComposerSheet',
26from autopilot.introspection import dbus26 'Empty',
2727 'Flickable',
2828 'get_keyboard',
29_NO_TABS_ERROR = 'The MainView has no Tabs.'29 'get_pointing_device',
3030 'Header',
31logger = logging.getLogger(__name__)31 'ItemSelector',
3232 'MainView',
3333 'MultiValue',
34class ToolkitEmulatorException(Exception):34 'OptionSelector',
35 """Exception raised when there is an error with the emulator."""35 'QQuickListView',
3636 'SingleControl',
3737 'SingleValue',
38def get_pointing_device():38 'Standard',
39 """Return the pointing device depending on the platform.39 'Subtitled',
4040 'TabBar',
41 If the platform is `Desktop`, the pointing device will be a `Mouse`.41 'Tabs',
42 If not, the pointing device will be `Touch`.42 'TextField',
4343 'Toolbar',
44 """44 'ToolkitException',
45 if platform.model() == 'Desktop':45 'UbuntuUIToolkitCustomProxyObjectBase',
46 input_device_class = input.Mouse46]
47 else:47
48 input_device_class = input.Touch48from ubuntuuitoolkit._custom_proxy_objects._checkbox import CheckBox
49 return input.Pointer(device=input_device_class.create())49from ubuntuuitoolkit._custom_proxy_objects._common import (
5050 check_autopilot_version,
5151 get_keyboard,
52def get_keyboard():52 get_pointing_device,
53 """Return the keyboard device."""53 ToolkitException,
54 # TODO return the OSK if we are on the phone. --elopio - 2014-01-1354 UbuntuUIToolkitCustomProxyObjectBase,
55 return input.Keyboard.create()55)
5656from ubuntuuitoolkit._custom_proxy_objects._flickable import Flickable
5757from ubuntuuitoolkit._custom_proxy_objects._header import Header
58def check_autopilot_version():58from ubuntuuitoolkit._custom_proxy_objects._listitems import (
59 """Check that the Autopilot installed version matches the one required.59 Base,
6060 Empty,
61 :raise ToolkitEmulatorException: If the installed Autopilot version does't61 ItemSelector,
62 match the required by the emulators.62 MultiValue,
6363 SingleControl,
64 """64 SingleValue,
65 installed_version = version.LooseVersion(autopilot.version)65 Standard,
66 if installed_version < version.LooseVersion('1.4'):66 Subtitled,
67 raise ToolkitEmulatorException(67)
68 'The emulators need Autopilot 1.4 or higher.')68from ubuntuuitoolkit._custom_proxy_objects._mainview import MainView
6969from ubuntuuitoolkit._custom_proxy_objects._optionselector import (
7070 OptionSelector
71# Containers helpers.71)
7272from ubuntuuitoolkit._custom_proxy_objects._popups import (
73def _get_visible_container_top(containers):73 ActionSelectionPopover,
74 containers_top = [container.globalRect.y for container in containers]74 ComposerSheet,
75 return max(containers_top)75)
7676from ubuntuuitoolkit._custom_proxy_objects._qquicklistview import (
7777 QQuickListView
78def _get_visible_container_bottom(containers):78)
79 containers_bottom = [79from ubuntuuitoolkit._custom_proxy_objects._tabbar import TabBar
80 container.globalRect.y + container.globalRect.height80from ubuntuuitoolkit._custom_proxy_objects._tabs import Tabs
81 for container in containers if container.globalRect.height > 0]81from ubuntuuitoolkit._custom_proxy_objects._textfield import TextField
82 return min(containers_bottom)82from ubuntuuitoolkit._custom_proxy_objects._toolbar import Toolbar
83
84
85class UbuntuUIToolkitEmulatorBase(dbus.CustomEmulatorBase):
86 """A base class for all the Ubuntu UI Toolkit emulators."""
87
88 def __init__(self, *args):
89 check_autopilot_version()
90 super(UbuntuUIToolkitEmulatorBase, self).__init__(*args)
91 self.pointing_device = get_pointing_device()
92
93
94class MainView(UbuntuUIToolkitEmulatorBase):
95 """MainView Autopilot emulator."""
96
97 def get_header(self):
98 """Return the Header emulator of the MainView."""
99 try:
100 return self.select_single('Header', objectName='MainView_Header')
101 except dbus.StateNotFoundError:
102 raise ToolkitEmulatorException('The main view has no header.')
103
104 def get_toolbar(self):
105 """Return the Toolbar emulator of the MainView."""
106 return self.select_single(Toolbar)
107
108 @autopilot_logging.log_action(logger.info)
109 def open_toolbar(self):
110 """Open the toolbar if it's not already opened.
111
112 :return: The toolbar.
113
114 """
115 return self.get_toolbar().open()
116
117 @autopilot_logging.log_action(logger.info)
118 def close_toolbar(self):
119 """Close the toolbar if it's opened."""
120 self.get_toolbar().close()
121
122 def get_tabs(self):
123 """Return the Tabs emulator of the MainView.
124
125 :raise ToolkitEmulatorException: If the main view has no tabs.
126
127 """
128 try:
129 return self.select_single(Tabs)
130 except dbus.StateNotFoundError:
131 raise ToolkitEmulatorException(_NO_TABS_ERROR)
132
133 @autopilot_logging.log_action(logger.info)
134 def switch_to_next_tab(self):
135 """Open the next tab.
136
137 :return: The newly opened tab.
138
139 """
140 logger.debug('Switch to next tab.')
141 self.get_header().switch_to_next_tab()
142 current_tab = self.get_tabs().get_current_tab()
143 current_tab.visible.wait_for(True)
144 return current_tab
145
146 @autopilot_logging.log_action(logger.info)
147 def switch_to_tab_by_index(self, index):
148 """Open a tab.
149
150 :parameter index: The index of the tab to open.
151 :return: The newly opened tab.
152 :raise ToolkitEmulatorException: If the tab index is out of range.
153
154 """
155 logger.debug('Switch to tab with index {0}.'.format(index))
156 tabs = self.get_tabs()
157 number_of_tabs = tabs.get_number_of_tabs()
158 if index >= number_of_tabs:
159 raise ToolkitEmulatorException('Tab index out of range.')
160 current_tab = tabs.get_current_tab()
161 number_of_switches = 0
162 while not tabs.selectedTabIndex == index:
163 logger.debug(
164 'Current tab index: {0}.'.format(tabs.selectedTabIndex))
165 if number_of_switches >= number_of_tabs - 1:
166 # This prevents a loop. But if this error is ever raised, it's
167 # likely there's a bug on the emulator or on the QML Tab.
168 raise ToolkitEmulatorException(
169 'The tab with index {0} was not selected.'.format(index))
170 current_tab = self.switch_to_next_tab()
171 number_of_switches += 1
172 return current_tab
173
174 @autopilot_logging.log_action(logger.info)
175 def switch_to_previous_tab(self):
176 """Open the previous tab.
177
178 :return: The newly opened tab.
179
180 """
181 tabs = self.get_tabs()
182 if tabs.selectedTabIndex == 0:
183 previous_tab_index = tabs.get_number_of_tabs() - 1
184 else:
185 previous_tab_index = tabs.selectedTabIndex - 1
186 return self.switch_to_tab_by_index(previous_tab_index)
187
188 @autopilot_logging.log_action(logger.info)
189 def switch_to_tab(self, object_name):
190 """Open a tab.
191
192 :parameter object_name: The QML objectName property of the tab.
193 :return: The newly opened tab.
194 :raise ToolkitEmulatorException: If there is no tab with that object
195 name.
196
197 """
198 tabs = self.get_tabs()
199 for index, tab in enumerate(tabs.select_many('Tab')):
200 if tab.objectName == object_name:
201 return self.switch_to_tab_by_index(tab.index)
202 raise ToolkitEmulatorException(
203 'Tab with objectName "{0}" not found.'.format(object_name))
204
205 def get_action_selection_popover(self, object_name):
206 """Return an ActionSelectionPopover emulator.
207
208 :parameter object_name: The QML objectName property of the popover.
209
210 """
211 return self.select_single(
212 ActionSelectionPopover, objectName=object_name)
213
214 @autopilot_logging.log_action(logger.info)
215 def go_back(self):
216 """Go to the previous page."""
217 toolbar = self.open_toolbar()
218 toolbar.click_back_button()
219
220
221class Header(UbuntuUIToolkitEmulatorBase):
222 """Header Autopilot emulator."""
223
224 def __init__(self, *args):
225 super(Header, self).__init__(*args)
226 self.pointing_device = get_pointing_device()
227
228 def _get_animating(self):
229 tab_bar_style = self.select_single('TabBarStyle')
230 return tab_bar_style.animating
231
232 @autopilot_logging.log_action(logger.info)
233 def switch_to_next_tab(self):
234 """Open the next tab.
235
236 :raise ToolkitEmulatorException: If the main view has no tabs.
237
238 """
239 try:
240 tab_bar = self.select_single(TabBar)
241 except dbus.StateNotFoundError:
242 raise ToolkitEmulatorException(_NO_TABS_ERROR)
243 tab_bar.switch_to_next_tab()
244 self._get_animating().wait_for(False)
245
246
247class Toolbar(UbuntuUIToolkitEmulatorBase):
248 """Toolbar Autopilot emulator."""
249
250 @autopilot_logging.log_action(logger.info)
251 def open(self):
252 """Open the toolbar if it's not already opened.
253
254 :return: The toolbar.
255
256 """
257 self.animating.wait_for(False)
258 if not self.opened:
259 self._drag_to_open()
260 self.opened.wait_for(True)
261 self.animating.wait_for(False)
262
263 return self
264
265 def _drag_to_open(self):
266 x, y, _, _ = self.globalRect
267 line_x = x + self.width * 0.50
268 start_y = y + self.height - 1
269 stop_y = y
270
271 self.pointing_device.drag(line_x, start_y, line_x, stop_y)
272
273 @autopilot_logging.log_action(logger.info)
274 def close(self):
275 """Close the toolbar if it's opened."""
276 self.animating.wait_for(False)
277 if self.opened:
278 self._drag_to_close()
279 self.opened.wait_for(False)
280 self.animating.wait_for(False)
281
282 def _drag_to_close(self):
283 x, y, _, _ = self.globalRect
284 line_x = x + self.width * 0.50
285 start_y = y
286 stop_y = y + self.height - 1
287
288 self.pointing_device.drag(line_x, start_y, line_x, stop_y)
289
290 @autopilot_logging.log_action(logger.info)
291 def click_button(self, object_name):
292 """Click a button of the toolbar.
293
294 The toolbar should be opened before clicking the button, or an
295 exception will be raised. If the toolbar is closed for some reason
296 (e.g., timer finishes) after moving the mouse cursor and before
297 clicking the button, it is re-opened automatically by this function.
298
299 :parameter object_name: The QML objectName property of the button.
300 :raise ToolkitEmulatorException: If there is no button with that object
301 name.
302
303 """
304 # ensure the toolbar is open
305 if not self.opened:
306 raise ToolkitEmulatorException(
307 'Toolbar must be opened before calling click_button().')
308 try:
309 button = self._get_button(object_name)
310 except dbus.StateNotFoundError:
311 raise ToolkitEmulatorException(
312 'Button with objectName "{0}" not found.'.format(object_name))
313 self.pointing_device.move_to_object(button)
314 # ensure the toolbar is still open (may have closed due to timeout)
315 self.open()
316 # click the button
317 self.pointing_device.click_object(button)
318
319 def _get_button(self, object_name):
320 return self.select_single('ActionItem', objectName=object_name)
321
322 @autopilot_logging.log_action(logger.info)
323 def click_back_button(self):
324 """Click the back button of the toolbar."""
325 self.click_button('back_toolbar_button')
326
327
328class Tabs(UbuntuUIToolkitEmulatorBase):
329 """Tabs Autopilot emulator."""
330
331 def get_current_tab(self):
332 """Return the currently selected tab."""
333 return self._get_tab(self.selectedTabIndex)
334
335 def _get_tab(self, index):
336 tabs = self._get_tabs()
337 for tab in tabs:
338 if tab.index == index:
339 return tab
340 else:
341 raise ToolkitEmulatorException(
342 'There is no tab with index {0}.'.format(index))
343
344 def _get_tabs(self):
345 return self.select_many('Tab')
346
347 def get_number_of_tabs(self):
348 """Return the number of tabs."""
349 return len(self._get_tabs())
350
351
352class TabBar(UbuntuUIToolkitEmulatorBase):
353 """TabBar Autopilot emulator."""
354
355 @autopilot_logging.log_action(logger.info)
356 def switch_to_next_tab(self):
357 """Open the next tab."""
358 self._activate_tab_bar()
359 logger.debug('Click the next tab bar button.')
360 self.pointing_device.click_object(self._get_next_tab_button())
361
362 def _activate_tab_bar(self):
363 # First move to the tab bar to avoid timing issues when we find it in
364 # selection mode but it's deselected while we move to it.
365 self.pointing_device.move_to_object(self)
366 if self.selectionMode:
367 logger.debug('Already in selection mode.')
368 else:
369 # Click the tab bar to switch to selection mode.
370 logger.debug('Click the tab bar to enable selection mode.')
371 self.pointing_device.click_object(self)
372
373 def _get_next_tab_button(self):
374 current_index = self._get_selected_button_index()
375 next_index = (current_index + 1) % self._get_number_of_tab_buttons()
376 return self._get_tab_button(next_index)
377
378 def _get_selected_button_index(self):
379 return self.select_single('QQuickPathView').selectedButtonIndex
380
381 def _get_number_of_tab_buttons(self):
382 return len(self._get_tab_buttons())
383
384 def _get_tab_buttons(self):
385 return self.select_many('AbstractButton')
386
387 def _get_tab_button(self, index):
388 buttons = self._get_tab_buttons()
389 for button in buttons:
390 if button.buttonIndex == index:
391 return button
392 raise ToolkitEmulatorException(
393 'There is no tab button with index {0}.'.format(index))
394
395
396class ActionSelectionPopover(UbuntuUIToolkitEmulatorBase):
397 """ActionSelectionPopover Autopilot emulator."""
398
399 def click_button_by_text(self, text):
400 """Click a button on the popover.
401
402 XXX We are receiving the text because there's no way to set the
403 objectName on the action. This is reported at
404 https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1205144
405 --elopio - 2013-07-25
406
407 :parameter text: The text of the button.
408 :raise ToolkitEmulatorException: If the popover is not open.
409
410 """
411 if not self.visible:
412 raise ToolkitEmulatorException('The popover is not open.')
413 button = self._get_button(text)
414 if button is None:
415 raise ToolkitEmulatorException(
416 'Button with text "{0}" not found.'.format(text))
417 self.pointing_device.click_object(button)
418 if self.autoClose:
419 try:
420 self.visible.wait_for(False)
421 except dbus.StateNotFoundError:
422 # The popover was removed from the tree.
423 pass
424
425 def _get_button(self, text):
426 buttons = self.select_many('Empty')
427 for button in buttons:
428 if button.text == text:
429 return button
430
431
432class CheckBox(UbuntuUIToolkitEmulatorBase):
433 """CheckBox Autopilot emulator."""
434
435 @autopilot_logging.log_action(logger.info)
436 def check(self, timeout=10):
437 """Check a CheckBox, if its not already checked.
438
439 :parameter timeout: number of seconds to wait for the CheckBox to be
440 checked. Default is 10.
441
442 """
443 if not self.checked:
444 self.change_state(timeout)
445
446 @autopilot_logging.log_action(logger.info)
447 def uncheck(self, timeout=10):
448 """Uncheck a CheckBox, if its not already unchecked.
449
450 :parameter timeout: number of seconds to wait for the CheckBox to be
451 unchecked. Default is 10.
452
453 """
454 if self.checked:
455 self.change_state(timeout)
456
457 @autopilot_logging.log_action(logger.info)
458 def change_state(self, timeout=10):
459 """Change the state of a CheckBox.
460
461 If it is checked, it will be unchecked. If it is unchecked, it will be
462 checked.
463
464 :parameter time_out: number of seconds to wait for the CheckBox state
465 to change. Default is 10.
466
467 """
468 original_state = self.checked
469 self.pointing_device.click_object(self)
470 self.checked.wait_for(not original_state, timeout)
471
472
473class TextField(UbuntuUIToolkitEmulatorBase):
474 """TextField Autopilot emulator."""
475
476 def __init__(self, *args):
477 super(TextField, self).__init__(*args)
478 self.keyboard = get_keyboard()
479
480 def write(self, text, clear=True):
481 """Write into the text field.
482
483 :parameter text: The text to write.
484 :parameter clear: If True, the text field will be cleared before
485 writing the text. If False, the text will be appended at the end
486 of the text field. Default is True.
487
488 """
489 with self.keyboard.focused_type(self):
490 self.focus.wait_for(True)
491 if clear:
492 self.clear()
493 else:
494 if not self.is_empty():
495 self.keyboard.press_and_release('End')
496 self.keyboard.type(text)
497
498 def clear(self):
499 """Clear the text field."""
500 if not self.is_empty():
501 if self.hasClearButton:
502 self._click_clear_button()
503 else:
504 self._clear_with_keys()
505 self.text.wait_for('')
506
507 def is_empty(self):
508 """Return True if the text field is empty. False otherwise."""
509 return self.text == ''
510
511 def _click_clear_button(self):
512 clear_button = self.select_single(
513 'AbstractButton', objectName='clear_button')
514 if not clear_button.visible:
515 self.pointing_device.click_object(self)
516 self.pointing_device.click_object(clear_button)
517
518 def _clear_with_keys(self):
519 if platform.model() == 'Desktop':
520 self._select_all()
521 else:
522 # Touch tap currently doesn't have a press_duration parameter, so
523 # we can't show the popover. Reported as bug http://pad.lv/1268782
524 # --elopio - 2014-01-13
525 self.keyboard.press_and_release('End')
526 while not self.is_empty():
527 # We delete with backspace because the on-screen keyboard has that
528 # key.
529 self.keyboard.press_and_release('BackSpace')
530
531 def _select_all(self):
532 self.pointing_device.click_object(self, press_duration=1)
533 root = self.get_root_instance()
534 main_view = root.select_single(MainView)
535 popover = main_view.get_action_selection_popover('text_input_popover')
536 popover.click_button_by_text('Select All')
537
538
539class Flickable(UbuntuUIToolkitEmulatorBase):
540
541 @autopilot_logging.log_action(logger.info)
542 def swipe_child_into_view(self, child):
543 """Make the child visible.
544
545 Currently it works only when the object needs to be swiped vertically.
546 TODO implement horizontal swiping. --elopio - 2014-03-21
547
548 """
549 containers = self._get_containers()
550 if not self._is_child_visible(child, containers):
551 self._swipe_non_visible_child_into_view(child, containers)
552 else:
553 logger.debug('The element is already visible.')
554
555 def _get_containers(self):
556 """Return a list with the containers to take into account when swiping.
557
558 The list includes this flickable and the top-most container.
559 TODO add additional flickables that are between this and the top
560 container. --elopio - 2014-03-22
561
562 """
563 containers = [self._get_top_container(), self]
564 return containers
565
566 def _get_top_container(self):
567 """Return the top-most container with a globalRect."""
568 root = self.get_root_instance()
569 containers = [root]
570 while len(containers) == 1:
571 try:
572 containers[0].globalRect
573 return containers[0]
574 except AttributeError:
575 containers = containers[0].get_children()
576
577 raise ToolkitEmulatorException("Couldn't find the top-most container.")
578
579 def _is_child_visible(self, child, containers):
580 """Check if the center of the child is visible.
581
582 :return: True if the center of the child is visible, False otherwise.
583
584 """
585 object_center = child.globalRect.y + child.globalRect.height // 2
586 visible_top = _get_visible_container_top(containers)
587 visible_bottom = _get_visible_container_bottom(containers)
588 return (object_center >= visible_top and
589 object_center <= visible_bottom)
590
591 @autopilot_logging.log_action(logger.info)
592 def _swipe_non_visible_child_into_view(self, child, containers):
593 while not self._is_child_visible(child, containers):
594 # Check the direction of the swipe based on the position of the
595 # child relative to the immediate flickable container.
596 if child.globalRect.y < self.globalRect.y:
597 self._swipe_to_show_more_above(containers)
598 else:
599 self._swipe_to_show_more_below(containers)
600
601 @autopilot_logging.log_action(logger.info)
602 def _swipe_to_show_more_above(self, containers):
603 if self.atYBeginning:
604 raise ToolkitEmulatorException(
605 "Can't swipe more, we are already at the top of the "
606 "container.")
607 else:
608 self._swipe_to_show_more('above', containers)
609
610 @autopilot_logging.log_action(logger.info)
611 def _swipe_to_show_more_below(self, containers):
612 if self.atYEnd:
613 raise ToolkitEmulatorException(
614 "Can't swipe more, we are already at the bottom of the "
615 "container.")
616 else:
617 self._swipe_to_show_more('below', containers)
618
619 def _swipe_to_show_more(self, direction, containers):
620 start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2)
621 # Start and stop just a little under the top and a little over the
622 # bottom.
623 top = _get_visible_container_top(containers) + 5
624 bottom = _get_visible_container_bottom(containers) - 5
625 if direction == 'below':
626 start_y = bottom
627 stop_y = top
628 elif direction == 'above':
629 start_y = top
630 stop_y = bottom
631 else:
632 raise ToolkitEmulatorException(
633 'Invalid direction {}.'.format(direction))
634 self._slow_drag(start_x, stop_x, start_y, stop_y)
635 self.dragging.wait_for(False)
636 self.moving.wait_for(False)
637
638 def _slow_drag(self, start_x, stop_x, start_y, stop_y):
639 # If we drag too fast, we end up scrolling more than what we
640 # should, sometimes missing the element we are looking for.
641 self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5)
642
643 @autopilot_logging.log_action(logger.info)
644 def _scroll_to_top(self):
645 if not self.atYBeginning:
646 containers = self._get_containers()
647 while not self.atYBeginning:
648 self._swipe_to_show_more_above(containers)
649
650
651class QQuickListView(Flickable):
652
653 @autopilot_logging.log_action(logger.info)
654 def click_element(self, object_name):
655 """Click an element from the list.
656
657 It swipes the element into view if it's center is not visible.
658
659 :parameter objectName: The objectName property of the element to click.
660
661 """
662 try:
663 element = self.select_single(objectName=object_name)
664 except dbus.StateNotFoundError:
665 # The element might be on a part of the list that hasn't been
666 # created yet. We have to search for it scrolling the entire list.
667 element = self._find_element(object_name)
668 self.swipe_child_into_view(element)
669 self.pointing_device.click_object(element)
670
671 @autopilot_logging.log_action(logger.info)
672 def _find_element(self, object_name):
673 self._scroll_to_top()
674 while not self.atYEnd:
675 containers = self._get_containers()
676 self._swipe_to_show_more_below(containers)
677 try:
678 return self.select_single(objectName=object_name)
679 except dbus.StateNotFoundError:
680 pass
681 raise ToolkitEmulatorException(
682 'List element with objectName "{}" not found.'.format(object_name))
683
684 def _is_element_clickable(self, object_name):
685 child = self.select_single(objectName=object_name)
686 containers = self._get_containers()
687 return self._is_child_visible(child, containers)
688
689
690class Empty(UbuntuUIToolkitEmulatorBase):
691 """Base class to emulate swipe to delete."""
692
693 def exists(self):
694 try:
695 return self.implicitHeight > 0
696 except dbus.StateNotFoundError:
697 return False
698
699 def _get_confirm_button(self):
700 return self.select_single(
701 'QQuickItem', objectName='confirmRemovalDialog')
702
703 @autopilot_logging.log_action(logger.info)
704 def swipe_to_delete(self, direction='right'):
705 """Swipe the item in a specific direction."""
706 if self.removable:
707 self._drag_pointing_device_to_delete(direction)
708 if self.confirmRemoval:
709 self.waitingConfirmationForRemoval.wait_for(True)
710 else:
711 self._wait_until_deleted()
712 else:
713 raise ToolkitEmulatorException(
714 'The item "{0}" is not removable'.format(self.objectName))
715
716 def _drag_pointing_device_to_delete(self, direction):
717 x, y, w, h = self.globalRect
718 tx = x + (w // 8)
719 ty = y + (h // 2)
720
721 if direction == 'right':
722 self.pointing_device.drag(tx, ty, w, ty)
723 elif direction == 'left':
724 self.pointing_device.drag(w - (w*0.1), ty, x, ty)
725 else:
726 raise ToolkitEmulatorException(
727 'Invalid direction "{0}" used on swipe to delete function'
728 .format(direction))
729
730 def _wait_until_deleted(self):
731 try:
732 # The item was hidden.
733 self.implicitHeight.wait_for(0)
734 except dbus.StateNotFoundError:
735 # The item was destroyed.
736 pass
737
738 @autopilot_logging.log_action(logger.info)
739 def confirm_removal(self):
740 """Comfirm item removal if this was already swiped."""
741 if self.waitingConfirmationForRemoval:
742 deleteButton = self._get_confirm_button()
743 self.pointing_device.click_object(deleteButton)
744 self._wait_until_deleted()
745 else:
746 raise ToolkitEmulatorException(
747 'The item "{0}" is not waiting for removal confirmation'.
748 format(self.objectName))
749
750
751class Base(Empty):
752 pass
753
754
755class Standard(Empty):
756 pass
757
758
759class ItemSelector(Empty):
760 pass
761
762
763class SingleControl(Empty):
764 pass
765
766
767class MultiValue(Base):
768 pass
769
770
771class SingleValue(Base):
772 pass
773
774
775class Subtitled(Base):
776 pass
777
778
779class ComposerSheet(UbuntuUIToolkitEmulatorBase):
780 """ComposerSheet Autopilot emulator."""
781
782 def __init__(self, *args):
783 super(ComposerSheet, self).__init__(*args)
784
785 @autopilot_logging.log_action(logger.info)
786 def confirm(self):
787 """Confirm the composer sheet."""
788 button = self.select_single('Button', objectName='confirmButton')
789 self.pointing_device.click_object(button)
790 self.wait_until_destroyed()
791
792 @autopilot_logging.log_action(logger.info)
793 def cancel(self):
794 """Cancel the composer sheet."""
795 button = self.select_single('Button', objectName='cancelButton')
796 self.pointing_device.click_object(button)
797 self.wait_until_destroyed()
79883
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_checkbox.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,65 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20
21from ubuntuuitoolkit._custom_proxy_objects import _common
22
23
24logger = logging.getLogger(__name__)
25
26
27class CheckBox(_common.UbuntuUIToolkitCustomProxyObjectBase):
28 """CheckBox Autopilot emulator."""
29
30 @autopilot_logging.log_action(logger.info)
31 def check(self, timeout=10):
32 """Check a CheckBox, if its not already checked.
33
34 :parameter timeout: number of seconds to wait for the CheckBox to be
35 checked. Default is 10.
36
37 """
38 if not self.checked:
39 self.change_state(timeout)
40
41 @autopilot_logging.log_action(logger.info)
42 def uncheck(self, timeout=10):
43 """Uncheck a CheckBox, if its not already unchecked.
44
45 :parameter timeout: number of seconds to wait for the CheckBox to be
46 unchecked. Default is 10.
47
48 """
49 if self.checked:
50 self.change_state(timeout)
51
52 @autopilot_logging.log_action(logger.info)
53 def change_state(self, timeout=10):
54 """Change the state of a CheckBox.
55
56 If it is checked, it will be unchecked. If it is unchecked, it will be
57 checked.
58
59 :parameter time_out: number of seconds to wait for the CheckBox state
60 to change. Default is 10.
61
62 """
63 original_state = self.checked
64 self.pointing_device.click_object(self)
65 self.checked.wait_for(not original_state, timeout)
066
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_common.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,69 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Common helpers for Ubuntu UI Toolkit Autopilot custom proxy objects."""
18
19from distutils import version
20
21import autopilot
22from autopilot import platform, input
23from autopilot.introspection import dbus
24
25
26class ToolkitException(Exception):
27 """Exception raised when there is an error with the emulator."""
28
29
30def get_pointing_device():
31 """Return the pointing device depending on the platform.
32
33 If the platform is `Desktop`, the pointing device will be a `Mouse`.
34 If not, the pointing device will be `Touch`.
35
36 """
37 if platform.model() == 'Desktop':
38 input_device_class = input.Mouse
39 else:
40 input_device_class = input.Touch
41 return input.Pointer(device=input_device_class.create())
42
43
44def get_keyboard():
45 """Return the keyboard device."""
46 # TODO return the OSK if we are on the phone. --elopio - 2014-01-13
47 return input.Keyboard.create()
48
49
50def check_autopilot_version():
51 """Check that the Autopilot installed version matches the one required.
52
53 :raise ToolkitException: If the installed Autopilot version does't
54 match the required by the emulators.
55
56 """
57 installed_version = version.LooseVersion(autopilot.version)
58 if installed_version < version.LooseVersion('1.4'):
59 raise ToolkitException(
60 'The emulators need Autopilot 1.4 or higher.')
61
62
63class UbuntuUIToolkitCustomProxyObjectBase(dbus.CustomEmulatorBase):
64 """A base class for all the Ubuntu UI Toolkit emulators."""
65
66 def __init__(self, *args):
67 check_autopilot_version()
68 super(UbuntuUIToolkitCustomProxyObjectBase, self).__init__(*args)
69 self.pointing_device = get_pointing_device()
070
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_flickable.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,150 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20
21from ubuntuuitoolkit._custom_proxy_objects import _common
22
23
24logger = logging.getLogger(__name__)
25
26
27# Containers helpers.
28
29def _get_visible_container_top(containers):
30 containers_top = [container.globalRect.y for container in containers]
31 return max(containers_top)
32
33
34def _get_visible_container_bottom(containers):
35 containers_bottom = [
36 container.globalRect.y + container.globalRect.height
37 for container in containers if container.globalRect.height > 0]
38 return min(containers_bottom)
39
40
41class Flickable(_common.UbuntuUIToolkitCustomProxyObjectBase):
42
43 @autopilot_logging.log_action(logger.info)
44 def swipe_child_into_view(self, child):
45 """Make the child visible.
46
47 Currently it works only when the object needs to be swiped vertically.
48 TODO implement horizontal swiping. --elopio - 2014-03-21
49
50 """
51 containers = self._get_containers()
52 if not self._is_child_visible(child, containers):
53 self._swipe_non_visible_child_into_view(child, containers)
54 else:
55 logger.debug('The element is already visible.')
56
57 def _get_containers(self):
58 """Return a list with the containers to take into account when swiping.
59
60 The list includes this flickable and the top-most container.
61 TODO add additional flickables that are between this and the top
62 container. --elopio - 2014-03-22
63
64 """
65 containers = [self._get_top_container(), self]
66 return containers
67
68 def _get_top_container(self):
69 """Return the top-most container with a globalRect."""
70 root = self.get_root_instance()
71 containers = [root]
72 while len(containers) == 1:
73 try:
74 containers[0].globalRect
75 return containers[0]
76 except AttributeError:
77 containers = containers[0].get_children()
78
79 raise _common.ToolkitException("Couldn't find the top-most container.")
80
81 def _is_child_visible(self, child, containers):
82 """Check if the center of the child is visible.
83
84 :return: True if the center of the child is visible, False otherwise.
85
86 """
87 object_center = child.globalRect.y + child.globalRect.height // 2
88 visible_top = _get_visible_container_top(containers)
89 visible_bottom = _get_visible_container_bottom(containers)
90 return (object_center >= visible_top and
91 object_center <= visible_bottom)
92
93 @autopilot_logging.log_action(logger.info)
94 def _swipe_non_visible_child_into_view(self, child, containers):
95 while not self._is_child_visible(child, containers):
96 # Check the direction of the swipe based on the position of the
97 # child relative to the immediate flickable container.
98 if child.globalRect.y < self.globalRect.y:
99 self._swipe_to_show_more_above(containers)
100 else:
101 self._swipe_to_show_more_below(containers)
102
103 @autopilot_logging.log_action(logger.info)
104 def _swipe_to_show_more_above(self, containers):
105 if self.atYBeginning:
106 raise _common.ToolkitException(
107 "Can't swipe more, we are already at the top of the "
108 "container.")
109 else:
110 self._swipe_to_show_more('above', containers)
111
112 @autopilot_logging.log_action(logger.info)
113 def _swipe_to_show_more_below(self, containers):
114 if self.atYEnd:
115 raise _common.ToolkitException(
116 "Can't swipe more, we are already at the bottom of the "
117 "container.")
118 else:
119 self._swipe_to_show_more('below', containers)
120
121 def _swipe_to_show_more(self, direction, containers):
122 start_x = stop_x = self.globalRect.x + (self.globalRect.width // 2)
123 # Start and stop just a little under the top and a little over the
124 # bottom.
125 top = _get_visible_container_top(containers) + 5
126 bottom = _get_visible_container_bottom(containers) - 5
127 if direction == 'below':
128 start_y = bottom
129 stop_y = top
130 elif direction == 'above':
131 start_y = top
132 stop_y = bottom
133 else:
134 raise _common.ToolkitException(
135 'Invalid direction {}.'.format(direction))
136 self._slow_drag(start_x, stop_x, start_y, stop_y)
137 self.dragging.wait_for(False)
138 self.moving.wait_for(False)
139
140 def _slow_drag(self, start_x, stop_x, start_y, stop_y):
141 # If we drag too fast, we end up scrolling more than what we
142 # should, sometimes missing the element we are looking for.
143 self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate=5)
144
145 @autopilot_logging.log_action(logger.info)
146 def _scroll_to_top(self):
147 if not self.atYBeginning:
148 containers = self._get_containers()
149 while not self.atYBeginning:
150 self._swipe_to_show_more_above(containers)
0151
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_header.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,57 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20from autopilot.introspection import dbus
21
22from ubuntuuitoolkit._custom_proxy_objects import (
23 _common,
24 _tabbar
25)
26
27
28_NO_TABS_ERROR = 'The MainView has no Tabs.'
29
30
31logger = logging.getLogger(__name__)
32
33
34class Header(_common.UbuntuUIToolkitCustomProxyObjectBase):
35 """Header Autopilot emulator."""
36
37 def __init__(self, *args):
38 super(Header, self).__init__(*args)
39 self.pointing_device = _common.get_pointing_device()
40
41 def _get_animating(self):
42 tab_bar_style = self.select_single('TabBarStyle')
43 return tab_bar_style.animating
44
45 @autopilot_logging.log_action(logger.info)
46 def switch_to_next_tab(self):
47 """Open the next tab.
48
49 :raise ToolkitException: If the main view has no tabs.
50
51 """
52 try:
53 tab_bar = self.select_single(_tabbar.TabBar)
54 except dbus.StateNotFoundError:
55 raise _common.ToolkitException(_NO_TABS_ERROR)
56 tab_bar.switch_to_next_tab()
57 self._get_animating().wait_for(False)
058
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_listitems.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,114 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20from autopilot.introspection import dbus
21
22from ubuntuuitoolkit._custom_proxy_objects import _common
23
24
25logger = logging.getLogger(__name__)
26
27
28class Empty(_common.UbuntuUIToolkitCustomProxyObjectBase):
29 """Base class to emulate swipe to delete."""
30
31 def exists(self):
32 try:
33 return self.implicitHeight > 0
34 except dbus.StateNotFoundError:
35 return False
36
37 def _get_confirm_button(self):
38 return self.select_single(
39 'QQuickItem', objectName='confirmRemovalDialog')
40
41 @autopilot_logging.log_action(logger.info)
42 def swipe_to_delete(self, direction='right'):
43 """Swipe the item in a specific direction."""
44 if self.removable:
45 self._drag_pointing_device_to_delete(direction)
46 if self.confirmRemoval:
47 self.waitingConfirmationForRemoval.wait_for(True)
48 else:
49 self._wait_until_deleted()
50 else:
51 raise _common.ToolkitException(
52 'The item "{0}" is not removable'.format(self.objectName))
53
54 def _drag_pointing_device_to_delete(self, direction):
55 x, y, w, h = self.globalRect
56 tx = x + (w // 8)
57 ty = y + (h // 2)
58
59 if direction == 'right':
60 self.pointing_device.drag(tx, ty, w, ty)
61 elif direction == 'left':
62 self.pointing_device.drag(w - (w*0.1), ty, x, ty)
63 else:
64 raise _common.ToolkitException(
65 'Invalid direction "{0}" used on swipe to delete function'
66 .format(direction))
67
68 def _wait_until_deleted(self):
69 try:
70 # The item was hidden.
71 self.implicitHeight.wait_for(0)
72 except dbus.StateNotFoundError:
73 # The item was destroyed.
74 pass
75
76 @autopilot_logging.log_action(logger.info)
77 def confirm_removal(self):
78 """Comfirm item removal if this was already swiped."""
79 if self.waitingConfirmationForRemoval:
80 deleteButton = self._get_confirm_button()
81 self.pointing_device.click_object(deleteButton)
82 self._wait_until_deleted()
83 else:
84 raise _common.ToolkitException(
85 'The item "{0}" is not waiting for removal confirmation'.
86 format(self.objectName))
87
88
89class Base(Empty):
90 pass
91
92
93class Standard(Empty):
94 pass
95
96
97class ItemSelector(Empty):
98 pass
99
100
101class SingleControl(Empty):
102 pass
103
104
105class MultiValue(Base):
106 pass
107
108
109class SingleValue(Base):
110 pass
111
112
113class Subtitled(Base):
114 pass
0115
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_mainview.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,162 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Ubuntu UI Toolkit Autopilot custom proxy objects."""
18
19import logging
20
21from autopilot import logging as autopilot_logging
22from autopilot.introspection import dbus
23
24from ubuntuuitoolkit._custom_proxy_objects import (
25 _common,
26 _popups,
27 _tabs,
28 _toolbar,
29)
30
31
32_NO_TABS_ERROR = 'The MainView has no Tabs.'
33
34
35logger = logging.getLogger(__name__)
36
37
38class MainView(_common.UbuntuUIToolkitCustomProxyObjectBase):
39 """MainView Autopilot emulator."""
40
41 def get_header(self):
42 """Return the Header emulator of the MainView."""
43 try:
44 return self.select_single('Header', objectName='MainView_Header')
45 except dbus.StateNotFoundError:
46 raise _common.ToolkitException('The main view has no header.')
47
48 def get_toolbar(self):
49 """Return the Toolbar emulator of the MainView."""
50 return self.select_single(_toolbar.Toolbar)
51
52 @autopilot_logging.log_action(logger.info)
53 def open_toolbar(self):
54 """Open the toolbar if it's not already opened.
55
56 :return: The toolbar.
57
58 """
59 return self.get_toolbar().open()
60
61 @autopilot_logging.log_action(logger.info)
62 def close_toolbar(self):
63 """Close the toolbar if it's opened."""
64 self.get_toolbar().close()
65
66 def get_tabs(self):
67 """Return the Tabs emulator of the MainView.
68
69 :raise ToolkitException: If the main view has no tabs.
70
71 """
72 try:
73 return self.select_single(_tabs.Tabs)
74 except dbus.StateNotFoundError:
75 raise _common.ToolkitException(_NO_TABS_ERROR)
76
77 @autopilot_logging.log_action(logger.info)
78 def switch_to_next_tab(self):
79 """Open the next tab.
80
81 :return: The newly opened tab.
82
83 """
84 logger.debug('Switch to next tab.')
85 self.get_header().switch_to_next_tab()
86 current_tab = self.get_tabs().get_current_tab()
87 current_tab.visible.wait_for(True)
88 return current_tab
89
90 @autopilot_logging.log_action(logger.info)
91 def switch_to_tab_by_index(self, index):
92 """Open a tab.
93
94 :parameter index: The index of the tab to open.
95 :return: The newly opened tab.
96 :raise ToolkitException: If the tab index is out of range.
97
98 """
99 logger.debug('Switch to tab with index {0}.'.format(index))
100 tabs = self.get_tabs()
101 number_of_tabs = tabs.get_number_of_tabs()
102 if index >= number_of_tabs:
103 raise _common.ToolkitException('Tab index out of range.')
104 current_tab = tabs.get_current_tab()
105 number_of_switches = 0
106 while not tabs.selectedTabIndex == index:
107 logger.debug(
108 'Current tab index: {0}.'.format(tabs.selectedTabIndex))
109 if number_of_switches >= number_of_tabs - 1:
110 # This prevents a loop. But if this error is ever raised, it's
111 # likely there's a bug on the emulator or on the QML Tab.
112 raise _common.ToolkitException(
113 'The tab with index {0} was not selected.'.format(index))
114 current_tab = self.switch_to_next_tab()
115 number_of_switches += 1
116 return current_tab
117
118 @autopilot_logging.log_action(logger.info)
119 def switch_to_previous_tab(self):
120 """Open the previous tab.
121
122 :return: The newly opened tab.
123
124 """
125 tabs = self.get_tabs()
126 if tabs.selectedTabIndex == 0:
127 previous_tab_index = tabs.get_number_of_tabs() - 1
128 else:
129 previous_tab_index = tabs.selectedTabIndex - 1
130 return self.switch_to_tab_by_index(previous_tab_index)
131
132 @autopilot_logging.log_action(logger.info)
133 def switch_to_tab(self, object_name):
134 """Open a tab.
135
136 :parameter object_name: The QML objectName property of the tab.
137 :return: The newly opened tab.
138 :raise ToolkitException: If there is no tab with that object
139 name.
140
141 """
142 tabs = self.get_tabs()
143 for index, tab in enumerate(tabs.select_many('Tab')):
144 if tab.objectName == object_name:
145 return self.switch_to_tab_by_index(tab.index)
146 raise _common.ToolkitException(
147 'Tab with objectName "{0}" not found.'.format(object_name))
148
149 def get_action_selection_popover(self, object_name):
150 """Return an ActionSelectionPopover emulator.
151
152 :parameter object_name: The QML objectName property of the popover.
153
154 """
155 return self.select_single(
156 _popups.ActionSelectionPopover, objectName=object_name)
157
158 @autopilot_logging.log_action(logger.info)
159 def go_back(self):
160 """Go to the previous page."""
161 toolbar = self.open_toolbar()
162 toolbar.click_back_button()
0163
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_popups.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,82 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20from autopilot.introspection import dbus
21
22from ubuntuuitoolkit._custom_proxy_objects import _common
23
24
25logger = logging.getLogger(__name__)
26
27
28class ActionSelectionPopover(_common.UbuntuUIToolkitCustomProxyObjectBase):
29 """ActionSelectionPopover Autopilot emulator."""
30
31 def click_button_by_text(self, text):
32 """Click a button on the popover.
33
34 XXX We are receiving the text because there's no way to set the
35 objectName on the action. This is reported at
36 https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1205144
37 --elopio - 2013-07-25
38
39 :parameter text: The text of the button.
40 :raise ToolkitException: If the popover is not open.
41
42 """
43 if not self.visible:
44 raise _common.ToolkitException('The popover is not open.')
45 button = self._get_button(text)
46 if button is None:
47 raise _common.ToolkitException(
48 'Button with text "{0}" not found.'.format(text))
49 self.pointing_device.click_object(button)
50 if self.autoClose:
51 try:
52 self.visible.wait_for(False)
53 except dbus.StateNotFoundError:
54 # The popover was removed from the tree.
55 pass
56
57 def _get_button(self, text):
58 buttons = self.select_many('Empty')
59 for button in buttons:
60 if button.text == text:
61 return button
62
63
64class ComposerSheet(_common.UbuntuUIToolkitCustomProxyObjectBase):
65 """ComposerSheet Autopilot emulator."""
66
67 def __init__(self, *args):
68 super(ComposerSheet, self).__init__(*args)
69
70 @autopilot_logging.log_action(logger.info)
71 def confirm(self):
72 """Confirm the composer sheet."""
73 button = self.select_single('Button', objectName='confirmButton')
74 self.pointing_device.click_object(button)
75 self.wait_until_destroyed()
76
77 @autopilot_logging.log_action(logger.info)
78 def cancel(self):
79 """Cancel the composer sheet."""
80 button = self.select_single('Button', objectName='cancelButton')
81 self.pointing_device.click_object(button)
82 self.wait_until_destroyed()
083
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_qquicklistview.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,65 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20from autopilot.introspection import dbus
21
22from ubuntuuitoolkit._custom_proxy_objects import _flickable
23from ubuntuuitoolkit._custom_proxy_objects import _common
24
25
26logger = logging.getLogger(__name__)
27
28
29class QQuickListView(_flickable.Flickable):
30
31 @autopilot_logging.log_action(logger.info)
32 def click_element(self, object_name):
33 """Click an element from the list.
34
35 It swipes the element into view if it's center is not visible.
36
37 :parameter objectName: The objectName property of the element to click.
38
39 """
40 try:
41 element = self.select_single(objectName=object_name)
42 except dbus.StateNotFoundError:
43 # The element might be on a part of the list that hasn't been
44 # created yet. We have to search for it scrolling the entire list.
45 element = self._find_element(object_name)
46 self.swipe_child_into_view(element)
47 self.pointing_device.click_object(element)
48
49 @autopilot_logging.log_action(logger.info)
50 def _find_element(self, object_name):
51 self._scroll_to_top()
52 while not self.atYEnd:
53 containers = self._get_containers()
54 self._swipe_to_show_more_below(containers)
55 try:
56 return self.select_single(objectName=object_name)
57 except dbus.StateNotFoundError:
58 pass
59 raise _common.ToolkitException(
60 'List element with objectName "{}" not found.'.format(object_name))
61
62 def _is_element_clickable(self, object_name):
63 child = self.select_single(objectName=object_name)
64 containers = self._get_containers()
65 return self._is_child_visible(child, containers)
066
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabbar.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,68 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20
21from ubuntuuitoolkit._custom_proxy_objects import _common
22
23
24logger = logging.getLogger(__name__)
25
26
27class TabBar(_common.UbuntuUIToolkitCustomProxyObjectBase):
28 """TabBar Autopilot emulator."""
29
30 @autopilot_logging.log_action(logger.info)
31 def switch_to_next_tab(self):
32 """Open the next tab."""
33 self._activate_tab_bar()
34 logger.debug('Click the next tab bar button.')
35 self.pointing_device.click_object(self._get_next_tab_button())
36
37 def _activate_tab_bar(self):
38 # First move to the tab bar to avoid timing issues when we find it in
39 # selection mode but it's deselected while we move to it.
40 self.pointing_device.move_to_object(self)
41 if self.selectionMode:
42 logger.debug('Already in selection mode.')
43 else:
44 # Click the tab bar to switch to selection mode.
45 logger.debug('Click the tab bar to enable selection mode.')
46 self.pointing_device.click_object(self)
47
48 def _get_next_tab_button(self):
49 current_index = self._get_selected_button_index()
50 next_index = (current_index + 1) % self._get_number_of_tab_buttons()
51 return self._get_tab_button(next_index)
52
53 def _get_selected_button_index(self):
54 return self.select_single('QQuickPathView').selectedButtonIndex
55
56 def _get_number_of_tab_buttons(self):
57 return len(self._get_tab_buttons())
58
59 def _get_tab_buttons(self):
60 return self.select_many('AbstractButton')
61
62 def _get_tab_button(self, index):
63 buttons = self._get_tab_buttons()
64 for button in buttons:
65 if button.buttonIndex == index:
66 return button
67 raise _common.ToolkitException(
68 'There is no tab button with index {0}.'.format(index))
069
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_tabs.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,41 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from ubuntuuitoolkit._custom_proxy_objects import _common
18
19
20class Tabs(_common.UbuntuUIToolkitCustomProxyObjectBase):
21 """Tabs Autopilot emulator."""
22
23 def get_current_tab(self):
24 """Return the currently selected tab."""
25 return self._get_tab(self.selectedTabIndex)
26
27 def _get_tab(self, index):
28 tabs = self._get_tabs()
29 for tab in tabs:
30 if tab.index == index:
31 return tab
32 else:
33 raise _common.ToolkitException(
34 'There is no tab with index {0}.'.format(index))
35
36 def _get_tabs(self):
37 return self.select_many('Tab')
38
39 def get_number_of_tabs(self):
40 """Return the number of tabs."""
41 return len(self._get_tabs())
042
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_textfield.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,88 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from autopilot import platform
18
19from ubuntuuitoolkit._custom_proxy_objects import (
20 _common,
21 _mainview
22)
23
24
25class TextField(_common.UbuntuUIToolkitCustomProxyObjectBase):
26 """TextField Autopilot emulator."""
27
28 def __init__(self, *args):
29 super(TextField, self).__init__(*args)
30 self.keyboard = _common.get_keyboard()
31
32 def write(self, text, clear=True):
33 """Write into the text field.
34
35 :parameter text: The text to write.
36 :parameter clear: If True, the text field will be cleared before
37 writing the text. If False, the text will be appended at the end
38 of the text field. Default is True.
39
40 """
41 with self.keyboard.focused_type(self):
42 self.focus.wait_for(True)
43 if clear:
44 self.clear()
45 else:
46 if not self.is_empty():
47 self.keyboard.press_and_release('End')
48 self.keyboard.type(text)
49
50 def clear(self):
51 """Clear the text field."""
52 if not self.is_empty():
53 if self.hasClearButton:
54 self._click_clear_button()
55 else:
56 self._clear_with_keys()
57 self.text.wait_for('')
58
59 def is_empty(self):
60 """Return True if the text field is empty. False otherwise."""
61 return self.text == ''
62
63 def _click_clear_button(self):
64 clear_button = self.select_single(
65 'AbstractButton', objectName='clear_button')
66 if not clear_button.visible:
67 self.pointing_device.click_object(self)
68 self.pointing_device.click_object(clear_button)
69
70 def _clear_with_keys(self):
71 if platform.model() == 'Desktop':
72 self._select_all()
73 else:
74 # Touch tap currently doesn't have a press_duration parameter, so
75 # we can't show the popover. Reported as bug http://pad.lv/1268782
76 # --elopio - 2014-01-13
77 self.keyboard.press_and_release('End')
78 while not self.is_empty():
79 # We delete with backspace because the on-screen keyboard has that
80 # key.
81 self.keyboard.press_and_release('BackSpace')
82
83 def _select_all(self):
84 self.pointing_device.click_object(self, press_duration=1)
85 root = self.get_root_instance()
86 main_view = root.select_single(_mainview.MainView)
87 popover = main_view.get_action_selection_popover('text_input_popover')
88 popover.click_button_by_text('Select All')
089
=== added file 'tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py'
--- tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/_custom_proxy_objects/_toolbar.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,106 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18
19from autopilot import logging as autopilot_logging
20from autopilot.introspection import dbus
21
22from ubuntuuitoolkit._custom_proxy_objects import _common
23
24
25logger = logging.getLogger(__name__)
26
27
28class Toolbar(_common.UbuntuUIToolkitCustomProxyObjectBase):
29 """Toolbar Autopilot emulator."""
30
31 @autopilot_logging.log_action(logger.info)
32 def open(self):
33 """Open the toolbar if it's not already opened.
34
35 :return: The toolbar.
36
37 """
38 self.animating.wait_for(False)
39 if not self.opened:
40 self._drag_to_open()
41 self.opened.wait_for(True)
42 self.animating.wait_for(False)
43
44 return self
45
46 def _drag_to_open(self):
47 x, y, _, _ = self.globalRect
48 line_x = x + self.width * 0.50
49 start_y = y + self.height - 1
50 stop_y = y
51
52 self.pointing_device.drag(line_x, start_y, line_x, stop_y)
53
54 @autopilot_logging.log_action(logger.info)
55 def close(self):
56 """Close the toolbar if it's opened."""
57 self.animating.wait_for(False)
58 if self.opened:
59 self._drag_to_close()
60 self.opened.wait_for(False)
61 self.animating.wait_for(False)
62
63 def _drag_to_close(self):
64 x, y, _, _ = self.globalRect
65 line_x = x + self.width * 0.50
66 start_y = y
67 stop_y = y + self.height - 1
68
69 self.pointing_device.drag(line_x, start_y, line_x, stop_y)
70
71 @autopilot_logging.log_action(logger.info)
72 def click_button(self, object_name):
73 """Click a button of the toolbar.
74
75 The toolbar should be opened before clicking the button, or an
76 exception will be raised. If the toolbar is closed for some reason
77 (e.g., timer finishes) after moving the mouse cursor and before
78 clicking the button, it is re-opened automatically by this function.
79
80 :parameter object_name: The QML objectName property of the button.
81 :raise ToolkitException: If there is no button with that object
82 name.
83
84 """
85 # ensure the toolbar is open
86 if not self.opened:
87 raise _common.ToolkitException(
88 'Toolbar must be opened before calling click_button().')
89 try:
90 button = self._get_button(object_name)
91 except dbus.StateNotFoundError:
92 raise _common.ToolkitException(
93 'Button with objectName "{0}" not found.'.format(object_name))
94 self.pointing_device.move_to_object(button)
95 # ensure the toolbar is still open (may have closed due to timeout)
96 self.open()
97 # click the button
98 self.pointing_device.click_object(button)
99
100 def _get_button(self, object_name):
101 return self.select_single('ActionItem', objectName=object_name)
102
103 @autopilot_logging.log_action(logger.info)
104 def click_back_button(self):
105 """Click the back button of the toolbar."""
106 self.click_button('back_toolbar_button')
0107
=== added file 'tests/autopilot/ubuntuuitoolkit/emulators.py'
--- tests/autopilot/ubuntuuitoolkit/emulators.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/emulators.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,85 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2012, 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17"""Emulators was the old name for the custom proxy objects."""
18
19import logging
20
21
22logger = logging.getLogger(__name__)
23
24
25logger.warning(
26 'The ubuntuuitoolkit.emulators module is deprecated. Import the autopilot '
27 'helpers from the top-level ubuntuuitoolkit module.')
28
29
30__all__ = [
31 'check_autopilot_version',
32 'get_keyboard',
33 'get_pointing_device',
34 'ActionSelectionPopover',
35 'Base',
36 'CheckBox',
37 'ComposerSheet',
38 'Empty',
39 'Flickable',
40 'Header',
41 'ItemSelector',
42 'MainView',
43 'MultiValue',
44 'OptionSelector',
45 'QQuickListView',
46 'SingleControl',
47 'SingleValue',
48 'Standard',
49 'Subtitled',
50 'TabBar',
51 'Tabs',
52 'TextField',
53 'Toolbar',
54 'ToolkitEmulatorException',
55 'UbuntuUIToolkitEmulatorBase',
56]
57
58
59from ubuntuuitoolkit import (
60 check_autopilot_version,
61 get_keyboard,
62 get_pointing_device,
63 ActionSelectionPopover,
64 Base,
65 CheckBox,
66 ComposerSheet,
67 Empty,
68 Flickable,
69 Header,
70 ItemSelector,
71 MainView,
72 MultiValue,
73 OptionSelector,
74 QQuickListView,
75 SingleControl,
76 SingleValue,
77 Standard,
78 Subtitled,
79 TabBar,
80 Tabs,
81 TextField,
82 Toolbar,
83 ToolkitException as ToolkitEmulatorException,
84 UbuntuUIToolkitCustomProxyObjectBase as UbuntuUIToolkitEmulatorBase,
85)
086
=== added directory 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects'
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/__init__.py'
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_checkbox.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,136 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import time
18
19from autopilot import input
20from testtools.matchers import GreaterThan, LessThan
21try:
22 from unittest import mock
23except ImportError:
24 import mock
25
26import ubuntuuitoolkit
27from ubuntuuitoolkit import tests
28
29
30TEST_QML_WITH_CHECKBOX = ("""
31import QtQuick 2.0
32import Ubuntu.Components 0.1
33
34MainView {
35 width: units.gu(48)
36 height: units.gu(60)
37
38 Item {
39 CheckBox {
40 checked: false
41 objectName: "test_checkbox"
42 }
43 }
44}
45""")
46
47
48TEST_QML_WITH_SWITCH = ("""
49import QtQuick 2.0
50import Ubuntu.Components 0.1
51
52MainView {
53 width: units.gu(48)
54 height: units.gu(60)
55
56 Item {
57 Switch {
58 checked: false
59 objectName: "test_switch"
60 }
61 }
62}
63""")
64
65
66class ToggleTestCase(tests.QMLStringAppTestCase):
67
68 scenarios = [
69 ('checkbox', dict(
70 test_qml=TEST_QML_WITH_CHECKBOX, objectName='test_checkbox')),
71 ('switch', dict(
72 test_qml=TEST_QML_WITH_SWITCH, objectName='test_switch'))
73 ]
74
75 def setUp(self):
76 super(ToggleTestCase, self).setUp()
77 self.toggle = self.main_view.select_single(
78 ubuntuuitoolkit.CheckBox, objectName=self.objectName)
79 self.assertFalse(self.toggle.checked)
80
81 def test_toggle_custom_proxy_object(self):
82 self.assertIsInstance(self.toggle, ubuntuuitoolkit.CheckBox)
83
84 def test_check_toggle(self):
85 self.toggle.check()
86 self.assertTrue(self.toggle.checked)
87
88 def test_check_toggle_already_checked(self):
89 self.toggle.check()
90 with mock.patch.object(input.Pointer, 'click_object') as mock_click:
91 self.toggle.check()
92 self.assertFalse(mock_click.called)
93
94 def test_uncheck_toggle(self):
95 self.toggle.check()
96 self.toggle.uncheck()
97 self.assertFalse(self.toggle.checked)
98
99 def test_uncheck_toggle_already_unchecked(self):
100 with mock.patch.object(input.Pointer, 'click_object') as mock_click:
101 self.toggle.uncheck()
102 self.assertFalse(mock_click.called)
103
104 def test_change_state_from_checked(self):
105 self.toggle.check()
106 self.toggle.change_state()
107 self.assertFalse(self.toggle.checked)
108
109 def test_change_state_from_unchecked(self):
110 self.toggle.change_state()
111 self.assertTrue(self.toggle.checked)
112
113 def test_check_with_timeout(self):
114 with mock.patch.object(
115 ubuntuuitoolkit.CheckBox, 'change_state') as mock_change:
116 self.toggle.check(timeout=1)
117
118 mock_change.assert_called_once_with(1)
119
120 def test_uncheck_with_timeout(self):
121 self.toggle.check()
122 with mock.patch.object(
123 ubuntuuitoolkit.CheckBox, 'change_state') as mock_change:
124 self.toggle.uncheck(timeout=1)
125
126 mock_change.assert_called_once_with(1)
127
128 def test_change_state_with_timeout(self):
129 with mock.patch.object(self.toggle, 'pointing_device'):
130 # mock the pointing device so the checkbox is not clicked.
131 timestamp_before_call = time.time()
132 self.assertRaises(AssertionError, self.toggle.change_state, 1)
133
134 waiting_time = time.time() - timestamp_before_call
135 self.assertThat(waiting_time, GreaterThan(1))
136 self.assertThat(waiting_time, LessThan(2))
0137
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_common.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,68 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import unittest
18
19import autopilot
20from autopilot import platform, input
21try:
22 from unittest import mock
23except ImportError:
24 import mock
25
26import ubuntuuitoolkit
27from ubuntuuitoolkit import tests
28
29
30class CheckAutopilotVersionTestCase(unittest.TestCase):
31
32 def test_lower_version_should_raise_exception(self):
33 with mock.patch.object(autopilot, 'version', '1.3'):
34 self.assertRaises(
35 ubuntuuitoolkit.ToolkitException,
36 ubuntuuitoolkit.check_autopilot_version)
37
38 def test_required_version_should_succeed(self):
39 with mock.patch.object(autopilot, 'version', '1.4'):
40 ubuntuuitoolkit.check_autopilot_version()
41
42 def test_higher_version_should_succeed(self):
43 with mock.patch.object(autopilot, 'version', '1.5'):
44 ubuntuuitoolkit.check_autopilot_version()
45
46
47class UbuntuUIToolkitCustomProxyObjectBaseTestCase(tests.QMLStringAppTestCase):
48
49 def test_pointing_device(self):
50 self.assertIsInstance(self.app.pointing_device, input.Pointer)
51
52 @unittest.skipIf(platform.model() != 'Desktop', 'Desktop only')
53 def test_pointing_device_in_desktop(self):
54 self.assertIsInstance(self.app.pointing_device._device, input.Mouse)
55
56 @unittest.skipIf(platform.model() == 'Desktop', 'Phablet only')
57 def test_pointing_device_in_phablet(self):
58 self.assertIsInstance(self.app.pointing_device._device, input.Touch)
59
60 def test_custom_proxy_objects_should_check_version_on_init(self):
61 check_name = (
62 'ubuntuuitoolkit._custom_proxy_objects._common.'
63 'check_autopilot_version')
64 with mock.patch(check_name, autospec=True) as mock_check:
65 # Instantiate any custom proxy object.
66 self.main_view
67
68 mock_check.assert_called_once_with()
069
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_header.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,41 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import ubuntuuitoolkit
18from ubuntuuitoolkit import tests
19
20
21class PageTestCase(tests.QMLStringAppTestCase):
22
23 test_qml = ("""
24import QtQuick 2.0
25import Ubuntu.Components 0.1
26
27MainView {
28 width: units.gu(48)
29 height: units.gu(60)
30
31 Page {
32 title: "Test title"
33 }
34}
35""")
36
37 def test_header_custom_proxy_object(self):
38 header = self.main_view.get_header()
39 self.assertIsInstance(header, ubuntuuitoolkit.Header)
40 self.assertTrue(header.visible)
41 self.assertEqual(header.title, "Test title")
042
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_listitems.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,170 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import ubuntuuitoolkit
18from ubuntuuitoolkit import tests
19
20
21class SwipeToDeleteTestCase(tests.QMLStringAppTestCase):
22
23 test_qml = ("""
24import QtQuick 2.0
25import Ubuntu.Components 0.1
26import Ubuntu.Components.ListItems 0.1
27
28
29MainView {
30 width: units.gu(48)
31 height: units.gu(60)
32
33 Page {
34
35 ListModel {
36 id: testModel
37
38 ListElement {
39 name: "listitem_destroyed_on_remove_with_confirm"
40 label: "Item destroyed on remove with confirmation"
41 confirm: true
42 }
43 ListElement {
44 name: "listitem_destroyed_on_remove_without_confirm"
45 label: "Item destroyed on remove without confirmation"
46 confirm: false
47 }
48 }
49
50 Column {
51 anchors { fill: parent }
52
53 Standard {
54 objectName: "listitem_standard"
55 confirmRemoval: true
56 removable: true
57 text: 'Slide to remove'
58 }
59
60 Empty {
61 objectName: "listitem_empty"
62 }
63
64 Standard {
65 objectName: "listitem_without_confirm"
66 confirmRemoval: false
67 removable: true
68 text: "Item without delete confirmation"
69 }
70
71 ListView {
72 anchors { left: parent.left; right: parent.right }
73 height: childrenRect.height
74 model: testModel
75
76 delegate: Standard {
77 removable: true
78 confirmRemoval: confirm
79 onItemRemoved: testModel.remove(index)
80 text: label
81 objectName: name
82 }
83 }
84 }
85 }
86}
87""")
88
89 def setUp(self):
90 super(SwipeToDeleteTestCase, self).setUp()
91 self._item = self.main_view.select_single(
92 ubuntuuitoolkit.Standard, objectName='listitem_standard')
93 self.assertTrue(self._item.exists())
94
95 def test_supported_class(self):
96 self.assertTrue(issubclass(
97 ubuntuuitoolkit.Base, ubuntuuitoolkit.Empty))
98 self.assertTrue(issubclass(
99 ubuntuuitoolkit.ItemSelector, ubuntuuitoolkit.Empty))
100 self.assertTrue(issubclass(
101 ubuntuuitoolkit.Standard, ubuntuuitoolkit.Empty))
102 self.assertTrue(issubclass(
103 ubuntuuitoolkit.SingleControl, ubuntuuitoolkit.Empty))
104 self.assertTrue(issubclass(
105 ubuntuuitoolkit.MultiValue, ubuntuuitoolkit.Base))
106 self.assertTrue(issubclass(
107 ubuntuuitoolkit.SingleValue, ubuntuuitoolkit.Base))
108 self.assertTrue(issubclass(
109 ubuntuuitoolkit.Subtitled, ubuntuuitoolkit.Base))
110
111 def test_standard_custom_proxy_object(self):
112 self.assertIsInstance(self._item, ubuntuuitoolkit.Standard)
113
114 def test_swipe_item(self):
115 self._item.swipe_to_delete()
116 self.assertTrue(self._item.waitingConfirmationForRemoval)
117
118 def test_swipe_item_to_right(self):
119 self._item.swipe_to_delete('right')
120 self.assertTrue(self._item.waitingConfirmationForRemoval)
121
122 def test_swipe_item_to_left(self):
123 self._item.swipe_to_delete('left')
124 self.assertTrue(self._item.waitingConfirmationForRemoval)
125
126 def test_swipe_item_to_wrong_direction(self):
127 self.assertRaises(
128 ubuntuuitoolkit.ToolkitException,
129 self._item.swipe_to_delete, 'up')
130
131 def test_delete_item_moving_right(self):
132 self._item.swipe_to_delete('right')
133 self._item.confirm_removal()
134 self.assertFalse(self._item.exists())
135
136 def test_delete_item_moving_left(self):
137 self._item.swipe_to_delete('left')
138 self._item.confirm_removal()
139 self.assertFalse(self._item.exists())
140
141 def test_delete_non_removable_item(self):
142 self._item = self.main_view.select_single(
143 ubuntuuitoolkit.Empty, objectName='listitem_empty')
144 self.assertRaises(
145 ubuntuuitoolkit.ToolkitException, self._item.swipe_to_delete)
146
147 def test_confirm_removal_when_item_was_not_swiped(self):
148 self.assertRaises(
149 ubuntuuitoolkit.ToolkitException, self._item.confirm_removal)
150
151 def test_delete_item_without_confirm(self):
152 item = self.main_view.select_single(
153 ubuntuuitoolkit.Standard, objectName='listitem_without_confirm')
154 item.swipe_to_delete()
155 self.assertFalse(item.exists())
156
157 def test_delete_item_with_confirmation_that_will_be_destroyed(self):
158 item = self.main_view.select_single(
159 ubuntuuitoolkit.Standard,
160 objectName='listitem_destroyed_on_remove_with_confirm')
161 item.swipe_to_delete()
162 item.confirm_removal()
163 self.assertFalse(item.exists())
164
165 def test_delete_item_without_confirmation_that_will_be_destroyed(self):
166 item = self.main_view.select_single(
167 ubuntuuitoolkit.Standard,
168 objectName='listitem_destroyed_on_remove_without_confirm')
169 item.swipe_to_delete()
170 self.assertFalse(item.exists())
0171
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_main_view.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,132 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17try:
18 from unittest import mock
19except ImportError:
20 import mock
21
22import ubuntuuitoolkit
23from ubuntuuitoolkit import tests
24
25
26class MainViewTestCase(tests.QMLStringAppTestCase):
27
28 test_qml = ("""
29import QtQuick 2.0
30import Ubuntu.Components 0.1
31
32MainView {
33 width: units.gu(48)
34 height: units.gu(60)
35}
36""")
37
38 def test_main_view_custom_proxy_object(self):
39 self.assertIsInstance(self.main_view, ubuntuuitoolkit.MainView)
40
41 def test_get_header_without_header(self):
42 header = self.main_view.get_header()
43 self.assertFalse(header.visible)
44
45 def test_toolbar_custom_proxy_object(self):
46 toolbar = self.main_view.get_toolbar()
47 self.assertIsInstance(toolbar, ubuntuuitoolkit.Toolbar)
48
49 def test_open_toolbar(self):
50 with mock.patch.object(ubuntuuitoolkit.Toolbar, 'open') as mock_open:
51 self.main_view.open_toolbar()
52
53 mock_open.assert_called_once_with()
54
55 def test_close_toolbar(self):
56 with mock.patch.object(ubuntuuitoolkit.Toolbar, 'close') as mock_close:
57 self.main_view.close_toolbar()
58
59 mock_close.assert_called_once_with()
60
61 def test_open_toolbar_returns_the_toolbar(self):
62 toolbar = self.main_view.open_toolbar()
63 self.assertIsInstance(toolbar, ubuntuuitoolkit.Toolbar)
64
65 def test_get_tabs_without_tabs(self):
66 error = self.assertRaises(
67 ubuntuuitoolkit.ToolkitException, self.main_view.get_tabs)
68 self.assertEqual(
69 str(error), 'The MainView has no Tabs.')
70
71 def test_switch_to_next_tab_without_tabs(self):
72 header = self.main_view.get_header()
73 error = self.assertRaises(
74 ubuntuuitoolkit.ToolkitException,
75 header.switch_to_next_tab)
76 self.assertEqual(
77 str(error), 'The MainView has no Tabs.')
78
79
80class GoBackTestCase(tests.QMLStringAppTestCase):
81
82 test_qml = ("""
83import QtQuick 2.0
84import Ubuntu.Components 0.1
85
86MainView {
87 width: units.gu(48)
88 height: units.gu(60)
89
90 PageStack {
91 id: pageStack
92 Component.onCompleted: push(page0)
93
94 Page {
95 id: page0
96 title: "Page 0"
97 visible: false
98
99 Button {
100 objectName: "go_to_page1"
101 text: "Go to page 1"
102 onClicked: pageStack.push(page1)
103 }
104 }
105
106 Page {
107 id: page1
108 title: "Page 1"
109 visible: false
110 }
111 }
112}
113""")
114
115 def setUp(self):
116 super(GoBackTestCase, self).setUp()
117 self.header = self.main_view.get_header()
118 self.assertEqual(self.header.title, 'Page 0')
119
120 def test_open_page(self):
121 self._go_to_page1()
122 self.assertEqual(self.header.title, 'Page 1')
123
124 def _go_to_page1(self):
125 button = self.main_view.select_single(
126 'Button', objectName='go_to_page1')
127 self.pointing_device.click_object(button)
128
129 def test_go_back(self):
130 self._go_to_page1()
131 self.main_view.go_back()
132 self.assertEqual(self.header.title, 'Page 0')
0133
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_popups.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,172 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from autopilot.introspection import dbus
18
19import ubuntuuitoolkit
20from ubuntuuitoolkit import tests
21
22
23class ActionSelectionPopoverTestCase(tests.QMLStringAppTestCase):
24
25 test_qml = ("""
26import QtQuick 2.0
27import Ubuntu.Components 0.1
28import Ubuntu.Components.Popups 0.1
29
30MainView {
31 width: units.gu(48)
32 height: units.gu(60)
33
34 Button {
35 objectName: "open_popover"
36 text: "Open Popover"
37 onClicked: testActionsPopover.show();
38 }
39
40 Label {
41 id: "label"
42 objectName: "clicked_label"
43 anchors.centerIn: parent
44 text: "Button not clicked."
45 }
46
47 ActionSelectionPopover {
48 objectName: "test_actions_popover"
49 id: testActionsPopover
50 actions: ActionList {
51 Action {
52 text: "Action one"
53 onTriggered: label.text = "Button clicked."
54 }
55 }
56 }
57}
58""")
59
60 def test_action_selection_popover_custom_proxy_object(self):
61 popover = self.main_view.get_action_selection_popover(
62 'test_actions_popover')
63 self.assertIsInstance(popover, ubuntuuitoolkit.ActionSelectionPopover)
64
65 def test_click_action_select_popover_button(self):
66 label = self.app.select_single('Label', objectName='clicked_label')
67 self.assertNotEqual(label.text, 'Button clicked.')
68 self._open_popover()
69 popover = self.main_view.get_action_selection_popover(
70 'test_actions_popover')
71 popover.click_button_by_text('Action one')
72 self.assertEqual(label.text, 'Button clicked.')
73
74 def _open_popover(self):
75 open_button = self.main_view.select_single(
76 'Button', objectName='open_popover')
77 self.pointing_device.click_object(open_button)
78
79 def test_click_unexisting_button(self):
80 self._open_popover()
81 popover = self.main_view.get_action_selection_popover(
82 'test_actions_popover')
83 error = self.assertRaises(
84 ubuntuuitoolkit.ToolkitException, popover.click_button_by_text,
85 'unexisting')
86 self.assertEqual(
87 str(error), 'Button with text "unexisting" not found.')
88
89 def test_click_button_with_closed_popover(self):
90 popover = self.main_view.get_action_selection_popover(
91 'test_actions_popover')
92 error = self.assertRaises(
93 ubuntuuitoolkit.ToolkitException, popover.click_button_by_text,
94 'Action one')
95 self.assertEqual(
96 str(error), 'The popover is not open.')
97
98
99class ComposerSheetTestCase(tests.QMLStringAppTestCase):
100
101 test_qml = ("""
102import QtQuick 2.0
103import Ubuntu.Components 0.1
104import Ubuntu.Components.Popups 0.1
105
106MainView {
107 width: units.gu(48)
108 height: units.gu(60)
109
110 Button {
111 objectName: "openComposerSheetButton"
112 text: "Open Composer Sheet"
113 onClicked: PopupUtils.open(testComposerSheet);
114 }
115
116 Label {
117 id: "label"
118 objectName: "actionLabel"
119 anchors.centerIn: parent
120 text: "No action taken."
121 }
122
123 Component {
124 id: testComposerSheet
125 ComposerSheet {
126 id: sheet
127 objectName: "testComposerSheet"
128 onCancelClicked: {
129 label.text = "Cancel selected."
130 }
131 onConfirmClicked: {
132 label.text = "Confirm selected."
133 }
134 }
135 }
136}
137""")
138
139 def setUp(self):
140 super(ComposerSheetTestCase, self).setUp()
141 self.label = self.main_view.select_single(
142 'Label', objectName='actionLabel')
143 self.assertEqual(self.label.text, 'No action taken.')
144 self._open_composer_sheet()
145 self.composer_sheet = self._select_composer_sheet()
146
147 def _open_composer_sheet(self):
148 button = self.main_view.select_single(
149 'Button', objectName='openComposerSheetButton')
150 self.pointing_device.click_object(button)
151
152 def _select_composer_sheet(self):
153 return self.main_view.select_single(
154 ubuntuuitoolkit.ComposerSheet, objectName='testComposerSheet')
155
156 def test_select_composer_sheet_custom_proxy_object(self):
157 self.assertIsInstance(
158 self.composer_sheet, ubuntuuitoolkit.ComposerSheet)
159
160 def test_confirm_composer_sheet(self):
161 self.composer_sheet.confirm()
162 self.assertEqual(self.label.text, 'Confirm selected.')
163 self._assert_composer_sheet_is_closed()
164
165 def _assert_composer_sheet_is_closed(self):
166 self.assertRaises(
167 dbus.StateNotFoundError, self._select_composer_sheet)
168
169 def test_cancel_composer_sheet(self):
170 self.composer_sheet.cancel()
171 self.assertEqual(self.label.text, 'Cancel selected.')
172 self._assert_composer_sheet_is_closed()
0173
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_qquicklistview.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,193 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from autopilot.introspection import dbus
18
19import ubuntuuitoolkit
20from ubuntuuitoolkit import tests
21
22
23class QQuickListViewTestCase(tests.QMLStringAppTestCase):
24
25 test_qml = ("""
26import QtQuick 2.0
27import Ubuntu.Components 0.1
28import Ubuntu.Components.ListItems 0.1 as ListItem
29
30MainView {
31 width: units.gu(48)
32 height: units.gu(20)
33
34 Page {
35
36 Column {
37 id: column
38 width: units.gu(48)
39 height: units.gu(20)
40
41 Label {
42 id: clickedLabel
43 objectName: "clickedLabel"
44 text: "No element clicked."
45 }
46
47 ListView {
48 id: testListView
49 objectName: "testListView"
50 anchors.left: parent.left
51 anchors.right: parent.right
52 height: column.height - clickedLabel.paintedHeight
53 clip: true
54 model: 20
55
56 delegate: ListItem.Standard {
57 objectName: "testListElement%1".arg(index)
58 text: "test list element %1".arg(index)
59 onClicked: clickedLabel.text = objectName
60 height: units.gu(5)
61 }
62 }
63 }
64 }
65}
66""")
67
68 def setUp(self):
69 super(QQuickListViewTestCase, self).setUp()
70 self.list_view = self.main_view.select_single(
71 ubuntuuitoolkit.QQuickListView, objectName='testListView')
72 self.label = self.main_view.select_single(
73 'Label', objectName='clickedLabel')
74 self.assertEqual(self.label.text, 'No element clicked.')
75
76 def test_qquicklistview_custom_proxy_object(self):
77 self.assertIsInstance(self.list_view, ubuntuuitoolkit.QQuickListView)
78
79 def test_click_element(self):
80 self.list_view.click_element('testListElement0')
81 self.assertEqual(self.label.text, 'testListElement0')
82
83 def test_click_element_outside_view_below(self):
84 # Click the first element out of view to make sure we are not scrolling
85 # to the bottom at once.
86 self.assertFalse(
87 self.list_view._is_element_clickable('testListElement5'))
88
89 self.list_view.click_element('testListElement5')
90 self.assertEqual(self.label.text, 'testListElement5')
91
92 def test_click_element_outside_view_above(self):
93 self.list_view.click_element('testListElement9')
94
95 # Click the first element out of view to make sure we are not scrolling
96 # to the top at once.
97 self.assertFalse(
98 self.list_view._is_element_clickable('testListElement4'))
99
100 self.list_view.click_element('testListElement4')
101 self.assertEqual(self.label.text, 'testListElement4')
102
103 def test_click_element_not_created_at_start(self):
104 objectName = 'testListElement19'
105 self.assertRaises(
106 dbus.StateNotFoundError,
107 self.list_view.select_single,
108 objectName=objectName)
109 self.list_view.click_element(objectName)
110
111 def test_click_unexisting_element(self):
112 error = self.assertRaises(
113 ubuntuuitoolkit.ToolkitException,
114 self.list_view.click_element,
115 'unexisting')
116 self.assertEqual(
117 str(error), 'List element with objectName "unexisting" not found.')
118
119
120class QQuickListViewOutOfViewTestCase(tests.QMLStringAppTestCase):
121 """Test that we can make elements visible when the list is out of view."""
122
123 test_qml = ("""
124import QtQuick 2.0
125import Ubuntu.Components 0.1
126import Ubuntu.Components.ListItems 0.1 as ListItem
127
128MainView {
129 width: units.gu(48)
130 height: units.gu(20)
131
132 Page {
133
134 Flickable {
135
136 Column {
137 id: column
138 width: units.gu(48)
139 // The column height is greater than the main view height, so
140 // the bottom of the list is out of view.
141 height: units.gu(40)
142
143 Label {
144 id: clickedLabel
145 objectName: "clickedLabel"
146 text: "No element clicked."
147 }
148
149 ListView {
150 id: testListView
151 objectName: "testListView"
152 anchors.left: parent.left
153 anchors.right: parent.right
154 height: column.height - clickedLabel.paintedHeight
155 clip: true
156 model: 20
157
158 delegate: ListItem.Standard {
159 objectName: "testListElement%1".arg(index)
160 text: "test list element %1".arg(index)
161 onClicked: clickedLabel.text = objectName
162 height: units.gu(5)
163 }
164 }
165 }
166 }
167 }
168}
169""")
170
171 def setUp(self):
172 super(QQuickListViewOutOfViewTestCase, self).setUp()
173 self.list_view = self.main_view.select_single(
174 ubuntuuitoolkit.QQuickListView, objectName='testListView')
175 self.label = self.main_view.select_single(
176 'Label', objectName='clickedLabel')
177 self.assertEqual(self.label.text, 'No element clicked.')
178
179 def test_click_element_outside_view_below(self):
180 """Test that we can click an element that's out of view below.
181
182 The list is also out of view, so we must scroll from the bottom of the
183 main view.
184
185 """
186 # Test for http://pad.lv/1275060.
187 # Click the first element out of view to make sure we are not scrolling
188 # to the bottom at once.
189 self.assertFalse(
190 self.list_view._is_element_clickable('testListElement9'))
191
192 self.list_view.click_element('testListElement9')
193 self.assertEqual(self.label.text, 'testListElement9')
0194
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_tabs.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,149 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17try:
18 from unittest import mock
19except ImportError:
20 import mock
21
22import ubuntuuitoolkit
23from ubuntuuitoolkit import tests
24
25
26class TabsTestCase(tests.QMLStringAppTestCase):
27
28 test_qml = ("""
29import QtQuick 2.0
30import Ubuntu.Components 0.1
31import Ubuntu.Components.ListItems 0.1 as ListItem
32
33MainView {
34 width: units.gu(70)
35 height: units.gu(60)
36
37 Tabs {
38 id: tabs
39 Tab {
40 objectName: "tab1"
41 title: "Tab1"
42 Page {
43 tools: ToolbarItems {
44 ToolbarButton {
45 text: "Test1"
46 }
47 }
48 }
49 }
50 Tab {
51 objectName: "tab2"
52 title: "Tab2"
53 Page {
54 tools: ToolbarItems {
55 ToolbarButton {
56 text: "Test2"
57 }
58 }
59 }
60 }
61 Tab {
62 objectName: "tab3"
63 title: "Tab3"
64 Page {
65 tools: ToolbarItems {
66 ToolbarButton {
67 text: "Test3"
68 }
69 }
70 }
71 }
72 }
73}
74""")
75
76 def test_tabs_custom_proxy_object(self):
77 self.assertIsInstance(self.main_view.get_tabs(), ubuntuuitoolkit.Tabs)
78
79 def test_get_current_tab(self):
80 tabs = self.main_view.get_tabs()
81 self.assertEqual(tabs.get_current_tab().title, 'Tab1')
82
83 def test_switch_to_next_tab_from_header(self):
84 header = self.main_view.get_header()
85 header.switch_to_next_tab()
86 current_tab = self.main_view.get_tabs().get_current_tab()
87 self.assertEqual(current_tab.title, 'Tab2')
88
89 def test_switch_to_next_tab_from_main_view(self):
90 current_tab = self.main_view.switch_to_next_tab()
91 self.assertEqual(current_tab.title, 'Tab2')
92
93 def test_switch_to_next_tab_from_last(self):
94 last_tab_index = self.main_view.get_tabs().get_number_of_tabs() - 1
95 self.main_view.switch_to_tab_by_index(last_tab_index)
96 current_tab = self.main_view.switch_to_next_tab()
97 self.assertEqual(current_tab.title, 'Tab1')
98
99 def test_switch_to_tab_by_index(self):
100 current_tab = self.main_view.switch_to_tab_by_index(2)
101 self.assertEqual(current_tab.title, 'Tab3')
102 current_tab = self.main_view.switch_to_tab_by_index(1)
103 self.assertEqual(current_tab.title, 'Tab2')
104 current_tab = self.main_view.switch_to_tab_by_index(0)
105 self.assertEqual(current_tab.title, 'Tab1')
106
107 def test_switch_to_opened_tab_is_not_opened_again(self):
108 with mock.patch.object(
109 ubuntuuitoolkit.Header, 'switch_to_next_tab') as mock_switch:
110 current_tab = self.main_view.switch_to_tab_by_index(0)
111
112 self.assertFalse(mock_switch.called)
113 self.assertEqual(current_tab.title, 'Tab1')
114
115 def test_get_number_of_tabs(self):
116 tabs = self.main_view.get_tabs()
117 self.assertEqual(tabs.get_number_of_tabs(), 3)
118
119 def test_swith_to_tab_by_index_out_of_range(self):
120 last_tab_index = self.main_view.get_tabs().get_number_of_tabs() - 1
121 error = self.assertRaises(
122 ubuntuuitoolkit.ToolkitException,
123 self.main_view.switch_to_tab_by_index,
124 last_tab_index + 1)
125 self.assertEqual(str(error), 'Tab index out of range.')
126
127 def test_switch_to_previous_tab_from_first(self):
128 current_tab = self.main_view.switch_to_previous_tab()
129 self.assertEqual(current_tab.title, 'Tab3')
130
131 def test_switch_to_previous_tab_not_from_first(self):
132 self.main_view.switch_to_tab_by_index(1)
133 current_tab = self.main_view.switch_to_previous_tab()
134 self.assertEqual(current_tab.title, 'Tab1')
135
136 def test_switch_to_tab_by_object_name(self):
137 current_tab = self.main_view.switch_to_tab('tab3')
138 self.assertEqual(current_tab.title, 'Tab3')
139 current_tab = self.main_view.switch_to_tab('tab2')
140 self.assertEqual(current_tab.title, 'Tab2')
141 current_tab = self.main_view.switch_to_tab('tab1')
142 self.assertEqual(current_tab.title, 'Tab1')
143
144 def test_switch_to_unexisting_tab(self):
145 error = self.assertRaises(
146 ubuntuuitoolkit.ToolkitException, self.main_view.switch_to_tab,
147 'unexisting')
148 self.assertEqual(
149 str(error), 'Tab with objectName "unexisting" not found.')
0150
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_textfield.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,93 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import ubuntuuitoolkit
18from ubuntuuitoolkit import tests
19
20
21class TextFieldTestCase(tests.QMLStringAppTestCase):
22
23 test_qml = ("""
24import QtQuick 2.0
25import Ubuntu.Components 0.1
26
27MainView {
28 width: units.gu(48)
29 height: units.gu(60)
30
31 Item {
32 TextField {
33 id: simpleTextField
34 objectName: "simple_text_field"
35 }
36 TextField {
37 id: textFieldWithoutClearButton
38 objectName: "text_field_without_clear_button"
39 hasClearButton: false
40 anchors.top: simpleTextField.bottom
41 }
42 }
43}
44""")
45
46 def setUp(self):
47 super(TextFieldTestCase, self).setUp()
48 self.simple_text_field = self.main_view.select_single(
49 ubuntuuitoolkit.TextField, objectName='simple_text_field')
50
51 def test_text_field_custom_proxy_object(self):
52 self.assertIsInstance(
53 self.simple_text_field, ubuntuuitoolkit.TextField)
54
55 def test_write(self):
56 self.simple_text_field.write('test')
57 self.assertEqual(self.simple_text_field.text, 'test')
58
59 def test_clear_with_clear_button(self):
60 self.simple_text_field.write('test')
61 self.simple_text_field.clear()
62 self.assertEqual(self.simple_text_field.text, '')
63
64 def test_clear_without_clear_button(self):
65 text_field = self.main_view.select_single(
66 ubuntuuitoolkit.TextField,
67 objectName='text_field_without_clear_button')
68 text_field.write('test')
69 text_field.clear()
70 self.assertEqual(text_field.text, '')
71
72 def test_clear_and_write(self):
73 self.simple_text_field.write('test1')
74 self.simple_text_field.write('test2')
75 self.assertEqual(self.simple_text_field.text, 'test2')
76
77 def test_write_without_clear(self):
78 self.simple_text_field.write('test1')
79 self.simple_text_field.write('test2', clear=False)
80 self.assertEqual(self.simple_text_field.text, 'test1test2')
81
82 def test_write_without_clear_writes_at_the_end(self):
83 self.simple_text_field.write(
84 'long text that will fill more than half of the text field.')
85 self.simple_text_field.write('append', clear=False)
86 self.assertEqual(
87 self.simple_text_field.text,
88 'long text that will fill more than half of the text field.append')
89
90 def test_is_empty(self):
91 self.assertTrue(self.simple_text_field.is_empty())
92 self.simple_text_field.write('test')
93 self.assertFalse(self.simple_text_field.is_empty())
094
=== added file 'tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py'
--- tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py 1970-01-01 00:00:00 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/custom_proxy_objects/test_toolbar.py 2014-04-17 01:29:24 +0000
@@ -0,0 +1,121 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013, 2014 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17try:
18 from unittest import mock
19except ImportError:
20 import mock
21
22import ubuntuuitoolkit
23from ubuntuuitoolkit import tests
24
25
26class ToolbarTestCase(tests.QMLStringAppTestCase):
27
28 test_qml = ("""
29import QtQuick 2.0
30import Ubuntu.Components 0.1
31
32MainView {
33 width: units.gu(50)
34 height: units.gu(50)
35
36 // Make sure that for these tests the toolbar starts closed.
37 Component.onCompleted: {
38 __propagated.toolbar.close();
39 }
40
41 Page {
42
43 Label {
44 id: "label"
45 objectName: "clicked_label"
46 anchors.centerIn: parent
47 text: "Button not clicked."
48 }
49
50 tools: ToolbarItems {
51 ToolbarButton {
52 objectName: "buttonName"
53 action: Action {
54 text: "buttonText"
55 onTriggered: label.text = "Button clicked."
56 }
57 }
58 }
59 }
60}
61""")
62
63 def setUp(self):
64 super(ToolbarTestCase, self).setUp()
65 self.toolbar = self.main_view.get_toolbar()
66 # toolbar may be opened or closed now, depending on whether
67 # the application has been deactivated and resumed already
68
69 def test_open_toolbar(self):
70 self.toolbar.open()
71 self.assertTrue(self.toolbar.opened)
72 self.assertFalse(self.toolbar.animating)
73
74 def test_opened_toolbar_is_not_opened_again(self):
75 self.toolbar.open()
76 with mock.patch.object(
77 self.main_view.pointing_device, 'drag') as mock_drag:
78 self.toolbar.open()
79
80 self.assertFalse(mock_drag.called)
81 self.assertTrue(self.toolbar.opened)
82
83 def test_close_toolbar(self):
84 self.toolbar.open()
85 self.toolbar.close()
86 self.assertFalse(self.toolbar.opened)
87 self.assertFalse(self.toolbar.animating)
88
89 def test_closed_toolbar_is_not_closed_again(self):
90 self.toolbar.close()
91 with mock.patch.object(
92 self.main_view.pointing_device, 'drag') as mock_drag:
93 self.toolbar.close()
94
95 self.assertFalse(mock_drag.called)
96 self.assertFalse(self.toolbar.opened)
97
98 def test_click_toolbar_button(self):
99 self.toolbar.close()
100 label = self.app.select_single('Label', objectName='clicked_label')
101 self.assertNotEqual(label.text, 'Button clicked.')
102 self.toolbar.open()
103 self.toolbar.click_button('buttonName')
104 self.assertEqual(label.text, 'Button clicked.')
105
106 def test_click_unexisting_button(self):
107 self.main_view.open_toolbar()
108 error = self.assertRaises(
109 ubuntuuitoolkit.ToolkitException, self.toolbar.click_button,
110 'unexisting')
111 self.assertEqual(
112 str(error), 'Button with objectName "unexisting" not found.')
113
114 def test_click_button_on_closed_toolbar(self):
115 self.toolbar.close()
116 error = self.assertRaises(
117 ubuntuuitoolkit.ToolkitException, self.toolbar.click_button,
118 'buttonName')
119 self.assertEqual(
120 str(error),
121 'Toolbar must be opened before calling click_button().')
0122
=== removed file 'tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py'
--- tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 2014-03-22 06:53:24 +0000
+++ tests/autopilot/ubuntuuitoolkit/tests/test_emulators.py 1970-01-01 00:00:00 +0000
@@ -1,1081 +0,0 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Copyright (C) 2013 Canonical Ltd.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation; version 3.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import time
18import unittest
19
20import autopilot
21from autopilot import input, platform
22from autopilot.introspection import dbus
23from testtools.matchers import GreaterThan, LessThan
24try:
25 from unittest import mock
26except ImportError:
27 import mock
28
29from ubuntuuitoolkit import emulators, tests
30
31
32class CheckAutopilotVersionTestCase(unittest.TestCase):
33
34 def test_lower_version_should_raise_exception(self):
35 with mock.patch.object(autopilot, 'version', '1.3'):
36 self.assertRaises(
37 emulators.ToolkitEmulatorException,
38 emulators.check_autopilot_version)
39
40 def test_required_version_should_succeed(self):
41 with mock.patch.object(autopilot, 'version', '1.4'):
42 emulators.check_autopilot_version()
43
44 def test_higher_version_should_succeed(self):
45 with mock.patch.object(autopilot, 'version', '1.5'):
46 emulators.check_autopilot_version()
47
48
49class UbuntuUIToolkitEmulatorBaseTestCase(tests.QMLStringAppTestCase):
50
51 def test_pointing_device(self):
52 self.assertIsInstance(self.app.pointing_device, input.Pointer)
53
54 @unittest.skipIf(platform.model() != 'Desktop', 'Desktop only')
55 def test_pointing_device_in_desktop(self):
56 self.assertIsInstance(self.app.pointing_device._device, input.Mouse)
57
58 @unittest.skipIf(platform.model() == 'Desktop', 'Phablet only')
59 def test_pointing_device_in_phablet(self):
60 self.assertIsInstance(self.app.pointing_device._device, input.Touch)
61
62 def test_emulators_should_check_version_on_init(self):
63 check_name = 'ubuntuuitoolkit.emulators.check_autopilot_version'
64 with mock.patch(check_name, autospec=True) as mock_check:
65 # Instantiate any emulator.
66 self.main_view
67
68 mock_check.assert_called_once_with()
69
70
71class MainViewTestCase(tests.QMLStringAppTestCase):
72
73 test_qml = ("""
74import QtQuick 2.0
75import Ubuntu.Components 0.1
76
77MainView {
78 width: units.gu(48)
79 height: units.gu(60)
80}
81""")
82
83 def test_main_view_custom_emulator(self):
84 self.assertIsInstance(self.main_view, emulators.MainView)
85
86 def test_get_header_without_header(self):
87 header = self.main_view.get_header()
88 self.assertFalse(header.visible)
89
90 def test_toolbar_custom_emulator(self):
91 toolbar = self.main_view.get_toolbar()
92 self.assertIsInstance(toolbar, emulators.Toolbar)
93
94 def test_open_toolbar(self):
95 with mock.patch.object(emulators.Toolbar, 'open') as mock_open:
96 self.main_view.open_toolbar()
97
98 mock_open.assert_called_once_with()
99
100 def test_close_toolbar(self):
101 with mock.patch.object(emulators.Toolbar, 'close') as mock_close:
102 self.main_view.close_toolbar()
103
104 mock_close.assert_called_once_with()
105
106 def test_open_toolbar_returns_the_toolbar(self):
107 toolbar = self.main_view.open_toolbar()
108 self.assertIsInstance(toolbar, emulators.Toolbar)
109
110 def test_get_tabs_without_tabs(self):
111 error = self.assertRaises(
112 emulators.ToolkitEmulatorException, self.main_view.get_tabs)
113 self.assertEqual(
114 str(error), 'The MainView has no Tabs.')
115
116 def test_switch_to_next_tab_without_tabs(self):
117 header = self.main_view.get_header()
118 error = self.assertRaises(
119 emulators.ToolkitEmulatorException, header.switch_to_next_tab)
120 self.assertEqual(
121 str(error), 'The MainView has no Tabs.')
122
123
124class PageTestCase(tests.QMLStringAppTestCase):
125
126 test_qml = ("""
127import QtQuick 2.0
128import Ubuntu.Components 0.1
129
130MainView {
131 width: units.gu(48)
132 height: units.gu(60)
133
134 Page {
135 title: "Test title"
136 }
137}
138""")
139
140 def test_header_custom_emulator(self):
141 header = self.main_view.get_header()
142 self.assertIsInstance(header, emulators.Header)
143 self.assertTrue(header.visible)
144 self.assertEqual(header.title, "Test title")
145
146
147class ToolbarTestCase(tests.QMLStringAppTestCase):
148
149 test_qml = ("""
150import QtQuick 2.0
151import Ubuntu.Components 0.1
152
153MainView {
154 width: units.gu(50)
155 height: units.gu(50)
156
157 // Make sure that for these tests the toolbar starts closed.
158 Component.onCompleted: {
159 __propagated.toolbar.close();
160 }
161
162 Page {
163
164 Label {
165 id: "label"
166 objectName: "clicked_label"
167 anchors.centerIn: parent
168 text: "Button not clicked."
169 }
170
171 tools: ToolbarItems {
172 ToolbarButton {
173 objectName: "buttonName"
174 action: Action {
175 text: "buttonText"
176 onTriggered: label.text = "Button clicked."
177 }
178 }
179 }
180 }
181}
182""")
183
184 def setUp(self):
185 super(ToolbarTestCase, self).setUp()
186 self.toolbar = self.main_view.get_toolbar()
187 # toolbar may be opened or closed now, depending on whether
188 # the application has been deactivated and resumed already
189
190 def test_open_toolbar(self):
191 self.toolbar.open()
192 self.assertTrue(self.toolbar.opened)
193 self.assertFalse(self.toolbar.animating)
194
195 def test_opened_toolbar_is_not_opened_again(self):
196 self.toolbar.open()
197 with mock.patch.object(
198 self.main_view.pointing_device, 'drag') as mock_drag:
199 self.toolbar.open()
200
201 self.assertFalse(mock_drag.called)
202 self.assertTrue(self.toolbar.opened)
203
204 def test_close_toolbar(self):
205 self.toolbar.open()
206 self.toolbar.close()
207 self.assertFalse(self.toolbar.opened)
208 self.assertFalse(self.toolbar.animating)
209
210 def test_closed_toolbar_is_not_closed_again(self):
211 self.toolbar.close()
212 with mock.patch.object(
213 self.main_view.pointing_device, 'drag') as mock_drag:
214 self.toolbar.close()
215
216 self.assertFalse(mock_drag.called)
217 self.assertFalse(self.toolbar.opened)
218
219 def test_click_toolbar_button(self):
220 self.toolbar.close()
221 label = self.app.select_single('Label', objectName='clicked_label')
222 self.assertNotEqual(label.text, 'Button clicked.')
223 self.toolbar.open()
224 self.toolbar.click_button('buttonName')
225 self.assertEqual(label.text, 'Button clicked.')
226
227 def test_click_unexisting_button(self):
228 self.main_view.open_toolbar()
229 error = self.assertRaises(
230 emulators.ToolkitEmulatorException, self.toolbar.click_button,
231 'unexisting')
232 self.assertEqual(
233 str(error), 'Button with objectName "unexisting" not found.')
234
235 def test_click_button_on_closed_toolbar(self):
236 self.toolbar.close()
237 error = self.assertRaises(
238 emulators.ToolkitEmulatorException, self.toolbar.click_button,
239 'buttonName')
240 self.assertEqual(
241 str(error),
242 'Toolbar must be opened before calling click_button().')
243
244
245class TabsTestCase(tests.QMLStringAppTestCase):
246
247 test_qml = ("""
248import QtQuick 2.0
249import Ubuntu.Components 0.1
250import Ubuntu.Components.ListItems 0.1 as ListItem
251
252MainView {
253 width: units.gu(70)
254 height: units.gu(60)
255
256 Tabs {
257 id: tabs
258 Tab {
259 objectName: "tab1"
260 title: "Tab1"
261 Page {
262 tools: ToolbarItems {
263 ToolbarButton {
264 text: "Test1"
265 }
266 }
267 }
268 }
269 Tab {
270 objectName: "tab2"
271 title: "Tab2"
272 Page {
273 tools: ToolbarItems {
274 ToolbarButton {
275 text: "Test2"
276 }
277 }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: