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/>.
20import QtMir.Application 0.1
21import WindowManager 1.0
22import Lomiri.Components 1.3
23import Lomiri.Test 1.0 as LomiriTest
24import Lomiri.SelfTest 0.1 as UT
29 property var util: TestUtil {id:util}
31 // This is needed for waitForRendering calls to return
32 // if the watched element already got rendered
37 parent: testCase.parent
38 border { width: units.dp(1); color: "black" }
41 visible: testCase.running
43 RotationAnimation on rotation {
44 running: rotatingRectangle.visible
47 loops: Animation.Infinite
53 target: WindowManagerObjects
54 restoreMode: Binding.RestoreBinding
55 property: "surfaceManager"
60 target: WindowManagerObjects
61 restoreMode: Binding.RestoreBinding
62 property: "applicationManager"
63 value: ApplicationManager
66 // Fake implementation to be provided to items under test
67 property var fakeDateTime: new function() {
68 this.currentTimeMs = 0
69 this.getCurrentTimeMs = function() {return this.currentTimeMs}
72 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
73 function mouseClick(item, x, y, button, modifiers, delay) {
75 qtest_fail("no item given", 1);
77 if (button === undefined)
78 button = Qt.LeftButton;
79 if (modifiers === undefined)
80 modifiers = Qt.NoModifier;
81 if (delay === undefined)
87 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
88 qtest_fail("window not shown", 2);
91 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
92 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
94 qtest_fail("no item given", 1);
96 if (button === undefined)
97 button = Qt.LeftButton;
98 if (modifiers === undefined)
99 modifiers = Qt.NoModifier;
100 if (delay === undefined)
106 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
107 qtest_fail("window not shown", 2)
110 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
111 function mousePress(item, x, y, button, modifiers, delay) {
113 qtest_fail("no item given", 1);
115 if (button === undefined)
116 button = Qt.LeftButton;
117 if (modifiers === undefined)
118 modifiers = Qt.NoModifier;
119 if (delay === undefined)
125 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
126 qtest_fail("window not shown", 2)
129 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
130 function mouseRelease(item, x, y, button, modifiers, delay) {
132 qtest_fail("no item given", 1);
134 if (button === undefined)
135 button = Qt.LeftButton;
136 if (modifiers === undefined)
137 modifiers = Qt.NoModifier;
138 if (delay === undefined)
144 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
145 qtest_fail("window not shown", 2)
149 // Flickable won't recognise a single mouse move as dragging the flickable.
150 // Use 5 steps because it's what
151 // Qt uses in QQuickViewTestUtil::flick
152 // speed is in pixels/second
153 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
156 qtest_fail("no item given", 1);
158 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
159 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
161 // set a default speed if not specified
162 speed = (speed != null) ? speed : units.gu(10);
164 // set a default iterations if not specified
165 iterations = (iterations !== undefined) ? iterations : 5
167 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
168 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
170 var timeStep = totalTime / iterations
171 var diffX = (toX - x) / iterations
172 var diffY = (toY - y) / iterations
174 fakeDateTime.currentTimeMs += timeStep
175 mousePress(item, x, y)
177 for (var i = 0; i < iterations; ++i) {
178 fakeDateTime.currentTimeMs += timeStep
179 if (i === iterations - 1) {
180 // Avoid any rounding errors by making the last move be at precisely
181 // the point specified
182 mouseMove(item, toX, toY, timeStep)
184 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, timeStep)
188 fakeDateTime.currentTimeMs += timeStep
189 mouseRelease(item, toX, toY)
194 // Find an object with the given name in the children tree of "obj"
195 function findChild(obj, objectName, timeout) {
197 qtest_fail("no obj given", 1);
199 return findChildInWithTimeout(obj, "children", objectName, timeout);
202 // Find an object with the given name in the children tree of "obj"
203 // Including invisible children like animations, timers etc.
204 // Note: you should use findChild if you're not sure you need this
205 // as this tree is much bigger and might contain stuff that goes
207 function findInvisibleChild(obj, objectName, timeout) {
209 qtest_fail("no obj given", 1);
211 return findChildInWithTimeout(obj, "data", objectName, timeout);
214 // Find a child in the named property with timeout
215 function findChildInWithTimeout(obj, prop, objectName, timeout) {
217 qtest_fail("no obj given", 1);
220 if (timeout === undefined)
223 var child = findChildIn(obj, prop, objectName);
225 while (timeSpent < timeout && !child) {
228 child = findChildIn(obj, prop, objectName);
233 // Find a child in the named property
234 function findChildIn(obj, prop, objectName) {
236 qtest_fail("no obj given", 1);
238 var childs = new Array(0);
240 while (childs.length > 0) {
241 if (childs[0].objectName == objectName) {
244 for (var i in childs[0][prop]) {
245 childs.push(childs[0][prop][i])
252 function findChildsByType(obj, typeName) {
254 qtest_fail("no obj given", 1);
256 var res = new Array(0);
257 for (var i in obj.children) {
258 var c = obj.children[i];
259 if (UT.Util.isInstanceOf(c, typeName)) {
262 res = res.concat(findChildsByType(c, typeName));
267 // Type a full string instead of keyClick letter by letter
268 function typeString(str) {
269 for (var i = 0; i < str.length; i++) {
274 // Keeps executing a given parameter-less function until it returns the given
275 // expected result or the timemout is reached (in which case a test failure
277 function tryCompareFunction(func, expectedResult, timeout, message) {
279 if (timeout === undefined)
283 while (timeSpent < timeout && !success) {
284 actualResult = func()
285 success = qtest_compareInternal(actualResult, expectedResult)
286 if (success === false) {
292 var act = qtest_results.stringify(actualResult)
293 var exp = qtest_results.stringify(expectedResult)
294 if (!qtest_results.compare(success,
295 message || "function returned unexpected result",
297 util.callerFile(), util.callerLine())) {
298 throw new Error("QtQuickTest::fail")
302 function flickToYEnd(item) {
304 qtest_fail("no item given", 1);
307 var x = item.width / 2;
308 var y = item.height - units.gu(1);
309 var toY = units.gu(1);
310 var maxIterations = 5 + item.contentHeight / item.height;
311 while (i < maxIterations && !item.atYEnd) {
312 touchFlick(item, x, y, x, toY);
313 tryCompare(item, "moving", false);
316 tryCompare(item, "atYEnd", true);
319 function touchEvent(item) {
320 return UT.Util.touchEvent(item)
323 // speed is in pixels/second
324 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
326 qtest_fail("no item given", 1);
328 // Make sure the item is rendered
329 waitForRendering(item);
331 var root = fetchRootItem(item);
332 var rootFrom = item.mapToItem(root, x, y);
333 var rootTo = item.mapToItem(root, toX, toY);
335 // Default to true for beginTouch if not present
336 beginTouch = (beginTouch !== undefined) ? beginTouch : true
338 // Default to true for endTouch if not present
339 endTouch = (endTouch !== undefined) ? endTouch : true
341 // Set a default speed if not specified
342 speed = (speed !== undefined) ? speed : units.gu(100)
344 // Set a default iterations if not specified
345 var iterations = (iterations !== undefined) ? iterations : 10
347 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.y - rootFrom.y, 2))
348 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
350 var timeStep = totalTime / iterations
351 var diffX = (rootTo.x - rootFrom.x) / iterations
352 var diffY = (rootTo.y - rootFrom.y) / iterations
354 fakeDateTime.currentTimeMs += timeStep
356 var event = touchEvent(item)
357 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
360 for (var i = 0; i < iterations; ++i) {
361 fakeDateTime.currentTimeMs += timeStep
362 if (i === iterations - 1) {
363 // Avoid any rounding errors by making the last move be at precisely
364 // the point specified
366 var event = touchEvent(item)
367 event.move(0 /* touchId */, rootTo.x, rootTo.y)
371 var event = touchEvent(item)
372 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
377 fakeDateTime.currentTimeMs += timeStep
378 var event = touchEvent(item)
379 event.release(0 /* touchId */, rootTo.x, rootTo.y)
384 // perform a drag in the given direction until the given condition is true
385 // The condition is a function to be evaluated after every step
386 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
388 qtest_fail("no item given", 1);
390 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
393 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
395 qtest_fail("no item given", 1);
397 var root = fetchRootItem(item);
398 var pos = item.mapToItem(root, startX, startY);
400 // convert step to scene coords
402 var stepStart = item.mapToItem(root, 0, 0);
403 var stepEnd = item.mapToItem(root, stepX, stepY);
405 stepX = stepEnd.x - stepStart.x;
406 stepY = stepEnd.y - stepStart.y;
408 var event = touchEvent(item)
409 for (var i = 0; i < touchIds.length; i++) {
410 event.press(touchIds[i], pos.x, pos.y)
414 // we have to stop at some point
418 while (!condition() && stepsDone < maxSteps) {
420 fakeDateTime.currentTimeMs += 25;
425 event = touchEvent(item);
426 for (i = 0; i < touchIds.length; i++) {
427 event.move(touchIds[i], pos.x, pos.y);
434 event = touchEvent(item)
435 for (i = 0; i < touchIds.length; i++) {
436 event.release(touchIds[i], pos.x, pos.y)
441 function touchMove(item, tox, toy) {
443 qtest_fail("no item given", 1);
445 multiTouchMove(0, item, tox, toy);
448 function multiTouchMove(touchId, item, tox, toy) {
450 qtest_fail("no item given", 1);
452 if (typeof touchId !== "number") touchId = 0;
453 var root = fetchRootItem(item)
454 var rootPoint = item.mapToItem(root, tox, toy)
456 var event = touchEvent(item);
457 event.move(touchId, rootPoint.x, rootPoint.y);
461 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
463 qtest_fail("no item given", 1);
465 // Make sure the item is rendered
466 waitForRendering(item);
468 var event1 = touchEvent(item);
470 event1.press(0, x1Start, y1Start);
473 event1.move(0, x1Start, y1Start);
474 event1.press(1, x2Start, y2Start);
478 for (var i = 0.0; i < 1.0; i += 0.02) {
479 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
480 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
485 event1.release(0, x1End, y1End);
486 event1.release(1, x2End, y2End);
490 function fetchRootItem(item) {
492 qtest_fail("no item given", 1);
495 return fetchRootItem(item.parent)
500 function touchPress(item, x, y) {
502 qtest_fail("no item given", 1);
504 multiTouchPress(0, item, x, y, []);
507 /*! \brief Release a touch point
509 \param touchId The touchId to be pressed
511 \param x The x coordinate of the press, defaults to horizontal center
512 \param y The y coordinate of the press, defaults to vertical center
513 \param stationaryPoints An array of touchIds which are "already touched"
515 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
517 qtest_fail("no item given", 1);
519 if (typeof touchId !== "number") touchId = 0;
520 if (typeof x !== "number") x = item.width / 2;
521 if (typeof y !== "number") y = item.height / 2;
522 if (typeof stationaryPoints !== "object") stationaryPoints = []
523 var root = fetchRootItem(item)
524 var rootPoint = item.mapToItem(root, x, y)
526 var event = touchEvent(item)
527 event.press(touchId, rootPoint.x, rootPoint.y)
528 for (var i = 0; i < stationaryPoints.length; i++) {
529 event.stationary(stationaryPoints[i]);
534 function touchRelease(item, x, y) {
536 qtest_fail("no item given", 1);
538 multiTouchRelease(0, item, x, y, []);
541 /*! \brief Release a touch point
543 \param touchId The touchId to be released
545 \param x The x coordinate of the release, defaults to horizontal center
546 \param y The y coordinate of the release, defaults to vertical center
547 \param stationaryPoints An array of touchIds which are "still touched"
549 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
551 qtest_fail("no item given", 1);
553 if (typeof touchId !== "number") touchId = 0;
554 if (typeof x !== "number") x = item.width / 2;
555 if (typeof y !== "number") y = item.height / 2;
556 if (typeof stationaryPoints !== "object") stationaryPoints = []
557 var root = fetchRootItem(item)
558 var rootPoint = item.mapToItem(root, x, y)
560 var event = touchEvent(item)
561 event.release(touchId, rootPoint.x, rootPoint.y)
562 for (var i = 0; i < stationaryPoints.length; i++) {
563 event.stationary(stationaryPoints[i]);
568 /*! \brief Tap the item with a touch event.
570 \param item The item to be tapped
571 \param x The x coordinate of the tap, defaults to horizontal center
572 \param y The y coordinate of the tap, defaults to vertical center
574 function tap(item, x, y) {
576 qtest_fail("no item given", 1);
578 multiTouchTap([0], item, x, y);
581 function multiTouchTap(touchIds, item, x, y) {
583 qtest_fail("no item given", 1);
585 if (typeof touchIds !== "object") touchIds = [0];
586 if (typeof x !== "number") x = item.width / 2;
587 if (typeof y !== "number") y = item.height / 2;
589 var root = fetchRootItem(item)
590 var rootPoint = item.mapToItem(root, x, y)
592 var event = touchEvent(item)
593 for (var i = 0; i < touchIds.length; i++) {
594 event.press(touchIds[i], rootPoint.x, rootPoint.y)
598 event = touchEvent(item)
599 for (i = 0; i < touchIds.length; i++) {
600 event.release(touchIds[i], rootPoint.x, rootPoint.y)
606 Component.onCompleted: {
607 var rootItem = parent;
608 while (rootItem.parent != undefined) {
609 rootItem = rootItem.parent;
611 removeTimeConstraintsFromSwipeAreas(rootItem);
615 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
616 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
617 Thus to remove a variable that qmltests cannot really control, namely time, this
618 function removes all constraints from SwipeAreas that are sensible to
621 This effectively makes SwipeAreas easier to fool.
623 function removeTimeConstraintsFromSwipeAreas(item) {
625 qtest_fail("no item given", 1);
627 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
628 LomiriTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
630 for (var i in item.children) {
631 removeTimeConstraintsFromSwipeAreas(item.children[i]);
637 Wait until any transition animation has finished for the given StateGroup or Item
639 function waitUntilTransitionsEnd(stateGroup) {
640 var transitions = stateGroup.transitions;
641 for (var i = 0; i < transitions.length; ++i) {
642 var transition = transitions[i];
643 tryCompare(transition, "running", false, 2000);
648 kill all (fake) running apps, bringing QtMir.Application back to its initial state
650 function killApps() {
651 while (ApplicationManager.count > 0) {
652 var application = ApplicationManager.get(0);
653 ApplicationManager.stopApplication(application.appId);
654 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
655 // They won't be gone until those surface items are destroyed.
656 tryCompareFunction(function() { return application.surfaceList.count }, 0);
657 tryCompare(application, "state", ApplicationInfo.Stopped);
659 compare(ApplicationManager.count, 0);
660 SurfaceManager.releaseInputMethodSurface();