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