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