Unity 8
 All Classes Functions
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  // Flickable won't recognise a single mouse move as dragging the flickable.
41  // Use 5 steps because it's what
42  // Qt uses in QQuickViewTestUtil::flick
43  // speed is in pixels/second
44  function mouseFlick(item, x, y, toX, toY, pressMouse, releaseMouse,
45  speed, iterations) {
46  pressMouse = ((pressMouse != null) ? pressMouse : true); // Default to true for pressMouse if not present
47  releaseMouse = ((releaseMouse != null) ? releaseMouse : true); // Default to true for releaseMouse if not present
48 
49  // set a default speed if not specified
50  speed = (speed != null) ? speed : units.gu(10);
51 
52  // set a default iterations if not specified
53  iterations = (iterations !== undefined) ? iterations : 5
54 
55  var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
56  var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
57 
58  var timeStep = totalTime / iterations
59  var diffX = (toX - x) / iterations
60  var diffY = (toY - y) / iterations
61  if (pressMouse) {
62  fakeDateTime.currentTimeMs += timeStep
63  mousePress(item, x, y)
64  }
65  for (var i = 0; i < iterations; ++i) {
66  fakeDateTime.currentTimeMs += timeStep
67  if (i === iterations - 1) {
68  // Avoid any rounding errors by making the last move be at precisely
69  // the point specified
70  mouseMove(item, toX, toY, iterations / speed)
71  } else {
72  mouseMove(item, x + (i + 1) * diffX, y + (i + 1) * diffY, iterations / speed)
73  }
74  }
75  if (releaseMouse) {
76  fakeDateTime.currentTimeMs += timeStep
77  mouseRelease(item, toX, toY)
78  }
79  }
80 
81 
82  // Find an object with the given name in the children tree of "obj"
83  function findChild(obj, objectName) {
84  return findChildIn(obj, "children", objectName);
85  }
86 
87  // Find an object with the given name in the children tree of "obj"
88  // Including invisible children like animations, timers etc.
89  // Note: you should use findChild if you're not sure you need this
90  // as this tree is much bigger and might contain stuff that goes
91  // away randomly.
92  function findInvisibleChild(obj, objectName) {
93  return findChildIn(obj, "data", objectName);
94  }
95 
96  // Find a child in the named property
97  function findChildIn(obj, prop, objectName) {
98  var childs = new Array(0);
99  childs.push(obj)
100  while (childs.length > 0) {
101  if (childs[0].objectName == objectName) {
102  return childs[0]
103  }
104  for (var i in childs[0][prop]) {
105  childs.push(childs[0][prop][i])
106  }
107  childs.splice(0, 1);
108  }
109  return null;
110  }
111 
112  // Type a full string instead of keyClick letter by letter
113  // TODO: this is not ugly, this is uber-ugly and does not support
114  // any special character. Remove the keyMap once keyClick(obj, char)
115  // has landed in upstream Qt.
116  function typeString(str) {
117  var keyMap = {
118  "a": Qt.Key_A,
119  "b": Qt.Key_B,
120  "c": Qt.Key_C,
121  "d": Qt.Key_D,
122  "e": Qt.Key_E,
123  "f": Qt.Key_F,
124  "g": Qt.Key_G,
125  "h": Qt.Key_H,
126  "i": Qt.Key_I,
127  "j": Qt.Key_J,
128  "k": Qt.Key_K,
129  "l": Qt.Key_L,
130  "m": Qt.Key_M,
131  "n": Qt.Key_N,
132  "o": Qt.Key_O,
133  "p": Qt.Key_P,
134  "q": Qt.Key_Q,
135  "r": Qt.Key_R,
136  "s": Qt.Key_S,
137  "t": Qt.Key_T,
138  "u": Qt.Key_U,
139  "v": Qt.Key_V,
140  "w": Qt.Key_W,
141  "x": Qt.Key_X,
142  "y": Qt.Key_Y,
143  "z": Qt.Key_Z,
144  "A": Qt.Key_A,
145  "B": Qt.Key_B,
146  "C": Qt.Key_C,
147  "D": Qt.Key_D,
148  "E": Qt.Key_E,
149  "F": Qt.Key_F,
150  "G": Qt.Key_G,
151  "H": Qt.Key_H,
152  "I": Qt.Key_I,
153  "J": Qt.Key_J,
154  "K": Qt.Key_K,
155  "L": Qt.Key_L,
156  "M": Qt.Key_M,
157  "N": Qt.Key_N,
158  "O": Qt.Key_O,
159  "P": Qt.Key_P,
160  "Q": Qt.Key_Q,
161  "R": Qt.Key_R,
162  "S": Qt.Key_S,
163  "T": Qt.Key_T,
164  "U": Qt.Key_U,
165  "V": Qt.Key_V,
166  "W": Qt.Key_W,
167  "X": Qt.Key_X,
168  "Y": Qt.Key_Y,
169  "Z": Qt.Key_Z,
170  "0": Qt.Key_0,
171  "1": Qt.Key_1,
172  "2": Qt.Key_2,
173  "3": Qt.Key_3,
174  "4": Qt.Key_4,
175  "5": Qt.Key_5,
176  "6": Qt.Key_6,
177  "7": Qt.Key_7,
178  "8": Qt.Key_8,
179  "9": Qt.Key_9,
180  " ": Qt.Key_Space,
181  }
182  for (var i = 0; i < str.length; i++) {
183  keyClick(keyMap[str[i]])
184  }
185  }
186 
187  // Keeps executing a given parameter-less function until it returns the given
188  // expected result or the timemout is reached (in which case a test failure
189  // is generated)
190  function tryCompareFunction(func, expectedResult, timeout) {
191  var timeSpent = 0
192  if (timeout === undefined)
193  timeout = 5000;
194  var success = false
195  var actualResult
196  while (timeSpent < timeout && !success) {
197  actualResult = func()
198  success = qtest_compareInternal(actualResult, expectedResult)
199  if (success === false) {
200  wait(50)
201  timeSpent += 50
202  }
203  }
204 
205  var act = qtest_results.stringify(actualResult)
206  var exp = qtest_results.stringify(expectedResult)
207  if (!qtest_results.compare(success,
208  "function returned unexpected result",
209  act, exp,
210  util.callerFile(), util.callerLine())) {
211  throw new Error("QtQuickTest::fail")
212  }
213  }
214 
215  function touchEvent() {
216  return UT.Util.touchEvent()
217  }
218 
219  // speed is in pixels/second
220  function touchFlick(item, x, y, toX, toY, beginTouch, endTouch, speed, iterations) {
221  // Make sure the item is rendered
222  waitForRendering(item);
223 
224  // Default to true for beginTouch if not present
225  beginTouch = (beginTouch !== undefined) ? beginTouch : true
226 
227  // Default to true for endTouch if not present
228  endTouch = (endTouch !== undefined) ? endTouch : true
229 
230  // Set a default speed if not specified
231  speed = (speed !== undefined) ? speed : units.gu(10)
232 
233  // Set a default iterations if not specified
234  var iterations = (iterations !== undefined) ? iterations : 5
235 
236  var distance = Math.sqrt(Math.pow(toX - x, 2) + Math.pow(toY - y, 2))
237  var totalTime = (distance / speed) * 1000 /* converting speed to pixels/ms */
238 
239  var timeStep = totalTime / iterations
240  var diffX = (toX - x) / iterations
241  var diffY = (toY - y) / iterations
242  if (beginTouch) {
243  fakeDateTime.currentTimeMs += timeStep
244 
245  var event = touchEvent()
246  event.press(0 /* touchId */, x, y)
247  event.commit()
248  }
249  for (var i = 0; i < iterations; ++i) {
250  fakeDateTime.currentTimeMs += timeStep
251  if (i === iterations - 1) {
252  // Avoid any rounding errors by making the last move be at precisely
253  // the point specified
254  wait(iterations / speed)
255  var event = touchEvent()
256  event.move(0 /* touchId */, toX, toY)
257  event.commit()
258  } else {
259  wait(iterations / speed)
260  var event = touchEvent()
261  event.move(0 /* touchId */, x + (i + 1) * diffX, y + (i + 1) * diffY)
262  event.commit()
263  }
264  }
265  if (endTouch) {
266  fakeDateTime.currentTimeMs += timeStep
267  var event = touchEvent()
268  event.release(0 /* touchId */, toX, toY)
269  event.commit()
270  }
271  }
272 
273  function touchPinch(item, x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End) {
274  // Make sure the item is rendered
275  waitForRendering(item);
276 
277  var event1 = touchEvent();
278  // first finger
279  event1.press(0, x1Start, y1Start);
280  event1.commit();
281  // second finger
282  event1.stationary(0);
283  event1.press(1, x2Start, y2Start);
284  event1.commit();
285 
286  // pinch
287  for (var i = 0.0; i < 1.0; i += 0.02) {
288  event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
289  event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
290  event1.commit();
291  }
292 
293  // release
294  event1.release(0, x1End, y1End);
295  event1.release(1, x2End, y2End);
296  event1.commit();
297  }
298 
299  function fetchRootItem(item) {
300  if (item.parent)
301  return fetchRootItem(item.parent)
302  else
303  return item
304  }
305 
306  function touchPress(item, x, y) {
307  var root = fetchRootItem(item)
308  var rootPoint = item.mapToItem(root, x, y)
309 
310  var event = touchEvent()
311  event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
312  event.commit()
313  }
314 
315  function touchRelease(item, x, y) {
316  var root = fetchRootItem(item)
317  var rootPoint = item.mapToItem(root, x, y)
318 
319  var event = touchEvent()
320  event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
321  event.commit()
322  }
323 
324  /*! \brief Tap the item with a touch event.
325 
326  \param item The item to be tapped
327  \param x The x coordinate of the tap, defaults to horizontal center
328  \param y The y coordinate of the tap, defaults to vertical center
329  */
330  function tap(item, x, y) {
331  if (typeof x !== "number") x = item.width / 2;
332  if (typeof y !== "number") y = item.height / 2;
333 
334  var root = fetchRootItem(item)
335  var rootPoint = item.mapToItem(root, x, y)
336 
337  var event = touchEvent()
338  event.press(0 /* touchId */, rootPoint.x, rootPoint.y)
339  event.commit()
340 
341  event = touchEvent()
342  event.release(0 /* touchId */, rootPoint.x, rootPoint.y)
343  event.commit()
344  }
345 
346  Component.onCompleted: {
347  UT.Util.ensureTouchRegistryInstalled();
348 
349  var rootItem = parent;
350  while (rootItem.parent != undefined) {
351  rootItem = rootItem.parent;
352  }
353  removeTimeConstraintsFromDirectionalDragAreas(rootItem);
354  }
355 
356  /*
357  In qmltests, sequences of touch events are sent all at once, unlike in "real life".
358  Also qmltests might run really slowly, e.g. when run from inside virtual machines.
359  Thus to remove a variable that qmltests cannot really control, namely time, this
360  function removes all constraints from DirectionalDragAreas that are sensible to
361  elapsed time.
362 
363  This effectively makes DirectionalDragAreas easier to fool.
364  */
365  function removeTimeConstraintsFromDirectionalDragAreas(item) {
366 
367  // use duck-typing to identify a DirectionalDragArea
368  if (item.minSpeed != undefined
369  && item.maxSilenceTime != undefined
370  && item.compositionTime != undefined) {
371  item.minSpeed = 0;
372  item.maxSilenceTime = 60 * 60 * 1000;
373  item.compositionTime = 0;
374  } else {
375  for (var i in item.children) {
376  removeTimeConstraintsFromDirectionalDragAreas(item.children[i]);
377  }
378  }
379  }
380 }