on 4a93bc7 help sign in
unity8/tests/utils/modules/Unity/Test/UnityTestCase.qml
618 lines 21 kB history raw
1/*
2 * Copyright 2013-2015 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 Ubuntu.Components 1.3
20import Unity.Test 0.1 as UT
22TestCase {
23 id: testCase
24 TestUtil {id:util}
26 // This is needed for waitForRendering calls to return
27 // if the watched element already got rendered
28 Rectangle {
29 id: rotatingRectangle
30 width: units.gu(1)
31 height: width
32 parent: testCase.parent
33 border { width: units.dp(1); color: "black" }
34 opacity: 0.6
36 visible: testCase.running
38 RotationAnimation on rotation {
39 running: rotatingRectangle.visible
40 from: 0
41 to: 360
42 loops: Animation.Infinite
43 duration: 1000
44 }
45 }
47 // Fake implementation to be provided to items under test
48 property var fakeDateTime: new function() {
49 this.currentTimeMs = 0
50 this.getCurrentTimeMs = function() {return this.currentTimeMs}
51 }
53 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
54 function mouseClick(item, x, y, button, modifiers, delay) {
55 if (!item)
56 qtest_fail("no item given", 1);
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.7 which has the same feature
73 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
74 if (!item)
75 qtest_fail("no item given", 1);
77 if (button === undefined)
78 button = Qt.LeftButton;
79 if (modifiers === undefined)
80 modifiers = Qt.NoModifier;
81 if (delay === undefined)
82 delay = -1;
83 if (x === undefined)
84 x = item.width / 2;
85 if (y === undefined)
86 y = item.height / 2;
87 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
88 qtest_fail("window not shown", 2)
89 }
91 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
92 function mousePress(item, x, y, button, modifiers, delay) {
93 if (!item)
94 qtest_fail("no item given", 1);
96 if (button === undefined)
97 button = Qt.LeftButton;
98 if (modifiers === undefined)
99 modifiers = Qt.NoModifier;
100 if (delay === undefined)
101 delay = -1;
102 if (x === undefined)
103 x = item.width / 2;
104 if (y === undefined)
105 y = item.height / 2;
106 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
107 qtest_fail("window not shown", 2)
108 }
110 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
111 function mouseRelease(item, x, y, button, modifiers, delay) {
112 if (!item)
113 qtest_fail("no item given", 1);
115 if (button === undefined)
116 button = Qt.LeftButton;
117 if (modifiers === undefined)
118 modifiers = Qt.NoModifier;
119 if (delay === undefined)
120 delay = -1;
121 if (x === undefined)
122 x = item.width / 2;
123 if (y === undefined)
124 y = item.height / 2;
125 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
126 qtest_fail("window not shown", 2)
127 }
130 // Flickable won't recognise a single mouse move as dragging the flickable.
131 // Use 5 steps because it's what
132 // Qt uses in QQuickViewTestUtil::flick
133 // speed is in pixels/second
134 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
135 speed, iterations) {
136 if (!item)
137 qtest_fail("no item given", 1);
139 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
140 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
142 // set a default speed if not specified
143 speed = (speed != null) ? speed : units.gu(10);
145 // set a default iterations if not specified
146 iterations = (iterations !== undefined) ? iterations : 5
148 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
149 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
151 var timeStep = totalTime / iterations
152 var diffX = (toX - x) / iterations
153 var diffY = (toY - y) / iterations
154 if (pressMouse) {
155 fakeDateTime.currentTimeMs += timeStep
156 mousePress(item, x, y)
157 }
158 for (var i = 0; i < iterations; ++i) {
159 fakeDateTime.currentTimeMs += timeStep
160 if (i === iterations - 1) {
161 // Avoid any rounding errors by making the last move be at precisely
162 // the point specified
163 mouseMove(item, toX, toY, iterations / speed)
164 } else {
165 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
166 }
167 }
168 if (releaseMouse) {
169 fakeDateTime.currentTimeMs += timeStep
170 mouseRelease(item, toX, toY)
171 }
172 }
175 // Find an object with the given name in the children tree of "obj"
176 function findChild(obj, objectName) {
177 if (!obj)
178 qtest_fail("no obj given", 1);
180 return findChildIn(obj, "children", objectName);
181 }
183 // Find an object with the given name in the children tree of "obj"
184 // Including invisible children like animations, timers etc.
185 // Note: you should use findChild if you're not sure you need this
186 // as this tree is much bigger and might contain stuff that goes
187 // away randomly.
188 function findInvisibleChild(obj, objectName) {
189 if (!obj)
190 qtest_fail("no obj given", 1);
192 return findChildIn(obj, "data", objectName);
193 }
195 // Find a child in the named property
196 function findChildIn(obj, prop, objectName) {
197 if (!obj)
198 qtest_fail("no obj given", 1);
200 var childs = new Array(0);
201 childs.push(obj)
202 while (childs.length > 0) {
203 if (childs[0].objectName == objectName) {
204 return childs[0]
205 }
206 for (var i in childs[0][prop]) {
207 childs.push(childs[0][prop][i])
208 }
209 childs.splice(0, 1);
210 }
211 return null;
212 }
214 function findChildsByType(obj, typeName) {
215 if (!obj)
216 qtest_fail("no obj given", 1);
218 var res = new Array(0);
219 for (var i in obj.children) {
220 var c = obj.children[i];
221 if (UT.Util.isInstanceOf(c, typeName)) {
222 res.push(c)
223 }
224 res = res.concat(findChildsByType(c, typeName));
225 }
226 return res;
227 }
229 // Type a full string instead of keyClick letter by letter
230 function typeString(str) {
231 for (var i = 0; i < str.length; i++) {
232 keyClick(str[i])
233 }
234 }
236 // Keeps executing a given parameter-less function until it returns the given
237 // expected result or the timemout is reached (in which case a test failure
238 // is generated)
239 function tryCompareFunction(func, expectedResult, timeout) {
240 var timeSpent = 0
241 if (timeout === undefined)
242 timeout = 5000;
243 var success = false
244 var actualResult
245 while (timeSpent < timeout && !success) {
246 actualResult = func()
247 success = qtest_compareInternal(actualResult, expectedResult)
248 if (success === false) {
249 wait(50)
250 timeSpent += 50
251 }
252 }
254 var act = qtest_results.stringify(actualResult)
255 var exp = qtest_results.stringify(expectedResult)
256 if (!qtest_results.compare(success,
257 "function returned unexpected result",
258 act, exp,
259 util.callerFile(), util.callerLine())) {
260 throw new Error("QtQuickTest::fail")
261 }
262 }
264 function flickToYEnd(item) {
265 if (!item)
266 qtest_fail("no item given", 1);
268 var i = 0;
269 var x = item.width / 2;
270 var y = item.height - units.gu(1);
271 var toY = units.gu(1);
272 var maxIterations = 5 + item.contentHeight / item.height;
273 while (i < maxIterations && !item.atYEnd) {
274 touchFlick(item, x, y, x, toY);
275 tryCompare(item, "moving", false);
276 ++i;
277 }
278 tryCompare(item, "atYEnd", true);
279 }
281 function touchEvent(item) {
282 return UT.Util.touchEvent(item)
283 }
285 // speed is in pixels/second
286 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
287 if (!item)
288 qtest_fail("no item given", 1);
290 // Make sure the item is rendered
291 waitForRendering(item);
293 var root = fetchRootItem(item);
294 var rootFrom = item.mapToItem(root, x, y);
295 var rootTo = item.mapToItem(root, toX, toY);
297 // Default to true for beginTouch if not present
298 beginTouch = (beginTouch !== undefined) ? beginTouch : true
300 // Default to true for endTouch if not present
301 endTouch = (endTouch !== undefined) ? endTouch : true
303 // Set a default speed if not specified
304 speed = (speed !== undefined) ? speed : units.gu(10)
306 // Set a default iterations if not specified
307 var iterations = (iterations !== undefined) ? iterations : 10
309 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
310 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
312 var timeStep = totalTime / iterations
313 var diffX = (rootTo.x - rootFrom.x) / iterations
314 var diffY = (rootTo.y - rootFrom.y) / iterations
315 if (beginTouch) {
316 fakeDateTime.currentTimeMs += timeStep
318 var event = touchEvent(item)
319 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
320 event.commit()
321 }
322 for (var i = 0; i < iterations; ++i) {
323 fakeDateTime.currentTimeMs += timeStep
324 if (i === iterations - 1) {
325 // Avoid any rounding errors by making the last move be at precisely
326 // the point specified
327 wait(iterations / speed)
328 var event = touchEvent(item)
329 event.move(0 /* touchId */, rootTo.x, rootTo.y)
330 event.commit()
331 } else {
332 wait(iterations / speed)
333 var event = touchEvent(item)
334 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
335 event.commit()
336 }
337 }
338 if (endTouch) {
339 fakeDateTime.currentTimeMs += timeStep
340 var event = touchEvent(item)
341 event.release(0 /* touchId */, rootTo.x, rootTo.y)
342 event.commit()
343 }
344 }
346 // perform a drag in the given direction until the given condition is true
347 // The condition is a function to be evaluated after every step
348 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
349 if (!item)
350 qtest_fail("no item given", 1);
352 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
353 }
355 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
356 if (!item)
357 qtest_fail("no item given", 1);
359 var root = fetchRootItem(item);
360 var pos = item.mapToItem(root, startX, startY);
362 // convert step to scene coords
363 {
364 var stepStart = item.mapToItem(root, 0, 0);
365 var stepEnd = item.mapToItem(root, stepX, stepY);
366 }
367 stepX = stepEnd.x - stepStart.x;
368 stepY = stepEnd.y - stepStart.y;
370 var event = touchEvent(item)
371 for (var i = 0; i < touchIds.length; i++) {
372 event.press(touchIds[i], pos.x, pos.y)
373 }
374 event.commit()
376 // we have to stop at some point
377 var maxSteps = 100;
378 var stepsDone = 0;
380 while (!condition() && stepsDone < maxSteps) {
381 wait(25);
382 fakeDateTime.currentTimeMs += 25;
384 pos.x += stepX;
385 pos.y += stepY;
387 event = touchEvent(item);
388 for (i = 0; i < touchIds.length; i++) {
389 event.move(touchIds[i], pos.x, pos.y);
390 }
391 event.commit();
393 stepsDone += 1;
394 }
396 event = touchEvent(item)
397 for (i = 0; i < touchIds.length; i++) {
398 event.release(touchIds[i], pos.x, pos.y)
399 }
400 event.commit()
401 }
403 function touchMove(item, tox, toy) {
404 if (!item)
405 qtest_fail("no item given", 1);
407 multiTouchMove(0, item, tox, toy);
408 }
410 function multiTouchMove(touchId, item, tox, toy) {
411 if (!item)
412 qtest_fail("no item given", 1);
414 if (typeof touchId !== "number") touchId = 0;
415 var root = fetchRootItem(item)
416 var rootPoint = item.mapToItem(root, tox, toy)
418 var event = touchEvent(item);
419 event.move(touchId, rootPoint.x, rootPoint.y);
420 event.commit();
421 }
423 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
424 if (!item)
425 qtest_fail("no item given", 1);
427 // Make sure the item is rendered
428 waitForRendering(item);
430 var event1 = touchEvent(item);
431 // first finger
432 event1.press(0, x1Start, y1Start);
433 event1.commit();
434 // second finger
435 event1.move(0, x1Start, y1Start);
436 event1.press(1, x2Start, y2Start);
437 event1.commit();
439 // pinch
440 for (var i = 0.0; i < 1.0; i += 0.02) {
441 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
442 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
443 event1.commit();
444 }
446 // release
447 event1.release(0, x1End, y1End);
448 event1.release(1, x2End, y2End);
449 event1.commit();
450 }
452 function fetchRootItem(item) {
453 if (!item)
454 qtest_fail("no item given", 1);
456 if (item.parent)
457 return fetchRootItem(item.parent)
458 else
459 return item
460 }
462 function touchPress(item, x, y) {
463 if (!item)
464 qtest_fail("no item given", 1);
466 multiTouchPress(0, item, x, y, []);
467 }
469 /*! \brief Release a touch point
471 \param touchId The touchId to be pressed
472 \param item The item
473 \param x The x coordinate of the press, defaults to horizontal center
474 \param y The y coordinate of the press, defaults to vertical center
475 \param stationaryPoints An array of touchIds which are "already touched"
476 */
477 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
478 if (!item)
479 qtest_fail("no item given", 1);
481 if (typeof touchId !== "number") touchId = 0;
482 if (typeof x !== "number") x = item.width / 2;
483 if (typeof y !== "number") y = item.height / 2;
484 if (typeof stationaryPoints !== "object") stationaryPoints = []
485 var root = fetchRootItem(item)
486 var rootPoint = item.mapToItem(root, x, y)
488 var event = touchEvent(item)
489 event.press(touchId, rootPoint.x, rootPoint.y)
490 for (var i = 0; i < stationaryPoints.length; i++) {
491 event.stationary(stationaryPoints[i]);
492 }
493 event.commit()
494 }
496 function touchRelease(item, x, y) {
497 if (!item)
498 qtest_fail("no item given", 1);
500 multiTouchRelease(0, item, x, y, []);
501 }
503 /*! \brief Release a touch point
505 \param touchId The touchId to be released
506 \param item The item
507 \param x The x coordinate of the release, defaults to horizontal center
508 \param y The y coordinate of the release, defaults to vertical center
509 \param stationaryPoints An array of touchIds which are "still touched"
510 */
511 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
512 if (!item)
513 qtest_fail("no item given", 1);
515 if (typeof touchId !== "number") touchId = 0;
516 if (typeof x !== "number") x = item.width / 2;
517 if (typeof y !== "number") y = item.height / 2;
518 if (typeof stationaryPoints !== "object") stationaryPoints = []
519 var root = fetchRootItem(item)
520 var rootPoint = item.mapToItem(root, x, y)
522 var event = touchEvent(item)
523 event.release(touchId, rootPoint.x, rootPoint.y)
524 for (var i = 0; i < stationaryPoints.length; i++) {
525 event.stationary(stationaryPoints[i]);
526 }
527 event.commit()
528 }
530 /*! \brief Tap the item with a touch event.
532 \param item The item to be tapped
533 \param x The x coordinate of the tap, defaults to horizontal center
534 \param y The y coordinate of the tap, defaults to vertical center
535 */
536 function tap(item, x, y) {
537 if (!item)
538 qtest_fail("no item given", 1);
540 multiTouchTap([0], item, x, y);
541 }
543 function multiTouchTap(touchIds, item, x, y) {
544 if (!item)
545 qtest_fail("no item given", 1);
547 if (typeof touchIds !== "object") touchIds = [0];
548 if (typeof x !== "number") x = item.width / 2;
549 if (typeof y !== "number") y = item.height / 2;
551 var root = fetchRootItem(item)
552 var rootPoint = item.mapToItem(root, x, y)
554 var event = touchEvent(item)
555 for (var i = 0; i < touchIds.length; i++) {
556 event.press(touchIds[i], rootPoint.x, rootPoint.y)
557 }
558 event.commit()
560 event = touchEvent(item)
561 for (i = 0; i < touchIds.length; i++) {
562 event.release(touchIds[i], rootPoint.x, rootPoint.y)
563 }
564 event.commit()
565 }
568 Component.onCompleted: {
569 var rootItem = parent;
570 while (rootItem.parent != undefined) {
571 rootItem = rootItem.parent;
572 }
573 removeTimeConstraintsFromDirectionalDragAreas(rootItem);
574 }
576 /*
577 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
578 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
579 Thus to remove a variable that qmltests cannot really control, namely time, this
580 function removes all constraints from DirectionalDragAreas that are sensible to
581 elapsed time.
583 This effectively makes DirectionalDragAreas easier to fool.
584 */
585 function removeTimeConstraintsFromDirectionalDragAreas(item) {
586 if (!item)
587 qtest_fail("no item given", 1);
589 // use duck-typing to identify a DirectionalDragArea
590 if (item.removeTimeConstraints != undefined) {
591 item.removeTimeConstraints();
592 } else {
593 for (var i in item.children) {
594 removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
595 }
596 }
597 }
599 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
600 function waitForRendering(item, timeout) {
601 if (timeout === undefined)
602 timeout = 5000;
603 if (!item)
604 qtest_fail("No item given to waitForRendering", 1);
605 return qtest_results.waitForRendering(item, timeout);
606 }
608 /*
609 Wait until any transition animation has finished for the given StateGroup or Item
610 */
611 function waitUntilTransitionsEnd(stateGroup) {
612 var transitions = stateGroup.transitions;
613 for (var i = 0; i < transitions.length; ++i) {
614 var transition = transitions[i];
615 tryCompare(transition, "running", false, 2000);
616 }
617 }