Merge lp:~chris.gagnon/gallery-app/autopilot-fix-flakyness-and-make-work-on-desktop into lp:~chris.gagnon/gallery-app/autopilot-test-cleanup
- autopilot-fix-flakyness-and-make-work-on-desktop
- Merge into autopilot-test-cleanup
Status: | Superseded |
---|---|
Proposed branch: | lp:~chris.gagnon/gallery-app/autopilot-fix-flakyness-and-make-work-on-desktop |
Merge into: | lp:~chris.gagnon/gallery-app/autopilot-test-cleanup |
Diff against target: |
933 lines (+234/-174) 20 files modified
debian/control (+4/-3) debian/gallery-app-autopilot.lintian-overrides (+1/-1) tests/autopilot/CMakeLists.txt (+1/-1) tests/autopilot/gallery_app/emulators/album_editor.py (+1/-1) tests/autopilot/gallery_app/emulators/album_view.py (+56/-1) tests/autopilot/gallery_app/emulators/albums_view.py (+1/-1) tests/autopilot/gallery_app/emulators/events_view.py (+50/-18) tests/autopilot/gallery_app/emulators/gallery_utils.py (+23/-1) tests/autopilot/gallery_app/emulators/media_selector.py (+1/-1) tests/autopilot/gallery_app/emulators/media_viewer.py (+5/-3) tests/autopilot/gallery_app/emulators/photo_viewer.py (+29/-1) tests/autopilot/gallery_app/emulators/photos_view.py (+1/-1) tests/autopilot/gallery_app/tests/__init__.py (+6/-5) tests/autopilot/gallery_app/tests/test_album_editor.py (+0/-2) tests/autopilot/gallery_app/tests/test_album_view.py (+4/-23) tests/autopilot/gallery_app/tests/test_albums_view.py (+0/-2) tests/autopilot/gallery_app/tests/test_events_view.py (+18/-39) tests/autopilot/gallery_app/tests/test_photo_viewer.py (+33/-66) tests/autopilot/gallery_app/tests/test_photos_view.py (+0/-2) tests/autopilot/gallery_app/tests/test_picker_mode.py (+0/-2) |
To merge this branch: | bzr merge lp:~chris.gagnon/gallery-app/autopilot-fix-flakyness-and-make-work-on-desktop |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Leo Arias (community) | Approve | ||
Barry Warsaw (community) | Approve | ||
Chris Gagnon | Pending | ||
Review via email: mp+217766@code.launchpad.net |
This proposal has been superseded by a proposal from 2014-05-02.
Commit message
refactor tests to run on desktop,
refactor tests based on dictonary index to file name
move functions out of tests in to emulator
[barry]
python3 port
Description of the change
- 958. By Chris Gagnon
-
remove unneeded sleep
Leo Arias (elopio) wrote : | # |
Leo Arias (elopio) : | # |
Barry Warsaw (barry) wrote : | # |
This looks great, but why not apply this patch too. Then you'll get the Python 3 port at the same time!
- 959. By Chris Gagnon
-
apply py3 patch from barry, [https:/
/launchpad. net/~barry] - 960. By Chris Gagnon
-
fix doc string, change _swipe_setup and swipes, use pointing_device from gallery_util, get self.media_view in setup instead of property
Leo Arias (elopio) : | # |
- 961. By Chris Gagnon
-
fix flakyness, pep8 fixes and make work on desktop
Unmerged revisions
- 961. By Chris Gagnon
-
fix flakyness, pep8 fixes and make work on desktop
- 960. By Chris Gagnon
-
fix doc string, change _swipe_setup and swipes, use pointing_device from gallery_util, get self.media_view in setup instead of property
- 959. By Chris Gagnon
-
apply py3 patch from barry, [https:/
/launchpad. net/~barry] - 958. By Chris Gagnon
-
remove unneeded sleep
- 957. By Chris Gagnon
-
refactor flaky tests
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2014-04-14 20:21:26 +0000 |
3 | +++ debian/control 2014-05-01 15:29:32 +0000 |
4 | @@ -15,8 +15,8 @@ |
5 | qt5-default, |
6 | qtbase5-dev, |
7 | qtdeclarative5-dev, |
8 | - python, |
9 | -Standards-Version: 3.9.4 |
10 | + python3-all, |
11 | +Standards-Version: 3.9.5 |
12 | # If you don't have commit rights to lp:gallery-app but need to upload |
13 | # packaging changes, just go ahead. The developers will notice and sync |
14 | # up the code again. |
15 | @@ -49,7 +49,8 @@ |
16 | libqt5test5, |
17 | ubuntu-ui-toolkit-autopilot, |
18 | unity8-autopilot, |
19 | - python-pkg-resources, |
20 | + python3-pkg-resources, |
21 | + python3-autopilot, |
22 | Description: Autopilot tests for the photo gallery for Ubuntu |
23 | gallery-app is a photo gallery for the Ubuntu platform. This package contains |
24 | autopilot tests for it. |
25 | |
26 | === modified file 'debian/gallery-app-autopilot.lintian-overrides' |
27 | --- debian/gallery-app-autopilot.lintian-overrides 2013-03-27 16:16:52 +0000 |
28 | +++ debian/gallery-app-autopilot.lintian-overrides 2014-05-01 15:29:32 +0000 |
29 | @@ -1,1 +1,1 @@ |
30 | -gallery-app-autopilot: image-file-in-usr-lib usr/lib/python2.7/dist-packages/gallery_app/data/sample.jpg |
31 | +gallery-app-autopilot: image-file-in-usr-lib usr/lib/python*/dist-packages/gallery_app/data/sample.jpg |
32 | |
33 | === modified file 'tests/autopilot/CMakeLists.txt' |
34 | --- tests/autopilot/CMakeLists.txt 2013-10-06 23:42:20 +0000 |
35 | +++ tests/autopilot/CMakeLists.txt 2014-05-01 15:29:32 +0000 |
36 | @@ -1,6 +1,6 @@ |
37 | set(AUTOPILOT_DIR gallery_app) |
38 | |
39 | -execute_process(COMMAND python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()" |
40 | +execute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" |
41 | OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) |
42 | |
43 | if(INSTALL_TESTS) |
44 | |
45 | === modified file 'tests/autopilot/gallery_app/emulators/album_editor.py' |
46 | --- tests/autopilot/gallery_app/emulators/album_editor.py 2014-04-23 18:56:20 +0000 |
47 | +++ tests/autopilot/gallery_app/emulators/album_editor.py 2014-05-01 15:29:32 +0000 |
48 | @@ -15,7 +15,7 @@ |
49 | cover_image = self.album_cover_image() |
50 | # click left of the cover |
51 | x, y, w, h = cover_image.globalRect |
52 | - self.pointing_device.move(x - 10, y + (h/2)) |
53 | + self.pointing_device.move(x - 10, y + (h // 2)) |
54 | # workaround lp:1247698 (get rid of toolbar) |
55 | self.pointing_device.click() |
56 | self.pointing_device.click() |
57 | |
58 | === modified file 'tests/autopilot/gallery_app/emulators/album_view.py' |
59 | --- tests/autopilot/gallery_app/emulators/album_view.py 2014-04-23 22:15:14 +0000 |
60 | +++ tests/autopilot/gallery_app/emulators/album_view.py 2014-05-01 15:29:32 +0000 |
61 | @@ -5,13 +5,19 @@ |
62 | # under the terms of the GNU General Public License version 3, as published |
63 | # by the Free Software Foundation. |
64 | |
65 | -from gallery_utils import GalleryUtils |
66 | +from testtools.matchers import GreaterThan, LessThan |
67 | + |
68 | +from gallery_app.emulators.gallery_utils import( |
69 | + GalleryUtils, |
70 | + GalleryAppException, |
71 | +) |
72 | |
73 | |
74 | class AlbumView(GalleryUtils): |
75 | """An emulator class that makes it easy to interact with the gallery app""" |
76 | |
77 | def __init__(self, app): |
78 | + super(AlbumView, self).__init__(self) |
79 | self.app = app |
80 | |
81 | def get_animated_album_view(self): |
82 | @@ -66,3 +72,52 @@ |
83 | animated_viewer = self.get_animated_album_view() |
84 | animated_viewer.isOpen.wait_for(False) |
85 | animated_viewer.animationRunning.wait_for(False) |
86 | + |
87 | + def ensure_media_selector_is_fully_closed(self): |
88 | + """Ensure media selector is fully closed""" |
89 | + loader = self.media_selector_loader() |
90 | + loader.status.wait_for(0) |
91 | + |
92 | + def _swipe_setup(self, page_number, direction): |
93 | + self.album = self.get_album_view() |
94 | + self.spread = self.get_spread_view() |
95 | + self.album.animationRunning.wait_for(False) |
96 | + |
97 | + x, y, w, h = self.spread.globalRect |
98 | + mid_y = y + h // 2 |
99 | + mid_x = x + w // 2 |
100 | + |
101 | + if 'left' == direction: |
102 | + matcher = LessThan |
103 | + self.pointing_device.drag( |
104 | + mid_x, mid_y, # Start |
105 | + x + w, mid_y # Finish |
106 | + ) |
107 | + |
108 | + elif 'right' == direction: |
109 | + matcher = GreaterThan |
110 | + self.pointing_device.drag( |
111 | + mid_x, mid_y, # Start |
112 | + x, mid_y # Finish |
113 | + ) |
114 | + else: |
115 | + raise GalleryAppException( |
116 | + 'Direction can be "right" or "left" ' |
117 | + 'you choose "{}"'.format(direction)) |
118 | + |
119 | + self.album.animationRunning.wait_for(False) |
120 | + self.spread.viewingPage.wait_for(matcher(page_number)) |
121 | + |
122 | + def swipe_page_left(self, page_number): |
123 | + '''Swipe page to the left |
124 | + |
125 | + :param page_number: The starting page number you are swiping from |
126 | + ''' |
127 | + self._swipe_setup(page_number, 'left') |
128 | + |
129 | + def swipe_page_right(self, page_number): |
130 | + '''Swipe page to the right |
131 | + |
132 | + :param page_number: The starting page number you are swiping from |
133 | + ''' |
134 | + self._swipe_setup(page_number, 'right') |
135 | |
136 | === modified file 'tests/autopilot/gallery_app/emulators/albums_view.py' |
137 | --- tests/autopilot/gallery_app/emulators/albums_view.py 2013-08-09 10:20:19 +0000 |
138 | +++ tests/autopilot/gallery_app/emulators/albums_view.py 2014-05-01 15:29:32 +0000 |
139 | @@ -5,7 +5,7 @@ |
140 | # under the terms of the GNU General Public License version 3, as published |
141 | # by the Free Software Foundation. |
142 | |
143 | -from gallery_utils import GalleryUtils |
144 | +from gallery_app.emulators.gallery_utils import GalleryUtils |
145 | |
146 | |
147 | class AlbumsView(GalleryUtils): |
148 | |
149 | === modified file 'tests/autopilot/gallery_app/emulators/events_view.py' |
150 | --- tests/autopilot/gallery_app/emulators/events_view.py 2014-04-23 18:56:20 +0000 |
151 | +++ tests/autopilot/gallery_app/emulators/events_view.py 2014-05-01 15:29:32 +0000 |
152 | @@ -5,37 +5,69 @@ |
153 | # under the terms of the GNU General Public License version 3, as published |
154 | # by the Free Software Foundation. |
155 | |
156 | -from gallery_utils import GalleryUtils |
157 | +from gallery_app.emulators.gallery_utils import( |
158 | + GalleryAppException, |
159 | + GalleryUtils |
160 | +) |
161 | |
162 | |
163 | class EventsView(GalleryUtils): |
164 | |
165 | def __init__(self, app): |
166 | + super(EventsView, self).__init__(self) |
167 | self.app = app |
168 | |
169 | - def get_first_event(self): |
170 | - """Returns the first event in the event view""" |
171 | - return self.app.select_single("OrganicMediaList", |
172 | - objectName="organicEventItem0") |
173 | + def get_event(self, event_number=0): |
174 | + """Return an event in the event view based on index number |
175 | + |
176 | + :param event_number: the index number of the organicEventItem to get |
177 | + """ |
178 | + return self.app.select_single( |
179 | + 'OrganicMediaList', |
180 | + objectName='organicEventItem{}'.format(int(event_number)) |
181 | + ) |
182 | |
183 | def number_of_events(self): |
184 | - """Returns the number of events in the model behind the event view""" |
185 | - return self.app.select_single("EventsOverview")._eventCount |
186 | + """Return the number of events in the model behind the event view""" |
187 | + return self.app.select_single('EventsOverview')._eventCount |
188 | |
189 | def number_of_photos_in_events(self): |
190 | - """Returns the number of events""" |
191 | - photo_delegates = self.app.select_many("QQuickItem", |
192 | - objectName="eventPhoto") |
193 | + """Return the number of photos in events""" |
194 | + |
195 | + overview = self.app.select_single('EventsOverview') |
196 | + photo_delegates = overview.select_many( |
197 | + "QQuickItem", |
198 | + objectName="eventPhoto" |
199 | + ) |
200 | return len(photo_delegates) |
201 | |
202 | def number_of_photos_in_event(self, event): |
203 | - """Returns the number of photo delgated in an event""" |
204 | - photo_delegates = event.select_many("QQuickItem", |
205 | - objectName="eventPhoto") |
206 | + """Return the number of photo delgated in an event""" |
207 | + photo_delegates = event.select_many(objectName='eventPhoto') |
208 | return len(photo_delegates) |
209 | |
210 | - def get_first_image_in_event_view(self): |
211 | - """Returns the first photo of the gallery.""" |
212 | - event = self.get_first_event() |
213 | - return event.select_many("OrganicItemInteraction", |
214 | - objectName='eventsViewPhoto')[1] |
215 | + def _get_image_in_event_view(self, image_name, event_index_num=0): |
216 | + """Return the photo of the gallery based on image name. |
217 | + |
218 | + :param image_name: the name of the photo in the event to return""" |
219 | + event = self.get_event(event_index_num) |
220 | + photos = event.select_many( |
221 | + 'QQuickItem', |
222 | + objectName='eventPhoto' |
223 | + ) |
224 | + for photo in photos: |
225 | + images = photo.select_many('QQuickImage') |
226 | + for image in images: |
227 | + if str(image.source).endswith(image_name): |
228 | + return image |
229 | + raise GalleryAppException( |
230 | + 'Photo with image name {} could not be found'.format(image_name)) |
231 | + |
232 | + def click_photo(self, photo_name, event_index_num=0): |
233 | + """Click photo with name and event |
234 | + |
235 | + :param photo_name: name of file to click |
236 | + :param event_index_num: index of event to click |
237 | + """ |
238 | + photo = self._get_image_in_event_view(photo_name, event_index_num) |
239 | + self.pointing_device.click_object(photo) |
240 | |
241 | === modified file 'tests/autopilot/gallery_app/emulators/gallery_utils.py' |
242 | --- tests/autopilot/gallery_app/emulators/gallery_utils.py 2014-04-23 18:56:20 +0000 |
243 | +++ tests/autopilot/gallery_app/emulators/gallery_utils.py 2014-05-01 15:29:32 +0000 |
244 | @@ -4,10 +4,15 @@ |
245 | # This program is free software: you can redistribute it and/or modify it |
246 | # under the terms of the GNU General Public License version 3, as published |
247 | # by the Free Software Foundation. |
248 | +import ubuntuuitoolkit.emulators |
249 | |
250 | from time import sleep |
251 | |
252 | |
253 | +class GalleryAppException(Exception): |
254 | + pass |
255 | + |
256 | + |
257 | class GalleryUtils(object): |
258 | """An emulator class that makes it easy to interact with |
259 | general components of the gallery app.""" |
260 | @@ -16,6 +21,7 @@ |
261 | |
262 | def __init__(self, app): |
263 | self.app = app |
264 | + self.pointing_device = ubuntuuitoolkit.emulators.get_pointing_device() |
265 | |
266 | def select_many_retry(self, object_type, **kwargs): |
267 | """Returns the item that is searched for with app.select_many |
268 | @@ -26,7 +32,7 @@ |
269 | while len(items) < 1 and tries > 0: |
270 | sleep(self.retry_delay) |
271 | items = self.app.select_many(object_type, **kwargs) |
272 | - tries = tries - 1 |
273 | + tries -= 1 |
274 | return items |
275 | |
276 | def get_qml_view(self): |
277 | @@ -103,3 +109,19 @@ |
278 | objectName="albumCoverMenuItem", |
279 | text=text |
280 | ) |
281 | + |
282 | + def _ensure_delete_dialog_visible(self): |
283 | + delete_dialog = self.get_delete_dialog() |
284 | + delete_dialog.opacity.wait_for(1) |
285 | + |
286 | + def _click_dialog_button(self, button): |
287 | + self._ensure_delete_dialog_visible() |
288 | + self.pointing_device.click_object(button) |
289 | + |
290 | + def click_delete_dialog_cancel_button(self): |
291 | + button = self.get_delete_dialog_cancel_button() |
292 | + self._click_dialog_button(button) |
293 | + |
294 | + def click_delete_dialog_delete_button(self): |
295 | + button = self.get_delete_dialog_delete_button() |
296 | + self._click_dialog_button(button) |
297 | |
298 | === modified file 'tests/autopilot/gallery_app/emulators/media_selector.py' |
299 | --- tests/autopilot/gallery_app/emulators/media_selector.py 2014-04-23 18:56:20 +0000 |
300 | +++ tests/autopilot/gallery_app/emulators/media_selector.py 2014-05-01 15:29:32 +0000 |
301 | @@ -5,7 +5,7 @@ |
302 | # under the terms of the GNU General Public License version 3, as published |
303 | # by the Free Software Foundation. |
304 | |
305 | -from gallery_utils import GalleryUtils |
306 | +from gallery_app.emulators.gallery_utils import GalleryUtils |
307 | |
308 | |
309 | class MediaSelector(GalleryUtils): |
310 | |
311 | === modified file 'tests/autopilot/gallery_app/emulators/media_viewer.py' |
312 | --- tests/autopilot/gallery_app/emulators/media_viewer.py 2014-04-23 18:56:20 +0000 |
313 | +++ tests/autopilot/gallery_app/emulators/media_viewer.py 2014-05-01 15:29:32 +0000 |
314 | @@ -11,11 +11,13 @@ |
315 | class MediaViewer(toolkit_emulators.UbuntuUIToolkitEmulatorBase): |
316 | """A class that makes it easy to interact with the media viewer""" |
317 | |
318 | - def __init__(self, *args): |
319 | - super(MediaViewer, self).__init__(*args) |
320 | - |
321 | def get_edit_spinner(self): |
322 | return self.select_single( |
323 | "ActivityIndicator", |
324 | objectName="busySpinner" |
325 | ) |
326 | + |
327 | + def ensure_spinner_not_running(self): |
328 | + """Wait for spinner to stop running""" |
329 | + spinner = self.get_edit_spinner() |
330 | + spinner.running.wait_for(False) |
331 | |
332 | === modified file 'tests/autopilot/gallery_app/emulators/photo_viewer.py' |
333 | --- tests/autopilot/gallery_app/emulators/photo_viewer.py 2014-02-04 17:37:28 +0000 |
334 | +++ tests/autopilot/gallery_app/emulators/photo_viewer.py 2014-05-01 15:29:32 +0000 |
335 | @@ -5,12 +5,13 @@ |
336 | # under the terms of the GNU General Public License version 3, as published |
337 | # by the Free Software Foundation. |
338 | |
339 | -from gallery_utils import GalleryUtils |
340 | +from gallery_app.emulators.gallery_utils import GalleryUtils |
341 | |
342 | |
343 | class PhotoViewer(GalleryUtils): |
344 | |
345 | def __init__(self, app): |
346 | + super(PhotoViewer, self).__init__(self) |
347 | self.app = app |
348 | |
349 | def get_delete_dialog(self): |
350 | @@ -116,3 +117,30 @@ |
351 | """Returns the edit preview.""" |
352 | return self.app.wait_select_single("EditPreview", |
353 | objectName="editPreview") |
354 | + |
355 | + def _click_item(self, item): |
356 | + self.pointing_device.click_object(item) |
357 | + |
358 | + def click_rotate_item(self): |
359 | + rotate_item = self.get_rotate_menu_item() |
360 | + self._click_item(rotate_item) |
361 | + |
362 | + def click_crop_item(self): |
363 | + crop_item = self.get_crop_menu_item() |
364 | + self._click_item(crop_item) |
365 | + |
366 | + def click_undo_item(self): |
367 | + undo_item = self.get_undo_menu_item() |
368 | + self._click_item(undo_item) |
369 | + |
370 | + def click_redo_item(self): |
371 | + redo_item = self.get_redo_menu_item() |
372 | + self._click_item(redo_item) |
373 | + |
374 | + def click_revert_item(self): |
375 | + revert_item = self.get_revert_menu_item() |
376 | + self._click_item(revert_item) |
377 | + |
378 | + def click_enhance_item(self): |
379 | + enhance_item = self.get_auto_enhance_menu_item() |
380 | + self._click_item(enhance_item) |
381 | |
382 | === modified file 'tests/autopilot/gallery_app/emulators/photos_view.py' |
383 | --- tests/autopilot/gallery_app/emulators/photos_view.py 2014-04-23 18:56:20 +0000 |
384 | +++ tests/autopilot/gallery_app/emulators/photos_view.py 2014-05-01 15:29:32 +0000 |
385 | @@ -5,7 +5,7 @@ |
386 | # under the terms of the GNU General Public License version 3, as published |
387 | # by the Free Software Foundation. |
388 | |
389 | -from gallery_utils import GalleryUtils |
390 | +from gallery_app.emulators.gallery_utils import GalleryUtils |
391 | |
392 | |
393 | class PhotosView(GalleryUtils): |
394 | |
395 | === modified file 'tests/autopilot/gallery_app/tests/__init__.py' |
396 | --- tests/autopilot/gallery_app/tests/__init__.py 2014-03-31 08:38:45 +0000 |
397 | +++ tests/autopilot/gallery_app/tests/__init__.py 2014-05-01 15:29:32 +0000 |
398 | @@ -7,8 +7,8 @@ |
399 | |
400 | """gallery autopilot tests.""" |
401 | |
402 | +import os |
403 | import logging |
404 | -import os.path |
405 | import shutil |
406 | import signal |
407 | |
408 | @@ -24,7 +24,6 @@ |
409 | from gallery_app.emulators.gallery_utils import GalleryUtils |
410 | |
411 | from time import sleep |
412 | -from os import remove |
413 | |
414 | logger = logging.getLogger(__name__) |
415 | |
416 | @@ -69,6 +68,8 @@ |
417 | # in /usr |
418 | if os.path.realpath(__file__).startswith("/usr/"): |
419 | return EnvironmentTypes.installed |
420 | + if model() == 'Desktop': |
421 | + return EnvironmentTypes.installed |
422 | else: |
423 | if os.path.exists(self.local_location): |
424 | return EnvironmentTypes.local |
425 | @@ -144,7 +145,7 @@ |
426 | config = os.path.expanduser( |
427 | os.path.join("~", ".config", "gallery-app.conf")) |
428 | if os.path.exists(config): |
429 | - remove(config) |
430 | + os.remove(config) |
431 | |
432 | def setUp(self): |
433 | self.pointing_device = toolkit_emulators.get_pointing_device() |
434 | @@ -164,8 +165,8 @@ |
435 | self.assertThat(self.gallery_utils.get_qml_view().visible, |
436 | Eventually(Equals(True))) |
437 | """FIXME somehow on the server gallery sometimes is not fully started |
438 | - for switching to the albums view. Therefore this hack of a second""" |
439 | - sleep(1) |
440 | + for switching to the albums view. Therefore this hack of sleeping""" |
441 | + sleep(2) |
442 | |
443 | def launch_gallery_app(self, env_type): |
444 | if env_type == EnvironmentTypes.installed: |
445 | |
446 | === modified file 'tests/autopilot/gallery_app/tests/test_album_editor.py' |
447 | --- tests/autopilot/gallery_app/tests/test_album_editor.py 2014-04-23 18:56:20 +0000 |
448 | +++ tests/autopilot/gallery_app/tests/test_album_editor.py 2014-05-01 15:29:32 +0000 |
449 | @@ -8,8 +8,6 @@ |
450 | |
451 | """Tests the album editor of the gallery app""" |
452 | |
453 | -from __future__ import absolute_import |
454 | - |
455 | from testtools.matchers import Equals |
456 | from autopilot.matchers import Eventually |
457 | |
458 | |
459 | === modified file 'tests/autopilot/gallery_app/tests/test_album_view.py' |
460 | --- tests/autopilot/gallery_app/tests/test_album_view.py 2014-04-23 19:29:33 +0000 |
461 | +++ tests/autopilot/gallery_app/tests/test_album_view.py 2014-05-01 15:29:32 +0000 |
462 | @@ -8,8 +8,6 @@ |
463 | |
464 | """Tests the album view of the gallery app""" |
465 | |
466 | -from __future__ import absolute_import |
467 | - |
468 | from testtools.matchers import Equals, GreaterThan, LessThan |
469 | from autopilot.matchers import Eventually |
470 | |
471 | @@ -42,10 +40,6 @@ |
472 | super(TestAlbumView, self).setUp() |
473 | self.switch_to_albums_tab() |
474 | |
475 | - def ensure_media_selector_is_fully_closed(self): |
476 | - loader = self.album_view.media_selector_loader() |
477 | - self.assertThat(loader.status, Eventually(Equals(0))) |
478 | - |
479 | def test_album_view_open_photo(self): |
480 | self.main_view.close_toolbar() |
481 | self.open_first_album() |
482 | @@ -66,32 +60,20 @@ |
483 | self.open_album_at(0) |
484 | self.main_view.close_toolbar() |
485 | |
486 | - album = self.album_view.get_album_view() |
487 | spread = self.album_view.get_spread_view() |
488 | |
489 | - self.assertThat(album.animationRunning, Eventually(Equals(False))) |
490 | - self.assertThat(spread.viewingPage, Eventually(Equals(1))) |
491 | - self.main_view.close_toolbar() |
492 | - |
493 | - x, y, w, h = spread.globalRect |
494 | - mid_y = y + h / 2 |
495 | - mid_x = x + w / 2 |
496 | - |
497 | # check that we can page to the cover and back (we check for lesser |
498 | # than 1 because it can either be 0 if we are on a one page spread |
499 | # or -1 if we are on a two page spread, for example on desktop) |
500 | - self.pointing_device.drag(mid_x - mid_x / 2, mid_y, x + w - 10, mid_y) |
501 | - self.album_view.get_animated_album_view() |
502 | + self.album_view.swipe_page_left(1) |
503 | self.assertThat(spread.viewingPage, Eventually(LessThan(1))) |
504 | - self.pointing_device.drag(mid_x + mid_x / 2, mid_y, x + 10, mid_y) |
505 | - self.album_view.get_animated_album_view() |
506 | + self.album_view.swipe_page_right(0) |
507 | self.assertThat(spread.viewingPage, Eventually(Equals(1))) |
508 | |
509 | # drag to next page and check we have flipped away from page 1 |
510 | # can't check precisely for page 2 because depending on form factor |
511 | # and orientation we might be displaying two pages at the same time |
512 | - self.pointing_device.drag(mid_x + mid_x / 2, mid_y, x + 10, mid_y) |
513 | - self.assertThat(album.animationRunning, Eventually(Equals(False))) |
514 | + self.album_view.swipe_page_right(1) |
515 | self.assertThat(spread.viewingPage, Eventually(GreaterThan(1))) |
516 | |
517 | def test_add_photo(self): |
518 | @@ -105,7 +87,7 @@ |
519 | self.media_selector.ensure_fully_open() |
520 | |
521 | self.main_view.get_toolbar().click_custom_button("cancelButton") |
522 | - self.ensure_media_selector_is_fully_closed() |
523 | + self.album_view.ensure_media_selector_is_fully_closed() |
524 | |
525 | num_photos = self.album_view.number_of_photos() |
526 | self.assertThat(num_photos, Equals(num_photos_start)) |
527 | @@ -122,7 +104,6 @@ |
528 | lambda: self.album_view.number_of_photos(), |
529 | Eventually(Equals(num_photos_start + 1))) |
530 | |
531 | - @skip("UnicodeEncodeError: 'ascii' codec can't encode character u'xa2'") |
532 | def test_add_photo_to_new_album(self): |
533 | self.main_view.open_toolbar().click_button("addButton") |
534 | self.ui_update() |
535 | |
536 | === modified file 'tests/autopilot/gallery_app/tests/test_albums_view.py' |
537 | --- tests/autopilot/gallery_app/tests/test_albums_view.py 2014-04-23 19:29:33 +0000 |
538 | +++ tests/autopilot/gallery_app/tests/test_albums_view.py 2014-05-01 15:29:32 +0000 |
539 | @@ -8,8 +8,6 @@ |
540 | |
541 | """Tests the albums view of the gallery app.""" |
542 | |
543 | -from __future__ import absolute_import |
544 | - |
545 | from testtools.matchers import Equals |
546 | from autopilot.matchers import Eventually |
547 | from autopilot.platform import model |
548 | |
549 | === modified file 'tests/autopilot/gallery_app/tests/test_events_view.py' |
550 | --- tests/autopilot/gallery_app/tests/test_events_view.py 2014-04-23 19:29:33 +0000 |
551 | +++ tests/autopilot/gallery_app/tests/test_events_view.py 2014-05-01 15:29:32 +0000 |
552 | @@ -8,8 +8,6 @@ |
553 | |
554 | """Tests for the Gallery App""" |
555 | |
556 | -from __future__ import absolute_import |
557 | - |
558 | from testtools.matchers import Equals, NotEquals, Is, GreaterThan |
559 | from autopilot.matchers import Eventually |
560 | from autopilot.platform import model |
561 | @@ -63,15 +61,6 @@ |
562 | def enable_select_mode(self): |
563 | self.main_view.open_toolbar().click_button("selectButton") |
564 | |
565 | - def click_first_photo(self): |
566 | - first_photo = self.events_view.get_first_image_in_event_view() |
567 | - self.click_item(first_photo) |
568 | - |
569 | - def assert_delete_dialog_visible(self): |
570 | - delete_dialog = self.gallery_utils.get_delete_dialog() |
571 | - |
572 | - self.assertThat(delete_dialog.opacity, Eventually(Equals(1))) |
573 | - |
574 | def test_select_button_cancel(self): |
575 | """Clicking the cancel button after clicking the select button must |
576 | hide the toolbar automatically.""" |
577 | @@ -87,60 +76,50 @@ |
578 | self.assertThat(toolbar.opened, Eventually(Equals(False))) |
579 | self.assertFalse(events_view.inSelectionMode) |
580 | |
581 | - first_photo = self.events_view.get_first_image_in_event_view() |
582 | - self.tap_item(first_photo) |
583 | - self.assertTrue(events_view.inSelectionMode) |
584 | - |
585 | def test_delete_a_photo(self): |
586 | """Selecting a photo must make the delete button clickable.""" |
587 | - number_of_photos = self.events_view.number_of_photos_in_events() |
588 | + self.assertThat(lambda: exists(self.sample_file), |
589 | + Eventually(Equals(True))) |
590 | + |
591 | self.enable_select_mode() |
592 | - self.click_first_photo() |
593 | + self.events_view.click_photo(self.sample_file) |
594 | self.main_view.open_toolbar().click_button("deleteButton") |
595 | - self.assert_delete_dialog_visible() |
596 | + self.assertThat(self.gallery_utils.delete_dialog_shown, |
597 | + Eventually(Is(True))) |
598 | |
599 | - cancel_item = self.gallery_utils.get_delete_dialog_cancel_button() |
600 | - self.click_item(cancel_item) |
601 | + self.gallery_utils.click_delete_dialog_cancel_button() |
602 | + self.assertThat(self.gallery_utils.delete_dialog_shown, |
603 | + Eventually(Is(False))) |
604 | |
605 | self.assertThat(lambda: exists(self.sample_file), |
606 | Eventually(Equals(True))) |
607 | |
608 | - new_number_of_photos = self.events_view.number_of_photos_in_events() |
609 | - self.assertThat(new_number_of_photos, Equals(number_of_photos)) |
610 | - |
611 | - self.assertThat(self.gallery_utils.delete_dialog_shown, |
612 | - Eventually(Is(False))) |
613 | - |
614 | self.main_view.open_toolbar().click_button("deleteButton") |
615 | - self.assert_delete_dialog_visible() |
616 | + self.assertThat(self.gallery_utils.delete_dialog_shown, |
617 | + Eventually(Is(True))) |
618 | |
619 | - delete_item = self.gallery_utils.get_delete_dialog_delete_button() |
620 | - self.click_item(delete_item) |
621 | + self.gallery_utils.click_delete_dialog_delete_button() |
622 | self.assertThat(self.gallery_utils.delete_dialog_shown, |
623 | Eventually(Is(False))) |
624 | |
625 | self.assertThat(lambda: exists(self.sample_file), |
626 | Eventually(Equals(False))) |
627 | |
628 | - self.ui_update() |
629 | - new_number_of_photos = self.events_view.number_of_photos_in_events() |
630 | - self.assertThat(new_number_of_photos, Equals(number_of_photos - 1)) |
631 | - |
632 | def test_adding_a_video(self): |
633 | if model() == "Desktop": |
634 | - first_before = self.events_view.get_first_event() |
635 | + before = self.events_view.get_event(0) |
636 | video_file = "video.mp4" |
637 | shutil.copyfile(self.sample_dir+"/option01/"+video_file, |
638 | self.sample_destination_dir+"/"+video_file) |
639 | video_file = "video.mkv" |
640 | shutil.copyfile(self.sample_dir+"/option01/"+video_file, |
641 | self.sample_destination_dir+"/"+video_file) |
642 | - first = self.events_view.get_first_event() |
643 | - self.assertThat(lambda: str(first), |
644 | - Eventually(NotEquals(str(first_before)))) |
645 | + after = self.events_view.get_event(0) |
646 | + self.assertThat(lambda: str(after), |
647 | + Eventually(NotEquals(str(before)))) |
648 | self.assertThat( |
649 | - lambda: self.events_view.number_of_photos_in_event(first), |
650 | - Eventually(Equals(2))) |
651 | + lambda: self.events_view.number_of_photos_in_events(), |
652 | + Eventually(Equals(3))) |
653 | |
654 | # Check if Camera Button is not visible at Desktop mode |
655 | def test_camera_button_visible(self): |
656 | |
657 | === modified file 'tests/autopilot/gallery_app/tests/test_photo_viewer.py' |
658 | --- tests/autopilot/gallery_app/tests/test_photo_viewer.py 2014-04-23 19:29:33 +0000 |
659 | +++ tests/autopilot/gallery_app/tests/test_photo_viewer.py 2014-05-01 15:29:32 +0000 |
660 | @@ -8,8 +8,6 @@ |
661 | |
662 | """Tests the Photo editor of the gallery app.""" |
663 | |
664 | -from __future__ import absolute_import |
665 | - |
666 | from testtools.matchers import Equals, NotEquals, GreaterThan, Is |
667 | from autopilot.matchers import Eventually |
668 | |
669 | @@ -18,7 +16,6 @@ |
670 | from gallery_app.emulators.events_view import EventsView |
671 | from gallery_app.tests import GalleryTestCase |
672 | |
673 | -from os.path import exists |
674 | import os |
675 | from time import sleep |
676 | import unittest |
677 | @@ -45,15 +42,16 @@ |
678 | self.main_view.open_toolbar() |
679 | |
680 | def open_first_photo(self): |
681 | - self.assertThat(lambda: self.events_view.number_of_photos_in_events(), |
682 | - Eventually(GreaterThan(0))) |
683 | - single_photo = self.events_view.get_first_image_in_event_view() |
684 | + self.assertThat( |
685 | + lambda: self.events_view.number_of_photos_in_events(), |
686 | + Eventually(GreaterThan(0)) |
687 | + ) |
688 | |
689 | # workaround lp:1247698 |
690 | # toolbar needs to be gone to click on an image. |
691 | self.main_view.close_toolbar() |
692 | |
693 | - self.click_item(single_photo) |
694 | + self.events_view.click_photo(self.sample_file) |
695 | |
696 | photo_viewer_loader = self.photo_viewer.get_main_photo_viewer_loader() |
697 | self.assertThat(photo_viewer_loader.loaded, Eventually(Equals(True))) |
698 | @@ -64,9 +62,6 @@ |
699 | |
700 | class TestPhotoViewer(TestPhotoViewerBase): |
701 | |
702 | - def setUp(self): |
703 | - super(TestPhotoViewer, self).setUp() |
704 | - |
705 | @unittest.skip("Temporarily disable as it fails in some cases, " |
706 | "supposedly due to problems with the infrastructure") |
707 | def test_save_state(self): |
708 | @@ -132,11 +127,11 @@ |
709 | self.click_item(cancel_item) |
710 | self.ensure_closed_delete_dialog() |
711 | |
712 | - self.assertThat(lambda: exists(self.sample_file), |
713 | + self.assertThat(lambda: os.path.exists(self.sample_file), |
714 | Eventually(Equals(True))) |
715 | |
716 | self.delete_one_picture() |
717 | - self.assertThat(lambda: exists(self.sample_file), |
718 | + self.assertThat(lambda: os.path.exists(self.sample_file), |
719 | Eventually(Equals(False))) |
720 | |
721 | # Delete all other pictures and make sure the photo viewer closes |
722 | @@ -183,8 +178,8 @@ |
723 | |
724 | # Slide left should move to the next image |
725 | x, y, w, h = list.globalRect |
726 | - mid_y = y + h / 2 |
727 | - mid_x = x + w / 2 |
728 | + mid_y = y + h // 2 |
729 | + mid_x = x + w // 2 |
730 | self.pointing_device.drag(mid_x, mid_y, x + 10, mid_y) |
731 | |
732 | self.assertThat(list.moving, Eventually(Equals(False))) |
733 | @@ -208,6 +203,7 @@ |
734 | def setUp(self): |
735 | super(TestPhotoEditor, self).setUp() |
736 | self.click_edit_button() |
737 | + self.media_view = self.app.select_single(MediaViewer) |
738 | |
739 | def click_edit_button(self): |
740 | self.main_view.open_toolbar().click_button("editButton") |
741 | @@ -215,35 +211,6 @@ |
742 | self.assertThat(edit_dialog.visible, (Eventually(Equals(True)))) |
743 | self.assertThat(edit_dialog.opacity, (Eventually(Equals(1)))) |
744 | |
745 | - def click_rotate_item(self): |
746 | - rotate_item = self.photo_viewer.get_rotate_menu_item() |
747 | - self.click_item(rotate_item) |
748 | - |
749 | - def click_crop_item(self): |
750 | - crop_item = self.photo_viewer.get_crop_menu_item() |
751 | - self.click_item(crop_item) |
752 | - |
753 | - def click_undo_item(self): |
754 | - undo_item = self.photo_viewer.get_undo_menu_item() |
755 | - self.click_item(undo_item) |
756 | - |
757 | - def click_redo_item(self): |
758 | - redo_item = self.photo_viewer.get_redo_menu_item() |
759 | - self.click_item(redo_item) |
760 | - |
761 | - def click_revert_item(self): |
762 | - revert_item = self.photo_viewer.get_revert_menu_item() |
763 | - self.click_item(revert_item) |
764 | - |
765 | - def click_enhance_item(self): |
766 | - enhance_item = self.photo_viewer.get_auto_enhance_menu_item() |
767 | - self.click_item(enhance_item) |
768 | - |
769 | - def ensure_spinner_not_running(self): |
770 | - media_view = self.app.select_single(MediaViewer) |
771 | - spinner = media_view.get_edit_spinner() |
772 | - self.assertThat(spinner.running, Eventually(Equals(False))) |
773 | - |
774 | def test_photo_editor_crop(self): |
775 | """Cropping a photo must crop it.""" |
776 | old_file_size = os.path.getsize(self.sample_file) |
777 | @@ -252,17 +219,17 @@ |
778 | item_width = crop_box.width |
779 | item_height = crop_box.height |
780 | |
781 | - self.click_crop_item() |
782 | + self.photo_viewer.click_crop_item() |
783 | |
784 | self.assertThat(crop_box.state, Eventually(Equals("shown"))) |
785 | self.assertThat(crop_box.opacity, Eventually(Equals(1))) |
786 | |
787 | crop_corner = self.photo_viewer.get_top_left_crop_corner() |
788 | x, y, h, w = crop_corner.globalRect |
789 | - x = x + w / 2 |
790 | - y = y + h / 2 |
791 | + x = x + w // 2 |
792 | + y = y + h // 2 |
793 | self.pointing_device.drag(x, y, |
794 | - x + item_width / 2, y + item_height / 2) |
795 | + x + item_width // 2, y + item_height // 2) |
796 | |
797 | # wait for animation being finished |
798 | crop_overlay = self.photo_viewer.get_crop_overlay() |
799 | @@ -271,7 +238,7 @@ |
800 | |
801 | crop_button = self.photo_viewer.get_crop_overlays_crop_icon() |
802 | self.click_item(crop_button) |
803 | - self.ensure_spinner_not_running() |
804 | + self.media_view.ensure_spinner_not_running() |
805 | |
806 | # wait for new photo being set/reloaded, so saving thumbnailing etc. |
807 | # is done |
808 | @@ -293,8 +260,8 @@ |
809 | return opened_photo.paintedWidth > opened_photo.paintedHeight |
810 | self.assertThat(is_landscape(), Equals(True)) |
811 | |
812 | - self.click_rotate_item() |
813 | - self.ensure_spinner_not_running() |
814 | + self.photo_viewer.click_rotate_item() |
815 | + self.media_view.ensure_spinner_not_running() |
816 | |
817 | self.assertThat(opened_photo.paintedHeight, |
818 | Eventually(Equals(item_height))) |
819 | @@ -303,8 +270,8 @@ |
820 | |
821 | self.main_view.open_toolbar() |
822 | self.click_edit_button() |
823 | - self.click_undo_item() |
824 | - self.ensure_spinner_not_running() |
825 | + self.photo_viewer.click_undo_item() |
826 | + self.media_view.ensure_spinner_not_running() |
827 | |
828 | self.assertThat(opened_photo.paintedHeight, |
829 | Eventually(NotEquals(item_height))) |
830 | @@ -313,8 +280,8 @@ |
831 | |
832 | self.main_view.open_toolbar() |
833 | self.click_edit_button() |
834 | - self.click_redo_item() |
835 | - self.ensure_spinner_not_running() |
836 | + self.photo_viewer.click_redo_item() |
837 | + self.media_view.ensure_spinner_not_running() |
838 | |
839 | self.assertThat(opened_photo.paintedHeight, |
840 | Eventually(Equals(item_height))) |
841 | @@ -323,10 +290,10 @@ |
842 | |
843 | self.main_view.open_toolbar() |
844 | self.click_edit_button() |
845 | - self.click_rotate_item() |
846 | + self.photo_viewer.click_rotate_item() |
847 | self.main_view.open_toolbar() |
848 | self.click_edit_button() |
849 | - self.click_revert_item() |
850 | + self.photo_viewer.click_revert_item() |
851 | |
852 | self.assertThat(opened_photo.paintedHeight, |
853 | Eventually(NotEquals(item_height))) |
854 | @@ -348,8 +315,8 @@ |
855 | self.assertThat(redo_item.enabled, Eventually(Equals(False))) |
856 | self.assertThat(revert_item.enabled, Eventually(Equals(False))) |
857 | |
858 | - self.click_rotate_item() |
859 | - self.ensure_spinner_not_running() |
860 | + self.photo_viewer.click_rotate_item() |
861 | + self.media_view.ensure_spinner_not_running() |
862 | |
863 | self.click_edit_button() |
864 | undo_item = self.photo_viewer.get_undo_menu_item() |
865 | @@ -360,8 +327,8 @@ |
866 | self.assertThat(redo_item.enabled, Eventually(Equals(False))) |
867 | self.assertThat(revert_item.enabled, Eventually(Equals(True))) |
868 | |
869 | - self.click_undo_item() |
870 | - self.ensure_spinner_not_running() |
871 | + self.photo_viewer.click_undo_item() |
872 | + self.media_view.ensure_spinner_not_running() |
873 | |
874 | self.click_edit_button() |
875 | undo_item = self.photo_viewer.get_undo_menu_item() |
876 | @@ -372,8 +339,8 @@ |
877 | self.assertThat(redo_item.enabled, Eventually(Equals(True))) |
878 | self.assertThat(revert_item.enabled, Eventually(Equals(False))) |
879 | |
880 | - self.click_redo_item() |
881 | - self.ensure_spinner_not_running() |
882 | + self.photo_viewer.click_redo_item() |
883 | + self.media_view.ensure_spinner_not_running() |
884 | |
885 | self.click_edit_button() |
886 | undo_item = self.photo_viewer.get_undo_menu_item() |
887 | @@ -384,8 +351,8 @@ |
888 | self.assertThat(redo_item.enabled, Eventually(Equals(False))) |
889 | self.assertThat(revert_item.enabled, Eventually(Equals(True))) |
890 | |
891 | - self.click_revert_item() |
892 | - self.ensure_spinner_not_running() |
893 | + self.photo_viewer.click_revert_item() |
894 | + self.media_view.ensure_spinner_not_running() |
895 | |
896 | self.click_edit_button() |
897 | undo_item = self.photo_viewer.get_undo_menu_item() |
898 | @@ -396,8 +363,8 @@ |
899 | self.assertThat(redo_item.enabled, Eventually(Equals(False))) |
900 | self.assertThat(revert_item.enabled, Eventually(Equals(False))) |
901 | |
902 | - self.click_enhance_item() |
903 | - self.ensure_spinner_not_running() |
904 | + self.photo_viewer.click_enhance_item() |
905 | + self.media_view.ensure_spinner_not_running() |
906 | |
907 | self.click_edit_button() |
908 | |
909 | |
910 | === modified file 'tests/autopilot/gallery_app/tests/test_photos_view.py' |
911 | --- tests/autopilot/gallery_app/tests/test_photos_view.py 2014-04-23 19:29:33 +0000 |
912 | +++ tests/autopilot/gallery_app/tests/test_photos_view.py 2014-05-01 15:29:32 +0000 |
913 | @@ -8,8 +8,6 @@ |
914 | |
915 | """Tests the Photos view of the gallery app.""" |
916 | |
917 | -from __future__ import absolute_import |
918 | - |
919 | from testtools.matchers import Equals, Is |
920 | from testtools import skipUnless |
921 | from autopilot.matchers import Eventually |
922 | |
923 | === modified file 'tests/autopilot/gallery_app/tests/test_picker_mode.py' |
924 | --- tests/autopilot/gallery_app/tests/test_picker_mode.py 2014-04-23 19:29:33 +0000 |
925 | +++ tests/autopilot/gallery_app/tests/test_picker_mode.py 2014-05-01 15:29:32 +0000 |
926 | @@ -8,8 +8,6 @@ |
927 | |
928 | """Tests the Photos view of the gallery app.""" |
929 | |
930 | -from __future__ import absolute_import |
931 | - |
932 | from testtools.matchers import Equals |
933 | from autopilot.matchers import Eventually |
934 |
22 + self.pointing_ device = ubuntuuitoolkit .emulators. get_pointing_ device( )
GalleryUtils already has a pointing_device attribute. You should just call the super __init__()
I would actually prefer not to inherit from something like GalleryUtils, but from an autopilot introspection object, but that probably should be in a later refactor.
35 + def _swipe_setup(self):
Here I don't like that you are adding attributes to the object on a method that's not __init__.
I think it could be clearer something like this:
def swipe_page_ left(self, page_number): _swipe_ page(page_ number, 'left')
self.
def _swipe_page(self, page_number, direction): album_view( ) spread_ view() animationRunnin g.wait_ for(False)
album = self.get_
spread = self.get_
album.
spread_center_x = spread.globalRect.x + spread.globalRect.w // 2
spread_center_y = spread.globalRect.y + spread.globalRect.h // 2
start_x = spread.globalRect.x
start_y = stop_y = spread_center_y
if direction == 'left':
expected_ page_matcher = LessThan
expected_ page_matcher = GreatherThan
stop_x = spread_center_x
elif direction == 'right':
stop_x = spread.globalRect.x
else:
raise Something.
self. pointing_ device. drag(
start_x, start_y, stop_x, stop_y)
album. animationRunnin g.wait_ for(False) viewingPage. wait_for( expected_ page_matcher( page_number) )
spread.
88 +class EventsViewExcep tion(Exception) :
I would prefer if this inherits from a general GalleryAppExcep tion. Then you could ignore all the GalleryAppExcep tions in some cases.
119 def number_ of_photos_ in_events( self):
[...]
123 + """Return the number of events"""
I think that docstring is wrong.
514 + def media_view(self):
If you do this as a property, then every time you call it the select_single will be executed. Is there a reason for not doing this on the __init__ like:
self.media_view = self.app. select_ single( MediaViewer)
?
In general, this is a big step forward, so +1. I still don't like some things, but they don't come from before your branch.
Like this:
203 + def click_delete_ dialog_ cancel_ button( self):
I don't like it being part of a utils module. There's probably a helper for dialogs on the toolkit, and we should return the dialog object from the method that opens it.
Anyway, thanks a lot for this. It's now a lot more understandable.