Unity 8
UnityTestCase.qml
1 /*
2  * Copyright 2013 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 
17 import QtQuick 2.0
18 import QtTest 1.0
19 import Ubuntu.Components 0.1
20 import Unity.Test 0.1 as UT
21 
22 TestCase {
23  id: testCase
24  TestUtil {id:util}
25 
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
34 
35  visible: testCase.running
36 
37  RotationAnimation on rotation {
38  running: parent.visible
39  from: 0
40  to: 360
41  loops: Animation.Infinite
42  duration: 1000
43  }
44  }
45 
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  }
51 
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  }
67 
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  }
83 
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  }
99 
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  }
115 
116 
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
125 
126  // set a default speed if not specified
127  speed = (speed != null) ? speed : units.gu(10);
128 
129  // set a default iterations if not specified
130  iterations = (iterations !== undefined) ? iterations : 5
131 
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 */
134 
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  }
157 
158 
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  }
163 
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  }
172 
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  }
188 
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  }
200 
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  }
207 
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  }
225 
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  }
235 
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  }
248 
249  function touchEvent(item) {
250  return UT.Util.touchEvent(item)
251  }
252 
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);
257 
258  var root = fetchRootItem(item);
259  var rootFrom = item.mapToItem(root, x, y);
260  var rootTo = item.mapToItem(root, toX, toY);
261 
262  // Default to true for beginTouch if not present
263  beginTouch = (beginTouch !== undefined) ? beginTouch : true
264 
265  // Default to true for endTouch if not present
266  endTouch = (endTouch !== undefined) ? endTouch : true
267 
268  // Set a default speed if not specified
269  speed = (speed !== undefined) ? speed : units.gu(10)
270 
271  // Set a default iterations if not specified
272  var iterations = (iterations !== undefined) ? iterations : 5
273 
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 */
276 
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
282 
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  }
310 
311  function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
312  // Make sure the item is rendered
313  waitForRendering(item);
314 
315  var event1 = touchEvent(item);
316  // first finger
317  event1.press(0, x1Start, y1Start);
318  event1.commit();
319  // second finger
320  event1.move(0, x1Start, y1Start);
321  event1.press(1, x2Start, y2Start);
322  event1.commit();
323 
324  // pinch
325  for (var i = 0.0; i < 1.0; i += 0.02) {
326  event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
327  event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
328  event1.commit();
329  }
330 
331  // release
332  event1.release(0, x1End, y1End);
333  event1.release(1, x2End, y2End);
334  event1.commit();
335  }
336 
337  function fetchRootItem(item) {
338  if (item.parent)
339  return fetchRootItem(item.parent)
340  else
341  return item
342  }
343 
344  function touchPress(item, x, y) {
345  var root = fetchRootItem(item)
346  var rootPoint = item.mapToItem(root, x, y)
347 
348  var event = touchEvent(item)
349  event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
350  event.commit()
351  }
352 
353  function touchRelease(item, x, y) {
354  var root = fetchRootItem(item)
355  var rootPoint = item.mapToItem(root, x, y)
356 
357  var event = touchEvent(item)
358  event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
359  event.commit()
360  }
361 
362  /*! \brief Tap the item with a touch event.
363 
364  \param item The item to be tapped
365  \param x The x coordinate of the tap, defaults to horizontal center
366  \param y The y coordinate of the tap, defaults to vertical center
367  */
368  function tap(item, x, y) {
369  if (typeof x !== "number") x = item.width / 2;
370  if (typeof y !== "number") y = item.height / 2;
371 
372  var root = fetchRootItem(item)
373  var rootPoint = item.mapToItem(root, x, y)
374 
375  var event = touchEvent(item)
376  event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
377  event.commit()
378 
379  event = touchEvent(item)
380  event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
381  event.commit()
382  }
383 
384  Component.onCompleted: {
385  UT.Util.ensureTouchRegistryInstalled();
386 
387  var rootItem = parent;
388  while (rootItem.parent != undefined) {
389  rootItem = rootItem.parent;
390  }
391  removeTimeConstraintsFromDirectionalDragAreas(rootItem);
392  }
393 
394  /*
395  In qmltests, sequences of touch events are sent all at once, unlike in "real life".
396  Also qmltests might run really slowly, e.g. when run from inside virtual machines.
397  Thus to remove a variable that qmltests cannot really control, namely time, this
398  function removes all constraints from DirectionalDragAreas that are sensible to
399  elapsed time.
400 
401  This effectively makes DirectionalDragAreas easier to fool.
402  */
403  function removeTimeConstraintsFromDirectionalDragAreas(item) {
404 
405  // use duck-typing to identify a DirectionalDragArea
406  if (item.minSpeed != undefined
407  && item.maxSilenceTime != undefined
408  && item.compositionTime != undefined) {
409  item.minSpeed = 0;
410  item.maxSilenceTime = 60 * 60 * 1000;
411  item.compositionTime = 0;
412  } else {
413  for (var i in item.children) {
414  removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
415  }
416  }
417  }
418 
419  // TODO This function can be removed altogether once we use Qt 5.5 which has the same feature
420  function waitForRendering(item, timeout) {
421  if (timeout === undefined)
422  timeout = 5000;
423  if (!item)
424  qtest_fail("No item given to waitForRendering", 1);
425  return qtest_results.waitForRendering(item, timeout);
426  }
427 
428 }