Lomiri
Loading...
Searching...
No Matches
LomiriTestCase.qml
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 */
16
17import QtQuick 2.12
18import QtTest 1.0
19import QtMir.Application 0.1
20import WindowManager 1.0
21import Lomiri.Components 1.3
22import Lomiri.Test 1.0 as LomiriTest
23import Lomiri.SelfTest 0.1 as UT
24import Utils 0.1
25
26TestCase {
27 id: testCase
28 property var util: TestUtil {id:util}
29
30 // This is needed for waitForRendering calls to return
31 // if the watched element already got rendered
32 Rectangle {
33 id: rotatingRectangle
34 width: units.gu(1)
35 height: width
36 parent: testCase.parent
37 border { width: units.dp(1); color: "black" }
38 opacity: 0.6
39
40 visible: testCase.running
41
42 RotationAnimation on rotation {
43 running: rotatingRectangle.visible
44 from: 0
45 to: 360
46 loops: Animation.Infinite
47 duration: 1000
48 }
49 }
50
51 Binding {
52 target: WindowManagerObjects
53 property: "surfaceManager"
54 value: SurfaceManager
55 }
56
57 Binding {
58 target: WindowManagerObjects
59 property: "applicationManager"
60 value: ApplicationManager
61 }
62
63 // Fake implementation to be provided to items under test
64 property var fakeDateTime: new function() {
65 this.currentTimeMs = 0
66 this.getCurrentTimeMs = function() {return this.currentTimeMs}
67 }
68
69 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
70 function mouseClick(item, x, y, button, modifiers, delay) {
71 if (!item)
72 qtest_fail("no item given", 1);
73
74 if (button === undefined)
75 button = Qt.LeftButton;
76 if (modifiers === undefined)
77 modifiers = Qt.NoModifier;
78 if (delay === undefined)
79 delay = -1;
80 if (x === undefined)
81 x = item.width / 2;
82 if (y === undefined)
83 y = item.height / 2;
84 if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
85 qtest_fail("window not shown", 2);
86 }
87
88 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
89 function mouseDoubleClick(item, x, y, button, modifiers, delay) {
90 if (!item)
91 qtest_fail("no item given", 1);
92
93 if (button === undefined)
94 button = Qt.LeftButton;
95 if (modifiers === undefined)
96 modifiers = Qt.NoModifier;
97 if (delay === undefined)
98 delay = -1;
99 if (x === undefined)
100 x = item.width / 2;
101 if (y === undefined)
102 y = item.height / 2;
103 if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
104 qtest_fail("window not shown", 2)
105 }
106
107 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
108 function mousePress(item, x, y, button, modifiers, delay) {
109 if (!item)
110 qtest_fail("no item given", 1);
111
112 if (button === undefined)
113 button = Qt.LeftButton;
114 if (modifiers === undefined)
115 modifiers = Qt.NoModifier;
116 if (delay === undefined)
117 delay = -1;
118 if (x === undefined)
119 x = item.width / 2;
120 if (y === undefined)
121 y = item.height / 2;
122 if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
123 qtest_fail("window not shown", 2)
124 }
125
126 // TODO This function can be removed altogether once we use Qt 5.7 which has the same feature
127 function mouseRelease(item, x, y, button, modifiers, delay) {
128 if (!item)
129 qtest_fail("no item given", 1);
130
131 if (button === undefined)
132 button = Qt.LeftButton;
133 if (modifiers === undefined)
134 modifiers = Qt.NoModifier;
135 if (delay === undefined)
136 delay = -1;
137 if (x === undefined)
138 x = item.width / 2;
139 if (y === undefined)
140 y = item.height / 2;
141 if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
142 qtest_fail("window not shown", 2)
143 }
144
145
146 // Flickable won't recognise a single mouse move as dragging the flickable.
147 // Use 5 steps because it's what
148 // Qt uses in QQuickViewTestUtil::flick
149 // speed is in pixels/second
150 function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
151 speed, iterations) {
152 if (!item)
153 qtest_fail("no item given", 1);
154
155 pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
156 releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
157
158 // set a default speed if not specified
159 speed = (speed != null) ? speed : units.gu(10);
160
161 // set a default iterations if not specified
162 iterations = (iterations !== undefined) ? iterations : 5
163
164 var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
165 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
166
167 var timeStep = totalTime / iterations
168 var diffX = (toX - x) / iterations
169 var diffY = (toY - y) / iterations
170 if (pressMouse) {
171 fakeDateTime.currentTimeMs += timeStep
172 mousePress(item, x, y)
173 }
174 for (var i = 0; i < iterations; ++i) {
175 fakeDateTime.currentTimeMs += timeStep
176 if (i === iterations - 1) {
177 // Avoid any rounding errors by making the last move be at precisely
178 // the point specified
179 mouseMove(item, toX, toY, timeStep)
180 } else {
181 mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, timeStep)
182 }
183 }
184 if (releaseMouse) {
185 fakeDateTime.currentTimeMs += timeStep
186 mouseRelease(item, toX, toY)
187 }
188 }
189
190
191 // Find an object with the given name in the children tree of "obj"
192 function findChild(obj, objectName, timeout) {
193 if (!obj)
194 qtest_fail("no obj given", 1);
195
196 return findChildInWithTimeout(obj, "children", objectName, timeout);
197 }
198
199 // Find an object with the given name in the children tree of "obj"
200 // Including invisible children like animations, timers etc.
201 // Note: you should use findChild if you're not sure you need this
202 // as this tree is much bigger and might contain stuff that goes
203 // away randomly.
204 function findInvisibleChild(obj, objectName, timeout) {
205 if (!obj)
206 qtest_fail("no obj given", 1);
207
208 return findChildInWithTimeout(obj, "data", objectName, timeout);
209 }
210
211 // Find a child in the named property with timeout
212 function findChildInWithTimeout(obj, prop, objectName, timeout) {
213 if (!obj)
214 qtest_fail("no obj given", 1);
215
216 var timeSpent = 0
217 if (timeout === undefined)
218 timeout = 5000;
219
220 var child = findChildIn(obj, prop, objectName);
221
222 while (timeSpent < timeout && !child) {
223 wait(50)
224 timeSpent += 50
225 child = findChildIn(obj, prop, objectName);
226 }
227 return child;
228 }
229
230 // Find a child in the named property
231 function findChildIn(obj, prop, objectName) {
232 if (!obj)
233 qtest_fail("no obj given", 1);
234
235 var childs = new Array(0);
236 childs.push(obj)
237 while (childs.length > 0) {
238 if (childs[0].objectName == objectName) {
239 return childs[0]
240 }
241 for (var i in childs[0][prop]) {
242 childs.push(childs[0][prop][i])
243 }
244 childs.splice(0, 1);
245 }
246 return null;
247 }
248
249 function findChildsByType(obj, typeName) {
250 if (!obj)
251 qtest_fail("no obj given", 1);
252
253 var res = new Array(0);
254 for (var i in obj.children) {
255 var c = obj.children[i];
256 if (UT.Util.isInstanceOf(c, typeName)) {
257 res.push(c)
258 }
259 res = res.concat(findChildsByType(c, typeName));
260 }
261 return res;
262 }
263
264 // Type a full string instead of keyClick letter by letter
265 function typeString(str) {
266 for (var i = 0; i < str.length; i++) {
267 keyClick(str[i])
268 }
269 }
270
271 // Keeps executing a given parameter-less function until it returns the given
272 // expected result or the timemout is reached (in which case a test failure
273 // is generated)
274 function tryCompareFunction(func, expectedResult, timeout, message) {
275 var timeSpent = 0
276 if (timeout === undefined)
277 timeout = 5000;
278 var success = false
279 var actualResult
280 while (timeSpent < timeout && !success) {
281 actualResult = func()
282 success = qtest_compareInternal(actualResult, expectedResult)
283 if (success === false) {
284 wait(50)
285 timeSpent += 50
286 }
287 }
288
289 var act = qtest_results.stringify(actualResult)
290 var exp = qtest_results.stringify(expectedResult)
291 if (!qtest_results.compare(success,
292 message || "function returned unexpected result",
293 act, exp,
294 util.callerFile(), util.callerLine())) {
295 throw new Error("QtQuickTest::fail")
296 }
297 }
298
299 function flickToYEnd(item) {
300 if (!item)
301 qtest_fail("no item given", 1);
302
303 var i = 0;
304 var x = item.width / 2;
305 var y = item.height - units.gu(1);
306 var toY = units.gu(1);
307 var maxIterations = 5 + item.contentHeight / item.height;
308 while (i < maxIterations && !item.atYEnd) {
309 touchFlick(item, x, y, x, toY);
310 tryCompare(item, "moving", false);
311 ++i;
312 }
313 tryCompare(item, "atYEnd", true);
314 }
315
316 function touchEvent(item) {
317 return UT.Util.touchEvent(item)
318 }
319
320 // speed is in pixels/second
321 function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
322 if (!item)
323 qtest_fail("no item given", 1);
324
325 // Make sure the item is rendered
326 waitForRendering(item);
327
328 var root = fetchRootItem(item);
329 var rootFrom = item.mapToItem(root, x, y);
330 var rootTo = item.mapToItem(root, toX, toY);
331
332 // Default to true for beginTouch if not present
333 beginTouch = (beginTouch !== undefined) ? beginTouch : true
334
335 // Default to true for endTouch if not present
336 endTouch = (endTouch !== undefined) ? endTouch : true
337
338 // Set a default speed if not specified
339 speed = (speed !== undefined) ? speed : units.gu(100)
340
341 // Set a default iterations if not specified
342 var iterations = (iterations !== undefined) ? iterations : 10
343
344 var distance = Math.sqrt(Math.pow(rootTo.x - rootFrom.x, 2) + Math.pow(rootTo.y - rootFrom.y, 2))
345 var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
346
347 var timeStep = totalTime / iterations
348 var diffX = (rootTo.x - rootFrom.x) / iterations
349 var diffY = (rootTo.y - rootFrom.y) / iterations
350 if (beginTouch) {
351 fakeDateTime.currentTimeMs += timeStep
352
353 var event = touchEvent(item)
354 event.press(0 /* touchId */, rootFrom.x, rootFrom.y)
355 event.commit()
356 }
357 for (var i = 0; i < iterations; ++i) {
358 fakeDateTime.currentTimeMs += timeStep
359 if (i === iterations - 1) {
360 // Avoid any rounding errors by making the last move be at precisely
361 // the point specified
362 wait(timeStep)
363 var event = touchEvent(item)
364 event.move(0 /* touchId */, rootTo.x, rootTo.y)
365 event.commit()
366 } else {
367 wait(timeStep)
368 var event = touchEvent(item)
369 event.move(0 /* touchId */, rootFrom.x + (i + 1) * diffX, rootFrom.y + (i + 1) * diffY)
370 event.commit()
371 }
372 }
373 if (endTouch) {
374 fakeDateTime.currentTimeMs += timeStep
375 var event = touchEvent(item)
376 event.release(0 /* touchId */, rootTo.x, rootTo.y)
377 event.commit()
378 }
379 }
380
381 // perform a drag in the given direction until the given condition is true
382 // The condition is a function to be evaluated after every step
383 function touchDragUntil(item, startX, startY, stepX, stepY, condition) {
384 if (!item)
385 qtest_fail("no item given", 1);
386
387 multiTouchDragUntil([0], item, startX, startY, stepX, stepY, condition);
388 }
389
390 function multiTouchDragUntil(touchIds, item, startX, startY, stepX, stepY, condition) {
391 if (!item)
392 qtest_fail("no item given", 1);
393
394 var root = fetchRootItem(item);
395 var pos = item.mapToItem(root, startX, startY);
396
397 // convert step to scene coords
398 {
399 var stepStart = item.mapToItem(root, 0, 0);
400 var stepEnd = item.mapToItem(root, stepX, stepY);
401 }
402 stepX = stepEnd.x - stepStart.x;
403 stepY = stepEnd.y - stepStart.y;
404
405 var event = touchEvent(item)
406 for (var i = 0; i < touchIds.length; i++) {
407 event.press(touchIds[i], pos.x, pos.y)
408 }
409 event.commit()
410
411 // we have to stop at some point
412 var maxSteps = 100;
413 var stepsDone = 0;
414
415 while (!condition() && stepsDone < maxSteps) {
416 wait(25);
417 fakeDateTime.currentTimeMs += 25;
418
419 pos.x += stepX;
420 pos.y += stepY;
421
422 event = touchEvent(item);
423 for (i = 0; i < touchIds.length; i++) {
424 event.move(touchIds[i], pos.x, pos.y);
425 }
426 event.commit();
427
428 stepsDone += 1;
429 }
430
431 event = touchEvent(item)
432 for (i = 0; i < touchIds.length; i++) {
433 event.release(touchIds[i], pos.x, pos.y)
434 }
435 event.commit()
436 }
437
438 function touchMove(item, tox, toy) {
439 if (!item)
440 qtest_fail("no item given", 1);
441
442 multiTouchMove(0, item, tox, toy);
443 }
444
445 function multiTouchMove(touchId, item, tox, toy) {
446 if (!item)
447 qtest_fail("no item given", 1);
448
449 if (typeof touchId !== "number") touchId = 0;
450 var root = fetchRootItem(item)
451 var rootPoint = item.mapToItem(root, tox, toy)
452
453 var event = touchEvent(item);
454 event.move(touchId, rootPoint.x, rootPoint.y);
455 event.commit();
456 }
457
458 function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
459 if (!item)
460 qtest_fail("no item given", 1);
461
462 // Make sure the item is rendered
463 waitForRendering(item);
464
465 var event1 = touchEvent(item);
466 // first finger
467 event1.press(0, x1Start, y1Start);
468 event1.commit();
469 // second finger
470 event1.move(0, x1Start, y1Start);
471 event1.press(1, x2Start, y2Start);
472 event1.commit();
473
474 // pinch
475 for (var i = 0.0; i < 1.0; i += 0.02) {
476 event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
477 event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
478 event1.commit();
479 }
480
481 // release
482 event1.release(0, x1End, y1End);
483 event1.release(1, x2End, y2End);
484 event1.commit();
485 }
486
487 function fetchRootItem(item) {
488 if (!item)
489 qtest_fail("no item given", 1);
490
491 if (item.parent)
492 return fetchRootItem(item.parent)
493 else
494 return item
495 }
496
497 function touchPress(item, x, y) {
498 if (!item)
499 qtest_fail("no item given", 1);
500
501 multiTouchPress(0, item, x, y, []);
502 }
503
504 /*! \brief Release a touch point
505
506 \param touchId The touchId to be pressed
507 \param item The item
508 \param x The x coordinate of the press, defaults to horizontal center
509 \param y The y coordinate of the press, defaults to vertical center
510 \param stationaryPoints An array of touchIds which are "already touched"
511 */
512 function multiTouchPress(touchId, item, x, y, stationaryPoints) {
513 if (!item)
514 qtest_fail("no item given", 1);
515
516 if (typeof touchId !== "number") touchId = 0;
517 if (typeof x !== "number") x = item.width / 2;
518 if (typeof y !== "number") y = item.height / 2;
519 if (typeof stationaryPoints !== "object") stationaryPoints = []
520 var root = fetchRootItem(item)
521 var rootPoint = item.mapToItem(root, x, y)
522
523 var event = touchEvent(item)
524 event.press(touchId, rootPoint.x, rootPoint.y)
525 for (var i = 0; i < stationaryPoints.length; i++) {
526 event.stationary(stationaryPoints[i]);
527 }
528 event.commit()
529 }
530
531 function touchRelease(item, x, y) {
532 if (!item)
533 qtest_fail("no item given", 1);
534
535 multiTouchRelease(0, item, x, y, []);
536 }
537
538 /*! \brief Release a touch point
539
540 \param touchId The touchId to be released
541 \param item The item
542 \param x The x coordinate of the release, defaults to horizontal center
543 \param y The y coordinate of the release, defaults to vertical center
544 \param stationaryPoints An array of touchIds which are "still touched"
545 */
546 function multiTouchRelease(touchId, item, x, y, stationaryPoints) {
547 if (!item)
548 qtest_fail("no item given", 1);
549
550 if (typeof touchId !== "number") touchId = 0;
551 if (typeof x !== "number") x = item.width / 2;
552 if (typeof y !== "number") y = item.height / 2;
553 if (typeof stationaryPoints !== "object") stationaryPoints = []
554 var root = fetchRootItem(item)
555 var rootPoint = item.mapToItem(root, x, y)
556
557 var event = touchEvent(item)
558 event.release(touchId, rootPoint.x, rootPoint.y)
559 for (var i = 0; i < stationaryPoints.length; i++) {
560 event.stationary(stationaryPoints[i]);
561 }
562 event.commit()
563 }
564
565 /*! \brief Tap the item with a touch event.
566
567 \param item The item to be tapped
568 \param x The x coordinate of the tap, defaults to horizontal center
569 \param y The y coordinate of the tap, defaults to vertical center
570 */
571 function tap(item, x, y) {
572 if (!item)
573 qtest_fail("no item given", 1);
574
575 multiTouchTap([0], item, x, y);
576 }
577
578 function multiTouchTap(touchIds, item, x, y) {
579 if (!item)
580 qtest_fail("no item given", 1);
581
582 if (typeof touchIds !== "object") touchIds = [0];
583 if (typeof x !== "number") x = item.width / 2;
584 if (typeof y !== "number") y = item.height / 2;
585
586 var root = fetchRootItem(item)
587 var rootPoint = item.mapToItem(root, x, y)
588
589 var event = touchEvent(item)
590 for (var i = 0; i < touchIds.length; i++) {
591 event.press(touchIds[i], rootPoint.x, rootPoint.y)
592 }
593 event.commit()
594
595 event = touchEvent(item)
596 for (i = 0; i < touchIds.length; i++) {
597 event.release(touchIds[i], rootPoint.x, rootPoint.y)
598 }
599 event.commit()
600 }
601
602
603 Component.onCompleted: {
604 var rootItem = parent;
605 while (rootItem.parent != undefined) {
606 rootItem = rootItem.parent;
607 }
608 removeTimeConstraintsFromSwipeAreas(rootItem);
609 }
610
611 /*
612 In qmltests, sequences of touch events are sent all at once, unlike in "real life".
613 Also qmltests might run really slowly, e.g. when run from inside virtual machines.
614 Thus to remove a variable that qmltests cannot really control, namely time, this
615 function removes all constraints from SwipeAreas that are sensible to
616 elapsed time.
617
618 This effectively makes SwipeAreas easier to fool.
619 */
620 function removeTimeConstraintsFromSwipeAreas(item) {
621 if (!item)
622 qtest_fail("no item given", 1);
623
624 if (UT.Util.isInstanceOf(item, "UCSwipeArea")) {
625 LomiriTest.TestExtras.removeTimeConstraintsFromSwipeArea(item);
626 } else {
627 for (var i in item.children) {
628 removeTimeConstraintsFromSwipeAreas(item.children[i]);
629 }
630 }
631 }
632
633 /*
634 Wait until any transition animation has finished for the given StateGroup or Item
635 */
636 function waitUntilTransitionsEnd(stateGroup) {
637 var transitions = stateGroup.transitions;
638 for (var i = 0; i < transitions.length; ++i) {
639 var transition = transitions[i];
640 tryCompare(transition, "running", false, 2000);
641 }
642 }
643
644 /*
645 kill all (fake) running apps, bringing QtMir.Application back to its initial state
646 */
647 function killApps() {
648 while (ApplicationManager.count > 0) {
649 var application = ApplicationManager.get(0);
650 ApplicationManager.stopApplication(application.appId);
651 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
652 // They won't be gone until those surface items are destroyed.
653 tryCompareFunction(function() { return application.surfaceList.count }, 0);
654 tryCompare(application, "state", ApplicationInfo.Stopped);
655 }
656 compare(ApplicationManager.count, 0);
657 SurfaceManager.releaseInputMethodSurface();
658 }
659}