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/>.
19import QtMir.Application 0.1
20import WindowManager 1.0
21import Lomiri.Components 1.3
22import Lomiri.Test 1.0 as LomiriTest
23import Lomiri.SelfTest 0.1 as UT
28 property var util: TestUtil {id:util}
30 // This is needed for waitForRendering calls to return
31 // if the watched element already got rendered
36 parent: testCase.parent
37 border { width: units.dp(1); color: "black" }
40 visible: testCase.running
42 RotationAnimation on rotation {
43 running: rotatingRectangle.visible
46 loops: Animation.Infinite
52 target: WindowManagerObjects
53 property: "surfaceManager"
58 target: WindowManagerObjects
59 property: "applicationManager"
60 value: ApplicationManager
63 // Fake implementation to be provided to items under test
64 property var fakeDateTime: new function() {
65 this.currentTimeMs = 0
66 this.getCurrentTimeMs = function() {return this.currentTimeMs}
69 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
70 function mouseClick(item, x, y, button, modifiers, delay) {
72 qtest_fail("no item given", 1);
74 if (button === undefined)
75 button = Qt.LeftButton;
76 if (modifiers === undefined)
77 modifiers = Qt.NoModifier;
78 if (delay === undefined)
84 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
85 qtest_fail("window not shown", 2);
88 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
89 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
91 qtest_fail("no item given", 1);
93 if (button === undefined)
94 button = Qt.LeftButton;
95 if (modifiers === undefined)
96 modifiers = Qt.NoModifier;
97 if (delay === undefined)
103 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
104 qtest_fail("window not shown", 2)
107 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
108 function mousePress(item, x, y, button, modifiers, delay) {
110 qtest_fail("no item given", 1);
112 if (button === undefined)
113 button = Qt.LeftButton;
114 if (modifiers === undefined)
115 modifiers = Qt.NoModifier;
116 if (delay === undefined)
122 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
123 qtest_fail("window not shown", 2)
126 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
127 function mouseRelease(item, x, y, button, modifiers, delay) {
129 qtest_fail("no item given", 1);
131 if (button === undefined)
132 button = Qt.LeftButton;
133 if (modifiers === undefined)
134 modifiers = Qt.NoModifier;
135 if (delay === undefined)
141 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
142 qtest_fail("window not shown", 2)
146 // Flickable won't recognise a single mouse move as dragging the flickable.
147 // Use 5 steps because it's what
148 // Qt uses in QQuickViewTestUtil::flick
149 // speed is in pixels/second
150 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
153 qtest_fail("no item given", 1);
155 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
156 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
158 // set a default speed if not specified
159 speed = (speed != null) ? speed : units.gu(10);
161 // set a default iterations if not specified
162 iterations = (iterations !== undefined) ? iterations : 5
164 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
165 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
167 var timeStep = totalTime / iterations
168 var diffX = (toX - x) / iterations
169 var diffY = (toY - y) / iterations
171 fakeDateTime.currentTimeMs += timeStep
172 mousePress(item, x, y)
174 for (var i = 0; i < iterations; ++i) {
175 fakeDateTime.currentTimeMs += timeStep
176 if (i === iterations - 1) {
177 // Avoid any rounding errors by making the last move be at precisely
178 // the point specified
179 mouseMove(item, toX, toY, timeStep)
181 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, timeStep)
185 fakeDateTime.currentTimeMs += timeStep
186 mouseRelease(item, toX, toY)
191 // Find an object with the given name in the children tree of "obj"
192 function findChild(obj, objectName, timeout) {
194 qtest_fail("no obj given", 1);
196 return findChildInWithTimeout(obj, "children", objectName, timeout);
199 // Find an object with the given name in the children tree of "obj"
200 // Including invisible children like animations, timers etc.
201 // Note: you should use findChild if you're not sure you need this
202 // as this tree is much bigger and might contain stuff that goes
204 function findInvisibleChild(obj, objectName, timeout) {
206 qtest_fail("no obj given", 1);
208 return findChildInWithTimeout(obj, "data", objectName, timeout);
211 // Find a child in the named property with timeout
212 function findChildInWithTimeout(obj, prop, objectName, timeout) {
214 qtest_fail("no obj given", 1);
217 if (timeout === undefined)
220 var child = findChildIn(obj, prop, objectName);
222 while (timeSpent < timeout && !child) {
225 child = findChildIn(obj, prop, objectName);
230 // Find a child in the named property
231 function findChildIn(obj, prop, objectName) {
233 qtest_fail("no obj given", 1);
235 var childs = new Array(0);
237 while (childs.length > 0) {
238 if (childs[0].objectName == objectName) {
241 for (var i in childs[0][prop]) {
242 childs.push(childs[0][prop][i])
249 function findChildsByType(obj, typeName) {
251 qtest_fail("no obj given", 1);
253 var res = new Array(0);
254 for (var i in obj.children) {
255 var c = obj.children[i];
256 if (UT.Util.isInstanceOf(c, typeName)) {
259 res = res.concat(findChildsByType(c, typeName));
264 // Type a full string instead of keyClick letter by letter
265 function typeString(str) {
266 for (var i = 0; i < str.length; i++) {
271 // Keeps executing a given parameter-less function until it returns the given
272 // expected result or the timemout is reached (in which case a test failure
274 function tryCompareFunction(func, expectedResult, timeout, message) {
276 if (timeout === undefined)
280 while (timeSpent < timeout && !success) {
281 actualResult = func()
282 success = qtest_compareInternal(actualResult, expectedResult)
283 if (success === false) {
289 var act = qtest_results.stringify(actualResult)
290 var exp = qtest_results.stringify(expectedResult)
291 if (!qtest_results.compare(success,
292 message || "function returned unexpected result",
294 util.callerFile(), util.callerLine())) {
295 throw new Error("QtQuickTest::fail")
299 function flickToYEnd(item) {
301 qtest_fail("no item given", 1);
304 var x = item.width / 2;
305 var y = item.height - units.gu(1);
306 var toY = units.gu(1);
307 var maxIterations = 5 + item.contentHeight / item.height;
308 while (i < maxIterations && !item.atYEnd) {
309 touchFlick(item, x, y, x, toY);
310 tryCompare(item, "moving", false);
313 tryCompare(item, "atYEnd", true);
316 function touchEvent(item) {
317 return UT.Util.touchEvent(item)
320 // speed is in pixels/second
321 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
323 qtest_fail("no item given", 1);
325 // Make sure the item is rendered
326 waitForRendering(item);
328 var root = fetchRootItem(item);
329 var rootFrom = item.mapToItem(root, x, y);
330 var rootTo = item.mapToItem(root, toX, toY);
332 // Default to true for beginTouch if not present
333 beginTouch = (beginTouch !== undefined) ? beginTouch : true
335 // Default to true for endTouch if not present
336 endTouch = (endTouch !== undefined) ? endTouch : true
338 // Set a default speed if not specified
339 speed = (speed !== undefined) ? speed : units.gu(100)
341 // Set a default iterations if not specified
342 var iterations = (iterations !== undefined) ? iterations : 10
344 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.y - rootFrom.y, 2))
345 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
347 var timeStep = totalTime / iterations
348 var diffX = (rootTo.x - rootFrom.x) / iterations
349 var diffY = (rootTo.y - rootFrom.y) / iterations
351 fakeDateTime.currentTimeMs += timeStep
353 var event = touchEvent(item)
354 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
357 for (var i = 0; i < iterations; ++i) {
358 fakeDateTime.currentTimeMs += timeStep
359 if (i === iterations - 1) {
360 // Avoid any rounding errors by making the last move be at precisely
361 // the point specified
363 var event = touchEvent(item)
364 event.move(0 /* touchId */, rootTo.x, rootTo.y)
368 var event = touchEvent(item)
369 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
374 fakeDateTime.currentTimeMs += timeStep
375 var event = touchEvent(item)
376 event.release(0 /* touchId */, rootTo.x, rootTo.y)
381 // perform a drag in the given direction until the given condition is true
382 // The condition is a function to be evaluated after every step
383 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
385 qtest_fail("no item given", 1);
387 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
390 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
392 qtest_fail("no item given", 1);
394 var root = fetchRootItem(item);
395 var pos = item.mapToItem(root, startX, startY);
397 // convert step to scene coords
399 var stepStart = item.mapToItem(root, 0, 0);
400 var stepEnd = item.mapToItem(root, stepX, stepY);
402 stepX = stepEnd.x - stepStart.x;
403 stepY = stepEnd.y - stepStart.y;
405 var event = touchEvent(item)
406 for (var i = 0; i < touchIds.length; i++) {
407 event.press(touchIds[i], pos.x, pos.y)
411 // we have to stop at some point
415 while (!condition() && stepsDone < maxSteps) {
417 fakeDateTime.currentTimeMs += 25;
422 event = touchEvent(item);
423 for (i = 0; i < touchIds.length; i++) {
424 event.move(touchIds[i], pos.x, pos.y);
431 event = touchEvent(item)
432 for (i = 0; i < touchIds.length; i++) {
433 event.release(touchIds[i], pos.x, pos.y)
438 function touchMove(item, tox, toy) {
440 qtest_fail("no item given", 1);
442 multiTouchMove(0, item, tox, toy);
445 function multiTouchMove(touchId, item, tox, toy) {
447 qtest_fail("no item given", 1);
449 if (typeof touchId !== "number") touchId = 0;
450 var root = fetchRootItem(item)
451 var rootPoint = item.mapToItem(root, tox, toy)
453 var event = touchEvent(item);
454 event.move(touchId, rootPoint.x, rootPoint.y);
458 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
460 qtest_fail("no item given", 1);
462 // Make sure the item is rendered
463 waitForRendering(item);
465 var event1 = touchEvent(item);
467 event1.press(0, x1Start, y1Start);
470 event1.move(0, x1Start, y1Start);
471 event1.press(1, x2Start, y2Start);
475 for (var i = 0.0; i < 1.0; i += 0.02) {
476 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
477 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
482 event1.release(0, x1End, y1End);
483 event1.release(1, x2End, y2End);
487 function fetchRootItem(item) {
489 qtest_fail("no item given", 1);
492 return fetchRootItem(item.parent)
497 function touchPress(item, x, y) {
499 qtest_fail("no item given", 1);
501 multiTouchPress(0, item, x, y, []);
504 /*! \brief Release a touch point
506 \param touchId The touchId to be pressed
508 \param x The x coordinate of the press, defaults to horizontal center
509 \param y The y coordinate of the press, defaults to vertical center
510 \param stationaryPoints An array of touchIds which are "already touched"
512 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
514 qtest_fail("no item given", 1);
516 if (typeof touchId !== "number") touchId = 0;
517 if (typeof x !== "number") x = item.width / 2;
518 if (typeof y !== "number") y = item.height / 2;
519 if (typeof stationaryPoints !== "object") stationaryPoints = []
520 var root = fetchRootItem(item)
521 var rootPoint = item.mapToItem(root, x, y)
523 var event = touchEvent(item)
524 event.press(touchId, rootPoint.x, rootPoint.y)
525 for (var i = 0; i < stationaryPoints.length; i++) {
526 event.stationary(stationaryPoints[i]);
531 function touchRelease(item, x, y) {
533 qtest_fail("no item given", 1);
535 multiTouchRelease(0, item, x, y, []);
538 /*! \brief Release a touch point
540 \param touchId The touchId to be released
542 \param x The x coordinate of the release, defaults to horizontal center
543 \param y The y coordinate of the release, defaults to vertical center
544 \param stationaryPoints An array of touchIds which are "still touched"
546 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
548 qtest_fail("no item given", 1);
550 if (typeof touchId !== "number") touchId = 0;
551 if (typeof x !== "number") x = item.width / 2;
552 if (typeof y !== "number") y = item.height / 2;
553 if (typeof stationaryPoints !== "object") stationaryPoints = []
554 var root = fetchRootItem(item)
555 var rootPoint = item.mapToItem(root, x, y)
557 var event = touchEvent(item)
558 event.release(touchId, rootPoint.x, rootPoint.y)
559 for (var i = 0; i < stationaryPoints.length; i++) {
560 event.stationary(stationaryPoints[i]);
565 /*! \brief Tap the item with a touch event.
567 \param item The item to be tapped
568 \param x The x coordinate of the tap, defaults to horizontal center
569 \param y The y coordinate of the tap, defaults to vertical center
571 function tap(item, x, y) {
573 qtest_fail("no item given", 1);
575 multiTouchTap([0], item, x, y);
578 function multiTouchTap(touchIds, item, x, y) {
580 qtest_fail("no item given", 1);
582 if (typeof touchIds !== "object") touchIds = [0];
583 if (typeof x !== "number") x = item.width / 2;
584 if (typeof y !== "number") y = item.height / 2;
586 var root = fetchRootItem(item)
587 var rootPoint = item.mapToItem(root, x, y)
589 var event = touchEvent(item)
590 for (var i = 0; i < touchIds.length; i++) {
591 event.press(touchIds[i], rootPoint.x, rootPoint.y)
595 event = touchEvent(item)
596 for (i = 0; i < touchIds.length; i++) {
597 event.release(touchIds[i], rootPoint.x, rootPoint.y)
603 Component.onCompleted: {
604 var rootItem = parent;
605 while (rootItem.parent != undefined) {
606 rootItem = rootItem.parent;
608 removeTimeConstraintsFromSwipeAreas(rootItem);
612 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
613 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
614 Thus to remove a variable that qmltests cannot really control, namely time, this
615 function removes all constraints from SwipeAreas that are sensible to
618 This effectively makes SwipeAreas easier to fool.
620 function removeTimeConstraintsFromSwipeAreas(item) {
622 qtest_fail("no item given", 1);
624 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
625 LomiriTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
627 for (var i in item.children) {
628 removeTimeConstraintsFromSwipeAreas(item.children[i]);
634 Wait until any transition animation has finished for the given StateGroup or Item
636 function waitUntilTransitionsEnd(stateGroup) {
637 var transitions = stateGroup.transitions;
638 for (var i = 0; i < transitions.length; ++i) {
639 var transition = transitions[i];
640 tryCompare(transition, "running", false, 2000);
645 kill all (fake) running apps, bringing QtMir.Application back to its initial state
647 function killApps() {
648 while (ApplicationManager.count > 0) {
649 var application = ApplicationManager.get(0);
650 ApplicationManager.stopApplication(application.appId);
651 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
652 // They won't be gone until those surface items are destroyed.
653 tryCompareFunction(function() { return application.surfaceList.count }, 0);
654 tryCompare(application, "state", ApplicationInfo.Stopped);
656 compare(ApplicationManager.count, 0);
657 SurfaceManager.releaseInputMethodSurface();