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