on 760bfdf help sign in
unity8/tests/utils/modules/Unity/Test/UnityTestCase.qml
636 lines 22 kB history raw
1/*
2 * Copyright 2013-2016 Canonical Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 */
17import QtQuick 2.4
18import QtTest 1.0
19import Unity.Application 0.1
20import Ubuntu.Components 1.3
21import Ubuntu.Test 1.0 as UbuntuTest
22import Unity.Test 0.1 as UT
23import Utils 0.1
25TestCase {
26 id: testCase
27 property var util: TestUtil {id:util}
29 // This is needed for waitForRendering calls to return
30 // if the watched element already got rendered
31 Rectangle {
32 id: rotatingRectangle
33 width: units.gu(1)
34 height: width
35 parent: testCase.parent
36 border { width: units.dp(1); color: "black" }
37 opacity: 0.6
39 visible: testCase.running
41 RotationAnimation on rotation {
42 running: rotatingRectangle.visible
43 from: 0
44 to: 360
45 loops: Animation.Infinite
46 duration: 1000
47 }
48 }
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}
54 }
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) {
58 if (!item)
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)
66 delay = -1;
67 if (x === undefined)
68 x = item.width / 2;
69 if (y === undefined)
70 y = item.height / 2;
71 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
72 qtest_fail("window not shown", 2);
73 }
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) {
77 if (!item)
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)
85 delay = -1;
86 if (x === undefined)
87 x = item.width / 2;
88 if (y === undefined)
89 y = item.height / 2;
90 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
91 qtest_fail("window not shown", 2)
92 }
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) {
96 if (!item)
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)
104 delay = -1;
105 if (x === undefined)
106 x = item.width / 2;
107 if (y === undefined)
108 y = item.height / 2;
109 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
110 qtest_fail("window not shown", 2)
111 }
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) {
115 if (!item)
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)
123 delay = -1;
124 if (x === undefined)
125 x = item.width / 2;
126 if (y === undefined)
127 y = item.height / 2;
128 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
129 qtest_fail("window not shown", 2)
130 }
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,
138 speed, iterations) {
139 if (!item)
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
157 if (pressMouse) {
158 fakeDateTime.currentTimeMs += timeStep
159 mousePress(item, x, y)
160 }
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, iterations / speed)
167 } else {
168 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
169 }
170 }
171 if (releaseMouse) {
172 fakeDateTime.currentTimeMs += timeStep
173 mouseRelease(item, toX, toY)
174 }
175 }
178 // Find an object with the given name in the children tree of "obj"
179 function findChild(obj, objectName) {
180 if (!obj)
181 qtest_fail("no obj given", 1);
183 return findChildIn(obj, "children", objectName);
184 }
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
190 // away randomly.
191 function findInvisibleChild(obj, objectName) {
192 if (!obj)
193 qtest_fail("no obj given", 1);
195 return findChildIn(obj, "data", objectName);
196 }
198 // Find a child in the named property
199 function findChildIn(obj, prop, objectName) {
200 if (!obj)
201 qtest_fail("no obj given", 1);
203 var childs = new Array(0);
204 childs.push(obj)
205 while (childs.length > 0) {
206 if (childs[0].objectName == objectName) {
207 return childs[0]
208 }
209 for (var i in childs[0][prop]) {
210 childs.push(childs[0][prop][i])
211 }
212 childs.splice(0, 1);
213 }
214 return null;
215 }
217 function findChildsByType(obj, typeName) {
218 if (!obj)
219 qtest_fail("no obj given", 1);
221 var res = new Array(0);
222 for (var i in obj.children) {
223 var c = obj.children[i];
224 if (UT.Util.isInstanceOf(c, typeName)) {
225 res.push(c)
226 }
227 res = res.concat(findChildsByType(c, typeName));
228 }
229 return res;
230 }
232 // Type a full string instead of keyClick letter by letter
233 function typeString(str) {
234 for (var i = 0; i < str.length; i++) {
235 keyClick(str[i])
236 }
237 }
239 // Keeps executing a given parameter-less function until it returns the given
240 // expected result or the timemout is reached (in which case a test failure
241 // is generated)
242 function tryCompareFunction(func, expectedResult, timeout) {
243 var timeSpent = 0
244 if (timeout === undefined)
245 timeout = 5000;
246 var success = false
247 var actualResult
248 while (timeSpent < timeout && !success) {
249 actualResult = func()
250 success = qtest_compareInternal(actualResult, expectedResult)
251 if (success === false) {
252 wait(50)
253 timeSpent += 50
254 }
255 }
257 var act = qtest_results.stringify(actualResult)
258 var exp = qtest_results.stringify(expectedResult)
259 if (!qtest_results.compare(success,
260 "function returned unexpected result",
261 act, exp,
262 util.callerFile(), util.callerLine())) {
263 throw new Error("QtQuickTest::fail")
264 }
265 }
267 function flickToYEnd(item) {
268 if (!item)
269 qtest_fail("no item given", 1);
271 var i = 0;
272 var x = item.width / 2;
273 var y = item.height - units.gu(1);
274 var toY = units.gu(1);
275 var maxIterations = 5 + item.contentHeight / item.height;
276 while (i < maxIterations && !item.atYEnd) {
277 touchFlick(item, x, y, x, toY);
278 tryCompare(item, "moving", false);
279 ++i;
280 }
281 tryCompare(item, "atYEnd", true);
282 }
284 function touchEvent(item) {
285 return UT.Util.touchEvent(item)
286 }
288 // speed is in pixels/second
289 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
290 if (!item)
291 qtest_fail("no item given", 1);
293 // Make sure the item is rendered
294 waitForRendering(item);
296 var root = fetchRootItem(item);
297 var rootFrom = item.mapToItem(root, x, y);
298 var rootTo = item.mapToItem(root, toX, toY);
300 // Default to true for beginTouch if not present
301 beginTouch = (beginTouch !== undefined) ? beginTouch : true
303 // Default to true for endTouch if not present
304 endTouch = (endTouch !== undefined) ? endTouch : true
306 // Set a default speed if not specified
307 speed = (speed !== undefined) ? speed : units.gu(10)
309 // Set a default iterations if not specified
310 var iterations = (iterations !== undefined) ? iterations : 10
312 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
313 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
315 var timeStep = totalTime / iterations
316 var diffX = (rootTo.x - rootFrom.x) / iterations
317 var diffY = (rootTo.y - rootFrom.y) / iterations
318 if (beginTouch) {
319 fakeDateTime.currentTimeMs += timeStep
321 var event = touchEvent(item)
322 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
323 event.commit()
324 }
325 for (var i = 0; i < iterations; ++i) {
326 fakeDateTime.currentTimeMs += timeStep
327 if (i === iterations - 1) {
328 // Avoid any rounding errors by making the last move be at precisely
329 // the point specified
330 wait(iterations / speed)
331 var event = touchEvent(item)
332 event.move(0 /* touchId */, rootTo.x, rootTo.y)
333 event.commit()
334 } else {
335 wait(iterations / speed)
336 var event = touchEvent(item)
337 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
338 event.commit()
339 }
340 }
341 if (endTouch) {
342 fakeDateTime.currentTimeMs += timeStep
343 var event = touchEvent(item)
344 event.release(0 /* touchId */, rootTo.x, rootTo.y)
345 event.commit()
346 }
347 }
349 // perform a drag in the given direction until the given condition is true
350 // The condition is a function to be evaluated after every step
351 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
352 if (!item)
353 qtest_fail("no item given", 1);
355 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
356 }
358 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
359 if (!item)
360 qtest_fail("no item given", 1);
362 var root = fetchRootItem(item);
363 var pos = item.mapToItem(root, startX, startY);
365 // convert step to scene coords
366 {
367 var stepStart = item.mapToItem(root, 0, 0);
368 var stepEnd = item.mapToItem(root, stepX, stepY);
369 }
370 stepX = stepEnd.x - stepStart.x;
371 stepY = stepEnd.y - stepStart.y;
373 var event = touchEvent(item)
374 for (var i = 0; i < touchIds.length; i++) {
375 event.press(touchIds[i], pos.x, pos.y)
376 }
377 event.commit()
379 // we have to stop at some point
380 var maxSteps = 100;
381 var stepsDone = 0;
383 while (!condition() && stepsDone < maxSteps) {
384 wait(25);
385 fakeDateTime.currentTimeMs += 25;
387 pos.x += stepX;
388 pos.y += stepY;
390 event = touchEvent(item);
391 for (i = 0; i < touchIds.length; i++) {
392 event.move(touchIds[i], pos.x, pos.y);
393 }
394 event.commit();
396 stepsDone += 1;
397 }
399 event = touchEvent(item)
400 for (i = 0; i < touchIds.length; i++) {
401 event.release(touchIds[i], pos.x, pos.y)
402 }
403 event.commit()
404 }
406 function touchMove(item, tox, toy) {
407 if (!item)
408 qtest_fail("no item given", 1);
410 multiTouchMove(0, item, tox, toy);
411 }
413 function multiTouchMove(touchId, item, tox, toy) {
414 if (!item)
415 qtest_fail("no item given", 1);
417 if (typeof touchId !== "number") touchId = 0;
418 var root = fetchRootItem(item)
419 var rootPoint = item.mapToItem(root, tox, toy)
421 var event = touchEvent(item);
422 event.move(touchId, rootPoint.x, rootPoint.y);
423 event.commit();
424 }
426 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
427 if (!item)
428 qtest_fail("no item given", 1);
430 // Make sure the item is rendered
431 waitForRendering(item);
433 var event1 = touchEvent(item);
434 // first finger
435 event1.press(0, x1Start, y1Start);
436 event1.commit();
437 // second finger
438 event1.move(0, x1Start, y1Start);
439 event1.press(1, x2Start, y2Start);
440 event1.commit();
442 // pinch
443 for (var i = 0.0; i < 1.0; i += 0.02) {
444 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
445 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
446 event1.commit();
447 }
449 // release
450 event1.release(0, x1End, y1End);
451 event1.release(1, x2End, y2End);
452 event1.commit();
453 }
455 function fetchRootItem(item) {
456 if (!item)
457 qtest_fail("no item given", 1);
459 if (item.parent)
460 return fetchRootItem(item.parent)
461 else
462 return item
463 }
465 function touchPress(item, x, y) {
466 if (!item)
467 qtest_fail("no item given", 1);
469 multiTouchPress(0, item, x, y, []);
470 }
472 /*! \brief Release a touch point
474 \param touchId The touchId to be pressed
475 \param item The item
476 \param x The x coordinate of the press, defaults to horizontal center
477 \param y The y coordinate of the press, defaults to vertical center
478 \param stationaryPoints An array of touchIds which are "already touched"
479 */
480 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
481 if (!item)
482 qtest_fail("no item given", 1);
484 if (typeof touchId !== "number") touchId = 0;
485 if (typeof x !== "number") x = item.width / 2;
486 if (typeof y !== "number") y = item.height / 2;
487 if (typeof stationaryPoints !== "object") stationaryPoints = []
488 var root = fetchRootItem(item)
489 var rootPoint = item.mapToItem(root, x, y)
491 var event = touchEvent(item)
492 event.press(touchId, rootPoint.x, rootPoint.y)
493 for (var i = 0; i < stationaryPoints.length; i++) {
494 event.stationary(stationaryPoints[i]);
495 }
496 event.commit()
497 }
499 function touchRelease(item, x, y) {
500 if (!item)
501 qtest_fail("no item given", 1);
503 multiTouchRelease(0, item, x, y, []);
504 }
506 /*! \brief Release a touch point
508 \param touchId The touchId to be released
509 \param item The item
510 \param x The x coordinate of the release, defaults to horizontal center
511 \param y The y coordinate of the release, defaults to vertical center
512 \param stationaryPoints An array of touchIds which are "still touched"
513 */
514 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
515 if (!item)
516 qtest_fail("no item given", 1);
518 if (typeof touchId !== "number") touchId = 0;
519 if (typeof x !== "number") x = item.width / 2;
520 if (typeof y !== "number") y = item.height / 2;
521 if (typeof stationaryPoints !== "object") stationaryPoints = []
522 var root = fetchRootItem(item)
523 var rootPoint = item.mapToItem(root, x, y)
525 var event = touchEvent(item)
526 event.release(touchId, rootPoint.x, rootPoint.y)
527 for (var i = 0; i < stationaryPoints.length; i++) {
528 event.stationary(stationaryPoints[i]);
529 }
530 event.commit()
531 }
533 /*! \brief Tap the item with a touch event.
535 \param item The item to be tapped
536 \param x The x coordinate of the tap, defaults to horizontal center
537 \param y The y coordinate of the tap, defaults to vertical center
538 */
539 function tap(item, x, y) {
540 if (!item)
541 qtest_fail("no item given", 1);
543 multiTouchTap([0], item, x, y);
544 }
546 function multiTouchTap(touchIds, item, x, y) {
547 if (!item)
548 qtest_fail("no item given", 1);
550 if (typeof touchIds !== "object") touchIds = [0];
551 if (typeof x !== "number") x = item.width / 2;
552 if (typeof y !== "number") y = item.height / 2;
554 var root = fetchRootItem(item)
555 var rootPoint = item.mapToItem(root, x, y)
557 var event = touchEvent(item)
558 for (var i = 0; i < touchIds.length; i++) {
559 event.press(touchIds[i], rootPoint.x, rootPoint.y)
560 }
561 event.commit()
563 event = touchEvent(item)
564 for (i = 0; i < touchIds.length; i++) {
565 event.release(touchIds[i], rootPoint.x, rootPoint.y)
566 }
567 event.commit()
568 }
571 Component.onCompleted: {
572 var rootItem = parent;
573 while (rootItem.parent != undefined) {
574 rootItem = rootItem.parent;
575 }
576 removeTimeConstraintsFromSwipeAreas(rootItem);
577 }
579 /*
580 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
581 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
582 Thus to remove a variable that qmltests cannot really control, namely time, this
583 function removes all constraints from SwipeAreas that are sensible to
584 elapsed time.
586 This effectively makes SwipeAreas easier to fool.
587 */
588 function removeTimeConstraintsFromSwipeAreas(item) {
589 if (!item)
590 qtest_fail("no item given", 1);
592 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
593 UbuntuTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
594 } else {
595 for (var i in item.children) {
596 removeTimeConstraintsFromSwipeAreas(item.children[i]);
597 }
598 }
599 }
601 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
602 function waitForRendering(item, timeout) {
603 if (timeout === undefined)
604 timeout = 5000;
605 if (!item)
606 qtest_fail("No item given to waitForRendering", 1);
607 return qtest_results.waitForRendering(item, timeout);
608 }
610 /*
611 Wait until any transition animation has finished for the given StateGroup or Item
612 */
613 function waitUntilTransitionsEnd(stateGroup) {
614 var transitions = stateGroup.transitions;
615 for (var i = 0; i < transitions.length; ++i) {
616 var transition = transitions[i];
617 tryCompare(transition, "running", false, 2000);
618 }
619 }
621 /*
622 kill all (fake) running apps but unity8-dash, bringing Unity.Application back to its initial state
623 */
624 function killApps() {
625 while (ApplicationManager.count > 1) {
626 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
627 var application = ApplicationManager.get(appIndex);
628 ApplicationManager.stopApplication(application.appId);
629 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
630 // They won't be gone until those surface items are destroyed.
631 tryCompareFunction(function() { return application.surfaceList.count }, 0);
632 tryCompare(application, "state", ApplicationInfo.Stopped);
633 }
634 compare(ApplicationManager.count, 1);
635 }