on 0ab7cfa help sign in
unity8/tests/utils/modules/Unity/Test/UnityTestCase.qml
558 lines 20 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.5 which has the same feature
57 function mouseClick(item, x, y, button, modifiers, delay) {
58 if (button === undefined)
59 button = Qt.LeftButton;
60 if (modifiers === undefined)
61 modifiers = Qt.NoModifier;
62 if (delay === undefined)
63 delay = -1;
64 if (x === undefined)
65 x = item.width / 2;
66 if (y === undefined)
67 y = item.height / 2;
68 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
69 qtest_fail("window not shown", 2);
70 }
72 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
73 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
74 if (button === undefined)
75 button = Qt.LeftButton;
76 if (modifiers === undefined)
77 modifiers = Qt.NoModifier;
78 if (delay === undefined)
79 delay = -1;
80 if (x === undefined)
81 x = item.width / 2;
82 if (y === undefined)
83 y = item.height / 2;
84 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
85 qtest_fail("window not shown", 2)
86 }
88 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
89 function mousePress(item, x, y, button, modifiers, delay) {
90 if (button === undefined)
91 button = Qt.LeftButton;
92 if (modifiers === undefined)
93 modifiers = Qt.NoModifier;
94 if (delay === undefined)
95 delay = -1;
96 if (x === undefined)
97 x = item.width / 2;
98 if (y === undefined)
99 y = item.height / 2;
100 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
101 qtest_fail("window not shown", 2)
102 }
104 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
105 function mouseRelease(item, x, y, button, modifiers, delay) {
106 if (button === undefined)
107 button = Qt.LeftButton;
108 if (modifiers === undefined)
109 modifiers = Qt.NoModifier;
110 if (delay === undefined)
111 delay = -1;
112 if (x === undefined)
113 x = item.width / 2;
114 if (y === undefined)
115 y = item.height / 2;
116 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
117 qtest_fail("window not shown", 2)
118 }
121 // Flickable won't recognise a single mouse move as dragging the flickable.
122 // Use 5 steps because it's what
123 // Qt uses in QQuickViewTestUtil::flick
124 // speed is in pixels/second
125 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
126 speed, iterations) {
127 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
128 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
130 // set a default speed if not specified
131 speed = (speed != null) ? speed : units.gu(10);
133 // set a default iterations if not specified
134 iterations = (iterations !== undefined) ? iterations : 5
136 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
137 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
139 var timeStep = totalTime / iterations
140 var diffX = (toX - x) / iterations
141 var diffY = (toY - y) / iterations
142 if (pressMouse) {
143 fakeDateTime.currentTimeMs += timeStep
144 mousePress(item, x, y)
145 }
146 for (var i = 0; i < iterations; ++i) {
147 fakeDateTime.currentTimeMs += timeStep
148 if (i === iterations - 1) {
149 // Avoid any rounding errors by making the last move be at precisely
150 // the point specified
151 mouseMove(item, toX, toY, iterations / speed)
152 } else {
153 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
154 }
155 }
156 if (releaseMouse) {
157 fakeDateTime.currentTimeMs += timeStep
158 mouseRelease(item, toX, toY)
159 }
160 }
163 // Find an object with the given name in the children tree of "obj"
164 function findChild(obj, objectName) {
165 return findChildIn(obj, "children", objectName);
166 }
168 // Find an object with the given name in the children tree of "obj"
169 // Including invisible children like animations, timers etc.
170 // Note: you should use findChild if you're not sure you need this
171 // as this tree is much bigger and might contain stuff that goes
172 // away randomly.
173 function findInvisibleChild(obj, objectName) {
174 return findChildIn(obj, "data", objectName);
175 }
177 // Find a child in the named property
178 function findChildIn(obj, prop, objectName) {
179 var childs = new Array(0);
180 childs.push(obj)
181 while (childs.length > 0) {
182 if (childs[0].objectName == objectName) {
183 return childs[0]
184 }
185 for (var i in childs[0][prop]) {
186 childs.push(childs[0][prop][i])
187 }
188 childs.splice(0, 1);
189 }
190 return null;
191 }
193 function findChildsByType(obj, typeName) {
194 var res = new Array(0);
195 for (var i in obj.children) {
196 var c = obj.children[i];
197 if (UT.Util.isInstanceOf(c, typeName)) {
198 res.push(c)
199 }
200 res = res.concat(findChildsByType(c, typeName));
201 }
202 return res;
203 }
205 // Type a full string instead of keyClick letter by letter
206 function typeString(str) {
207 for (var i = 0; i < str.length; i++) {
208 keyClick(str[i])
209 }
210 }
212 // Keeps executing a given parameter-less function until it returns the given
213 // expected result or the timemout is reached (in which case a test failure
214 // is generated)
215 function tryCompareFunction(func, expectedResult, timeout) {
216 var timeSpent = 0
217 if (timeout === undefined)
218 timeout = 5000;
219 var success = false
220 var actualResult
221 while (timeSpent < timeout && !success) {
222 actualResult = func()
223 success = qtest_compareInternal(actualResult, expectedResult)
224 if (success === false) {
225 wait(50)
226 timeSpent += 50
227 }
228 }
230 var act = qtest_results.stringify(actualResult)
231 var exp = qtest_results.stringify(expectedResult)
232 if (!qtest_results.compare(success,
233 "function returned unexpected result",
234 act, exp,
235 util.callerFile(), util.callerLine())) {
236 throw new Error("QtQuickTest::fail")
237 }
238 }
240 function flickToYEnd(item) {
241 var i = 0;
242 var x = item.width / 2;
243 var y = item.height - units.gu(1);
244 var toY = units.gu(1);
245 var maxIterations = 5 + item.contentHeight / item.height;
246 while (i < maxIterations && !item.atYEnd) {
247 touchFlick(item, x, y, x, toY);
248 tryCompare(item, "moving", false);
249 ++i;
250 }
251 tryCompare(item, "atYEnd", true);
252 }
254 function touchEvent(item) {
255 return UT.Util.touchEvent(item)
256 }
258 // speed is in pixels/second
259 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
260 // Make sure the item is rendered
261 waitForRendering(item);
263 var root = fetchRootItem(item);
264 var rootFrom = item.mapToItem(root, x, y);
265 var rootTo = item.mapToItem(root, toX, toY);
267 // Default to true for beginTouch if not present
268 beginTouch = (beginTouch !== undefined) ? beginTouch : true
270 // Default to true for endTouch if not present
271 endTouch = (endTouch !== undefined) ? endTouch : true
273 // Set a default speed if not specified
274 speed = (speed !== undefined) ? speed : units.gu(10)
276 // Set a default iterations if not specified
277 var iterations = (iterations !== undefined) ? iterations : 10
279 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
280 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
282 var timeStep = totalTime / iterations
283 var diffX = (rootTo.x - rootFrom.x) / iterations
284 var diffY = (rootTo.y - rootFrom.y) / iterations
285 if (beginTouch) {
286 fakeDateTime.currentTimeMs += timeStep
288 var event = touchEvent(item)
289 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
290 event.commit()
291 }
292 for (var i = 0; i < iterations; ++i) {
293 fakeDateTime.currentTimeMs += timeStep
294 if (i === iterations - 1) {
295 // Avoid any rounding errors by making the last move be at precisely
296 // the point specified
297 wait(iterations / speed)
298 var event = touchEvent(item)
299 event.move(0 /* touchId */, rootTo.x, rootTo.y)
300 event.commit()
301 } else {
302 wait(iterations / speed)
303 var event = touchEvent(item)
304 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
305 event.commit()
306 }
307 }
308 if (endTouch) {
309 fakeDateTime.currentTimeMs += timeStep
310 var event = touchEvent(item)
311 event.release(0 /* touchId */, rootTo.x, rootTo.y)
312 event.commit()
313 }
314 }
316 // perform a drag in the given direction until the given condition is true
317 // The condition is a function to be evaluated after every step
318 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
319 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
320 }
322 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
323 var root = fetchRootItem(item);
324 var pos = item.mapToItem(root, startX, startY);
326 // convert step to scene coords
327 {
328 var stepStart = item.mapToItem(root, 0, 0);
329 var stepEnd = item.mapToItem(root, stepX, stepY);
330 }
331 stepX = stepEnd.x - stepStart.x;
332 stepY = stepEnd.y - stepStart.y;
334 var event = touchEvent(item)
335 for (var i = 0; i < touchIds.length; i++) {
336 event.press(touchIds[i], pos.x, pos.y)
337 }
338 event.commit()
340 // we have to stop at some point
341 var maxSteps = 100;
342 var stepsDone = 0;
344 while (!condition() && stepsDone < maxSteps) {
345 wait(25);
346 fakeDateTime.currentTimeMs += 25;
348 pos.x += stepX;
349 pos.y += stepY;
351 event = touchEvent(item);
352 for (i = 0; i < touchIds.length; i++) {
353 event.move(touchIds[i], pos.x, pos.y);
354 }
355 event.commit();
357 stepsDone += 1;
358 }
360 event = touchEvent(item)
361 for (i = 0; i < touchIds.length; i++) {
362 event.release(touchIds[i], pos.x, pos.y)
363 }
364 event.commit()
365 }
367 function touchMove(item, tox, toy) { multiTouchMove(0, item, tox, toy); }
369 function multiTouchMove(touchId, item, tox, toy) {
370 if (typeof touchId !== "number") touchId = 0;
371 var root = fetchRootItem(item)
372 var rootPoint = item.mapToItem(root, tox, toy)
374 var event = touchEvent(item);
375 event.move(touchId, rootPoint.x, rootPoint.y);
376 event.commit();
377 }
379 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
380 // Make sure the item is rendered
381 waitForRendering(item);
383 var event1 = touchEvent(item);
384 // first finger
385 event1.press(0, x1Start, y1Start);
386 event1.commit();
387 // second finger
388 event1.move(0, x1Start, y1Start);
389 event1.press(1, x2Start, y2Start);
390 event1.commit();
392 // pinch
393 for (var i = 0.0; i < 1.0; i += 0.02) {
394 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
395 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
396 event1.commit();
397 }
399 // release
400 event1.release(0, x1End, y1End);
401 event1.release(1, x2End, y2End);
402 event1.commit();
403 }
405 function fetchRootItem(item) {
406 if (item.parent)
407 return fetchRootItem(item.parent)
408 else
409 return item
410 }
412 function touchPress(item, x, y) { multiTouchPress(0, item, x, y, []); }
414 /*! \brief Release a touch point
416 \param touchId The touchId to be pressed
417 \param item The item
418 \param x The x coordinate of the press, defaults to horizontal center
419 \param y The y coordinate of the press, defaults to vertical center
420 \param stationaryPoints An array of touchIds which are "already touched"
421 */
422 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
423 if (typeof touchId !== "number") touchId = 0;
424 if (typeof x !== "number") x = item.width / 2;
425 if (typeof y !== "number") y = item.height / 2;
426 if (typeof stationaryPoints !== "object") stationaryPoints = []
427 var root = fetchRootItem(item)
428 var rootPoint = item.mapToItem(root, x, y)
430 var event = touchEvent(item)
431 event.press(touchId, rootPoint.x, rootPoint.y)
432 for (var i = 0; i < stationaryPoints.length; i++) {
433 event.stationary(stationaryPoints[i]);
434 }
435 event.commit()
436 }
438 function touchRelease(item, x, y) { multiTouchRelease(0, item, x, y, []); }
440 /*! \brief Release a touch point
442 \param touchId The touchId to be released
443 \param item The item
444 \param x The x coordinate of the release, defaults to horizontal center
445 \param y The y coordinate of the release, defaults to vertical center
446 \param stationaryPoints An array of touchIds which are "still touched"
447 */
448 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
449 if (typeof touchId !== "number") touchId = 0;
450 if (typeof x !== "number") x = item.width / 2;
451 if (typeof y !== "number") y = item.height / 2;
452 if (typeof stationaryPoints !== "object") stationaryPoints = []
453 var root = fetchRootItem(item)
454 var rootPoint = item.mapToItem(root, x, y)
456 var event = touchEvent(item)
457 event.release(touchId, rootPoint.x, rootPoint.y)
458 for (var i = 0; i < stationaryPoints.length; i++) {
459 event.stationary(stationaryPoints[i]);
460 }
461 event.commit()
462 }
464 /*! \brief Tap the item with a touch event.
466 \param item The item to be tapped
467 \param x The x coordinate of the tap, defaults to horizontal center
468 \param y The y coordinate of the tap, defaults to vertical center
469 */
470 function tap(item, x, y) {
471 multiTouchTap([0], item, x, y);
472 }
474 function multiTouchTap(touchIds, item, x, y) {
475 if (typeof touchIds !== "object") touchIds = [0];
476 if (typeof x !== "number") x = item.width / 2;
477 if (typeof y !== "number") y = item.height / 2;
479 var root = fetchRootItem(item)
480 var rootPoint = item.mapToItem(root, x, y)
482 var event = touchEvent(item)
483 for (var i = 0; i < touchIds.length; i++) {
484 event.press(touchIds[i], rootPoint.x, rootPoint.y)
485 }
486 event.commit()
488 event = touchEvent(item)
489 for (i = 0; i < touchIds.length; i++) {
490 event.release(touchIds[i], rootPoint.x, rootPoint.y)
491 }
492 event.commit()
493 }
496 Component.onCompleted: {
497 var rootItem = parent;
498 while (rootItem.parent != undefined) {
499 rootItem = rootItem.parent;
500 }
501 removeTimeConstraintsFromSwipeAreas(rootItem);
502 }
504 /*
505 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
506 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
507 Thus to remove a variable that qmltests cannot really control, namely time, this
508 function removes all constraints from SwipeAreas that are sensible to
509 elapsed time.
511 This effectively makes SwipeAreas easier to fool.
512 */
513 function removeTimeConstraintsFromSwipeAreas(item) {
514 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
515 UbuntuTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
516 } else {
517 for (var i in item.children) {
518 removeTimeConstraintsFromSwipeAreas(item.children[i]);
519 }
520 }
521 }
523 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
524 function waitForRendering(item, timeout) {
525 if (timeout === undefined)
526 timeout = 5000;
527 if (!item)
528 qtest_fail("No item given to waitForRendering", 1);
529 return qtest_results.waitForRendering(item, timeout);
530 }
532 /*
533 Wait until any transition animation has finished for the given StateGroup or Item
534 */
535 function waitUntilTransitionsEnd(stateGroup) {
536 var transitions = stateGroup.transitions;
537 for (var i = 0; i < transitions.length; ++i) {
538 var transition = transitions[i];
539 tryCompare(transition, "running", false, 2000);
540 }
541 }
543 /*
544 kill all (fake) running apps but unity8-dash, bringing Unity.Application back to its initial state
545 */
546 function killApps() {
547 while (ApplicationManager.count > 1) {
548 var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
549 var application = ApplicationManager.get(appIndex);
550 ApplicationManager.stopApplication(application.appId);
551 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
552 // They won't be gone until those surface items are destroyed.
553 tryCompareFunction(function() { return application.surfaceList.count }, 0);
554 tryCompare(application, "state", ApplicationInfo.Stopped);
555 }
556 compare(ApplicationManager.count, 1);
557 }