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