on d28fa91 help sign in
unity8/tests/utils/modules/Unity/Test/UnityTestCase.qml
484 lines 17 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.0
18import QtTest 1.0
19import Ubuntu.Components 0.1
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 while (i < 5 && !item.atYEnd) {
243 touchFlick(item, x, y, x, toY);
244 tryCompare(item, "moving", false);
245 ++i;
246 }
247 tryCompare(item, "atYEnd", true);
248 }
250 function touchEvent(item) {
251 return UT.Util.touchEvent(item)
252 }
254 // speed is in pixels/second
255 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
256 // Make sure the item is rendered
257 waitForRendering(item);
259 var root = fetchRootItem(item);
260 var rootFrom = item.mapToItem(root, x, y);
261 var rootTo = item.mapToItem(root, toX, toY);
263 // Default to true for beginTouch if not present
264 beginTouch = (beginTouch !== undefined) ? beginTouch : true
266 // Default to true for endTouch if not present
267 endTouch = (endTouch !== undefined) ? endTouch : true
269 // Set a default speed if not specified
270 speed = (speed !== undefined) ? speed : units.gu(10)
272 // Set a default iterations if not specified
273 var iterations = (iterations !== undefined) ? iterations : 10
275 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
276 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
278 var timeStep = totalTime / iterations
279 var diffX = (rootTo.x - rootFrom.x) / iterations
280 var diffY = (rootTo.y - rootFrom.y) / iterations
281 if (beginTouch) {
282 fakeDateTime.currentTimeMs += timeStep
284 var event = touchEvent(item)
285 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
286 event.commit()
287 }
288 for (var i = 0; i < iterations; ++i) {
289 fakeDateTime.currentTimeMs += timeStep
290 if (i === iterations - 1) {
291 // Avoid any rounding errors by making the last move be at precisely
292 // the point specified
293 wait(iterations / speed)
294 var event = touchEvent(item)
295 event.move(0 /* touchId */, rootTo.x, rootTo.y)
296 event.commit()
297 } else {
298 wait(iterations / speed)
299 var event = touchEvent(item)
300 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
301 event.commit()
302 }
303 }
304 if (endTouch) {
305 fakeDateTime.currentTimeMs += timeStep
306 var event = touchEvent(item)
307 event.release(0 /* touchId */, rootTo.x, rootTo.y)
308 event.commit()
309 }
310 }
312 // perform a drag in the given direction until the given condition is true
313 // The condition is a function to be evaluated after every step
314 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
316 var root = fetchRootItem(item);
317 var pos = item.mapToItem(root, startX, startY);
319 // convert step to scene coords
320 {
321 var stepStart = item.mapToItem(root, 0, 0);
322 var stepEnd = item.mapToItem(root, stepX, stepY);
323 }
324 stepX = stepEnd.x - stepStart.x;
325 stepY = stepEnd.y - stepStart.y;
327 var event = touchEvent(item)
328 event.press(0 /* touchId */, pos.x, pos.y)
329 event.commit()
331 // we have to stop at some point
332 var maxSteps = 100;
333 var stepsDone = 0;
335 while (!condition() && stepsDone < maxSteps) {
336 wait(25);
337 fakeDateTime.currentTimeMs += 25;
339 pos.x += stepX;
340 pos.y += stepY;
342 event = touchEvent(item);
343 event.move(0 /* touchId */, pos.x, pos.y);
344 event.commit();
346 stepsDone += 1;
347 }
349 event = touchEvent(item)
350 event.release(0 /* touchId */, pos.x, pos.y)
351 event.commit()
352 }
354 function touchMove(item, tox, toy) {
355 var root = fetchRootItem(item)
356 var rootPoint = item.mapToItem(root, tox, toy)
358 var event = touchEvent(item);
359 event.move(0 /* touchId */, rootPoint.x, rootPoint.y);
360 event.commit();
361 }
363 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
364 // Make sure the item is rendered
365 waitForRendering(item);
367 var event1 = touchEvent(item);
368 // first finger
369 event1.press(0, x1Start, y1Start);
370 event1.commit();
371 // second finger
372 event1.move(0, x1Start, y1Start);
373 event1.press(1, x2Start, y2Start);
374 event1.commit();
376 // pinch
377 for (var i = 0.0; i < 1.0; i += 0.02) {
378 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
379 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
380 event1.commit();
381 }
383 // release
384 event1.release(0, x1End, y1End);
385 event1.release(1, x2End, y2End);
386 event1.commit();
387 }
389 function fetchRootItem(item) {
390 if (item.parent)
391 return fetchRootItem(item.parent)
392 else
393 return item
394 }
396 function touchPress(item, x, y) {
397 var root = fetchRootItem(item)
398 var rootPoint = item.mapToItem(root, x, y)
400 var event = touchEvent(item)
401 event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
402 event.commit()
403 }
405 function touchRelease(item, x, y) {
406 var root = fetchRootItem(item)
407 var rootPoint = item.mapToItem(root, x, y)
409 var event = touchEvent(item)
410 event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
411 event.commit()
412 }
414 /*! \brief Tap the item with a touch event.
416 \param item The item to be tapped
417 \param x The x coordinate of the tap, defaults to horizontal center
418 \param y The y coordinate of the tap, defaults to vertical center
419 */
420 function tap(item, x, y) {
421 if (typeof x !== "number") x = item.width / 2;
422 if (typeof y !== "number") y = item.height / 2;
424 var root = fetchRootItem(item)
425 var rootPoint = item.mapToItem(root, x, y)
427 var event = touchEvent(item)
428 event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
429 event.commit()
431 event = touchEvent(item)
432 event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
433 event.commit()
434 }
436 Component.onCompleted: {
437 var rootItem = parent;
438 while (rootItem.parent != undefined) {
439 rootItem = rootItem.parent;
440 }
441 removeTimeConstraintsFromDirectionalDragAreas(rootItem);
442 }
444 /*
445 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
446 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
447 Thus to remove a variable that qmltests cannot really control, namely time, this
448 function removes all constraints from DirectionalDragAreas that are sensible to
449 elapsed time.
451 This effectively makes DirectionalDragAreas easier to fool.
452 */
453 function removeTimeConstraintsFromDirectionalDragAreas(item) {
455 // use duck-typing to identify a DirectionalDragArea
456 if (item.removeTimeConstraints != undefined) {
457 item.removeTimeConstraints();
458 } else {
459 for (var i in item.children) {
460 removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
461 }
462 }
463 }
465 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
466 function waitForRendering(item, timeout) {
467 if (timeout === undefined)
468 timeout = 5000;
469 if (!item)
470 qtest_fail("No item given to waitForRendering", 1);
471 return qtest_results.waitForRendering(item, timeout);
472 }
474 /*
475 Wait until any transition animation has finished for the given StateGroup or Item
476 */
477 function waitUntilTransitionsEnd(stateGroup) {
478 var transitions = stateGroup.transitions;
479 for (var i = 0; i < transitions.length; ++i) {
480 var transition = transitions[i];
481 tryCompare(transition, "running", false, 2000);
482 }
483 }