on dfaa73f 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 Unity.Test 0.1 as UT
22import Utils 0.1
24TestCase {
25 id: testCase
26 property var util: TestUtil {id:util}
28 // This is needed for waitForRendering calls to return
29 // if the watched element already got rendered
30 Rectangle {
31 id: rotatingRectangle
32 width: units.gu(1)
33 height: width
34 parent: testCase.parent
35 border { width: units.dp(1); color: "black" }
36 opacity: 0.6
38 visible: testCase.running
40 RotationAnimation on rotation {
41 running: rotatingRectangle.visible
42 from: 0
43 to: 360
44 loops: Animation.Infinite
45 duration: 1000
46 }
47 }
49 // Fake implementation to be provided to items under test
50 property var fakeDateTime: new function() {
51 this.currentTimeMs = 0
52 this.getCurrentTimeMs = function() {return this.currentTimeMs}
53 }
55 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
56 function mouseClick(item, x, y, button, modifiers, delay) {
57 if (!item)
58 qtest_fail("no item given", 1);
60 if (button === undefined)
61 button = Qt.LeftButton;
62 if (modifiers === undefined)
63 modifiers = Qt.NoModifier;
64 if (delay === undefined)
65 delay = -1;
66 if (x === undefined)
67 x = item.width / 2;
68 if (y === undefined)
69 y = item.height / 2;
70 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
71 qtest_fail("window not shown", 2);
72 }
74 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
75 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
76 if (!item)
77 qtest_fail("no item given", 1);
79 if (button === undefined)
80 button = Qt.LeftButton;
81 if (modifiers === undefined)
82 modifiers = Qt.NoModifier;
83 if (delay === undefined)
84 delay = -1;
85 if (x === undefined)
86 x = item.width / 2;
87 if (y === undefined)
88 y = item.height / 2;
89 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
90 qtest_fail("window not shown", 2)
91 }
93 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
94 function mousePress(item, x, y, button, modifiers, delay) {
95 if (!item)
96 qtest_fail("no item given", 1);
98 if (button === undefined)
99 button = Qt.LeftButton;
100 if (modifiers === undefined)
101 modifiers = Qt.NoModifier;
102 if (delay === undefined)
103 delay = -1;
104 if (x === undefined)
105 x = item.width / 2;
106 if (y === undefined)
107 y = item.height / 2;
108 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
109 qtest_fail("window not shown", 2)
110 }
112 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
113 function mouseRelease(item, x, y, button, modifiers, delay) {
114 if (!item)
115 qtest_fail("no item given", 1);
117 if (button === undefined)
118 button = Qt.LeftButton;
119 if (modifiers === undefined)
120 modifiers = Qt.NoModifier;
121 if (delay === undefined)
122 delay = -1;
123 if (x === undefined)
124 x = item.width / 2;
125 if (y === undefined)
126 y = item.height / 2;
127 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
128 qtest_fail("window not shown", 2)
129 }
132 // Flickable won't recognise a single mouse move as dragging the flickable.
133 // Use 5 steps because it's what
134 // Qt uses in QQuickViewTestUtil::flick
135 // speed is in pixels/second
136 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
137 speed, iterations) {
138 if (!item)
139 qtest_fail("no item given", 1);
141 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
142 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
144 // set a default speed if not specified
145 speed = (speed != null) ? speed : units.gu(10);
147 // set a default iterations if not specified
148 iterations = (iterations !== undefined) ? iterations : 5
150 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
151 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
153 var timeStep = totalTime / iterations
154 var diffX = (toX - x) / iterations
155 var diffY = (toY - y) / iterations
156 if (pressMouse) {
157 fakeDateTime.currentTimeMs += timeStep
158 mousePress(item, x, y)
159 }
160 for (var i = 0; i < iterations; ++i) {
161 fakeDateTime.currentTimeMs += timeStep
162 if (i === iterations - 1) {
163 // Avoid any rounding errors by making the last move be at precisely
164 // the point specified
165 mouseMove(item, toX, toY, iterations / speed)
166 } else {
167 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
168 }
169 }
170 if (releaseMouse) {
171 fakeDateTime.currentTimeMs += timeStep
172 mouseRelease(item, toX, toY)
173 }
174 }
177 // Find an object with the given name in the children tree of "obj"
178 function findChild(obj, objectName) {
179 if (!obj)
180 qtest_fail("no obj given", 1);
182 return findChildIn(obj, "children", objectName);
183 }
185 // Find an object with the given name in the children tree of "obj"
186 // Including invisible children like animations, timers etc.
187 // Note: you should use findChild if you're not sure you need this
188 // as this tree is much bigger and might contain stuff that goes
189 // away randomly.
190 function findInvisibleChild(obj, objectName) {
191 if (!obj)
192 qtest_fail("no obj given", 1);
194 return findChildIn(obj, "data", objectName);
195 }
197 // Find a child in the named property
198 function findChildIn(obj, prop, objectName) {
199 if (!obj)
200 qtest_fail("no obj given", 1);
202 var childs = new Array(0);
203 childs.push(obj)
204 while (childs.length > 0) {
205 if (childs[0].objectName == objectName) {
206 return childs[0]
207 }
208 for (var i in childs[0][prop]) {
209 childs.push(childs[0][prop][i])
210 }
211 childs.splice(0, 1);
212 }
213 return null;
214 }
216 function findChildsByType(obj, typeName) {
217 if (!obj)
218 qtest_fail("no obj given", 1);
220 var res = new Array(0);
221 for (var i in obj.children) {
222 var c = obj.children[i];
223 if (UT.Util.isInstanceOf(c, typeName)) {
224 res.push(c)
225 }
226 res = res.concat(findChildsByType(c, typeName));
227 }
228 return res;
229 }
231 // Type a full string instead of keyClick letter by letter
232 function typeString(str) {
233 for (var i = 0; i < str.length; i++) {
234 keyClick(str[i])
235 }
236 }
238 // Keeps executing a given parameter-less function until it returns the given
239 // expected result or the timemout is reached (in which case a test failure
240 // is generated)
241 function tryCompareFunction(func, expectedResult, timeout) {
242 var timeSpent = 0
243 if (timeout === undefined)
244 timeout = 5000;
245 var success = false
246 var actualResult
247 while (timeSpent < timeout && !success) {
248 actualResult = func()
249 success = qtest_compareInternal(actualResult, expectedResult)
250 if (success === false) {
251 wait(50)
252 timeSpent += 50
253 }
254 }
256 var act = qtest_results.stringify(actualResult)
257 var exp = qtest_results.stringify(expectedResult)
258 if (!qtest_results.compare(success,
259 "function returned unexpected result",
260 act, exp,
261 util.callerFile(), util.callerLine())) {
262 throw new Error("QtQuickTest::fail")
263 }
264 }
266 function flickToYEnd(item) {
267 if (!item)
268 qtest_fail("no item given", 1);
270 var i = 0;
271 var x = item.width / 2;
272 var y = item.height - units.gu(1);
273 var toY = units.gu(1);
274 var maxIterations = 5 + item.contentHeight / item.height;
275 while (i < maxIterations && !item.atYEnd) {
276 touchFlick(item, x, y, x, toY);
277 tryCompare(item, "moving", false);
278 ++i;
279 }
280 tryCompare(item, "atYEnd", true);
281 }
283 function touchEvent(item) {
284 return UT.Util.touchEvent(item)
285 }
287 // speed is in pixels/second
288 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
289 if (!item)
290 qtest_fail("no item given", 1);
292 // Make sure the item is rendered
293 waitForRendering(item);
295 var root = fetchRootItem(item);
296 var rootFrom = item.mapToItem(root, x, y);
297 var rootTo = item.mapToItem(root, toX, toY);
299 // Default to true for beginTouch if not present
300 beginTouch = (beginTouch !== undefined) ? beginTouch : true
302 // Default to true for endTouch if not present
303 endTouch = (endTouch !== undefined) ? endTouch : true
305 // Set a default speed if not specified
306 speed = (speed !== undefined) ? speed : units.gu(10)
308 // Set a default iterations if not specified
309 var iterations = (iterations !== undefined) ? iterations : 10
311 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
312 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
314 var timeStep = totalTime / iterations
315 var diffX = (rootTo.x - rootFrom.x) / iterations
316 var diffY = (rootTo.y - rootFrom.y) / iterations
317 if (beginTouch) {
318 fakeDateTime.currentTimeMs += timeStep
320 var event = touchEvent(item)
321 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
322 event.commit()
323 }
324 for (var i = 0; i < iterations; ++i) {
325 fakeDateTime.currentTimeMs += timeStep
326 if (i === iterations - 1) {
327 // Avoid any rounding errors by making the last move be at precisely
328 // the point specified
329 wait(iterations / speed)
330 var event = touchEvent(item)
331 event.move(0 /* touchId */, rootTo.x, rootTo.y)
332 event.commit()
333 } else {
334 wait(iterations / speed)
335 var event = touchEvent(item)
336 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
337 event.commit()
338 }
339 }
340 if (endTouch) {
341 fakeDateTime.currentTimeMs += timeStep
342 var event = touchEvent(item)
343 event.release(0 /* touchId */, rootTo.x, rootTo.y)
344 event.commit()
345 }
346 }
348 // perform a drag in the given direction until the given condition is true
349 // The condition is a function to be evaluated after every step
350 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
351 if (!item)
352 qtest_fail("no item given", 1);
354 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
355 }
357 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
358 if (!item)
359 qtest_fail("no item given", 1);
361 var root = fetchRootItem(item);
362 var pos = item.mapToItem(root, startX, startY);
364 // convert step to scene coords
365 {
366 var stepStart = item.mapToItem(root, 0, 0);
367 var stepEnd = item.mapToItem(root, stepX, stepY);
368 }
369 stepX = stepEnd.x - stepStart.x;
370 stepY = stepEnd.y - stepStart.y;
372 var event = touchEvent(item)
373 for (var i = 0; i < touchIds.length; i++) {
374 event.press(touchIds[i], pos.x, pos.y)
375 }
376 event.commit()
378 // we have to stop at some point
379 var maxSteps = 100;
380 var stepsDone = 0;
382 while (!condition() && stepsDone < maxSteps) {
383 wait(25);
384 fakeDateTime.currentTimeMs += 25;
386 pos.x += stepX;
387 pos.y += stepY;
389 event = touchEvent(item);
390 for (i = 0; i < touchIds.length; i++) {
391 event.move(touchIds[i], pos.x, pos.y);
392 }
393 event.commit();
395 stepsDone += 1;
396 }
398 event = touchEvent(item)
399 for (i = 0; i < touchIds.length; i++) {
400 event.release(touchIds[i], pos.x, pos.y)
401 }
402 event.commit()
403 }
405 function touchMove(item, tox, toy) {
406 if (!item)
407 qtest_fail("no item given", 1);
409 multiTouchMove(0, item, tox, toy);
410 }
412 function multiTouchMove(touchId, item, tox, toy) {
413 if (!item)
414 qtest_fail("no item given", 1);
416 if (typeof touchId !== "number") touchId = 0;
417 var root = fetchRootItem(item)
418 var rootPoint = item.mapToItem(root, tox, toy)
420 var event = touchEvent(item);
421 event.move(touchId, rootPoint.x, rootPoint.y);
422 event.commit();
423 }
425 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
426 if (!item)
427 qtest_fail("no item given", 1);
429 // Make sure the item is rendered
430 waitForRendering(item);
432 var event1 = touchEvent(item);
433 // first finger
434 event1.press(0, x1Start, y1Start);
435 event1.commit();
436 // second finger
437 event1.move(0, x1Start, y1Start);
438 event1.press(1, x2Start, y2Start);
439 event1.commit();
441 // pinch
442 for (var i = 0.0; i < 1.0; i += 0.02) {
443 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
444 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
445 event1.commit();
446 }
448 // release
449 event1.release(0, x1End, y1End);
450 event1.release(1, x2End, y2End);
451 event1.commit();
452 }
454 function fetchRootItem(item) {
455 if (!item)
456 qtest_fail("no item given", 1);
458 if (item.parent)
459 return fetchRootItem(item.parent)
460 else
461 return item
462 }
464 function touchPress(item, x, y) {
465 if (!item)
466 qtest_fail("no item given", 1);
468 multiTouchPress(0, item, x, y, []);
469 }
471 /*! \brief Release a touch point
473 \param touchId The touchId to be pressed
474 \param item The item
475 \param x The x coordinate of the press, defaults to horizontal center
476 \param y The y coordinate of the press, defaults to vertical center
477 \param stationaryPoints An array of touchIds which are "already touched"
478 */
479 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
480 if (!item)
481 qtest_fail("no item given", 1);
483 if (typeof touchId !== "number") touchId = 0;
484 if (typeof x !== "number") x = item.width / 2;
485 if (typeof y !== "number") y = item.height / 2;
486 if (typeof stationaryPoints !== "object") stationaryPoints = []
487 var root = fetchRootItem(item)
488 var rootPoint = item.mapToItem(root, x, y)
490 var event = touchEvent(item)
491 event.press(touchId, rootPoint.x, rootPoint.y)
492 for (var i = 0; i < stationaryPoints.length; i++) {
493 event.stationary(stationaryPoints[i]);
494 }
495 event.commit()
496 }
498 function touchRelease(item, x, y) {
499 if (!item)
500 qtest_fail("no item given", 1);
502 multiTouchRelease(0, item, x, y, []);
503 }
505 /*! \brief Release a touch point
507 \param touchId The touchId to be released
508 \param item The item
509 \param x The x coordinate of the release, defaults to horizontal center
510 \param y The y coordinate of the release, defaults to vertical center
511 \param stationaryPoints An array of touchIds which are "still touched"
512 */
513 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
514 if (!item)
515 qtest_fail("no item given", 1);
517 if (typeof touchId !== "number") touchId = 0;
518 if (typeof x !== "number") x = item.width / 2;
519 if (typeof y !== "number") y = item.height / 2;
520 if (typeof stationaryPoints !== "object") stationaryPoints = []
521 var root = fetchRootItem(item)
522 var rootPoint = item.mapToItem(root, x, y)
524 var event = touchEvent(item)
525 event.release(touchId, rootPoint.x, rootPoint.y)
526 for (var i = 0; i < stationaryPoints.length; i++) {
527 event.stationary(stationaryPoints[i]);
528 }
529 event.commit()
530 }
532 /*! \brief Tap the item with a touch event.
534 \param item The item to be tapped
535 \param x The x coordinate of the tap, defaults to horizontal center
536 \param y The y coordinate of the tap, defaults to vertical center
537 */
538 function tap(item, x, y) {
539 if (!item)
540 qtest_fail("no item given", 1);
542 multiTouchTap([0], item, x, y);
543 }
545 function multiTouchTap(touchIds, item, x, y) {
546 if (!item)
547 qtest_fail("no item given", 1);
549 if (typeof touchIds !== "object") touchIds = [0];
550 if (typeof x !== "number") x = item.width / 2;
551 if (typeof y !== "number") y = item.height / 2;
553 var root = fetchRootItem(item)
554 var rootPoint = item.mapToItem(root, x, y)
556 var event = touchEvent(item)
557 for (var i = 0; i < touchIds.length; i++) {
558 event.press(touchIds[i], rootPoint.x, rootPoint.y)
559 }
560 event.commit()
562 event = touchEvent(item)
563 for (i = 0; i < touchIds.length; i++) {
564 event.release(touchIds[i], rootPoint.x, rootPoint.y)
565 }
566 event.commit()
567 }
570 Component.onCompleted: {
571 var rootItem = parent;
572 while (rootItem.parent != undefined) {
573 rootItem = rootItem.parent;
574 }
575 removeTimeConstraintsFromDirectionalDragAreas(rootItem);
576 }
578 /*
579 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
580 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
581 Thus to remove a variable that qmltests cannot really control, namely time, this
582 function removes all constraints from DirectionalDragAreas that are sensible to
583 elapsed time.
585 This effectively makes DirectionalDragAreas easier to fool.
586 */
587 function removeTimeConstraintsFromDirectionalDragAreas(item) {
588 if (!item)
589 qtest_fail("no item given", 1);
591 // use duck-typing to identify a DirectionalDragArea
592 if (item.removeTimeConstraints != undefined) {
593 item.removeTimeConstraints();
594 } else {
595 for (var i in item.children) {
596 removeTimeConstraintsFromDirectionalDragAreas(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 }