2 * Copyright 2013-2016 Canonical Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import Unity.Application 0.1
20 import Ubuntu.Components 1.3
21 import Ubuntu.Test 1.0 as UbuntuTest
22 import Unity.Test 0.1 as UT
27 property var util: TestUtil {id:util}
29 // This is needed for waitForRendering calls to return
30 // if the watched element already got rendered
35 parent: testCase.parent
36 border { width: units.dp(1); color: "black" }
39 visible: testCase.running
41 RotationAnimation on rotation {
42 running: rotatingRectangle.visible
45 loops: Animation.Infinite
50 // Fake implementation to be provided to items under test
51 property var fakeDateTime: new function() {
52 this.currentTimeMs = 0
53 this.getCurrentTimeMs = function() {return this.currentTimeMs}
56 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
57 function mouseClick(item, x, y, button, modifiers, delay) {
59 qtest_fail("no item given", 1);
61 if (button === undefined)
62 button = Qt.LeftButton;
63 if (modifiers === undefined)
64 modifiers = Qt.NoModifier;
65 if (delay === undefined)
71 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
72 qtest_fail("window not shown", 2);
75 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
76 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
78 qtest_fail("no item given", 1);
80 if (button === undefined)
81 button = Qt.LeftButton;
82 if (modifiers === undefined)
83 modifiers = Qt.NoModifier;
84 if (delay === undefined)
90 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
91 qtest_fail("window not shown", 2)
94 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
95 function mousePress(item, x, y, button, modifiers, delay) {
97 qtest_fail("no item given", 1);
99 if (button === undefined)
100 button = Qt.LeftButton;
101 if (modifiers === undefined)
102 modifiers = Qt.NoModifier;
103 if (delay === undefined)
109 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
110 qtest_fail("window not shown", 2)
113 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
114 function mouseRelease(item, x, y, button, modifiers, delay) {
116 qtest_fail("no item given", 1);
118 if (button === undefined)
119 button = Qt.LeftButton;
120 if (modifiers === undefined)
121 modifiers = Qt.NoModifier;
122 if (delay === undefined)
128 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
129 qtest_fail("window not shown", 2)
133 // Flickable won't recognise a single mouse move as dragging the flickable.
134 // Use 5 steps because it's what
135 // Qt uses in QQuickViewTestUtil::flick
136 // speed is in pixels/second
137 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
140 qtest_fail("no item given", 1);
142 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
143 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
145 // set a default speed if not specified
146 speed = (speed != null) ? speed : units.gu(10);
148 // set a default iterations if not specified
149 iterations = (iterations !== undefined) ? iterations : 5
151 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
152 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
154 var timeStep = totalTime / iterations
155 var diffX = (toX - x) / iterations
156 var diffY = (toY - y) / iterations
158 fakeDateTime.currentTimeMs += timeStep
159 mousePress(item, x, y)
161 for (var i = 0; i < iterations; ++i) {
162 fakeDateTime.currentTimeMs += timeStep
163 if (i === iterations - 1) {
164 // Avoid any rounding errors by making the last move be at precisely
165 // the point specified
166 mouseMove(item, toX, toY, timeStep)
168 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, timeStep)
172 fakeDateTime.currentTimeMs += timeStep
173 mouseRelease(item, toX, toY)
178 // Find an object with the given name in the children tree of "obj"
179 function findChild(obj, objectName, timeout) {
181 qtest_fail("no obj given", 1);
183 return findChildInWithTimeout(obj, "children", objectName, timeout);
186 // Find an object with the given name in the children tree of "obj"
187 // Including invisible children like animations, timers etc.
188 // Note: you should use findChild if you're not sure you need this
189 // as this tree is much bigger and might contain stuff that goes
191 function findInvisibleChild(obj, objectName, timeout) {
193 qtest_fail("no obj given", 1);
195 return findChildInWithTimeout(obj, "data", objectName, timeout);
198 // Find a child in the named property with timeout
199 function findChildInWithTimeout(obj, prop, objectName, timeout) {
201 qtest_fail("no obj given", 1);
204 if (timeout === undefined)
207 var child = findChildIn(obj, prop, objectName);
209 while (timeSpent < timeout && !child) {
212 child = findChildIn(obj, prop, objectName);
217 // Find a child in the named property
218 function findChildIn(obj, prop, objectName) {
220 qtest_fail("no obj given", 1);
222 var childs = new Array(0);
224 while (childs.length > 0) {
225 if (childs[0].objectName == objectName) {
228 for (var i in childs[0][prop]) {
229 childs.push(childs[0][prop][i])
236 function findChildsByType(obj, typeName) {
238 qtest_fail("no obj given", 1);
240 var res = new Array(0);
241 for (var i in obj.children) {
242 var c = obj.children[i];
243 if (UT.Util.isInstanceOf(c, typeName)) {
246 res = res.concat(findChildsByType(c, typeName));
251 // Type a full string instead of keyClick letter by letter
252 function typeString(str) {
253 for (var i = 0; i < str.length; i++) {
258 // Keeps executing a given parameter-less function until it returns the given
259 // expected result or the timemout is reached (in which case a test failure
261 function tryCompareFunction(func, expectedResult, timeout, message) {
263 if (timeout === undefined)
267 while (timeSpent < timeout && !success) {
268 actualResult = func()
269 success = qtest_compareInternal(actualResult, expectedResult)
270 if (success === false) {
276 var act = qtest_results.stringify(actualResult)
277 var exp = qtest_results.stringify(expectedResult)
278 if (!qtest_results.compare(success,
279 message || "function returned unexpected result",
281 util.callerFile(), util.callerLine())) {
282 throw new Error("QtQuickTest::fail")
286 function flickToYEnd(item) {
288 qtest_fail("no item given", 1);
291 var x = item.width / 2;
292 var y = item.height - units.gu(1);
293 var toY = units.gu(1);
294 var maxIterations = 5 + item.contentHeight / item.height;
295 while (i < maxIterations && !item.atYEnd) {
296 touchFlick(item, x, y, x, toY);
297 tryCompare(item, "moving", false);
300 tryCompare(item, "atYEnd", true);
303 function touchEvent(item) {
304 return UT.Util.touchEvent(item)
307 // speed is in pixels/second
308 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
310 qtest_fail("no item given", 1);
312 // Make sure the item is rendered
313 waitForRendering(item);
315 var root = fetchRootItem(item);
316 var rootFrom = item.mapToItem(root, x, y);
317 var rootTo = item.mapToItem(root, toX, toY);
319 // Default to true for beginTouch if not present
320 beginTouch = (beginTouch !== undefined) ? beginTouch : true
322 // Default to true for endTouch if not present
323 endTouch = (endTouch !== undefined) ? endTouch : true
325 // Set a default speed if not specified
326 speed = (speed !== undefined) ? speed : units.gu(10)
328 // Set a default iterations if not specified
329 var iterations = (iterations !== undefined) ? iterations : 10
331 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
332 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
334 var timeStep = totalTime / iterations
335 var diffX = (rootTo.x - rootFrom.x) / iterations
336 var diffY = (rootTo.y - rootFrom.y) / iterations
338 fakeDateTime.currentTimeMs += timeStep
340 var event = touchEvent(item)
341 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
344 for (var i = 0; i < iterations; ++i) {
345 fakeDateTime.currentTimeMs += timeStep
346 if (i === iterations - 1) {
347 // Avoid any rounding errors by making the last move be at precisely
348 // the point specified
350 var event = touchEvent(item)
351 event.move(0 /* touchId */, rootTo.x, rootTo.y)
355 var event = touchEvent(item)
356 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
361 fakeDateTime.currentTimeMs += timeStep
362 var event = touchEvent(item)
363 event.release(0 /* touchId */, rootTo.x, rootTo.y)
368 // perform a drag in the given direction until the given condition is true
369 // The condition is a function to be evaluated after every step
370 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
372 qtest_fail("no item given", 1);
374 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
377 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
379 qtest_fail("no item given", 1);
381 var root = fetchRootItem(item);
382 var pos = item.mapToItem(root, startX, startY);
384 // convert step to scene coords
386 var stepStart = item.mapToItem(root, 0, 0);
387 var stepEnd = item.mapToItem(root, stepX, stepY);
389 stepX = stepEnd.x - stepStart.x;
390 stepY = stepEnd.y - stepStart.y;
392 var event = touchEvent(item)
393 for (var i = 0; i < touchIds.length; i++) {
394 event.press(touchIds[i], pos.x, pos.y)
398 // we have to stop at some point
402 while (!condition() && stepsDone < maxSteps) {
404 fakeDateTime.currentTimeMs += 25;
409 event = touchEvent(item);
410 for (i = 0; i < touchIds.length; i++) {
411 event.move(touchIds[i], pos.x, pos.y);
418 event = touchEvent(item)
419 for (i = 0; i < touchIds.length; i++) {
420 event.release(touchIds[i], pos.x, pos.y)
425 function touchMove(item, tox, toy) {
427 qtest_fail("no item given", 1);
429 multiTouchMove(0, item, tox, toy);
432 function multiTouchMove(touchId, item, tox, toy) {
434 qtest_fail("no item given", 1);
436 if (typeof touchId !== "number") touchId = 0;
437 var root = fetchRootItem(item)
438 var rootPoint = item.mapToItem(root, tox, toy)
440 var event = touchEvent(item);
441 event.move(touchId, rootPoint.x, rootPoint.y);
445 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
447 qtest_fail("no item given", 1);
449 // Make sure the item is rendered
450 waitForRendering(item);
452 var event1 = touchEvent(item);
454 event1.press(0, x1Start, y1Start);
457 event1.move(0, x1Start, y1Start);
458 event1.press(1, x2Start, y2Start);
462 for (var i = 0.0; i < 1.0; i += 0.02) {
463 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
464 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
469 event1.release(0, x1End, y1End);
470 event1.release(1, x2End, y2End);
474 function fetchRootItem(item) {
476 qtest_fail("no item given", 1);
479 return fetchRootItem(item.parent)
484 function touchPress(item, x, y) {
486 qtest_fail("no item given", 1);
488 multiTouchPress(0, item, x, y, []);
491 /*! \brief Release a touch point
493 \param touchId The touchId to be pressed
495 \param x The x coordinate of the press, defaults to horizontal center
496 \param y The y coordinate of the press, defaults to vertical center
497 \param stationaryPoints An array of touchIds which are "already touched"
499 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
501 qtest_fail("no item given", 1);
503 if (typeof touchId !== "number") touchId = 0;
504 if (typeof x !== "number") x = item.width / 2;
505 if (typeof y !== "number") y = item.height / 2;
506 if (typeof stationaryPoints !== "object") stationaryPoints = []
507 var root = fetchRootItem(item)
508 var rootPoint = item.mapToItem(root, x, y)
510 var event = touchEvent(item)
511 event.press(touchId, rootPoint.x, rootPoint.y)
512 for (var i = 0; i < stationaryPoints.length; i++) {
513 event.stationary(stationaryPoints[i]);
518 function touchRelease(item, x, y) {
520 qtest_fail("no item given", 1);
522 multiTouchRelease(0, item, x, y, []);
525 /*! \brief Release a touch point
527 \param touchId The touchId to be released
529 \param x The x coordinate of the release, defaults to horizontal center
530 \param y The y coordinate of the release, defaults to vertical center
531 \param stationaryPoints An array of touchIds which are "still touched"
533 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
535 qtest_fail("no item given", 1);
537 if (typeof touchId !== "number") touchId = 0;
538 if (typeof x !== "number") x = item.width / 2;
539 if (typeof y !== "number") y = item.height / 2;
540 if (typeof stationaryPoints !== "object") stationaryPoints = []
541 var root = fetchRootItem(item)
542 var rootPoint = item.mapToItem(root, x, y)
544 var event = touchEvent(item)
545 event.release(touchId, rootPoint.x, rootPoint.y)
546 for (var i = 0; i < stationaryPoints.length; i++) {
547 event.stationary(stationaryPoints[i]);
552 /*! \brief Tap the item with a touch event.
554 \param item The item to be tapped
555 \param x The x coordinate of the tap, defaults to horizontal center
556 \param y The y coordinate of the tap, defaults to vertical center
558 function tap(item, x, y) {
560 qtest_fail("no item given", 1);
562 multiTouchTap([0], item, x, y);
565 function multiTouchTap(touchIds, item, x, y) {
567 qtest_fail("no item given", 1);
569 if (typeof touchIds !== "object") touchIds = [0];
570 if (typeof x !== "number") x = item.width / 2;
571 if (typeof y !== "number") y = item.height / 2;
573 var root = fetchRootItem(item)
574 var rootPoint = item.mapToItem(root, x, y)
576 var event = touchEvent(item)
577 for (var i = 0; i < touchIds.length; i++) {
578 event.press(touchIds[i], rootPoint.x, rootPoint.y)
582 event = touchEvent(item)
583 for (i = 0; i < touchIds.length; i++) {
584 event.release(touchIds[i], rootPoint.x, rootPoint.y)
590 Component.onCompleted: {
591 var rootItem = parent;
592 while (rootItem.parent != undefined) {
593 rootItem = rootItem.parent;
595 removeTimeConstraintsFromSwipeAreas(rootItem);
599 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
600 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
601 Thus to remove a variable that qmltests cannot really control, namely time, this
602 function removes all constraints from SwipeAreas that are sensible to
605 This effectively makes SwipeAreas easier to fool.
607 function removeTimeConstraintsFromSwipeAreas(item) {
609 qtest_fail("no item given", 1);
611 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
612 UbuntuTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
614 for (var i in item.children) {
615 removeTimeConstraintsFromSwipeAreas(item.children[i]);
621 Wait until any transition animation has finished for the given StateGroup or Item
623 function waitUntilTransitionsEnd(stateGroup) {
624 var transitions = stateGroup.transitions;
625 for (var i = 0; i < transitions.length; ++i) {
626 var transition = transitions[i];
627 tryCompare(transition, "running", false, 2000);
632 kill all (fake) running apps, bringing Unity.Application back to its initial state
634 function killApps() {
635 while (ApplicationManager.count > 0) {
636 var application = ApplicationManager.get(0);
637 ApplicationManager.stopApplication(application.appId);
638 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
639 // They won't be gone until those surface items are destroyed.
640 tryCompareFunction(function() { return application.surfaceList.count }, 0);
641 tryCompare(application, "state", ApplicationInfo.Stopped);
643 compare(ApplicationManager.count, 0);