on bionic help sign in
unity8/tests/utils/modules/Unity/Test/UnityTestCase.qml
645 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, timeStep)
167 } else {
168 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, timeStep)
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, timeout) {
180 if (!obj)
181 qtest_fail("no obj given", 1);
183 return findChildInWithTimeout(obj, "children", objectName, timeout);
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, timeout) {
192 if (!obj)
193 qtest_fail("no obj given", 1);
195 return findChildInWithTimeout(obj, "data", objectName, timeout);
196 }
198 // Find a child in the named property with timeout
199 function findChildInWithTimeout(obj, prop, objectName, timeout) {
200 if (!obj)
201 qtest_fail("no obj given", 1);
203 var timeSpent = 0
204 if (timeout === undefined)
205 timeout = 5000;
207 var child = findChildIn(obj, prop, objectName);
209 while (timeSpent < timeout && !child) {
210 wait(50)
211 timeSpent += 50
212 child = findChildIn(obj, prop, objectName);
213 }
214 return child;
215 }
217 // Find a child in the named property
218 function findChildIn(obj, prop, objectName) {
219 if (!obj)
220 qtest_fail("no obj given", 1);
222 var childs = new Array(0);
223 childs.push(obj)
224 while (childs.length > 0) {
225 if (childs[0].objectName == objectName) {
226 return childs[0]
227 }
228 for (var i in childs[0][prop]) {
229 childs.push(childs[0][prop][i])
230 }
231 childs.splice(0, 1);
232 }
233 return null;
234 }
236 function findChildsByType(obj, typeName) {
237 if (!obj)
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)) {
244 res.push(c)
245 }
246 res = res.concat(findChildsByType(c, typeName));
247 }
248 return res;
249 }
251 // Type a full string instead of keyClick letter by letter
252 function typeString(str) {
253 for (var i = 0; i < str.length; i++) {
254 keyClick(str[i])
255 }
256 }
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
260 // is generated)
261 function tryCompareFunction(func, expectedResult, timeout, message) {
262 var timeSpent = 0
263 if (timeout === undefined)
264 timeout = 5000;
265 var success = false
266 var actualResult
267 while (timeSpent < timeout && !success) {
268 actualResult = func()
269 success = qtest_compareInternal(actualResult, expectedResult)
270 if (success === false) {
271 wait(50)
272 timeSpent += 50
273 }
274 }
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",
280 act, exp,
281 util.callerFile(), util.callerLine())) {
282 throw new Error("QtQuickTest::fail")
283 }
284 }
286 function flickToYEnd(item) {
287 if (!item)
288 qtest_fail("no item given", 1);
290 var i = 0;
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);
298 ++i;
299 }
300 tryCompare(item, "atYEnd", true);
301 }
303 function touchEvent(item) {
304 return UT.Util.touchEvent(item)
305 }
307 // speed is in pixels/second
308 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
309 if (!item)
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
337 if (beginTouch) {
338 fakeDateTime.currentTimeMs += timeStep
340 var event = touchEvent(item)
341 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
342 event.commit()
343 }
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
349 wait(timeStep)
350 var event = touchEvent(item)
351 event.move(0 /* touchId */, rootTo.x, rootTo.y)
352 event.commit()
353 } else {
354 wait(timeStep)
355 var event = touchEvent(item)
356 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
357 event.commit()
358 }
359 }
360 if (endTouch) {
361 fakeDateTime.currentTimeMs += timeStep
362 var event = touchEvent(item)
363 event.release(0 /* touchId */, rootTo.x, rootTo.y)
364 event.commit()
365 }
366 }
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) {
371 if (!item)
372 qtest_fail("no item given", 1);
374 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
375 }
377 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
378 if (!item)
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
385 {
386 var stepStart = item.mapToItem(root, 0, 0);
387 var stepEnd = item.mapToItem(root, stepX, stepY);
388 }
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)
395 }
396 event.commit()
398 // we have to stop at some point
399 var maxSteps = 100;
400 var stepsDone = 0;
402 while (!condition() && stepsDone < maxSteps) {
403 wait(25);
404 fakeDateTime.currentTimeMs += 25;
406 pos.x += stepX;
407 pos.y += stepY;
409 event = touchEvent(item);
410 for (i = 0; i < touchIds.length; i++) {
411 event.move(touchIds[i], pos.x, pos.y);
412 }
413 event.commit();
415 stepsDone += 1;
416 }
418 event = touchEvent(item)
419 for (i = 0; i < touchIds.length; i++) {
420 event.release(touchIds[i], pos.x, pos.y)
421 }
422 event.commit()
423 }
425 function touchMove(item, tox, toy) {
426 if (!item)
427 qtest_fail("no item given", 1);
429 multiTouchMove(0, item, tox, toy);
430 }
432 function multiTouchMove(touchId, item, tox, toy) {
433 if (!item)
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);
442 event.commit();
443 }
445 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
446 if (!item)
447 qtest_fail("no item given", 1);
449 // Make sure the item is rendered
450 waitForRendering(item);
452 var event1 = touchEvent(item);
453 // first finger
454 event1.press(0, x1Start, y1Start);
455 event1.commit();
456 // second finger
457 event1.move(0, x1Start, y1Start);
458 event1.press(1, x2Start, y2Start);
459 event1.commit();
461 // pinch
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);
465 event1.commit();
466 }
468 // release
469 event1.release(0, x1End, y1End);
470 event1.release(1, x2End, y2End);
471 event1.commit();
472 }
474 function fetchRootItem(item) {
475 if (!item)
476 qtest_fail("no item given", 1);
478 if (item.parent)
479 return fetchRootItem(item.parent)
480 else
481 return item
482 }
484 function touchPress(item, x, y) {
485 if (!item)
486 qtest_fail("no item given", 1);
488 multiTouchPress(0, item, x, y, []);
489 }
491 /*! \brief Release a touch point
493 \param touchId The touchId to be pressed
494 \param item The item
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"
498 */
499 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
500 if (!item)
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]);
514 }
515 event.commit()
516 }
518 function touchRelease(item, x, y) {
519 if (!item)
520 qtest_fail("no item given", 1);
522 multiTouchRelease(0, item, x, y, []);
523 }
525 /*! \brief Release a touch point
527 \param touchId The touchId to be released
528 \param item The item
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"
532 */
533 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
534 if (!item)
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]);
548 }
549 event.commit()
550 }
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
557 */
558 function tap(item, x, y) {
559 if (!item)
560 qtest_fail("no item given", 1);
562 multiTouchTap([0], item, x, y);
563 }
565 function multiTouchTap(touchIds, item, x, y) {
566 if (!item)
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)
579 }
580 event.commit()
582 event = touchEvent(item)
583 for (i = 0; i < touchIds.length; i++) {
584 event.release(touchIds[i], rootPoint.x, rootPoint.y)
585 }
586 event.commit()
587 }
590 Component.onCompleted: {
591 var rootItem = parent;
592 while (rootItem.parent != undefined) {
593 rootItem = rootItem.parent;
594 }
595 removeTimeConstraintsFromSwipeAreas(rootItem);
596 }
598 /*
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
603 elapsed time.
605 This effectively makes SwipeAreas easier to fool.
606 */
607 function removeTimeConstraintsFromSwipeAreas(item) {
608 if (!item)
609 qtest_fail("no item given", 1);
611 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
612 UbuntuTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
613 } else {
614 for (var i in item.children) {
615 removeTimeConstraintsFromSwipeAreas(item.children[i]);
616 }
617 }
618 }
620 /*
621 Wait until any transition animation has finished for the given StateGroup or Item
622 */
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);
628 }
629 }
631 /*
632 kill all (fake) running apps, bringing Unity.Application back to its initial state
633 */
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);
642 }
643 compare(ApplicationManager.count, 0);
644 }