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