on 2aa2abc help sign in
unity8/tests/utils/modules/Unity/Test/UnityTestCase.qml
474 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 width: units.gu(1)
30 height: width
31 parent: testCase.parent
32 border { width: units.dp(1); color: "black" }
33 opacity: 0.6
35 visible: testCase.running
37 RotationAnimation on rotation {
38 running: parent.visible
39 from: 0
40 to: 360
41 loops: Animation.Infinite
42 duration: 1000
43 }
44 }
46 // Fake implementation to be provided to items under test
47 property var fakeDateTime: new function() {
48 this.currentTimeMs = 0
49 this.getCurrentTimeMs = function() {return this.currentTimeMs}
50 }
52 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
53 function mouseClick(item, x, y, button, modifiers, delay) {
54 if (button === undefined)
55 button = Qt.LeftButton;
56 if (modifiers === undefined)
57 modifiers = Qt.NoModifier;
58 if (delay === undefined)
59 delay = -1;
60 if (x === undefined)
61 x = item.width / 2;
62 if (y === undefined)
63 y = item.height / 2;
64 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
65 qtest_fail("window not shown", 2);
66 }
68 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
69 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
70 if (button === undefined)
71 button = Qt.LeftButton;
72 if (modifiers === undefined)
73 modifiers = Qt.NoModifier;
74 if (delay === undefined)
75 delay = -1;
76 if (x === undefined)
77 x = item.width / 2;
78 if (y === undefined)
79 y = item.height / 2;
80 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
81 qtest_fail("window not shown", 2)
82 }
84 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
85 function mousePress(item, x, y, button, modifiers, delay) {
86 if (button === undefined)
87 button = Qt.LeftButton;
88 if (modifiers === undefined)
89 modifiers = Qt.NoModifier;
90 if (delay === undefined)
91 delay = -1;
92 if (x === undefined)
93 x = item.width / 2;
94 if (y === undefined)
95 y = item.height / 2;
96 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
97 qtest_fail("window not shown", 2)
98 }
100 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
101 function mouseRelease(item, x, y, button, modifiers, delay) {
102 if (button === undefined)
103 button = Qt.LeftButton;
104 if (modifiers === undefined)
105 modifiers = Qt.NoModifier;
106 if (delay === undefined)
107 delay = -1;
108 if (x === undefined)
109 x = item.width / 2;
110 if (y === undefined)
111 y = item.height / 2;
112 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
113 qtest_fail("window not shown", 2)
114 }
117 // Flickable won't recognise a single mouse move as dragging the flickable.
118 // Use 5 steps because it's what
119 // Qt uses in QQuickViewTestUtil::flick
120 // speed is in pixels/second
121 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
122 speed, iterations) {
123 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
124 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
126 // set a default speed if not specified
127 speed = (speed != null) ? speed : units.gu(10);
129 // set a default iterations if not specified
130 iterations = (iterations !== undefined) ? iterations : 5
132 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
133 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
135 var timeStep = totalTime / iterations
136 var diffX = (toX - x) / iterations
137 var diffY = (toY - y) / iterations
138 if (pressMouse) {
139 fakeDateTime.currentTimeMs += timeStep
140 mousePress(item, x, y)
141 }
142 for (var i = 0; i < iterations; ++i) {
143 fakeDateTime.currentTimeMs += timeStep
144 if (i === iterations - 1) {
145 // Avoid any rounding errors by making the last move be at precisely
146 // the point specified
147 mouseMove(item, toX, toY, iterations / speed)
148 } else {
149 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
150 }
151 }
152 if (releaseMouse) {
153 fakeDateTime.currentTimeMs += timeStep
154 mouseRelease(item, toX, toY)
155 }
156 }
159 // Find an object with the given name in the children tree of "obj"
160 function findChild(obj, objectName) {
161 return findChildIn(obj, "children", objectName);
162 }
164 // Find an object with the given name in the children tree of "obj"
165 // Including invisible children like animations, timers etc.
166 // Note: you should use findChild if you're not sure you need this
167 // as this tree is much bigger and might contain stuff that goes
168 // away randomly.
169 function findInvisibleChild(obj, objectName) {
170 return findChildIn(obj, "data", objectName);
171 }
173 // Find a child in the named property
174 function findChildIn(obj, prop, objectName) {
175 var childs = new Array(0);
176 childs.push(obj)
177 while (childs.length > 0) {
178 if (childs[0].objectName == objectName) {
179 return childs[0]
180 }
181 for (var i in childs[0][prop]) {
182 childs.push(childs[0][prop][i])
183 }
184 childs.splice(0, 1);
185 }
186 return null;
187 }
189 function findChildsByType(obj, typeName) {
190 var res = new Array(0);
191 for (var i in obj.children) {
192 var c = obj.children[i];
193 if (UT.Util.isInstanceOf(c, typeName)) {
194 res.push(c)
195 }
196 res = res.concat(findChildsByType(c, typeName));
197 }
198 return res;
199 }
201 // Type a full string instead of keyClick letter by letter
202 function typeString(str) {
203 for (var i = 0; i < str.length; i++) {
204 keyClick(str[i])
205 }
206 }
208 // Keeps executing a given parameter-less function until it returns the given
209 // expected result or the timemout is reached (in which case a test failure
210 // is generated)
211 function tryCompareFunction(func, expectedResult, timeout) {
212 var timeSpent = 0
213 if (timeout === undefined)
214 timeout = 5000;
215 var success = false
216 var actualResult
217 while (timeSpent < timeout && !success) {
218 actualResult = func()
219 success = qtest_compareInternal(actualResult, expectedResult)
220 if (success === false) {
221 wait(50)
222 timeSpent += 50
223 }
224 }
226 var act = qtest_results.stringify(actualResult)
227 var exp = qtest_results.stringify(expectedResult)
228 if (!qtest_results.compare(success,
229 "function returned unexpected result",
230 act, exp,
231 util.callerFile(), util.callerLine())) {
232 throw new Error("QtQuickTest::fail")
233 }
234 }
236 function flickToYEnd(item) {
237 var i = 0;
238 var x = item.width / 2;
239 var y = item.height - units.gu(1);
240 var toY = units.gu(1);
241 while (i < 5 && !item.atYEnd) {
242 touchFlick(item, x, y, x, toY);
243 tryCompare(item, "moving", false);
244 ++i;
245 }
246 tryCompare(item, "atYEnd", true);
247 }
249 function touchEvent(item) {
250 return UT.Util.touchEvent(item)
251 }
253 // speed is in pixels/second
254 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
255 // Make sure the item is rendered
256 waitForRendering(item);
258 var root = fetchRootItem(item);
259 var rootFrom = item.mapToItem(root, x, y);
260 var rootTo = item.mapToItem(root, toX, toY);
262 // Default to true for beginTouch if not present
263 beginTouch = (beginTouch !== undefined) ? beginTouch : true
265 // Default to true for endTouch if not present
266 endTouch = (endTouch !== undefined) ? endTouch : true
268 // Set a default speed if not specified
269 speed = (speed !== undefined) ? speed : units.gu(10)
271 // Set a default iterations if not specified
272 var iterations = (iterations !== undefined) ? iterations : 10
274 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.Y - rootFrom.y, 2))
275 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
277 var timeStep = totalTime / iterations
278 var diffX = (rootTo.x - rootFrom.x) / iterations
279 var diffY = (rootTo.y - rootFrom.y) / iterations
280 if (beginTouch) {
281 fakeDateTime.currentTimeMs += timeStep
283 var event = touchEvent(item)
284 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
285 event.commit()
286 }
287 for (var i = 0; i < iterations; ++i) {
288 fakeDateTime.currentTimeMs += timeStep
289 if (i === iterations - 1) {
290 // Avoid any rounding errors by making the last move be at precisely
291 // the point specified
292 wait(iterations / speed)
293 var event = touchEvent(item)
294 event.move(0 /* touchId */, rootTo.x, rootTo.y)
295 event.commit()
296 } else {
297 wait(iterations / speed)
298 var event = touchEvent(item)
299 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
300 event.commit()
301 }
302 }
303 if (endTouch) {
304 fakeDateTime.currentTimeMs += timeStep
305 var event = touchEvent(item)
306 event.release(0 /* touchId */, rootTo.x, rootTo.y)
307 event.commit()
308 }
309 }
311 // perform a drag in the given direction until the given condition is true
312 // The condition is a function to be evaluated after every step
313 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
315 var root = fetchRootItem(item);
316 var pos = item.mapToItem(root, startX, startY);
318 // convert step to scene coords
319 {
320 var stepStart = item.mapToItem(root, 0, 0);
321 var stepEnd = item.mapToItem(root, stepX, stepY);
322 }
323 stepX = stepEnd.x - stepStart.x;
324 stepY = stepEnd.y - stepStart.y;
326 var event = touchEvent(item)
327 event.press(0 /* touchId */, pos.x, pos.y)
328 event.commit()
330 // we have to stop at some point
331 var maxSteps = 100;
332 var stepsDone = 0;
334 while (!condition() && stepsDone < maxSteps) {
335 wait(25);
336 fakeDateTime.currentTimeMs += 25;
338 pos.x += stepX;
339 pos.y += stepY;
341 event = touchEvent(item);
342 event.move(0 /* touchId */, pos.x, pos.y);
343 event.commit();
345 stepsDone += 1;
346 }
348 event = touchEvent(item)
349 event.release(0 /* touchId */, pos.x, pos.y)
350 event.commit()
351 }
353 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
354 // Make sure the item is rendered
355 waitForRendering(item);
357 var event1 = touchEvent(item);
358 // first finger
359 event1.press(0, x1Start, y1Start);
360 event1.commit();
361 // second finger
362 event1.move(0, x1Start, y1Start);
363 event1.press(1, x2Start, y2Start);
364 event1.commit();
366 // pinch
367 for (var i = 0.0; i < 1.0; i += 0.02) {
368 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
369 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
370 event1.commit();
371 }
373 // release
374 event1.release(0, x1End, y1End);
375 event1.release(1, x2End, y2End);
376 event1.commit();
377 }
379 function fetchRootItem(item) {
380 if (item.parent)
381 return fetchRootItem(item.parent)
382 else
383 return item
384 }
386 function touchPress(item, x, y) {
387 var root = fetchRootItem(item)
388 var rootPoint = item.mapToItem(root, x, y)
390 var event = touchEvent(item)
391 event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
392 event.commit()
393 }
395 function touchRelease(item, x, y) {
396 var root = fetchRootItem(item)
397 var rootPoint = item.mapToItem(root, x, y)
399 var event = touchEvent(item)
400 event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
401 event.commit()
402 }
404 /*! \brief Tap the item with a touch event.
406 \param item The item to be tapped
407 \param x The x coordinate of the tap, defaults to horizontal center
408 \param y The y coordinate of the tap, defaults to vertical center
409 */
410 function tap(item, x, y) {
411 if (typeof x !== "number") x = item.width / 2;
412 if (typeof y !== "number") y = item.height / 2;
414 var root = fetchRootItem(item)
415 var rootPoint = item.mapToItem(root, x, y)
417 var event = touchEvent(item)
418 event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
419 event.commit()
421 event = touchEvent(item)
422 event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
423 event.commit()
424 }
426 Component.onCompleted: {
427 var rootItem = parent;
428 while (rootItem.parent != undefined) {
429 rootItem = rootItem.parent;
430 }
431 removeTimeConstraintsFromDirectionalDragAreas(rootItem);
432 }
434 /*
435 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
436 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
437 Thus to remove a variable that qmltests cannot really control, namely time, this
438 function removes all constraints from DirectionalDragAreas that are sensible to
439 elapsed time.
441 This effectively makes DirectionalDragAreas easier to fool.
442 */
443 function removeTimeConstraintsFromDirectionalDragAreas(item) {
445 // use duck-typing to identify a DirectionalDragArea
446 if (item.removeTimeConstraints != undefined) {
447 item.removeTimeConstraints();
448 } else {
449 for (var i in item.children) {
450 removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
451 }
452 }
453 }
455 // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
456 function waitForRendering(item, timeout) {
457 if (timeout === undefined)
458 timeout = 5000;
459 if (!item)
460 qtest_fail("No item given to waitForRendering", 1);
461 return qtest_results.waitForRendering(item, timeout);
462 }
464 /*
465 Wait until any transition animation has finished for the given StateGroup or Item
466 */
467 function waitUntilTransitionsEnd(stateGroup) {
468 var transitions = stateGroup.transitions;
469 for (var i = 0; i < transitions.length; ++i) {
470 var transition = transitions[i];
471 tryCompare(transition, "running", false, 2000);
472 }
473 }